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

  • 至此大功告成
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章