Once grammar is complete, what's the best way to walk an ANTLR v4 tree?

前端 未结 1 1143
被撕碎了的回忆
被撕碎了的回忆 2020-12-07 11:52

Goal

I\'m working on a project to create a Varscoper for Coldfusion CFscript. Basically, this means checking through source code files to ensure tha

相关标签:
1条回答
  • 2020-12-07 12:23

    I wouldn't walk this manually if I were you. After generating a lexer and parser, ANTLR would also have generated a file called CfscriptBaseListener that has empty methods for all of your parser rules. You can let ANTLR walk your tree and attach a custom tree-listener in which you override only those methods/rules you're interested in.

    In your case, you probably want to be notified whenever a new function is created (to create a new scope) and you'll probably be interested in variable assignments (variableStatement and nonVarVariableStatement). Your listener, let's call is VarListener will keep track of all scopes as ANTLR walks the tree.

    I did change 1 rule slightly (I added objectLiteralEntry):

    objectLiteral
        : '{' (objectLiteralEntry (',' objectLiteralEntry)*)? '}'
        ;
    
    objectLiteralEntry
        : Identifier '=' expression
        ;
        

    which makes life easier in the following demo:

    VarListener.java

    public class VarListener extends CfscriptBaseListener {
    
        private Stack<Scope> scopes;
    
        public VarListener() {
            scopes = new Stack<Scope>();
            scopes.push(new Scope(null));
        } 
    
        @Override
        public void enterVariableStatement(CfscriptParser.VariableStatementContext ctx) {
            String varName = ctx.variableName().getText();
            Scope scope = scopes.peek();
            scope.add(varName);
        }
    
        @Override
        public void enterNonVarVariableStatement(CfscriptParser.NonVarVariableStatementContext ctx) {
            String varName = ctx.variableName().getText();
            checkVarName(varName);
        }
    
        @Override
        public void enterObjectLiteralEntry(CfscriptParser.ObjectLiteralEntryContext ctx) {
            String varName = ctx.Identifier().getText();
            checkVarName(varName);
        }
    
        @Override
        public void enterFunctionDeclaration(CfscriptParser.FunctionDeclarationContext ctx) {
            scopes.push(new Scope(scopes.peek()));
        }
    
        @Override
        public void exitFunctionDeclaration(CfscriptParser.FunctionDeclarationContext ctx) {
            scopes.pop();        
        }
    
        private void checkVarName(String varName) {
            Scope scope = scopes.peek();
            if(scope.inScope(varName)) {
                System.out.println("OK   : " + varName);
            }
            else {
                System.out.println("Oops : " + varName);
            }
        }
    }
    

    A Scope object could be as simple as:

    Scope.java

    class Scope extends HashSet<String> {
    
        final Scope parent;
    
        public Scope(Scope parent) {
            this.parent = parent;
        }
    
        boolean inScope(String varName) {
            if(super.contains(varName)) {
                return true;
            }
            return parent == null ? false : parent.inScope(varName);
        }
    }
    

    Now, to test this all, here's a small main class:

    Main.java

    import org.antlr.v4.runtime.*;
    import org.antlr.v4.runtime.tree.*;
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
    
            CfscriptLexer lexer = new CfscriptLexer(new ANTLRFileStream("Test.cfc"));
            CfscriptParser parser = new CfscriptParser(new CommonTokenStream(lexer));
            ParseTree tree = parser.component();
            ParseTreeWalker.DEFAULT.walk(new VarListener(), tree);
        }
    }
    

    If you run this Main class, the following will be printed:

    Oops : testing
    Oops : testingagain
    OK   : test
    Oops : mystuff
    Oops : interior
    Oops : third
    Oops : other
    Oops : something

    Without a doubt, this is not exactly what you want and I probably goofed up some scoping rules of Coldfusion. But I think this will give you some insight in how to solve your problem properly. I think the code is pretty self explanatory, but if this is not the case, don't hesitate to ask for clarification.

    HTH

    0 讨论(0)
提交回复
热议问题