【问题标题】:If/else statements in ANTLR using listenersANTLR 中使用监听器的 if/else 语句
【发布时间】:2021-12-17 11:27:46
【问题描述】:

我正在为一个学校项目创建一种简单的编程语言。我正在使用 ANTLR 4 从我的语法中生成词法分析器和解析器。到目前为止,我一直在使用 ANTLR 侦听器模式来应用编程语言的实际功能。

现在我想实现 if/else 语句,但我不确定在使用侦听器模式时是否可以实际实现这些语句,因为 ANTLR 决定在使用侦听器时遍历解析树的顺序,我想实现的 if/else 语句将需要根据语句中满足的条件在解析树中跳转。

谁能告诉我是否可以使用 ANTLR 实现 if/else 语句,或者我是否必须自己实现访问者模式?另外,谁能给出一个非常简单的实现语句的例子吗?

【问题讨论】:

    标签: java listener antlr antlr4


    【解决方案1】:

    默认情况下,ANTLR 4 会生成侦听器。但是如果你给org.antlr.v4.Tool 提供命令行参数-visitor,ANTLR 会为你生成访问者类。这些工作很像侦听器,但让您可以更好地控制遍历/访问哪些(子)树。如果您想排除某些(子)树(如 else/if 块,如您的情况),这将特别有用。虽然这可以使用侦听器来完成,但使用访问者来做这件事要干净得多。使用侦听器,您需要引入全局变量来跟踪是否需要评估(子)树,而哪些不需要。

    事实上,我正在编写一个小型 ANTLR 4 教程。它还没有完成,但我将发布一个小型工作示例,演示这些访问者类和 if 语句构造的使用。


    1。语法

    这是一个支持基本表达式的简单语法,if-、while- 和 log-statements:

    Mu.g4

    grammar Mu;
    
    parse
     : block EOF
     ;
    
    block
     : stat*
     ;
    
    stat
     : assignment
     | if_stat
     | while_stat
     | log
     | OTHER {System.err.println("unknown char: " + $OTHER.text);}
     ;
    
    assignment
     : ID ASSIGN expr SCOL
     ;
    
    if_stat
     : IF condition_block (ELSE IF condition_block)* (ELSE stat_block)?
     ;
    
    condition_block
     : expr stat_block
     ;
    
    stat_block
     : OBRACE block CBRACE
     | stat
     ;
    
    while_stat
     : WHILE expr stat_block
     ;
    
    log
     : LOG expr SCOL
     ;
    
    expr
     : expr POW<assoc=right> expr           #powExpr
     | MINUS expr                           #unaryMinusExpr
     | NOT expr                             #notExpr
     | expr op=(MULT | DIV | MOD) expr      #multiplicationExpr
     | expr op=(PLUS | MINUS) expr          #additiveExpr
     | expr op=(LTEQ | GTEQ | LT | GT) expr #relationalExpr
     | expr op=(EQ | NEQ) expr              #equalityExpr
     | expr AND expr                        #andExpr
     | expr OR expr                         #orExpr
     | atom                                 #atomExpr
     ;
    
    atom
     : OPAR expr CPAR #parExpr
     | (INT | FLOAT)  #numberAtom
     | (TRUE | FALSE) #booleanAtom
     | ID             #idAtom
     | STRING         #stringAtom
     | NIL            #nilAtom
     ;
    
    OR : '||';
    AND : '&&';
    EQ : '==';
    NEQ : '!=';
    GT : '>';
    LT : '<';
    GTEQ : '>=';
    LTEQ : '<=';
    PLUS : '+';
    MINUS : '-';
    MULT : '*';
    DIV : '/';
    MOD : '%';
    POW : '^';
    NOT : '!';
    
    SCOL : ';';
    ASSIGN : '=';
    OPAR : '(';
    CPAR : ')';
    OBRACE : '{';
    CBRACE : '}';
    
    TRUE : 'true';
    FALSE : 'false';
    NIL : 'nil';
    IF : 'if';
    ELSE : 'else';
    WHILE : 'while';
    LOG : 'log';
    
    ID
     : [a-zA-Z_] [a-zA-Z_0-9]*
     ;
    
    INT
     : [0-9]+
     ;
    
    FLOAT
     : [0-9]+ '.' [0-9]* 
     | '.' [0-9]+
     ;
    
    STRING
     : '"' (~["\r\n] | '""')* '"'
     ;
    
    COMMENT
     : '#' ~[\r\n]* -> skip
     ;
    
    SPACE
     : [ \t\r\n] -> skip
     ;
    
    OTHER
     : . 
     ;
    

    现在假设您想像这样解析和评估输入:

    test.mu

    a = true;
    b = false;
    
    if a && b {
      log "1 :: a=" + a +", b=" + b;
    }
    else if a || b {
      log "2 :: a=" + a +", b=" + b;
    }
    else {
      log "3 :: a=" + a +", b=" + b;
    }
    
    log "Done!";
    

    2。访客我

    首先生成解析器和访问者类:

    java -cp antlr-4.0-complete.jar org.antlr.v4.Tool Mu.g4 -visitor
    

    上面的命令会生成文件MuBaseVisitor&lt;T&gt;。这是我们要在没有自己的逻辑的情况下扩展的类:

    EvalVisitor.java

    public class EvalVisitor extends MuBaseVisitor<Value> {
        // ...
    }
    

    其中Value 只是我们任何语言类型的包装器(StringBooleanDouble):

    值.java

    public class Value {
    
        public static Value VOID = new Value(new Object());
    
        final Object value;
        
        public Value(Object value) {
            this.value = value;
        }
    
        public Boolean asBoolean() {
            return (Boolean)value;
        }
    
        public Double asDouble() {
            return (Double)value;
        }
    
        public String asString() {
            return String.valueOf(value);
        }
    
        public boolean isDouble() {
            return value instanceof Double;
        }
    
        @Override
        public int hashCode() {
    
            if(value == null) {
                return 0;
            }
    
            return this.value.hashCode();
        }
    
        @Override
        public boolean equals(Object o) {
    
            if(value == o) {
                return true;
            }
    
            if(value == null || o == null || o.getClass() != this.getClass()) {
                return false;
            }
    
            Value that = (Value)o;
    
            return this.value.equals(that.value);
        }
    
        @Override
        public String toString() {
            return String.valueOf(value);
        }
    }
    

    3。测试我

    要测试这些类,请使用以下Main 类:

    Main.java

    import org.antlr.v4.runtime.ANTLRFileStream;
    import org.antlr.v4.runtime.CommonTokenStream;
    import org.antlr.v4.runtime.tree.ParseTree;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            MuLexer lexer = new MuLexer(new ANTLRFileStream("test.mu"));
            MuParser parser = new MuParser(new CommonTokenStream(lexer));
            ParseTree tree = parser.parse();
            EvalVisitor visitor = new EvalVisitor();
            visitor.visit(tree);
        }
    }
    

    并编译并运行源文件:

    javac -cp antlr-4.0-complete.jar *.java
    java -cp .:antlr-4.0-complete.jar Main
    

    (在 Windows 上,最后一个命令是:java -cp .;antlr-4.0-complete.jar Main

    运行Main 后,什么也没有发生(当然?)。这是因为我们没有在 EvalVisitor 类中实现任何规则。为了能够正确评估文件test.mu,我们需要为以下规则提供正确的实现:

    • if_stat
    • andExpr
    • orExpr
    • plusExpr
    • assignment
    • idAtom
    • booleanAtom
    • stringAtom
    • log

    #4。访客 II & 测试 II

    以下是这些规则的实现:

    import org.antlr.v4.runtime.misc.NotNull;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class EvalVisitor extends MuBaseVisitor<Value> {
    
        // used to compare floating point numbers
        public static final double SMALL_VALUE = 0.00000000001;
    
        // store variables (there's only one global scope!)
        private Map<String, Value> memory = new HashMap<String, Value>();
    
        // assignment/id overrides
        @Override
        public Value visitAssignment(MuParser.AssignmentContext ctx) {
            String id = ctx.ID().getText();
            Value value = this.visit(ctx.expr());
            return memory.put(id, value);
        }
    
        @Override
        public Value visitIdAtom(MuParser.IdAtomContext ctx) {
            String id = ctx.getText();
            Value value = memory.get(id);
            if(value == null) {
                throw new RuntimeException("no such variable: " + id);
            }
            return value;
        }
    
        // atom overrides
        @Override
        public Value visitStringAtom(MuParser.StringAtomContext ctx) {
            String str = ctx.getText();
            // strip quotes
            str = str.substring(1, str.length() - 1).replace("\"\"", "\"");
            return new Value(str);
        }
    
        @Override
        public Value visitNumberAtom(MuParser.NumberAtomContext ctx) {
            return new Value(Double.valueOf(ctx.getText()));
        }
    
        @Override
        public Value visitBooleanAtom(MuParser.BooleanAtomContext ctx) {
            return new Value(Boolean.valueOf(ctx.getText()));
        }
    
        @Override
        public Value visitNilAtom(MuParser.NilAtomContext ctx) {
            return new Value(null);
        }
    
        // expr overrides
        @Override
        public Value visitParExpr(MuParser.ParExprContext ctx) {
            return this.visit(ctx.expr());
        }
    
        @Override
        public Value visitPowExpr(MuParser.PowExprContext ctx) {
            Value left = this.visit(ctx.expr(0));
            Value right = this.visit(ctx.expr(1));
            return new Value(Math.pow(left.asDouble(), right.asDouble()));
        }
    
        @Override
        public Value visitUnaryMinusExpr(MuParser.UnaryMinusExprContext ctx) {
            Value value = this.visit(ctx.expr());
            return new Value(-value.asDouble());
        }
    
        @Override
        public Value visitNotExpr(MuParser.NotExprContext ctx) {
            Value value = this.visit(ctx.expr());
            return new Value(!value.asBoolean());
        }
    
        @Override
        public Value visitMultiplicationExpr(@NotNull MuParser.MultiplicationExprContext ctx) {
    
            Value left = this.visit(ctx.expr(0));
            Value right = this.visit(ctx.expr(1));
    
            switch (ctx.op.getType()) {
                case MuParser.MULT:
                    return new Value(left.asDouble() * right.asDouble());
                case MuParser.DIV:
                    return new Value(left.asDouble() / right.asDouble());
                case MuParser.MOD:
                    return new Value(left.asDouble() % right.asDouble());
                default:
                    throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
            }
        }
    
        @Override
        public Value visitAdditiveExpr(@NotNull MuParser.AdditiveExprContext ctx) {
    
            Value left = this.visit(ctx.expr(0));
            Value right = this.visit(ctx.expr(1));
    
            switch (ctx.op.getType()) {
                case MuParser.PLUS:
                    return left.isDouble() && right.isDouble() ?
                            new Value(left.asDouble() + right.asDouble()) :
                            new Value(left.asString() + right.asString());
                case MuParser.MINUS:
                    return new Value(left.asDouble() - right.asDouble());
                default:
                    throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
            }
        }
    
        @Override
        public Value visitRelationalExpr(@NotNull MuParser.RelationalExprContext ctx) {
    
            Value left = this.visit(ctx.expr(0));
            Value right = this.visit(ctx.expr(1));
    
            switch (ctx.op.getType()) {
                case MuParser.LT:
                    return new Value(left.asDouble() < right.asDouble());
                case MuParser.LTEQ:
                    return new Value(left.asDouble() <= right.asDouble());
                case MuParser.GT:
                    return new Value(left.asDouble() > right.asDouble());
                case MuParser.GTEQ:
                    return new Value(left.asDouble() >= right.asDouble());
                default:
                    throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
            }
        }
    
        @Override
        public Value visitEqualityExpr(@NotNull MuParser.EqualityExprContext ctx) {
    
            Value left = this.visit(ctx.expr(0));
            Value right = this.visit(ctx.expr(1));
    
            switch (ctx.op.getType()) {
                case MuParser.EQ:
                    return left.isDouble() && right.isDouble() ?
                            new Value(Math.abs(left.asDouble() - right.asDouble()) < SMALL_VALUE) :
                            new Value(left.equals(right));
                case MuParser.NEQ:
                    return left.isDouble() && right.isDouble() ?
                            new Value(Math.abs(left.asDouble() - right.asDouble()) >= SMALL_VALUE) :
                            new Value(!left.equals(right));
                default:
                    throw new RuntimeException("unknown operator: " + MuParser.tokenNames[ctx.op.getType()]);
            }
        }
    
        @Override
        public Value visitAndExpr(MuParser.AndExprContext ctx) {
            Value left = this.visit(ctx.expr(0));
            Value right = this.visit(ctx.expr(1));
            return new Value(left.asBoolean() && right.asBoolean());
        }
    
        @Override
        public Value visitOrExpr(MuParser.OrExprContext ctx) {
            Value left = this.visit(ctx.expr(0));
            Value right = this.visit(ctx.expr(1));
            return new Value(left.asBoolean() || right.asBoolean());
        }
    
        // log override
        @Override
        public Value visitLog(MuParser.LogContext ctx) {
            Value value = this.visit(ctx.expr());
            System.out.println(value);
            return value;
        }
    
        // if override
        @Override
        public Value visitIf_stat(MuParser.If_statContext ctx) {
    
            List<MuParser.Condition_blockContext> conditions =  ctx.condition_block();
    
            boolean evaluatedBlock = false;
    
            for(MuParser.Condition_blockContext condition : conditions) {
    
                Value evaluated = this.visit(condition.expr());
    
                if(evaluated.asBoolean()) {
                    evaluatedBlock = true;
                    // evaluate this block whose expr==true
                    this.visit(condition.stat_block());
                    break;
                }
            }
    
            if(!evaluatedBlock && ctx.stat_block() != null) {
                // evaluate the else-stat_block (if present == not null)
                this.visit(ctx.stat_block());
            }
    
            return Value.VOID;
        }
    
        // while override
        @Override
        public Value visitWhile_stat(MuParser.While_statContext ctx) {
    
            Value value = this.visit(ctx.expr());
    
            while(value.asBoolean()) {
    
                // evaluate the code block
                this.visit(ctx.stat_block());
    
                // evaluate the expression
                value = this.visit(ctx.expr());
            }
    
            return Value.VOID;
        }
    }
    

    当您重新编译并运行Main 时,以下内容将打印到您的控制台:

    2 :: a=true, b=false
    Done!
    

    有关所有其他规则的实施,请参阅:https://github.com/bkiers/Mu

    编辑

    来自@pwwpche,在 cmets 中:

    对于那些使用jdk1.8并遇到IndexOutOfBoundsException,antlr 4.0 不知何故与jdk1.8不兼容。下载 antlr-4.6-complete.jar,然后 将expr POW&lt;assoc=right&gt; expr 替换为&lt;assoc=right&gt;expr POW expr 将 消除错误和警告。

    【讨论】:

    • 在某些情况下,如果您愿意包含ParseTreeWalker 的实现,控制监听器并不难。在我的项目中,我有一个 ParseTreeWalkers 类,其中包含 SINGLE_TREE 之类的字段(默认情况下不会带孩子走路的 ParseTreeWalker)。
    • 另外,查看AbstractParseTreeVisitor&lt;T&gt; 中的受保护方法。对于诸如实现布尔表达式求值器之类的事情,可以重写这些方法以实现 andor 运算符的短路求值。
    • 感谢您提供的出色示例。而对于那些使用jdk1.8并遇到IndexOutOfBoundsException的人来说,antlr 4.0不知何故与jdk1.8不兼容。下载antlr-4.6-complete.jar,将expr POW&lt;assoc=right&gt; expr 替换为&lt;assoc=right&gt;expr POW expr 即可消除错误和警告。
    • 确实——非常有用。 @BartKiers 您是否发布了您在上面的回复中提到的教程?我想知道您是否有任何建议如何向语法和访问者添加返回语句。谢谢
    • 谢谢@DanSchien,不,唉,我从来没有完成它。不过我可以推荐这个:tomassetti.me/antlr-mega-tutorial
    猜你喜欢
    • 2020-12-22
    • 2020-05-20
    • 1970-01-01
    • 1970-01-01
    • 2019-04-09
    • 2015-10-28
    相关资源
    最近更新 更多