Search code examples
javaparsingantlrantlr4

ANTLR How to specify which listener (or lexer/parser) I want to be used based on input on my multi-grammar structure?


MainLangFile.g4

grammar MainLangFile;

import Child1LangFile, Child2LangFile;

parse:
    A (child1 | child2) EOF
;

A: 'A';
B: 'B';
C: 'C';

Child1LangFile.g4

grammar Child1LangFile;

child1:
    CHILD1
;

CHILD1: 'CHILD1';

Child2LangFile.g4

grammar Child2LangFile;

child2:
    CHILD2
;

CHILD2: 'CHILD2';

My Java application:

public static class MyApp {

    public void execute(String query) {

        MainLangFileLexer lexer = new MainLangFileLexer(CharStreams.fromString(query));
        MainLangFileParser parser = new MainLangFileParser(new CommonTokenStream(lexer));
        MainLangFileParser.ParseContext parse = parser.parse();

        ParseTreeWalker walker = new ParseTreeWalker();
        walker.walk(new Child1Listener(), parse);
        walker.walk(new Child2Listener(), parse);
    }

    static class Child1Listener extends MainLangFileBaseListener {

        @Override
        public void enterChild1(MainLangFileParser.Child1Context ctx) {
        }
    }


    static class Child2Listener extends MainLangFileBaseListener {

        @Override
        public void enterChild2(MainLangFileParser.Child2Context ctx) {
        }
    }
}

I'm trying to have a multi-grammar structure. The solution I've above works perfectly in the sense that each ChildXListener is properly invoked based on the query. It would be the same as just having a single listener and implementing both methods.

The issue begins where there is a enterCommon() method invoked, it is invoked for both listeners even though I "forked" the language and went only on one specific direction.

I need a clever way of selecting which listener should be used by ParseTreeWalker.

Also, I'd rather have each listener, extending their respective grammars, i.e.: Child1Listener should extends Child1LangFileBaseListener, by doing so I have a clear scope of what methods I can safely override as only Child1LangFile.g4 rules will be available.

I couldn't figure out a way of doing this without initializing Child1Lexer, Child1Parser and parsing everything again, either way, this brings me back to the previous issue, how do I know which lexer/parser to use?

Any thoughts? Thanks in advance.


Solution

  • There is no way to tell up front which parser rule(s) are going to be invoked for a given input source.

    Since a visitor will not cause the entire parse tree to be visited (you determine that by overriding only the method you wish to visit), you could start off by writing a visitor that can be used up until the rule that contains the (child1 | child2) and inside that visit... method, invoke the specific listener.

    That could look something like this:

    class MainVisitor extends MainLangFileBaseVisitor<Object> {
    
        @Override
        public Object visitParse(MainLangFileParser.ParseContext ctx) {
    
            if (ctx.child1() != null) {
                ParseTreeWalker.DEFAULT.walk(new Child1Listener(), ctx);
            }
            else {
                ParseTreeWalker.DEFAULT.walk(new Child2Listener(), ctx);
            }
    
            return null;
        }
    }
    
    class Child1Listener extends Child1LangFileBaseListener {
        // ...
    }
    
    class Child2Listener extends Child2LangFileBaseListener {
        // ...
    }