Search code examples
antlr4visitor-pattern

ANTLR Visitor of a rule with alternatives


I've a grammar rule like this:

fctDecl
    : id AS FUNCTION O_PAR (argDecl (COMMA argDecl)*)? C_PAR COLON (scalar|VOID)
    (DECLARE LOCAL (varDecl SEMICOLON)+)?
    DO (instruction)+ (RETURN id)? DONE;

When i'm in the visitFctDecl, I have to visit all children of FctDecl. As children can be different type of rule, how can I know which is the type of the current child?

@Override
public FunctionNode visitFctDecl(B314Parser.FctDeclContext ctx) {

    for (int i = 0; i < ctx.children.size() ;i++)
    {
       //what kind of rule is ?
       ctx.children.get(i);
    }

    return null;
} 

I'm not sure at all how to use visitor as it should be.


Solution

  • Start by chopping up that big parser rule. Something like this perhaps:

    fctDecl
      : id AS FUNCTION fctParams COLON ( scalar | VOID ) localDecl doStat
      ;
    
    fctParams
     : O_PAR ( argDecl ( COMMA argDecl )* )? C_PAR
     ;
    
    localDecl
     : ( DECLARE LOCAL ( varDecl SEMICOLON )+ )?
     ;
    
    doStat
     : DO instruction+ ( RETURN id )? DONE
     ;
    

    and then in your visitor, you simply return custom classes like this (I'm returning Maps and Lists, but that could be your own domain objects):

    public class DemoVisitor extends B314BaseVisitor<Object> {
    
      // fctDecl
      //  : id AS FUNCTION fctParams COLON ( scalar | VOID ) localDecl doStat
      //  ;
      @Override
      public Object visitFctDecl(B314Parser.FctDeclContext ctx) {
        Map<String, Object> result = new LinkedHashMap<String, Object>();
        result.put("id", ctx.id().getText());
        result.put("fctParams", visit(ctx.fctParams()));
        result.put("type", ctx.scalar() != null ? ctx.scalar().getText() : ctx.VOID().getText());
        result.put("localDecl", visit(ctx.localDecl()));
        result.put("doStat", visit(ctx.doStat()));
        return result;
      }
    
      // fctParams
      //  : O_PAR ( argDecl ( COMMA argDecl )* )? C_PAR
      //  ;
      //
      // argDecl
      //  : varDecl
      //  ;
      //
      // varDecl
      //  : ID AS type
      //  ;
      @Override
      public Object visitFctParams(B314Parser.FctParamsContext ctx) {
        List<Map<String, String>> declarations = new ArrayList<Map<String, String>>();
        if (ctx.argDecl() != null) {
          for (B314Parser.ArgDeclContext argDecl : ctx.argDecl()) {
            Map<String, String> declaration = new LinkedHashMap<String, String>();
            declaration.put(argDecl.varDecl().ID().getText(), argDecl.varDecl().type().getText());
            declarations.add(declaration);
          }
        }
        return declarations;
      }
    
      // localDecl
      //  : ( DECLARE LOCAL ( varDecl SEMICOLON )+ )?
      //  ;
      @Override
      public Object visitLocalDecl(B314Parser.LocalDeclContext ctx) {
        // See `visitFctParams(...)` how to traverse `( varDecl SEMICOLON )+`
        return "TODO";
      }
    
      // doStat
      //  : DO instruction+ ( RETURN id )? DONE
      //  ;
      @Override
      public Object visitDoStat(B314Parser.DoStatContext ctx) {
        // See `visitFctParams(...)` how to traverse `instruction+`
        return "TODO";
      }
    }
    

    If you now run the following class that uses the visitor above:

    public class Main {
    
      public static void main(String[] args) {
    
        String source =
            "mu as function(i as integer, b as boolean): integer\n" +
            "declare local x as square; y as square;\n" +
            "do\n" +
            "  skip\n" +
            "done";
    
        B314Lexer lexer = new B314Lexer(CharStreams.fromString(source));
        B314Parser parser = new B314Parser(new CommonTokenStream(lexer));
        ParseTree fctDeclTree = parser.fctDecl();
        Object result = new DemoVisitor().visit(fctDeclTree);
        System.out.printf("source:\n\n%s\n\nresult:\n\n%s\n", source, result);
      }
    }
    

    the following will be printed on your console:

    source:
    
    mu as function(i as integer, b as boolean): integer
    declare local x as square; y as square;
    do
      skip
    done
    
    result:
    
    {id=mu, fctParams=[{i=integer}, {b=boolean}], type=integer, localDecl=TODO, doStat=TODO}