由于最近参加的Talent Plan
,需要自己实现一个基于Raft的KV引擎,所以之前说的分布式事务的内容,还要再鸽一段时间,所以为了补偿大家,我们来学学antlr
吧,这次我们不在外部维护变量表,而是通过设置一个特殊的变量类型,由其自身来维护一个静态变量表,从而大大简化了程序逻辑,仅仅通过200行代码,就实现了一个mini版的科学计算器。Let's GO!!!
定义语法
定义语法文件,首先通过 prog: stat+ ;
允许多条语句, 然后定义规则,支持打印表达式expr NEWLINE
,表达式赋值expr NEWLINE
, 清理内存表CLEANMEM
,以及换行。
接下来定义表达式 expr
这里我们支阶乘、乘方以及普通四则运算。
语法文件如下:
grammar CalExpr;
prog: stat+ ;
stat: expr NEWLINE # printExpr| ID '=' expr NEWLINE # assign| CLEANMEM # cleanmem| NEWLINE # blank;
expr: expr '!' # fac| expr '^' expr # pow| expr op=('*'|'/') expr # MulDiv| expr op=('+'|'-') expr # AddSub| NUMBER # number| ID # id| '(' expr ')' # parens;
CLEANMEM: 'CLEANMEM';
FAC : '!' ;
POW : '^' ;
MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
DOT : '.' ;
ID : [a-zA-Z]+ ;
NUMBER : [0-9]+ (DOT)? [0-9]*;
NEWLINE:'\r'? '\n' ;
WS : [ \t]+ -> skip ;
定义数据类型
根据我们的文法定义,我们将数据分为两类,数值和变量。由变量自己维护一个静态变量表,来维护值,从而简化处理流程。
我们定义整个数据类型的接口
package wang.datahub.datatype;
public interface IType {/*** 获取当前对象的值* */Double getValue();
/*** 设置当前值* */void setValue(Double d);
/*** 判断是否为值类型* */Boolean isValue();
/*** 是否是变量* */Boolean isVarb();
}
定义值类型
package wang.datahub.datatype;
public class CalNumber implements IType{private Double _value;
public CalNumber(String str){_value = Double.valueOf(str);}public void setValue(Double d){_value = d;}
@Overridepublic Double getValue() {return _value;}
@Overridepublic Boolean isValue() {return true;}
@Overridepublic Boolean isVarb() {return false;}
@Overridepublic String toString() {return "CalNumber{" +"_value=" + _value +'}';}
}
定义变量类型
package wang.datahub.datatype;
import java.util.Hashtable;
import java.util.Map;
public class CalVarb implements IType{private String _name;private static Map<String, CalNumber> _map = new Hashtable<>();
public CalVarb(String str){_name = str;
}
public static void cleanmem(){
// System.out.println("clean");_map.clear();_map = new Hashtable<>();}
@Overridepublic Double getValue() {
// System.out.println(_map);return _map.get(_name).getValue();}
@Overridepublic void setValue(Double d) {_map.put(_name,new CalNumber(d.toString()));}
@Overridepublic Boolean isValue() {return false;}
@Overridepublic Boolean isVarb() {return true;}
@Overridepublic String toString() {return "CalVarb{" +"_name='" + _name + '\'' +'}';}
}
遍历AST
现在有了语法文件,有了数据类型,我们只需要再完成vistor的编写,就搞定了。 那么如何来撰写vistor呢?个人建议按照语法文件的格式,从下网上实现,
本文就先完成id,number的获取,再完成赋值和打印方法,就可以通过简单测试了。
@Overridepublic T visitId(CalExprParser.IdContext ctx) {CalVarb calVarb = new CalVarb(ctx.ID().getText());return (T)calVarb;}
@Overridepublic T visitNumber(CalExprParser.NumberContext ctx) {CalNumber number = new CalNumber(ctx.getText());return (T) number;}
这里,我们就是完成变量和值类型的初始化。
@Overridepublic T visitPrintExpr(CalExprParser.PrintExprContext ctx) {IType iType = ctx.expr().accept(this);if(iType.isValue()){System.out.println(iType.getValue());}else if(iType.isPointer()){System.out.println(ctx.getText().trim()+" = "+iType.getValue());}return (T)iType;}
@Overridepublic T visitAssign(CalExprParser.AssignContext ctx) {CalVarb calVarb = new CalVarb(ctx.ID().getText());IType iType = ctx.expr().accept(this);calVarb.setValue(iType.getValue());return (T)calVarb;}
这里我们就完成了赋值和打印变量。
@Overridepublic T visitMulDiv(CalExprParser.MulDivContext ctx) {CalExprParser.ExprContext left = ctx.expr().get(0);CalExprParser.ExprContext right = ctx.expr().get(1);IType leftItype = left.accept(this);IType rightItype = right.accept(this);Double temp = 0d;if(ctx.MUL()!=null){temp = leftItype.getValue()*rightItype.getValue();}if(ctx.DIV()!=null){temp = leftItype.getValue()/rightItype.getValue();}
return (T)new CalNumber(temp.toString());}
获取左右表达式的值,并且进行运算。
完整vistor代码如下:
package wang.datahub;
import org.apache.commons.math3.util.ArithmeticUtils;
import wang.datahub.datatype.CalNumber;
import wang.datahub.datatype.CalVarb;
import wang.datahub.datatype.IType;
public class MyCalVistor<T extends IType> extends CalExprBaseVisitor<T>{
@Overridepublic T visitCleanmem(CalExprParser.CleanmemContext ctx) {CalVarb.cleanmem();return super.visitCleanmem(ctx);}
@Overridepublic T visitPrintExpr(CalExprParser.PrintExprContext ctx) {IType iType = ctx.expr().accept(this);if(iType.isValue()){System.out.println(iType.getValue());}else if(iType.isPointer()){System.out.println(ctx.getText().trim()+" = "+iType.getValue());}return (T)iType;}
@Overridepublic T visitAssign(CalExprParser.AssignContext ctx) {CalVarb calVarb = new CalVarb(ctx.ID().getText());IType iType = ctx.expr().accept(this);calVarb.setValue(iType.getValue());return (T)calVarb;}
@Overridepublic T visitFac(CalExprParser.FacContext ctx) {IType iType = ctx.expr().accept(this);Double l = ArithmeticUtils.factorialDouble(iType.getValue().intValue());return (T) new CalNumber(l.toString());}
@Overridepublic T visitPow(CalExprParser.PowContext ctx) {CalExprParser.ExprContext left = ctx.expr().get(0);CalExprParser.ExprContext right = ctx.expr().get(1);IType leftItype = left.accept(this);IType rightItype = right.accept(this);
Integer l = ArithmeticUtils.pow(leftItype.getValue().intValue(),rightItype.getValue().intValue());return (T) new CalNumber(l.toString());}
@Overridepublic T visitParens(CalExprParser.ParensContext ctx) {IType iType = ctx.expr().accept(this);return (T)iType;}
@Overridepublic T visitMulDiv(CalExprParser.MulDivContext ctx) {CalExprParser.ExprContext left = ctx.expr().get(0);CalExprParser.ExprContext right = ctx.expr().get(1);IType leftItype = left.accept(this);IType rightItype = right.accept(this);Double temp = 0d;if(ctx.MUL()!=null){temp = leftItype.getValue()*rightItype.getValue();}if(ctx.DIV()!=null){temp = leftItype.getValue()/rightItype.getValue();}
return (T)new CalNumber(temp.toString());}
@Overridepublic T visitAddSub(CalExprParser.AddSubContext ctx) {CalExprParser.ExprContext left = ctx.expr().get(0);CalExprParser.ExprContext right = ctx.expr().get(1);IType leftItype = left.accept(this);IType rightItype = right.accept(this);Double temp = 0d;if(ctx.ADD()!=null){temp = leftItype.getValue()+rightItype.getValue();}if(ctx.SUB()!=null){temp = leftItype.getValue()-rightItype.getValue();}return (T)new CalNumber(temp.toString());}
@Overridepublic T visitId(CalExprParser.IdContext ctx) {CalVarb calVarb = new CalVarb(ctx.ID().getText());return (T)calVarb;}
@Overridepublic T visitNumber(CalExprParser.NumberContext ctx) {CalNumber number = new CalNumber(ctx.getText());return (T) number;}
}
测试
我们来构建一个测试类
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import wang.datahub.CalExprLexer;
import wang.datahub.CalExprParser;
import wang.datahub.MyCalVistor;
import wang.datahub.datatype.IType;
public class Test {public static void main(String[] args) {String expr = "a=1 \n" +"b=2 \n" +"c=(a+b)/2 \n" +"c\n" +"d=a\n" +"f=d\n" +"f\n" +"11\n" +"f=a+b+c\n" +"f\n" +"CLEANMEM \n" +"a=4\n" +"a!+(2^2)+1\n";
System.out.println(expr);
CharStream stream= CharStreams.fromString(expr);CalExprLexer lexer=new CalExprLexer(stream);CalExprParser parser = new CalExprParser(new CommonTokenStream(lexer));
ParseTree parseTree = parser.prog();MyCalVistor<IType> parseTreeWalker = new MyCalVistor();
String res = parseTree.toStringTree(parser);System.out.println(res);
IType value = parseTreeWalker.visit(parseTree);System.out.println(value.getValue());}
}
执行结果,完全符合预期
a=1
b=2
c=(a+b)/2
c
d=a
f=d
f
11
f=a+b+c
f
CLEANMEM
a=4
a!+(2^2)+1
(prog (stat a = (expr 1) \n) (stat b = (expr 2) \n) (stat c = (expr (expr ( (expr (expr a) + (expr b)) )) / (expr 2)) \n) (stat (expr c) \n) (stat d = (expr a) \n) (stat f = (expr d) \n) (stat (expr f) \n) (stat (expr 11) \n) (stat f = (expr (expr (expr a) + (expr b)) + (expr c)) \n) (stat (expr f) \n) (stat CLEANMEM) (stat \n) (stat a = (expr 4) \n) (stat (expr (expr (expr (expr a) !) + (expr ( (expr (expr 2) ^ (expr 2)) ))) + (expr 1)) \n))
c = 1.5
f = 1.0
11.0
f = 4.5
29.0
29.0