Spring Boot集成antlr实现词法和语法分析
xsobi 2024-12-06 20:28 1 浏览
1.什么是antlr?
Antlr4 是一款强大的语法生成器工具,可用于读取、处理、执行和翻译结构化的文本或二进制文件。基本上是当前 Java 语言中使用最为广泛的语法生成器工具。Twitter搜索使用ANTLR进行语法分析,每天处理超过20亿次查询;Hadoop生态系统中的Hive、Pig、数据仓库和分析系统所使用的语言都用到了ANTLR;Lex Machina将ANTLR用于分析法律文本;Oracle公司在SQL开发者IDE和迁移工具中使用了ANTLR;NetBeans公司的IDE使用ANTLR来解析C++;Hibernate对象-关系映射框架(ORM)使用ANTLR来处理HQL语言
基本概念
语法分析器(parser)是用来识别语言的程序,本身包含两个部分:词法分析器(lexer)和语法分析器(parser)。词法分析阶段主要解决的关键词以及各种标识符,例如 INT、ID 等,语法分析主要是基于词法分析的结果,构造一颗语法分析树。大致的流程如下图参考2所示。
因此,为了让词法分析和语法分析能够正常工作,在使用 Antlr4 的时候,需要定义语法(grammar),这部分就是 Antlr 元语言。
使用 ANTLR4 编程的基本流程是固定的,通常分为如下三步:
- 基于需求按照 ANTLR4 的规则编写自定义语法的语义规则, 保存成以 g4 为后缀的文件。
- 使用 ANTLR4 工具处理 g4 文件,生成词法分析器、句法分析器代码、词典文件。
- 编写代码继承 Visitor 类或实现 Listener 接口,开发自己的业务逻辑代码。
Listener 模式和 Visitor 模式的区别
Listener 模式:
Visitor 模式:
- Listener 模式通过 walker 对象自行遍历,不用考虑其语法树上下级关系。Vistor 需要自行控制访问的子节点,如果遗漏了某个子节点,那么整个子节点都访问不到了。
- Listener 模式的方法没有返回值,Vistor 模式可以设定任意返回值。
- Listener 模式的访问栈清晰明确,Vistor 模式是方法调用栈,如果实现出错有可能导致 StackOverFlow。
2.代码工程
实验目的:实现基于antlr的计算器
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>
元语言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
简单解读一下 LabeledExpr.g4 文件。ANTLR4 规则是基于正则表达式定义定义。规则的理解是自顶向下的,每个分号结束的语句表示一个规则 。例如第一行:grammar LabeledExpr; 表示我们的语法名称是 LabeledExpr, 这个名字需要跟文件名需要保持一致。Java 编码也有相似的规则:类名跟类文件一致。
- 规则 prog 表示 prog 是一个或多个 stat。
- 规则 stat 适配三种子规则:空行、表达式 expr、赋值表达式 ID’=’expr。
- 表达式 expr 适配五种子规则:乘除法、加减法、整型、ID、括号表达式。很显然,这是一个递归的定义。
最后定义的是组成复合规则的基础元素,比如:规则 ID: [a-zA-Z]+表示 ID 限于大小写英文字符串;INT: [0-9]+; 表示 INT 这个规则是 0-9 之间的一个或多个数字,当然这个定义其实并不严格。再严格一点,应该限制其长度。
在理解正则表达式的基础上,ANTLR4 的 g4 语法规则还是比较好理解的。
定义 ANTLR4 规则需要注意一种情况,即可能出现一个字符串同时支持多种规则,如以下的两个规则:
ID: [a-zA-Z]+;
FROM: ‘from’;
很明显,字符串” from”同时满足上述两个规则,ANTLR4 处理的方式是按照定义的顺序决定。这里 ID 定义在 FROM 前面,所以字符串 from 会优先匹配到 ID 这个规则上。
其实在定义好与法规中,编写完成 g4 文件后,ANTLR4 已经为我们完成了 50%的工作:帮我们实现了整个架构及接口了,剩下的开发工作就是基于接口或抽象类进行具体的实现。实现上有两种方式来处理生成的语法树,其一 Visitor 模式,另一种方式是 Listener(监听器模式)。
生成词法和语法解析器
基于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>
执行命令
mvn antlr4:antlr4
使用ideal插件生成
实现运算逻辑
第一种:基于visitor实现
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实现
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()));
}
}
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
- https://github.com/Harries/springboot-demo
3.测试
测试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);
}
}
测试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);
}
}
运行上述测试用例,计算结果符合预期
4.引用
- https://www.antlr.org/
- http://www.liuhaihua.cn/archives/710686.html
相关推荐
- 【互联网那些事】高效开发Android App的10个建议
-
假如要GooglePlay上做一个最失败的案例,那最好的秘诀就是界面奇慢无比、耗电、耗内存。接下来就会得到用户的消极评论,最后名声也就臭了。即使你的应用设计精良、创意无限也没用。 耗电或者内存...
- 手机APP开发方式有哪些? 手机app的开发模式有哪三种?
-
微信小程序开发定制_软件开发_APP开发_网站制作-优软软件开发...
- Android开发入门(一):Android系统简介
-
Android系统是Google公司在2008年推出的一款智能移动设备操作系统,通过不断地版本迭代,目前已经推出到Android11版本了。Android系统广泛应用在手机、平板、电视等各种电子设...
- 物联网app开发流程 物联网app开发工具
-
现在随着科技的发展,很多产品都想用一个手机app去显示他的参数数据或者通过手机app去控制它。但是很多人不知道他的流程。今天我就来说下物联网app开发流程。首先需要把物联网app开发流程分2个步骤,一...
- Android开发进阶 | 如何学习 Android Framework?
-
大部分有“如何学习Framework源码”这个疑问的,应该大都是应用层开发。应用层是被Framework层调用执行的,知道自己的代码是怎么被调用的,才能理解程序的本质,理解本质有助于解决遇到的...
- 快速实现APP混合开发(Hybrid App开发)攻略
-
前言:...
- 三个阶段带你了解一款app开发的完整流程
-
第一个阶段需求阶段:1.需求讨论--开发类型、开发平台、具体的产品功能需求、项目预计完成时间、预算2.需求评估--确认合作后评估具体的预算3.界面设计--设计部门进行产品界面设计,形成效果图...
- Android 开发中文引导-应用小部件
-
应用小部件是可以嵌入其它应用(例如主屏幕)并收到定期更新的微型应用视图。这些视图在用户界面中被叫做小部件,并可以用应用小部件提供者发布。可以容纳其他应用部件的应用组件叫做应用部件的宿主(1)。下面的截...
- 手机软件开发从零开始【Android第2篇Hello】
-
Hello,朋友们我们又见面了。上一篇我们讲到了《Android开发环境搭建【Android基础第1篇】》,错过的朋友可以点击文章末尾的“阅读原文”查看。另外需要下载JDK和ADT-bundle工具的...
- 「全栈工程师之梦的开始--安卓开发(二)」开发安卓app
-
在配置好jdk开发环境、安装好开发工具Androidstudio后,我们就可以开始开发安卓app了。首先,我们需要先了解下android的术语。...
- 二、Android界面开发 android 开发
-
学习目标了解Android常用布局了解Android常用控件...
- 如何开发一款APP既快捷也简便 开发一款app的步骤
-
具体较为简单的步骤可以选择用androidstudio开发app1、打开软件,在菜单中选择file-》newproject打开创建向导。2、配置项目,确定各个名称和存放项目的存放路径;Applic...
- 安卓开发中的“Android高手”,需要具备哪些技术?
-
前言成为一名安卓开发者很容易,但是要成为一名“Android高手”却不那么容易;...
- 移动开发(一):使用.NET MAUI开发第一个安卓APP
-
对于工作多年的C#程序员来说,近来想尝试开发一款安卓APP,考虑了很久最终选择使用.NETMAUI这个微软官方的框架来尝试体验开发安卓APP,毕竟是使用VisualStudio开发工具,使用起来也...
- 微软推出PowerApps:零基础开发Win10/iOS/安卓企业应用
-
IT之家讯微软今天面向企业宣布了全新的应用开发解决方案PowerApps,让Windows(包括Win10)、iOS以及安卓应用的开发和分发变得更加简单。PowerApps的用户界面与Office办...
- 一周热门
- 最近发表
- 标签列表
-
- grid 设置 (58)
- 移位运算 (48)
- not specified (45)
- patch补丁 (31)
- 导航栏 (58)
- context xml (46)
- scroll (43)
- element style (30)
- dedecms模版 (53)
- vs打不开 (29)
- nmap (30)
- c 视频教程下载 (33)
- paddleocr (28)
- listview排序 (33)
- firebug 使用 (31)
- transactionmanager (30)
- characterencodingfilter (33)
- getmonth (34)
- commandtimeout (30)
- hibernate教程 (31)
- label换行 (33)
- curlpost (31)
- android studio 3 0 (34)
- android开发视频 (30)
- android应用开发 (31)