diff --git a/runtime-testsuite/pom.xml b/runtime-testsuite/pom.xml index 189714b33b..a7e0819c37 100644 --- a/runtime-testsuite/pom.xml +++ b/runtime-testsuite/pom.xml @@ -117,8 +117,8 @@ maven-compiler-plugin 8 - 9 - 9 + 11 + 11 diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonEmpty.txt b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonEmpty.txt new file mode 100644 index 0000000000..a89efbb5ac --- /dev/null +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonEmpty.txt @@ -0,0 +1,53 @@ +[type] +Parser + +[grammar] +grammar VisitorCalc; + +s +@init { + +} +@after { + +} + : expr EOF + | + ; + +expr + : INT # number + | ID # var + | ID '(' ')' # func + | expr (MUL | DIV) expr # multiply + | expr (ADD | SUB) expr # add + ; + +INT : [0-9]+; +ID : [a-zA-Z_]+ ; +MUL : '*'; +DIV : '/'; +ADD : '+'; +SUB : '-'; +WS : [ \t]+ -> channel(HIDDEN); + +[start] +s + +[input] + + +[output] +"""{"rules":["s","expr"],"input":"","tokens":[{"type":-1,"line":1,"pos":0,"channel":0,"start":0,"stop":-1}],"tree":"0"} +""" + +[skip] +Cpp +CSharp +Dart +Go +JavaScript +PHP +Swift +Python2 +Python3 diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonExpr.txt b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonExpr.txt new file mode 100644 index 0000000000..1aa4864b49 --- /dev/null +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonExpr.txt @@ -0,0 +1,53 @@ +[type] +Parser + +[grammar] +grammar VisitorCalc; + +s +@init { + +} +@after { + +} + : expr EOF + | + ; + +expr + : INT # number + | ID # var + | ID '(' ')' # func + | expr (MUL | DIV) expr # multiply + | expr (ADD | SUB) expr # add + ; + +INT : [0-9]+; +ID : [a-zA-Z_]+ ; +MUL : '*'; +DIV : '/'; +ADD : '+'; +SUB : '-'; +WS : [ \t]+ -> channel(HIDDEN); + +[start] +s + +[input] +1+2 + +[output] +"""{"rules":["s","expr"],"input":"1+2","tokens":[{"type":3,"line":1,"pos":0,"channel":0,"start":0,"stop":0},{"type":7,"line":1,"pos":1,"channel":0,"start":1,"stop":1},{"type":3,"line":1,"pos":2,"channel":0,"start":2,"stop":2},{"type":-1,"line":1,"pos":3,"channel":0,"start":3,"stop":2}],"tree":{"0":[{"1":[{"1":[0]},1,{"1":[2]}]},3]}} +""" + +[skip] +Cpp +CSharp +Dart +Go +JavaScript +PHP +Swift +Python2 +Python3 diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonMismatchedToken.txt b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonMismatchedToken.txt new file mode 100644 index 0000000000..ddbd7554d1 --- /dev/null +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonMismatchedToken.txt @@ -0,0 +1,57 @@ +[type] +Parser + +[grammar] +grammar VisitorCalc; + +s + : expr EOF + | + ; + +expr +@init { + +} +@after { + +} + : INT # number + | ID # var + | ID '(' ')' # func + | expr (MUL | DIV) expr # multiply + | expr (ADD | SUB) expr # add + ; + +INT : [0-9]+; +ID : [a-zA-Z_]+ ; +MUL : '*'; +DIV : '/'; +ADD : '+'; +SUB : '-'; +WS : [ \t]+ -> channel(HIDDEN); + +[start] +expr + +[input] +f( + +[output] +"""{"rules":["s","expr"],"input":"f(","tokens":[{"type":4,"line":1,"pos":0,"channel":0,"start":0,"stop":0},{"type":1,"line":1,"pos":1,"channel":0,"start":1,"stop":1},{"type":-1,"line":1,"pos":2,"channel":0,"start":2,"stop":1}],"tree":{"1":[0,1,{"error":""}]}} +""" + +[errors] +"""line 1:2 missing ')' at '' +""" + +[skip] +Cpp +CSharp +Dart +Go +JavaScript +PHP +Swift +Python2 +Python3 diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonNotTiny.txt b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonNotTiny.txt new file mode 100644 index 0000000000..469af44eb0 --- /dev/null +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonNotTiny.txt @@ -0,0 +1,59 @@ +[type] +Parser + +[grammar] +grammar Expr; + +program +@init { + +} +@after { + + +} + : stat EOF + | def EOF + ; + +stat: ID '=' expr ';' + | expr ';' + ; + +def : ID '(' ID (',' ID)* ')' '{' stat* '}' ; + +expr: ID + | INT + | func + | 'not' expr + | expr 'and' expr + | expr 'or' expr + ; + +func : ID '(' expr (',' expr)* ')' ; + +INT : [0-9]+ ; +ID: [a-zA-Z_][a-zA-Z_0-9]* ; +WS: [ \t\n\r\f]+ -> skip ; + +[start] +program + +[input] +f(x, y, z) { x = x and y or z; g(30); } + +[output] +"""(program (def f ( x , y , z ) { (stat x = (expr (expr (expr x) and (expr y)) or (expr z)) ;) (stat (expr (func g ( (expr 30) ))) ;) }) ) +{"rules":["program","stat","def","expr","func"],"input":"f(x, y, z) { x = x and y or z; g(30); }","tokens":[{"type":12,"line":1,"pos":0,"channel":0,"start":0,"stop":0},{"type":3,"line":1,"pos":1,"channel":0,"start":1,"stop":1},{"type":12,"line":1,"pos":2,"channel":0,"start":2,"stop":2},{"type":4,"line":1,"pos":3,"channel":0,"start":3,"stop":3},{"type":12,"line":1,"pos":5,"channel":0,"start":5,"stop":5},{"type":4,"line":1,"pos":6,"channel":0,"start":6,"stop":6},{"type":12,"line":1,"pos":8,"channel":0,"start":8,"stop":8},{"type":5,"line":1,"pos":9,"channel":0,"start":9,"stop":9},{"type":6,"line":1,"pos":11,"channel":0,"start":11,"stop":11},{"type":12,"line":1,"pos":13,"channel":0,"start":13,"stop":13},{"type":1,"line":1,"pos":15,"channel":0,"start":15,"stop":15},{"type":12,"line":1,"pos":17,"channel":0,"start":17,"stop":17},{"type":9,"line":1,"pos":19,"channel":0,"start":19,"stop":21},{"type":12,"line":1,"pos":23,"channel":0,"start":23,"stop":23},{"type":10,"line":1,"pos":25,"channel":0,"start":25,"stop":26},{"type":12,"line":1,"pos":28,"channel":0,"start":28,"stop":28},{"type":2,"line":1,"pos":29,"channel":0,"start":29,"stop":29},{"type":12,"line":1,"pos":31,"channel":0,"start":31,"stop":31},{"type":3,"line":1,"pos":32,"channel":0,"start":32,"stop":32},{"type":11,"line":1,"pos":33,"channel":0,"start":33,"stop":34},{"type":5,"line":1,"pos":35,"channel":0,"start":35,"stop":35},{"type":2,"line":1,"pos":36,"channel":0,"start":36,"stop":36},{"type":7,"line":1,"pos":38,"channel":0,"start":38,"stop":38},{"type":-1,"line":1,"pos":39,"channel":0,"start":39,"stop":38}],"tree":{"0":[{"2":[0,1,2,3,4,5,6,7,8,{"1":[9,10,{"3":[{"3":[{"3":[11]},12,{"3":[13]}]},14,{"3":[15]}]},16]},{"1":[{"3":[{"4":[17,18,{"3":[19]},20]}]},21]},22]},23]}} +""" + +[skip] +Cpp +CSharp +Dart +Go +JavaScript +PHP +Swift +Python2 +Python3 diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonOneToken.txt b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonOneToken.txt new file mode 100644 index 0000000000..0cbf762df8 --- /dev/null +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonOneToken.txt @@ -0,0 +1,53 @@ +[type] +Parser + +[grammar] +grammar VisitorCalc; + +s + : expr EOF + | + ; + +expr +@init { + +} +@after { + +} + : INT # number + | ID # var + | ID '(' ')' # func + | expr (MUL | DIV) expr # multiply + | expr (ADD | SUB) expr # add + ; + +INT : [0-9]+; +ID : [a-zA-Z_]+ ; +MUL : '*'; +DIV : '/'; +ADD : '+'; +SUB : '-'; +WS : [ \t]+ -> channel(HIDDEN); + +[start] +expr + +[input] +8 + +[output] +"""{"rules":["s","expr"],"input":"8","tokens":[{"type":3,"line":1,"pos":0,"channel":0,"start":0,"stop":0},{"type":-1,"line":1,"pos":1,"channel":0,"start":1,"stop":0}],"tree":{"1":[0]}} +""" + +[skip] +Cpp +CSharp +Dart +Go +JavaScript +PHP +Swift +Python2 +Python3 diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonOneTokenOneRule.txt b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonOneTokenOneRule.txt new file mode 100644 index 0000000000..843037e079 --- /dev/null +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/descriptors/ParseTrees/JsonOneTokenOneRule.txt @@ -0,0 +1,53 @@ +[type] +Parser + +[grammar] +grammar VisitorCalc; + +s +@init { + +} +@after { + +} + : expr EOF + | + ; + +expr + : INT # number + | ID # var + | ID '(' ')' # func + | expr (MUL | DIV) expr # multiply + | expr (ADD | SUB) expr # add + ; + +INT : [0-9]+; +ID : [a-zA-Z_]+ ; +MUL : '*'; +DIV : '/'; +ADD : '+'; +SUB : '-'; +WS : [ \t]+ -> channel(HIDDEN); + +[start] +s + +[input] +99 + +[output] +"""{"rules":["s","expr"],"input":"99","tokens":[{"type":3,"line":1,"pos":0,"channel":0,"start":0,"stop":1},{"type":-1,"line":1,"pos":2,"channel":0,"start":2,"stop":1}],"tree":{"0":[{"1":[0]},1]}} +""" + +[skip] +Cpp +CSharp +Dart +Go +JavaScript +PHP +Swift +Python2 +Python3 diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/CSharp.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/CSharp.test.stg index 14def7e03d..a0ce031103 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/CSharp.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/CSharp.test.stg @@ -56,6 +56,8 @@ BailErrorStrategy() ::= <%ErrorHandler = new BailErrorStrategy();%> ToStringTree(s) ::= <%.ToStringTree(this)%> +ToJSON(s) ::= "" + Column() ::= "this.Column" Text() ::= "this.Text" diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Cpp.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Cpp.test.stg index 61efce7a28..9d84c058b6 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Cpp.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Cpp.test.stg @@ -35,6 +35,8 @@ BuildParseTrees() ::= "setBuildParseTree(true);" BailErrorStrategy() ::= "_errHandler = std::make_shared\();" ToStringTree(s) ::= "->toStringTree(this)" +ToJSON(s) ::= "" + Column() ::= "getCharPositionInLine()" Text() ::= "getText()" ValEquals(a,b) ::= " == " diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Dart.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Dart.test.stg index a9c224fe47..94e5bbe2c7 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Dart.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Dart.test.stg @@ -55,6 +55,7 @@ BuildParseTrees() ::= "buildParseTree = true;" BailErrorStrategy() ::= <%errorHandler = new BailErrorStrategy();%> ToStringTree(s) ::= <%.toStringTree(parser: this)%> +ToJSON(s) ::= "" Column() ::= "this.charPositionInLine" diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Go.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Go.test.stg index c1f62e6f9e..cb5b4856ac 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Go.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Go.test.stg @@ -55,6 +55,7 @@ BuildParseTrees() ::= "p.BuildParseTrees = true" BailErrorStrategy() ::= <%p.SetErrorHandler(antlr.NewBailErrorStrategy())%> ToStringTree(s) ::= <%.ToStringTree(nil, p)%> +ToJSON(s) ::= "" Column() ::= "p.GetCharPositionInLine()" diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Java.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Java.test.stg index b1aa46db10..20a1ba7e2d 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Java.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Java.test.stg @@ -56,6 +56,8 @@ BailErrorStrategy() ::= <%setErrorHandler(new BailErrorStrategy());%> ToStringTree(s) ::= <%.toStringTree(this)%> +ToJSON(s) ::= <%JsonSerializer.toJSON(,this)%> + Column() ::= "this.getCharPositionInLine()" Text() ::= "this.getText()" diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/JavaScript.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/JavaScript.test.stg index 709f13e39a..43b9352a48 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/JavaScript.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/JavaScript.test.stg @@ -55,6 +55,7 @@ BuildParseTrees() ::= "this.buildParseTrees = true;" BailErrorStrategy() ::= <%this._errHandler = new antlr4.error.BailErrorStrategy();%> ToStringTree(s) ::= <%.toStringTree(null, this)%> +ToJSON(s) ::= "" Column() ::= "this.column" diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/PHP.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/PHP.test.stg index a2ea874663..6f66e14d51 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/PHP.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/PHP.test.stg @@ -38,6 +38,7 @@ BuildParseTrees() ::= "\$this->setBuildParseTree(true);" BailErrorStrategy() ::= <%\$this->setErrorHandler(new Antlr\\Antlr4\\Runtime\\Error\\BailErrorStrategy());%> ToStringTree(s) ::= <%->toStringTree(\$this->getRuleNames())%> +ToJSON(s) ::= "" Column() ::= "\$this->getCharPositionInLine()" Text() ::= "\$this->getText()" ValEquals(a,b) ::= <%===%> diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python2.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python2.test.stg index 2292cafe7b..214269625f 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python2.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python2.test.stg @@ -55,6 +55,7 @@ BuildParseTrees() ::= "self._buildParseTrees = True" BailErrorStrategy() ::= <%self._errHandler = BailErrorStrategy()%> ToStringTree(s) ::= <%.toStringTree(recog=self)%> +ToJSON(s) ::= "" Column() ::= "self.column" diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python3.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python3.test.stg index 65dcdcd83a..f0059f9534 100644 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python3.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Python3.test.stg @@ -55,6 +55,7 @@ BuildParseTrees() ::= "self._buildParseTrees = True" BailErrorStrategy() ::= <%self._errHandler = BailErrorStrategy()%> ToStringTree(s) ::= <%.toStringTree(recog=self)%> +ToJSON(s) ::= "" Column() ::= "self.column" diff --git a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Swift.test.stg b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Swift.test.stg index c2c12b63c2..0a8e03c46c 100755 --- a/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Swift.test.stg +++ b/runtime-testsuite/resources/org/antlr/v4/test/runtime/templates/Swift.test.stg @@ -55,6 +55,7 @@ BuildParseTrees() ::= "setBuildParseTree(true)" BailErrorStrategy() ::= <%setErrorHandler(BailErrorStrategy())%> ToStringTree(s) ::= <%.toStringTree(self)%> +ToJSON(s) ::= "" Column() ::= "self.getCharPositionInLine()" diff --git a/runtime-testsuite/test/org/antlr/v4/test/runtime/java/api/VisitorCalc.g4 b/runtime-testsuite/test/org/antlr/v4/test/runtime/java/api/VisitorCalc.g4 index 6ca1c2e835..899287828e 100644 --- a/runtime-testsuite/test/org/antlr/v4/test/runtime/java/api/VisitorCalc.g4 +++ b/runtime-testsuite/test/org/antlr/v4/test/runtime/java/api/VisitorCalc.g4 @@ -2,15 +2,19 @@ grammar VisitorCalc; s : expr EOF + | ; expr : INT # number + | ID # var + | ID '(' ')' # func | expr (MUL | DIV) expr # multiply | expr (ADD | SUB) expr # add ; INT : [0-9]+; +ID : [a-zA-Z_]+ ; MUL : '*'; DIV : '/'; ADD : '+'; diff --git a/runtime/Java/src/org/antlr/v4/runtime/misc/Utils.java b/runtime/Java/src/org/antlr/v4/runtime/misc/Utils.java index e4d91a1910..56817d6a6b 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/misc/Utils.java +++ b/runtime/Java/src/org/antlr/v4/runtime/misc/Utils.java @@ -70,6 +70,16 @@ public static String escapeWhitespace(String s, boolean escapeSpaces) { return buf.toString(); } + /** @since 4.10.2 */ + public static String escapeJSONString(String s) { + s = s.replace("\\", "\\\\"); + s = s.replace("\"", "\\\""); + s = s.replace("\n", "\\n"); + s = s.replace("\r", "\\r"); + s = s.replace("\t", "\\t"); + return s; + } + public static void writeFile(String fileName, String content) throws IOException { writeFile(fileName, content, null); } diff --git a/runtime/Java/src/org/antlr/v4/runtime/tree/JsonSerializer.java b/runtime/Java/src/org/antlr/v4/runtime/tree/JsonSerializer.java new file mode 100644 index 0000000000..de5a8abe74 --- /dev/null +++ b/runtime/Java/src/org/antlr/v4/runtime/tree/JsonSerializer.java @@ -0,0 +1,198 @@ +package org.antlr.v4.runtime.tree; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.misc.Interval; +import org.antlr.v4.runtime.misc.Utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** This "class" wraps support functions that generate JSON for parse trees. + * The JSON includes everything needed to reconstruct a parse tree: + * + * Rule names (field: "rules") + * Input chars (field: "input") + * Tokens (field: "tokens") + * Parse tree (field: "tree"; refs rule indexes and token indexes) + * + * For example, given input "99" and a simple expression grammar giving parse tree + * "(s (expr 99) EOF)", the full JSON (formatted by jq) looks like: + * + * { + * "rules": [ + * "s", + * "expr" + * ], + * "input": "99", + * "tokens": [ + * { + * "type": 3, + * "line": 1, + * "pos": 0, + * "channel": 0, + * "start": 0, + * "stop": 1 + * }, + * { + * "type": -1, + * "line": 1, + * "pos": 2, + * "channel": 0, + * "start": 2, + * "stop": 1 + * } + * ], + * "tree": { + * "0": [ + * { + * "1": [ + * 0 + * ] + * }, + * 1 + * ] + * } + * } + * + * Notice that the tree is just a series of nested references to integers, which refer to rules + * and tokens. + * + * One potential use case: Create an ANTLR server that accepts a grammar and input as parameters then + * returns JSON for the parse tree and the tokens. This can be deserialized by JavaScript in a web browser + * to display the parse result. + * + * To load and dump elements from Python 3: + * + * import json + * + * with open("/tmp/t.json") as f: + * data = f.read() + * + * data = json.loads(data) + * print(data['rules']) + * print(data['input']) + * for t in data['tokens']: + * print(t) + * print(data['tree']) + * + * @since 4.10.2 + */ +public class JsonSerializer { + /** Create a JSON representation of a parse tree and include all other information necessary to reconstruct + * a printable parse tree: the rules, input, tokens, and the tree structure that refers to the rule + * and token indexes. Extract all information from the parser, which is assumed to be in a state + * post-parse and the object that created tree t. + * + * @param t The parse tree to serialize as JSON + * @param recog The parser that created the parse tree and is in the post-recognition state + * @return JSON representing the parse tree + */ + public static String toJSON(Tree t, Parser recog) { + String[] ruleNames = recog != null ? recog.getRuleNames() : null; + if ( t==null || ruleNames==null ) { + return null; + } + TokenStream tokenStream = recog.getInputStream(); + CharStream inputStream = tokenStream.getTokenSource().getInputStream(); + return toJSON(t, Arrays.asList(ruleNames), tokenStream, inputStream); + } + + /** Create a JSON representation of a parse tree and include all other information necessary to reconstruct + * a printable parse tree: the rules, input, tokens, and the tree structure that refers to the rule + * and token indexes. The tree and rule names are required but the token stream and input stream are optional. + */ + public static String toJSON(Tree t, + final List ruleNames, + final TokenStream tokenStream, + final CharStream inputStream) + { + if ( t==null || ruleNames==null ) { + return null; + } + + StringBuilder buf = new StringBuilder(); + buf.append("{"); + buf.append("\"rules\":[\""); + buf.append(String.join("\",\"", ruleNames)); + buf.append("\"],"); + + if ( inputStream!=null ) { + Interval allchar = Interval.of(0, inputStream.size() - 1); + String input = inputStream.getText(allchar); + input = Utils.escapeJSONString(input); + buf.append("\"input\":\""); + buf.append(input); + buf.append("\","); + } + + if ( tokenStream!=null ) { + List tokenStrings = new ArrayList<>(); + for (int i = 0; i < tokenStream.size(); i++) { + Token tok = tokenStream.get(i); + String s = String.format("{\"type\":%d,\"line\":%d,\"pos\":%d,\"channel\":%d,\"start\":%d,\"stop\":%d}", + tok.getType(), tok.getLine(), tok.getCharPositionInLine(), tok.getChannel(), + tok.getStartIndex(), tok.getStopIndex()); + tokenStrings.add(s); + } + buf.append("\"tokens\":["); + buf.append(String.join(",", tokenStrings)); + buf.append("],"); + } + + String tree = toJSONTree(t); + buf.append("\"tree\":"); + buf.append(tree); + buf.append("}"); + + return buf.toString(); + } + + /** Create a JSON representation of a parse tree. The tree is just a series of nested references + * to integers, which refer to rules and tokens. + */ + public static String toJSONTree(final Tree t) { + StringBuilder buf = new StringBuilder(); + if ( t.getChildCount()==0 ) { + return getJSONNodeText(t); + } + buf.append("{"); + buf.append(getJSONNodeText(t)); + buf.append(":["); + for (int i = 0; i0 ) buf.append(','); + buf.append(toJSONTree(t.getChild(i))); + } + buf.append("]"); + buf.append("}"); + return buf.toString(); + } + + /** Create appropriate JSON text for a tree node */ + public static String getJSONNodeText(Tree t) { + if ( t instanceof RuleContext) { + int ruleIndex = ((RuleContext)t).getRuleContext().getRuleIndex(); + int altNumber = ((RuleContext) t).getAltNumber(); + if ( altNumber!= ATN.INVALID_ALT_NUMBER ) { + return String.format("\"%d:%d\"",ruleIndex,altNumber); + } + return String.format("\"%d\"",ruleIndex); + } + else if ( t instanceof ErrorNode) { + Token symbol = ((TerminalNode)t).getSymbol(); + if (symbol != null) { + return "{\"error\":\"" + symbol.getText() + "\"}"; + } + return "{\"error\":\""+t.getPayload().toString()+"\"}"; + } + else if ( t instanceof TerminalNode) { + Token symbol = ((TerminalNode)t).getSymbol(); + if (symbol != null) { + return String.valueOf(symbol.getTokenIndex()); + } + return "-1"; + } + return ""; + } +} diff --git a/runtime/Java/src/org/antlr/v4/runtime/tree/Trees.java b/runtime/Java/src/org/antlr/v4/runtime/tree/Trees.java index bf6531b7aa..b114a6c485 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/tree/Trees.java +++ b/runtime/Java/src/org/antlr/v4/runtime/tree/Trees.java @@ -6,11 +6,7 @@ package org.antlr.v4.runtime.tree; -import org.antlr.v4.runtime.CommonToken; -import org.antlr.v4.runtime.Parser; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.RuleContext; -import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.atn.ATN; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.misc.Predicate;