仅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真是太强大了的主要内容,如果未能解决你的问题,请参考以下文章

仅200行代码实现科学计算器,Antlr真是太强大了

仅200行代码实现科学计算器,Antlr真是太强大了

Python 和 Flask 真是太强大了

AI科学语言大模型火了,数学生物计算机样样行,编代码写综述也不在话下

200行代码实现简版react🔥

一点点魔法,驯服野生Spark