ANTLR 4中用于while循环的访问者/监听器代码

7
迄今为止,我已经在整本《The Definitive ANTLR 4 Reference》书籍以及许多网站上搜索了答案,但是仍然找不到有关这个主题的任何信息。
我正在使用ANTLR 4创建C语言的子集,该子集执行一些基本功能。我不知道如何在我的Visitor类中实现一个简单的while循环。到目前为止,在我的语法中,我得到了以下内容:
grammar Expr;

prog:   stat+ ;

stat:   expr NEWLINE                # printExpr
    |   ID '=' expr NEWLINE         # assign
    |   loop NEWLINE                # whileLoop
    |   relational NEWLINE          # relat
    |   NEWLINE                     # blank 
    ;

expr:   expr op=('*'|'/') expr      # MulDiv
    |   expr op=('+'|'-') expr      # AddSub
    |   INT                         # int
    |   ID                          # id
    |   '(' expr ')'                # parens
    ;

relational:     expr op=(GREATER|LESS) expr     # GreaterEqual
          ;

loop:   'while' '('relational')' NEWLINE? '{'stat*'}'   #while
    ;

GREATER : '>' ;
LESS : '<' ;
MUL :   '*' ; // assigns token name to '*' used above in grammar
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace

这样我就可以在 while 循环中有多个语句了。我的 Visitor 类看起来像这样:
public class EvalVisitor extends ExprBaseVisitor<Integer> {

/** "memory" for our calculator; variable/value pairs go here */
Map<String, Integer> memory;

public EvalVisitor() {
    memory = new HashMap<String, Integer>();
}

/** ID '=' expr NEWLINE */
@Override
public Integer visitAssign(ExprParser.AssignContext ctx) {      
    String id = ctx.ID().getText();  // id is left-hand side of '='
    int value = super.visit(ctx.expr());   // compute value of expression on right        
    memory.put(id, value);           // store it in our memory
    return value;
}

/** expr NEWLINE */
@Override
public Integer visitPrintExpr(ExprParser.PrintExprContext ctx) {
    Integer value = super.visit(ctx.expr()); // evaluate the expr child
    System.out.println(value);         // print the result
    return 0;                          // return dummy value
}

/** INT */
@Override
public Integer visitInt(ExprParser.IntContext ctx) {
    return Integer.valueOf(ctx.INT().getText());
}

/** ID */
@Override
public Integer visitId(ExprParser.IdContext ctx) {
    String id = ctx.ID().getText();
    if ( memory.containsKey(id) ) return memory.get(id);        
    return 0;
}

/** expr op=('*'|'/') expr */
@Override
public Integer visitMulDiv(ExprParser.MulDivContext ctx) {
    int left = super.visit(ctx.expr(0));  // get value of left subexpression
    int right = super.visit(ctx.expr(1)); // get value of right subexpression
    if ( ctx.op.getType() == ExprParser.MUL ) return left * right;
    return left / right; // must be DIV
}

/** expr op=('+'|'-') expr */
@Override
public Integer visitAddSub(ExprParser.AddSubContext ctx) {
    int left = super.visit(ctx.expr(0));  // get value of left subexpression
    int right = super.visit(ctx.expr(1)); // get value of right subexpression        
    if ( ctx.op.getType() == ExprParser.ADD ) return left + right;
    return left - right; // must be SUB

}

/** '(' expr ')' */
@Override
public Integer visitParens(ExprParser.ParensContext ctx) {
    return super.visit(ctx.expr()); // return child expr's value
}

@Override
public boolean visitGreaterEqual(GreaterEqualContext ctx) {
    int left = super.visit(ctx.expr(0));
    int right = super.visit(ctx.expr(1));

    if(ctx.op.getType() == ExprParser.GREATER) {
        return left > right;
    }
    else {
        return left < right;
    }

}

@Override
public Integer visitWhileLoop(WhileLoopContext ctx) {

    if(visit(ctx.getRuleContext())) {

    }

    return super.visitWhileLoop(ctx);
}

我从书中学习ANTLR 4,所以大部分访问者类的代码都是来自于书中。除了while循环语法之外,Terence Parr所写的书籍中似乎没有提及如何为简单的While循环实现任何访问者、监听器或操作。请有经验的人帮我编写While循环的Visitor/Listener Java代码吗?


嗨,这是来自《语言实现模式》的更多内容:http://www.amazon.com/Language-Implementation-Patterns-Domain-Specific-Programming/dp/193435645X/ref=pd_bxgy_b_img_y - Terence Parr
@TheANTLRGuy我有那本书,快速翻了一下,想看看是否有实现while循环的方法,但令人遗憾的是没有。 - Greg
你看过叫做基于树的解释器的模式吗?在第230页。我认为它并没有特别针对while循环,但它确实实现了更为复杂的Colin return。它清晰地展示了如何通过一棵树移动虚拟程序计数器。 - Terence Parr
1个回答

12
我很难相信,在Terence Parr写的书中,除了while循环的语法之外,没有提到如何实现任何访问者/监听器或操作。

那是因为ANTLR参考手册主要关注解析,而不是解析之后的阶段。

您不能这样做:

@Override
public boolean visitGreaterEqual(GreaterEqualContext ctx) {
    ...
您已经声明您的访问者返回 Integer,因此每个规则都应该返回这种类型。可以创建一个自定义包装器 Value 来封装您语言的值(数字、字符串、布尔值),或者当关系表达式为假时只返回 0:
@Override
public Integer visitGreaterEqual(ExprParser.GreaterEqualContext ctx) {
    int left = this.visit(ctx.expr(0));
    int right = this.visit(ctx.expr(1));

    if (ctx.op.getType() == ExprParser.GREATER) {
        return left > right ? 1 : 0; // 0 is false (all other values are true)
    }
    else {
        return left < right ? 1 : 0;
    }
}

那么,您可以按照以下方式编写您的while

@Override
public Integer visitWhile(ExprParser.WhileContext ctx) {

    // Evaluate the relational expression and continue the while
    // loop as long as it is true (does not equal zero).
    while (this.visit(ctx.relational()) != 0) {

        // Evaluate all statements inside the while loop.
        for (ExprParser.StatContext stat : ctx.stat()) {
            this.visit(stat);
        }
    }

    // 0 now also is false, so maybe return null instead which would be
    // some sort of VOID value (or make a proper Value class).
    return 0;
}

请注意,以上代码在嵌套while语句时将失败,因为内部的while会返回0,从而导致外部的while停止循环。在这种情况下,最好创建一个自定义的Value类,并引入一些不会导致循环停止的Value.VOID实例。

运行以下主方法:

public static void main(String[] args) throws Exception {

    String expression = "n = 1\n" +
            "while (n < 10) {\n" +
            "  n\n" +
            "  n = n + 1\n" +
            "}\n";
    ExprLexer lexer = new ExprLexer(new ANTLRInputStream(expression));
    ExprParser parser = new ExprParser(new CommonTokenStream(lexer));
    new EvalVisitor().visit(parser.prog());
}

将会输出:

1
2
3
4
5
6
7
8
9

还可以查看这个演示语言,它使用ANTLR4和ifwhile语句,以及自定义的Value对象:https://github.com/bkiers/Mu


非常感谢您,Bart。您的解决方案非常有效!不过我有一个问题——我正在努力理解访问者方法中所做的事情,这使得所有这些都能够工作。以您对if语句的实现为例:您正在将所有条件及其代码块收集到列表中。然后,您检查哪些条件为真,以便您可以访问与该特定条件相对应的语句块——这就是为什么您合并了条件和语句块,以便轻松定位应运行的块。我的理解正确吗? - Greg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接