仅200行代码实现科学计算器,Antlr真是太强大了
Posted 麒思妙想
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仅200行代码实现科学计算器,Antlr真是太强大了相关的知识,希望对你有一定的参考价值。
由于最近参加的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;
@Override
public Double getValue()
return _value;
@Override
public Boolean isValue()
return true;
@Override
public Boolean isVarb()
return false;
@Override
public 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<>();
@Override
public Double getValue()
// System.out.println(_map);
return _map.get(_name).getValue();
@Override
public void setValue(Double d)
_map.put(_name,new CalNumber(d.toString()));
@Override
public Boolean isValue()
return false;
@Override
public Boolean isVarb()
return true;
@Override
public String toString()
return "CalVarb" +
"_name='" + _name + '\\'' +
'';
遍历AST
现在有了语法文件,有了数据类型,我们只需要再完成vistor的编写,就搞定了。 那么如何来撰写vistor呢?个人建议按照语法文件的格式,从下网上实现,
本文就先完成id,number的获取,再完成赋值和打印方法,就可以通过简单测试了。
@Override
public T visitId(CalExprParser.IdContext ctx)
CalVarb calVarb = new CalVarb(ctx.ID().getText());
return (T)calVarb;
@Override
public T visitNumber(CalExprParser.NumberContext ctx)
CalNumber number = new CalNumber(ctx.getText());
return (T) number;
这里,我们就是完成变量和值类型的初始化。
@Override
public 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;
@Override
public 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;
这里我们就完成了赋值和打印变量。
@Override
public 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>
@Override
public T visitCleanmem(CalExprParser.CleanmemContext ctx)
CalVarb.cleanmem();
return super.visitCleanmem(ctx);
@Override
public 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;
@Override
public 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;
@Override
public T visitFac(CalExprParser.FacContext ctx)
IType iType = ctx.expr().accept(this);
Double l = ArithmeticUtils.factorialDouble(iType.getValue().intValue());
return (T) new CalNumber(l.toString());
@Override
public 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());
@Override
public T visitParens(CalExprParser.ParensContext ctx)
IType iType = ctx.expr().accept(this);
return (T)iType;
@Override
public 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());
@Override
public 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());
@Override
public T visitId(CalExprParser.IdContext ctx)
CalVarb calVarb = new CalVarb(ctx.ID().getText());
return (T)calVarb;
@Override
public 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
与50位技术专家面对面 20年技术见证,附赠技术全景图
以上是关于仅200行代码实现科学计算器,Antlr真是太强大了的主要内容,如果未能解决你的问题,请参考以下文章