ANTLR4(二) Vistor Listener

26 阅读3分钟

Visitor Calculator

我们将以访问者模式做一个计算器。111

预期效果

输入:

193
a=5
b=6
a+b*2
(1+2)*3

输出

193
17
9

PS:每次操作都需要换行,输入’!'可以重置标识符对应的数值。

20200828163908577.png

语法文件

Visitor模式:通过在语法规则的每条分支后加上 # Identifier (注意不能和规则名冲突)这样类似标签的形式。
使得对于每种输入我们都有不同的处理方法,后续会介绍如何定义这些处理方法。

此外分享一下在设计clear也就是清零语法时的一些心得:

  1. 文中采用的是,输入时,自动释放标识符与数值的所有对应,并且输出clear并且换行。但一开始考虑的是输入字符串clear,清零后自动换行。放弃这种方法的理由是:clear字符串本身会先被expr规则中的ID识别出来。如果要在ID的visit处理函数中识别clear,又会因为返回值必须是Integar类型而矛盾,最后放弃了输入clear这个方法。
  2. 要注意CLEAR后必须要跟NEWLINE,因为每一行都需要以回车或者换行结束操作。
//LabeledExpr.g4
grammar LabeledExpr; 

prog:   stat+ ;

stat:   expr NEWLINE                # printExpr
    |   ID '=' expr NEWLINE         # assign
    |   CLEAR NEWLINE               # clearMemory
    |   NEWLINE                     # blank
    ;

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

CLEAR :   '!' ;
MUL :   '*' ; 
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

生成代码

使用IDEA插件生成,可以参照上一节生成。

重写Visitor

观察以下代码我们可以发现,Visitor生成的处理函数命名为 visitor+标签名

理论上,我们需要重写每个分支处理函数来应对不同的输入情况。

有一句格外的显眼:visit(ctx.expr())。

我们不管括号内的expr(),实际上是左循环的token,为了获取子分支的情况,我们需要显式地调用visit()。

visitPrintExpr为例,这句语法的目的是打印出表达式的结果。而表达式本身有多种情况,可以是INT、标识符、嵌套表达式,但是我们

不需要在这个分支中去操心这些,上文说过我们需要重写每个子分支的处理函数,因此在这里只需要通过visit得到expr的值就可以了。

而在下面的visitInt、visitId等分支处理函数中,最后返回了处理过后的值

因此我们以这样一种由上而下的方式,满足了各种输入的情况。

/** "memory" for our calculator; variable/value pairs go here */
    Map<String, Integer> memory = new HashMap<String, Integer>();

    @Override
    public Integer visitProg(LabeledExprParser.ProgContext ctx) {
        return super.visitProg(ctx);
    }


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


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

    /** CLEAR MEMORY */
    @Override
    public Integer visitClearMemory(LabeledExprParser.ClearMemoryContext ctx) {
        memory.clear();
        System.out.println("clear");
        return 0;
    }

    @Override
    public Integer visitBlank(LabeledExprParser.BlankContext ctx) {
        return super.visitBlank(ctx);
    }

    @Override
    public Integer visitParens(LabeledExprParser.ParensContext ctx) {
        return super.visitParens(ctx);
    }

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

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

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

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