Visitor Calculator
我们将以访问者模式做一个计算器。111
预期效果
输入
:
193
a=5
b=6
a+b*2
(1+2)*3
输出
:
193
17
9
PS:每次操作都需要换行
,输入’!'可以重置
标识符对应的数值。
语法文件
Visitor模式:通过在语法规则的每条分支后加上 # Identifier
(注意不能和规则名冲突)这样类似标签
的形式。
使得对于每种输入我们都有不同的处理方法,后续会介绍如何定义这些处理方法。
此外分享一下在设计clear也就是清零语法时的一些心得:
- 文中采用的是,输入
!
时,自动释放标识符与数值的所有对应,并且输出clear并且换行。但一开始考虑的是输入字符串clear
,清零后自动换行。放弃这种方法的理由是:clear字符串本身会先被expr规则
中的ID识别出来。如果要在ID的visit处理函数
中识别clear,又会因为返回值
必须是Integar类型而矛盾,最后放弃了输入clear这个方法。 - 要注意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());
}