Spring Boot集成antlr实现词法和语法分析

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所示。

concept

  因此,为了让词法分析和语法分析能够正常工作,在使用 Antlr4 的时候,需要定义语法(grammar),这部分就是 Antlr 元语言。

parser-tree

   

使用 ANTLR4 编程的基本流程是固定的,通常分为如下三步:

  • 基于需求按照 ANTLR4 的规则编写自定义语法的语义规则, 保存成以 g4 为后缀的文件。

  • 使用 ANTLR4 工具处理 g4 文件,生成词法分析器、句法分析器代码、词典文件。

  • 编写代码继承 Visitor 类或实现 Listener 接口,开发自己的业务逻辑代码。

Listener 模式和 Visitor 模式的区别

Listener 模式:

49

Visitor 模式:

56

 

  • 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.g4prog:   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

802

使用ideal插件生成

847

100%

实现运算逻辑

第一种:基于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 */@Overridepublic Integer visitPrintExpr(LabeledExprParser.PrintExprContext ctx) {Integer value = visit(ctx.expr()); // evaluate the expr child// System.out.println(value);         // print the resultreturn value;                          // return dummy value}/** stat : ID '=' expr NEWLINE */@Overridepublic 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 rightmemory.put(id, value);          // store it in our memoryreturn value;}/** expr : expr op=('*'|'/') expr */@Overridepublic Integer visitMulDiv(LabeledExprParser.MulDivContext ctx) {int left = visit(ctx.expr(0));  // get value of left subexpressionint right = visit(ctx.expr(1)); // get value of right subexpressionif (ctx.op.getType() == LabeledExprParser.MUL) return left * right;return left / right; // must be DIV}/** expr : expr op=('+'|'-') expr */@Overridepublic Integer visitAddSub(LabeledExprParser.AddSubContext ctx) {int left = visit(ctx.expr(0));  // get value of left subexpressionint right = visit(ctx.expr(1)); // get value of right subexpressionif (ctx.op.getType() == LabeledExprParser.ADD) return left + right;return left - right; // must be SUB}/** expr : INT */@Overridepublic Integer visitInt(LabeledExprParser.IntContext ctx) {return Integer.valueOf(ctx.INT().getText());}/** expr : ID */@Overridepublic 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 ')' */@Overridepublic Integer visitParens(LabeledExprParser.ParensContext ctx) {return visit(ctx.expr()); // return child expr's value}/** stat : NEWLINE */@Overridepublic 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 resultsprivate final ParseTreeProperty<Integer> values = new ParseTreeProperty<>();private int result=0;@Overridepublic void exitPrintExpr(LabeledExprParser.PrintExprContext ctx) {int value = values.get(ctx.expr());//System.out.println(value);result=value;}public int getResult() {return result;}@Overridepublic void exitAssign(LabeledExprParser.AssignContext ctx) {String id = ctx.ID().getText();int value = values.get(ctx.expr());memory.put(id, value);}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic void exitInt(LabeledExprParser.IntContext ctx) {int value = Integer.parseInt(ctx.INT().getText());values.put(ctx, value);}@Overridepublic 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}}@Overridepublic 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(); // parseEvalVisitor 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(); // parseParseTreeWalker walker = new ParseTreeWalker();EvalListener evalListener =new EvalListener();walker.walk(evalListener, tree);int result=evalListener.getResult();System.out.println(result);}
}

运行上述测试用例,计算结果符合预期

4.引用

  • ANTLR
  • Spring Boot集成antlr实现词法和语法分析 | Harries Blog™

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/354673.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

离线安装zabbix-agent,自制yum源方式安装

文章目录 1&#xff0c;机器准备大致思路 2&#xff0c;在机器A上操作2.1 执行完后会在/etc/yum.repos.d/下面自动生成yum文件&#xff08;zabbix.repo&#xff09;2.2 将官方源改为国内源2.3 修改zabbix.repo文件的[zabbix-frontend]的参数项2.4 清除缓存即可2.5 下载所需zabb…

Semantic Kernel 直接调用本地大模型与阿里云灵积 DashScope

本文主要介绍如何在无需网关&#xff0c;无需配置 HttpClient 的情况下&#xff0c;使用 Semantic Kernel 直接调用本地大模型与阿里云灵积 DashScope 等 OpenAI 接口兼容的大模型服务。 1. 背景 一直以来&#xff0c;我们都在探索如何更好地利用大型语言模型&#xff08;LLM&…

AI 已经在污染互联网了。。赛博喂屎成为现实

大家好&#xff0c;我是程序员鱼皮。这两年 AI 发展势头迅猛&#xff0c;更好的性能、更低的成本、更优的效果&#xff0c;让 AI 这一曾经高高在上的技术也走入大众的视野&#xff0c;能够被我们大多数普通人轻松使用&#xff0c;无需理解复杂的技术和原理。 其中&#xff0c;…

2024: 有效使用OKR的10个技巧

2023年是许多前所未有的一年。从真正意义上讲&#xff0c;这一年让我们为不可预测的事情做好了准备&#xff0c;也为不确定的事情提供了训练。在我们身边发生了这么多事情&#xff0c;而下一步的行动却依然不甚明朗的情况下&#xff0c;领导者们更应该开始制定战略&#xff0c;…

wvp-GB28181-pro 源码分析-查询设备信息和通道流程(二)

文章目录 一、SIP通信方法介绍1.1 核心方法(Core Methods)1.2 扩展方法(Extension Methods)二、源码分析2.1 SIP协议处理过程2.2 查询设备信息的sip过程(CmdType=DeviceInfo)2.2.1 摄像机注册成功后,wvp会发命令查询设备信息2.2.2 查询信令发出2.2.3 处理设备查询返回的XML2…

还在为Android开发找不到图片测试资源发愁吗? DummyImage来助你加速开发

使用 DummyImage 模拟电影应用数据 在开发和测试过程中&#xff0c;模拟数据是不可或缺的工具。它可以帮助我们在没有真实数据的情况下测试应用程序的功能和性能。本文将介绍如何使用 [DummyImage]https://dummyimage.com生成占位符图像来模拟电影应用的数据&#xff0c;并深入…

蓝牙技术|蓝牙耳机将成钥匙,佩戴时靠近设备即解锁

微软公司于今年 6 月 13 日获批一项技术专利&#xff0c;探索耳机验证技术&#xff0c;未来用户不仅可以拿耳机来听歌、通话&#xff0c;而且可以变身钥匙&#xff0c;配合其它计算设备进行身份验证。 微软在专利中概述称用户佩戴这款耳机之后&#xff0c;可以发出超声波信号…

国产数据库也开始堆砌功能了?试图在行业中炫技!

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

每日一练:攻防世界:ewm

这道题我尝试了使用montagegaps解题&#xff0c;但是没有解出来&#xff0c;图片数量不是很多&#xff0c;可以尝试用PS直接拼图&#xff0c;但是这样学不到东西&#xff0c;我也就没尝试&#xff0c;直接看的官方WP 这段代码应该是改变工作目录到small&#xff0c;并且变量当…

【DevOps】Nginx配置文件详解与实战部署PHP站点

目录 引言 Nginx配置文件概述 基本结构 关键指令 Nginx配置文件实战 全局指令配置 HTTP指令配置 服务器指令配置 位置指令配置 实战部署PHP站点 步骤1&#xff1a;安装Nginx和PHP 步骤2&#xff1a;创建网站目录和文件 步骤3&#xff1a;配置Nginx服务器块 步骤4…

家具市场规模超7660亿美元,赛盈分销这些卧室家具在海外大卖!

今年&#xff0c;全球家具市场似乎表现都十分乐观。 根据海关出口总署的相关数据&#xff0c;我国的家具出口额1-5月份达到了2121.5亿元&#xff0c;同比增长20.5%。国内家具出口增速显著&#xff0c;换句话说&#xff0c;就是老外对家具又有更多的消费能力了。 在Statista的…

阿里云PAI大模型评测最佳实践

作者&#xff1a;施晨、之用、南茵、求伯、一耘、临在 背景信息 内容简介 在大模型时代&#xff0c;随着模型效果的显著提升&#xff0c;模型评测的重要性日益凸显。科学、高效的模型评测&#xff0c;不仅能帮助开发者有效地衡量和对比不同模型的性能&#xff0c;更能指导他…

深入浅出Git原理与Gitflow流程

1 Git原理 版本控制系统在软件开发和团队协作中扮演着至关重要的角色。它们帮助开发人员跟踪和管理代码的变化&#xff0c;协调多人同时编辑同一代码库&#xff0c;回溯历史版本&#xff0c;并解决代码冲突等问题。Git作为当今最流行的分布式版本控制系统&#xff0c;为开发人…

刷代码随想录有感(109):动态规划——01背包问题|一和零

题干&#xff1a; 代码 : class Solution { public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<int>>dp(m 1, vector<int>(n 1, 0));dp[0][0] 0;for(string i : strs){int oneNum 0;int zeroNum 0;for(char c : i…

Web渗透:XSS-反射型存储型

跨站脚本攻击&#xff08;Cross-Site Scripting&#xff0c;XSS&#xff09;是一种常见的网络安全漏洞&#xff0c;它允许攻击者将恶意脚本注入到网页中&#xff0c;其他用户在浏览这些页面时&#xff0c;可能会执行这些恶意脚本&#xff0c;从而导致各种安全问题&#xff0c;如…

innovus:如何设置timing报告格式

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 在flow中添加如下设置即可设置好timing report的格式。 set report_timing_format [list timing_point arc net cell fanout load slew incr_delay delay arrival total_derate…

示例:WPF中应用MarkupExtention自定义IValueConverter

一、目的&#xff1a;应用MarkupExtention定义IValueConverter&#xff0c;使得应用起来更简单和高效 二、实现 public abstract class MarkupValueConverterBase : MarkupExtension, IValueConverter{public abstract object Convert(object value, Type targetType, object …

高考志愿选专业,文科生如何分析选择专业?

每到高考时节&#xff0c;学生们最关注的就是专业选择&#xff0c;以及未来职业发展问题&#xff0c;对于文科生来说&#xff0c;面对文科专业的众多选择&#xff0c;很多人都有些不知所措&#xff0c;如何选择适合自己兴趣爱好&#xff0c;又有良好就业前景的工作。从哪些方面…

Tailwind CSS 响应式设计实战指南

title: Tailwind CSS 响应式设计实战指南 date: 2024/6/13 updated: 2024/6/13 author: cmdragon excerpt: 这篇文章介绍了如何运用Tailwind CSS框架创建响应式网页设计&#xff0c;涵盖博客、电商网站及企业官网的布局实例&#xff0c;包括头部导航、内容区域、侧边栏、页脚…

18个机器学习核心算法模型总结

最强总结&#xff01;18个机器学习核心算法模型&#xff01;&#xff01; 大家好~ 在学习机器学习之后&#xff0c;你认为最重要的算法模型有哪些&#xff1f; 今儿的内容涉及到~ 线性回归逻辑回归决策树支持向量机朴素贝叶斯K近邻算法聚类算法神经网络集成方法降维算法主成…