Antlr - 使用antlr4实现一个计算器,配合变量可以实现程序里的复合指标运算

开发环境准备

idea

  • 我使用idea开发,所以使用一idea作为环境参考,idea版本是

  • 打开preferences,选择plugins
    在这里插入图片描述

  • 输入antlr,没有安装过,点击下面进入repositories
    在这里插入图片描述

  • 点击install,等待一会

  • 安装完成重启idea,看此处插件的antlr版本是4.7.2
    在这里插入图片描述

项目配置

  • pom文件,配置antlr版本和插件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>test</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.6.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr4</artifactId>
            <version>4.7.2</version>
        </dependency>

        <dependency>
            <groupId>org.antlr</groupId>
            <artifactId>antlr4-runtime</artifactId>
            <version>4.7.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.antlr</groupId>
                <artifactId>antlr4-maven-plugin</artifactId>
                <version>4.7.2</version>
                <executions>
                    <execution>
                        <id>antlr</id>
                        <goals>
                            <goal>antlr4</goal>
                        </goals>
                        <phase>none</phase>
                    </execution>
                </executions>
                <configuration>
                    <outputDirectory>src/test/java</outputDirectory>
                    <listener>true</listener>
                    <treatWarningsAsErrors>true</treatWarningsAsErrors>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

antlr配置文件

  • 创建配置文件Calculate.g4
grammar Calculate;

prog : stat+;

# 定义语句
stat :expr NEWLINE        #printExpr
    |ID '=' expr NEWLINE  #assign
    |NEWLINE              #blank
    ;

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

# 通过正则定义词法
ID : [a-zA-Z]+ ;
INT : [0-9]+('.'([0-9]+)?)?
        | [0-9]+;
NEWLINE : '\r' ? '\n';
WS : [ \t]+ -> skip;

# 定义运算符
ADD : '+' ;
SUB : '-' ;
MUL : '*' ;
DIV : '/' ;

  • 具体语法内容大家可以自行百度
  • 配置antlr工具,选择项目代码位置和生成后的包名
    在这里插入图片描述
  • 在g4文件右键,生成java代码
    在这里插入图片描述
  • 生成完成
日志
2019-08-10 15:06:05: antlr4 -o /Users/ruiliu/IdeaProjects/test-java/src/main/youling/studio/antlr/gencode -package youling.studio.antlr.gencode -listener -visitor -lib /Users/ruiliu/IdeaProjects/test-java/src/main/youling/studio/antlr /Users/ruiliu/IdeaProjects/test-java/src/main/youling/studio/antlr/Calculate.g4

在这里插入图片描述

代码编写

  • 编写CalculateBaseVisitor实现类,实现计算逻辑
package youling.studio.antlr;

import youling.studio.antlr.gencode.CalculateBaseVisitor;
import youling.studio.antlr.gencode.CalculateLexer;
import youling.studio.antlr.gencode.CalculateParser;

import java.util.HashMap;
import java.util.Map;

/**
 * @author liurui
 * @date 2019/8/10 下午4:22
 */
public class DoubleVisitor extends CalculateBaseVisitor<Double> {

    Map<String,Double> map=new HashMap<String,Double>();

    @Override
    public Double visitParens(CalculateParser.ParensContext ctx) {
        return super.visit(ctx.expr());
    }

    @Override
    public Double visitBlank(CalculateParser.BlankContext ctx) {
        return super.visitBlank(ctx);
    }

    @Override
    public Double visitAddSub(CalculateParser.AddSubContext ctx) {

        Double left=visit(ctx.expr(0));        //获取左边表达式最终值
        Double right=visit(ctx.expr(1));       //获取右边表达式最终值

        if(ctx.op.getType()== CalculateLexer.ADD) return left+right;   //如果是加法
        else return left-right;                                     //如果是减法
    }

    @Override
    public Double visitMulDiv(CalculateParser.MulDivContext ctx) {
        Double left=visit(ctx.expr(0));        //获取左边表达式最终值
        Double right=visit(ctx.expr(1));       //获取右边表达式最终值

        if(ctx.op.getType()==CalculateLexer.DIV) return left/right;   //如果是除法
        else return left*right;                                     //如果是乘法
    }

    @Override
    public Double visitId(CalculateParser.IdContext ctx) {
        String key=ctx.ID().getText();

        if(map.containsKey(key)){   //如果变量被赋值
            return map.get(key);
        }
        return 0d;
    }

    @Override
    public Double visitInt(CalculateParser.IntContext ctx) {
        return Double.parseDouble(ctx.INT().getText());
    }

    @Override
    public Double visitPrintExpr(CalculateParser.PrintExprContext ctx) {
        Double value=visit(ctx.expr());
        System.out.println(value);
        return 0d;
    }

    @Override
    public Double visitAssign(CalculateParser.AssignContext ctx) {

        String key=ctx.ID().getText();
        Double value=visit(ctx.expr());
        map.put(key, value);
        return value;                   // 返回 value :a=b=6 则 a==6
    }

}
  • 编写入口类实现计算功能
package youling.studio.antlr;

import org.antlr.v4.runtime.ANTLRInputStream;
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 youling.studio.antlr.gencode.CalculateLexer;
import youling.studio.antlr.gencode.CalculateParser;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * @author liurui
 * @date 2019/8/10 下午4:26
 */
public class AntlrTest {

    public static void main(String[] args) throws Exception {
        // 原子指标池
        Map<String,Double> baseMeasure = new HashMap();
        baseMeasure.put("sale_amt",12000d); //销售额
        baseMeasure.put("order_cnt",120d); //订单量

        // 单均销售额 = 销售额/订单量 此处定义符合指标计算逻辑
        String avgOrderSaleAmtStr = "{sale_amt}/{order_cnt}\n";
        for(String key : baseMeasure.keySet()){
            avgOrderSaleAmtStr = avgOrderSaleAmtStr.replace("{"+key+"}",baseMeasure.get(key).toString());
        }
        CharStream input = CharStreams.fromStream(getStringStream(avgOrderSaleAmtStr));

        CalculateLexer lexer = new CalculateLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        CalculateParser parser = new CalculateParser(tokens);

        // 生成语法树
        ParseTree tree = parser.prog();

        // 打印语法树
        System.out.println(tree.toStringTree(parser));

        // 计算符合指标结果
        DoubleVisitor visitor = new DoubleVisitor();
        Double avgOrderSaleAmt = visitor.visit(tree);
    }

    /**
     * string 转流
     * @param sInputString
     * @return
     */
    public static InputStream getStringStream(String sInputString){
        if (sInputString != null && !sInputString.trim().equals("")){
            try{
                ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());
                return tInputStringStream;
            }catch (Exception ex){
                ex.printStackTrace();
            }
        }
        return null;
    }
}

测试

  • 直接执行main方法得到如下结果

(prog (stat (expr (expr 12000.0) / (expr 120.0)) \n))
100.0

Process finished with exit code 0

  • 至此大功告成
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页