Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert ANTLR parse tree to JSON

I have a working grammar and have implemented a listener (in Java). I can display the parse tree in the console with indentation however what I would like is to export it to a JSON structure so that it can be used in any generic viewer.

Is there an already made method that can do this or will I have to create the json file absolutely from scratch somehow?

Thanks!

PS: I also managed to display in Swing via the TreeView class...

like image 995
bluiska Avatar asked Jan 28 '23 13:01

bluiska


2 Answers

Is there an already made method that can do this or will I have to create the json file absolutely from scratch somehow?

As already mentioned in the comments: no, there is no such method in the core API of ANTLR. You will have to roll your own.

I have a utility method for debugging purposes that transfers an ANTLR ParseTree into a java.util.Map which can easily be transformed into a JSON object. I'm happy to share it here.

Given the following grammar:

grammar Expression;

parse
 : expr EOF
 ;

expr
 : '(' expr ')'                          #nestedExpr
 | '-' expr                              #unartyMinusExpr
 | expr ( '*' | '/' | '%' ) expr         #multExpr
 | expr ( '+' | '-' ) expr               #addExpr
 | expr ( '>' | '>=' | '<' | '<=' ) expr #compareExpr
 | expr ( '=' | '!=' ) expr              #eqExpr
 | expr AND expr                         #andExpr
 | expr OR expr                          #orExpr
 | function_call                         #functionCallExpr
 | ID                                    #idExpr
 | NUMBER                                #numberExpr
 | STRING                                #stringExpr
 ;

function_call
 : ID args
 ;

args
 : '(' ( expr ( ',' expr )* )? ')'
 ;

ADD    : '+';
MINUS  : '-';
MULT   : '*';
DIV    : '/';
MOD    : '%';
OPAR   : '(';
CPAR   : ')';
LTE    : '<=';
LT     : '<';
GTE    : '>=';
GT     : '>';
EQ     : '=';
NEQ    : '!=';
AND    : 'and';
OR     : 'or';
NUMBER : ( [0-9]* '.' )? [0-9]+;
ID     : [a-zA-Z_] [a-zA-Z_0-9]*;
STRING : '"' ~["\r\n]* '"';
NL     : '\r'? '\n' | '\r';
SPACE  : [ \t] -> skip;

and the following Main class, which handles the input (1 + 2) * 3:

public class Examples {

  private static final Gson PRETTY_PRINT_GSON = new GsonBuilder().setPrettyPrinting().create();
  private static final Gson GSON = new Gson();

  public static String toJson(ParseTree tree) {
    return toJson(tree, true);
  }

  public static String toJson(ParseTree tree, boolean prettyPrint) {
    return prettyPrint ? PRETTY_PRINT_GSON.toJson(toMap(tree)) : GSON.toJson(toMap(tree));
  }

  public static Map<String, Object> toMap(ParseTree tree) {
    Map<String, Object> map = new LinkedHashMap<>();
    traverse(tree, map);
    return map;
  }

  public static void traverse(ParseTree tree, Map<String, Object> map) {

    if (tree instanceof TerminalNodeImpl) {
      Token token = ((TerminalNodeImpl) tree).getSymbol();
      map.put("type", token.getType());
      map.put("text", token.getText());
    }
    else {
      List<Map<String, Object>> children = new ArrayList<>();
      String name = tree.getClass().getSimpleName().replaceAll("Context$", "");
      map.put(Character.toLowerCase(name.charAt(0)) + name.substring(1), children);

      for (int i = 0; i < tree.getChildCount(); i++) {
        Map<String, Object> nested = new LinkedHashMap<>();
        children.add(nested);
        traverse(tree.getChild(i), nested);
      }
    }
  }

  public static void main(String[] args) {
    String source = "(1 + 2) * 3";
    ExpressionLexer lexer = new ExpressionLexer(CharStreams.fromString(source));
    ExpressionParser parser = new ExpressionParser(new CommonTokenStream(lexer));
    System.out.println(toJson(parser.parse()));
  }
}

you will see the following printed to your console:

{
  "parse": [
    {
      "multExpr": [
        {
          "nestedExpr": [
            {
              "type": 7,
              "text": "("
            },
            {
              "addExpr": [
                {
                  "numberExpr": [
                    {
                      "type": 17,
                      "text": "1"
                    }
                  ]
                },
                {
                  "type": 2,
                  "text": "+"
                },
                {
                  "numberExpr": [
                    {
                      "type": 17,
                      "text": "2"
                    }
                  ]
                }
              ]
            },
            {
              "type": 8,
              "text": ")"
            }
          ]
        },
        {
          "type": 4,
          "text": "*"
        },
        {
          "numberExpr": [
            {
              "type": 17,
              "text": "3"
            }
          ]
        }
      ]
    },
    {
      "type": -1,
      "text": "\u003cEOF\u003e"
    }
  ]
}
like image 195
Bart Kiers Avatar answered Jan 31 '23 07:01

Bart Kiers


Translated Bart's solution to C#


public static Dictionary<String, Object> toMap(IParseTree tree, YOURPARSERHERE parser)
{
    Dictionary<String, Object> map = new Dictionary<String, Object>();
    traverse(tree, map);
    return map;
}

public static void traverse(IParseTree tree, Dictionary<String, Object> map)
{
    if (tree is TerminalNodeImpl) 
    {
        map.Add("type", tree.GetType());
        map.Add("text", tree.GetText());
    }
    else
    {
        List<Dictionary<String, Object>> children = new List<Dictionary<String,Object>>();
        String name = tree.GetType().Name.Replace("Context", "");
        map.Add(char.ToLower(name.ElementAt(0)) + name.Substring(1), children);

        for (int i = 0; i < tree.ChildCount; i++)
        {
            Dictionary<String, Object> nested = new Dictionary<string, object>();
            children.Add(nested);
            traverse(tree.GetChild(i), nested);
        }
    }
}
like image 44
spencer741 Avatar answered Jan 31 '23 07:01

spencer741