SpringBoot集成antlr實(shí)現(xiàn)詞法和語(yǔ)法分析
1.什么是antlr?
Antlr4 是一款強(qiáng)大的語(yǔ)法生成器工具,可用于讀取、處理、執(zhí)行和翻譯結(jié)構(gòu)化的文本或二進(jìn)制文件?;旧鲜钱?dāng)前 Java 語(yǔ)言中使用最為廣泛的語(yǔ)法生成器工具。Twitter搜索使用ANTLR進(jìn)行語(yǔ)法分析,每天處理超過(guò)20億次查詢;Hadoop生態(tài)系統(tǒng)中的Hive、Pig、數(shù)據(jù)倉(cāng)庫(kù)和分析系統(tǒng)所使用的語(yǔ)言都用到了ANTLR;Lex Machina將ANTLR用于分析法律文本;Oracle公司在SQL開(kāi)發(fā)者IDE和遷移工具中使用了ANTLR;NetBeans公司的IDE使用ANTLR來(lái)解析C++;Hibernate對(duì)象-關(guān)系映射框架(ORM)使用ANTLR來(lái)處理HQL語(yǔ)言
基本概念
語(yǔ)法分析器(parser)是用來(lái)識(shí)別語(yǔ)言的程序,本身包含兩個(gè)部分:詞法分析器(lexer)和語(yǔ)法分析器(parser)。詞法分析階段主要解決的關(guān)鍵詞以及各種標(biāo)識(shí)符,例如 INT、ID 等,語(yǔ)法分析主要是基于詞法分析的結(jié)果,構(gòu)造一顆語(yǔ)法分析樹(shù)。大致的流程如下圖參考2所示。

因此,為了讓詞法分析和語(yǔ)法分析能夠正常工作,在使用 Antlr4 的時(shí)候,需要定義語(yǔ)法(grammar),這部分就是 Antlr 元語(yǔ)言。

使用 ANTLR4 編程的基本流程是固定的,通常分為如下三步:
基于需求按照 ANTLR4 的規(guī)則編寫(xiě)自定義語(yǔ)法的語(yǔ)義規(guī)則, 保存成以 g4 為后綴的文件。
使用 ANTLR4 工具處理 g4 文件,生成詞法分析器、句法分析器代碼、詞典文件。
編寫(xiě)代碼繼承 Visitor 類(lèi)或?qū)崿F(xiàn) Listener 接口,開(kāi)發(fā)自己的業(yè)務(wù)邏輯代碼。
Listener 模式和 Visitor 模式的區(qū)別
Listener 模式:

Visitor 模式:

- Listener 模式通過(guò) walker 對(duì)象自行遍歷,不用考慮其語(yǔ)法樹(shù)上下級(jí)關(guān)系。Vistor 需要自行控制訪問(wèn)的子節(jié)點(diǎn),如果遺漏了某個(gè)子節(jié)點(diǎn),那么整個(gè)子節(jié)點(diǎn)都訪問(wèn)不到了。
- Listener 模式的方法沒(méi)有返回值,Vistor 模式可以設(shè)定任意返回值。
- Listener 模式的訪問(wèn)棧清晰明確,Vistor 模式是方法調(diào)用棧,如果實(shí)現(xiàn)出錯(cuò)有可能導(dǎo)致 StackOverFlow。
2.代碼工程
實(shí)驗(yàn)?zāi)康模簩?shí)現(xiàn)基于antlr的計(jì)算器
pom.xml
<?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">
<parent>
<artifactId>springboot-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ANTLR</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<antlr4.version>4.9.1</antlr4.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>${antlr4.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>${antlr4.version}</version>
<configuration>
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>src/main/java</outputDirectory>
<arguments>
<argument>-visitor</argument>
<argument>-listener</argument>
</arguments>
</configuration>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
元語(yǔ)言LabeledExpr.g4
grammar LabeledExpr; // rename to distinguish from Expr.g4
prog: stat+ ;
stat: expr NEWLINE # printExpr
| ID '=' expr NEWLINE # assign
| NEWLINE # blank
;
expr: expr op=('*'|'/') expr # MulDiv
| expr op=('+'|'-') expr # AddSub
| INT # int
| ID # id
| '(' expr ')' # parens
;
MUL : '*' ; // assigns token name to '*' used above in grammar
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
ID : [a-zA-Z]+ ; // match identifiers
INT : [0-9]+ ; // match integers
NEWLINE:'\r'? '\n' ; // return newlines to parser (is end-statement signal)
WS : [ \t]+ -> skip ; // toss out whitespace
簡(jiǎn)單解讀一下 LabeledExpr.g4 文件。ANTLR4 規(guī)則是基于正則表達(dá)式定義定義。規(guī)則的理解是自頂向下的,每個(gè)分號(hào)結(jié)束的語(yǔ)句表示一個(gè)規(guī)則 。例如第一行:grammar LabeledExpr; 表示我們的語(yǔ)法名稱(chēng)是 LabeledExpr, 這個(gè)名字需要跟文件名需要保持一致。Java 編碼也有相似的規(guī)則:類(lèi)名跟類(lèi)文件一致。
- 規(guī)則 prog 表示 prog 是一個(gè)或多個(gè) stat。
- 規(guī)則 stat 適配三種子規(guī)則:空行、表達(dá)式 expr、賦值表達(dá)式 ID’=’expr。
- 表達(dá)式 expr 適配五種子規(guī)則:乘除法、加減法、整型、ID、括號(hào)表達(dá)式。很顯然,這是一個(gè)遞歸的定義。
最后定義的是組成復(fù)合規(guī)則的基礎(chǔ)元素,比如:規(guī)則 **ID: [a-zA-Z]+**表示 ID 限于大小寫(xiě)英文字符串;INT: [0-9]+; 表示 INT 這個(gè)規(guī)則是 0-9 之間的一個(gè)或多個(gè)數(shù)字,當(dāng)然這個(gè)定義其實(shí)并不嚴(yán)格。再?lài)?yán)格一點(diǎn),應(yīng)該限制其長(zhǎng)度。
在理解正則表達(dá)式的基礎(chǔ)上,ANTLR4 的 g4 語(yǔ)法規(guī)則還是比較好理解的。
定義 ANTLR4 規(guī)則需要注意一種情況,即可能出現(xiàn)一個(gè)字符串同時(shí)支持多種規(guī)則,如以下的兩個(gè)規(guī)則:
ID: [a-zA-Z]+;
FROM: ‘from’;
很明顯,字符串” from”同時(shí)滿足上述兩個(gè)規(guī)則,ANTLR4 處理的方式是按照定義的順序決定。這里 ID 定義在 FROM 前面,所以字符串 from 會(huì)優(yōu)先匹配到 ID 這個(gè)規(guī)則上。
其實(shí)在定義好與法規(guī)中,編寫(xiě)完成 g4 文件后,ANTLR4 已經(jīng)為我們完成了 50%的工作:幫我們實(shí)現(xiàn)了整個(gè)架構(gòu)及接口了,剩下的開(kāi)發(fā)工作就是基于接口或抽象類(lèi)進(jìn)行具體的實(shí)現(xiàn)。實(shí)現(xiàn)上有兩種方式來(lái)處理生成的語(yǔ)法樹(shù),其一 Visitor 模式,另一種方式是 Listener(監(jiān)聽(tīng)器模式)。
生成詞法和語(yǔ)法解析器
基于maven插件生成
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>${antlr4.version}</version>
<configuration>
<sourceDirectory>src/main/java</sourceDirectory>
<outputDirectory>src/main/java</outputDirectory>
<arguments>
<argument>-visitor</argument>
<argument>-listener</argument>
</arguments>
</configuration>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
執(zhí)行命令
mvn antlr4:antlr4

使用ideal插件生成


實(shí)現(xiàn)運(yùn)算邏輯
第一種:基于visitor實(shí)現(xiàn)
package com.et.antlr;
import java.util.HashMap;
import java.util.Map;
public class EvalVisitor extends LabeledExprBaseVisitor<Integer> {
// Store variables (for assignment)
Map<String, Integer> memory = new HashMap<>();
/** stat : expr NEWLINE */
@Override
public Integer visitPrintExpr(LabeledExprParser.PrintExprContext ctx) {
Integer value = visit(ctx.expr()); // evaluate the expr child
// System.out.println(value); // print the result
return value; // return dummy value
}
/** stat : ID '=' expr NEWLINE */
@Override
public Integer visitAssign(LabeledExprParser.AssignContext ctx) {
String id = ctx.ID().getText(); // id is left-hand side of '='
int value = visit(ctx.expr()); // compute value of expression on right
memory.put(id, value); // store it in our memory
return value;
}
/** expr : expr op=('*'|'/') expr */
@Override
public Integer visitMulDiv(LabeledExprParser.MulDivContext ctx) {
int left = visit(ctx.expr(0)); // get value of left subexpression
int right = visit(ctx.expr(1)); // get value of right subexpression
if (ctx.op.getType() == LabeledExprParser.MUL) return left * right;
return left / right; // must be DIV
}
/** expr : expr op=('+'|'-') expr */
@Override
public Integer visitAddSub(LabeledExprParser.AddSubContext ctx) {
int left = visit(ctx.expr(0)); // get value of left subexpression
int right = visit(ctx.expr(1)); // get value of right subexpression
if (ctx.op.getType() == LabeledExprParser.ADD) return left + right;
return left - right; // must be SUB
}
/** expr : INT */
@Override
public Integer visitInt(LabeledExprParser.IntContext ctx) {
return Integer.valueOf(ctx.INT().getText());
}
/** expr : ID */
@Override
public Integer visitId(LabeledExprParser.IdContext ctx) {
String id = ctx.ID().getText();
if (memory.containsKey(id)) return memory.get(id);
return 0; // default value if the variable is not found
}
/** expr : '(' expr ')' */
@Override
public Integer visitParens(LabeledExprParser.ParensContext ctx) {
return visit(ctx.expr()); // return child expr's value
}
/** stat : NEWLINE */
@Override
public Integer visitBlank(LabeledExprParser.BlankContext ctx) {
return 0; // return dummy value
}
}
第二種:基于listener實(shí)現(xiàn)
package com.et.antlr;
import org.antlr.v4.runtime.tree.ParseTreeProperty;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.HashMap;
import java.util.Map;
public class EvalListener extends LabeledExprBaseListener {
// Store variables (for assignment)
private final Map<String, Integer> memory = new HashMap<>();
// Store expression results
private final ParseTreeProperty<Integer> values = new ParseTreeProperty<>();
private int result=0;
@Override
public void exitPrintExpr(LabeledExprParser.PrintExprContext ctx) {
int value = values.get(ctx.expr());
//System.out.println(value);
result=value;
}
public int getResult() {
return result;
}
@Override
public void exitAssign(LabeledExprParser.AssignContext ctx) {
String id = ctx.ID().getText();
int value = values.get(ctx.expr());
memory.put(id, value);
}
@Override
public void exitMulDiv(LabeledExprParser.MulDivContext ctx) {
int left = values.get(ctx.expr(0));
int right = values.get(ctx.expr(1));
if (ctx.op.getType() == LabeledExprParser.MUL) {
values.put(ctx, left * right);
} else {
values.put(ctx, left / right);
}
}
@Override
public void exitAddSub(LabeledExprParser.AddSubContext ctx) {
int left = values.get(ctx.expr(0));
int right = values.get(ctx.expr(1));
if (ctx.op.getType() == LabeledExprParser.ADD) {
values.put(ctx, left + right);
} else {
values.put(ctx, left - right);
}
}
@Override
public void exitInt(LabeledExprParser.IntContext ctx) {
int value = Integer.parseInt(ctx.INT().getText());
values.put(ctx, value);
}
@Override
public void exitId(LabeledExprParser.IdContext ctx) {
String id = ctx.ID().getText();
if (memory.containsKey(id)) {
values.put(ctx, memory.get(id));
} else {
values.put(ctx, 0); // default value if the variable is not found
}
}
@Override
public void exitParens(LabeledExprParser.ParensContext ctx) {
values.put(ctx, values.get(ctx.expr()));
}
}
以上只是一些關(guān)鍵代碼,所有代碼請(qǐng)參見(jiàn)下面代碼倉(cāng)庫(kù)
代碼倉(cāng)庫(kù)
3.測(cè)試
測(cè)試vistor方式
package com.et.antlr; /***
* Excerpted from "The Definitive ANTLR 4 Reference",
* published by The Pragmatic Bookshelf.
* Copyrights apply to this code. It may not be used to create training material,
* courses, books, articles, and the like. Contact us if you are in doubt.
* We make no guarantees that this code is fit for any purpose.
* Visit http://www.pragmaticprogrammer.com/titles/tpantlr2 for more book information.
***/
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.FileInputStream;
import java.io.InputStream;
public class CalcByVisit {
public static void main(String[] args) throws Exception {
/* String inputFile = null;
if ( args.length>0 ) inputFile = args[0];
InputStream is = System.in;
if ( inputFile!=null ) is = new FileInputStream(inputFile);*/
ANTLRInputStream input = new ANTLRInputStream("1+2*3\n");
LabeledExprLexer lexer = new LabeledExprLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
LabeledExprParser parser = new LabeledExprParser(tokens);
ParseTree tree = parser.prog(); // parse
EvalVisitor eval = new EvalVisitor();
int result =eval.visit(tree);
System.out.println(result);
}
}
測(cè)試listener方式
package com.et.antlr;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author liuhaihua
* @version 1.0
* @ClassName CalbyLisenter
* @Description todo
* @date 2024年06月06日 16:40
*/
public class CalbyLisener {
public static void main(String[] args) throws IOException {
/* String inputFile = null;
if ( args.length>0 ) inputFile = args[0];
InputStream is = System.in;
if ( inputFile!=null ) is = new FileInputStream(inputFile);*/
ANTLRInputStream input = new ANTLRInputStream("1+2*3\n");
LabeledExprLexer lexer = new LabeledExprLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
LabeledExprParser parser = new LabeledExprParser(tokens);
ParseTree tree = parser.prog(); // parse
ParseTreeWalker walker = new ParseTreeWalker();
EvalListener evalListener =new EvalListener();
walker.walk(evalListener, tree);
int result=evalListener.getResult();
System.out.println(result);
}
}
運(yùn)行上述測(cè)試用例,計(jì)算結(jié)果符合預(yù)期
4.引用
以上就是SpringBoot集成antlr實(shí)現(xiàn)詞法和語(yǔ)法分析的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot antlr詞法和語(yǔ)法分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java使用POI讀取properties文件并寫(xiě)到Excel的方法
這篇文章主要介紹了java使用POI讀取properties文件并寫(xiě)到Excel的方法,涉及java操作properties文件及Excel文件的相關(guān)技巧,需要的朋友可以參考下2015-06-06
Java輕松使用工具類(lèi)實(shí)現(xiàn)獲取wav時(shí)間長(zhǎng)度
在Java中,工具類(lèi)定義了一組公共方法,這篇文章將介紹Java中使用工具類(lèi)來(lái)獲取一個(gè)wav文件的時(shí)間長(zhǎng)度,感興趣的同學(xué)繼續(xù)往下閱讀吧2021-10-10
Java?Spring?事件監(jiān)聽(tīng)詳情解析
這篇文章主要介紹了Java?Spring?事件監(jiān)聽(tīng)詳情解析,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07
java實(shí)現(xiàn)MapReduce對(duì)文件進(jìn)行切分的示例代碼
本文主要介紹了java實(shí)現(xiàn)MapReduce對(duì)文件進(jìn)行切分的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
java高并發(fā)鎖的3種實(shí)現(xiàn)示例代碼
本篇文章主要介紹了java高并發(fā)鎖的3種實(shí)現(xiàn)示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
Spring中的DefaultResourceLoader使用方法解讀
這篇文章主要介紹了Spring中的DefaultResourceLoader使用方法解讀,DefaultResourceLoader是spring提供的一個(gè)默認(rèn)的資源加載器,DefaultResourceLoader實(shí)現(xiàn)了ResourceLoader接口,提供了基本的資源加載能力,需要的朋友可以參考下2024-02-02

