首页 文章

如果ANTLR中的/ else语句使用侦听器

提问于
浏览
38

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

现在我想实现if / else语句,但是我不确定在使用监听器模式时这些实际上是可以实现的,因为ANTLR决定在使用监听器时遍历解析树的顺序,我想是if /的实现else语句将要求在解析树周围跳转,具体取决于语句中的条件是否满足 .

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

1 回答

  • 88

    默认情况下,ANTLR 4会生成侦听器 . 但是如果你给命令行参数 -visitor org.antlr.v4.Tool ,ANTLR会为你生成访问者类 . 这些工作与听众非常相似,但可以让您更好地控制走路/访问哪些(子)树 . 如果要排除某些(子)树(如else / if块,如您的情况),这将特别有用 . 虽然这可以使用侦听器来完成,但它需要引入全局变量来跟踪是否需要评估(子)树,哪些不需要 .

    碰巧的是,我还没有完成,但是我将发布一个小的工作示例,演示如何使用这些访问者类和 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<T> . 这是我们将用自己的逻辑扩展的类:

    EvalVisitor.java

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

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

    Value.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() != value.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,在评论中:

    对于那些使用jdk1.8并遇到IndexOutOfBoundsException的人,antlr 4.0在某种程度上与jdk1.8不兼容 . 下载antlr-4.6-complete.jar,并用<assoc = right> expr替换expr POW <assoc = right> expr POW expr将消除错误和警告 .

相关问题