golang 整合antlr语法校验

news2024/9/27 12:08:17

1. 背景

在项目中我们可能会遇到表达式检索的场景,例如,输入以下表达式检索,需要解析表达式并得到检索结果。

ip="192.168.1.3" && (port="80" || protocol="http")

此时,我们需要对语法进行校验、解析,应当如何做呢?

下面给大家推荐一种使用语法校验工具——Antlr

Antlr是一个语法分析器,本身是用java实现的,然是Runtime的库也支持Golang、Java、Python等。

接下来给大家演示一下使用golang整合antlr进行语法解析。

2. goland安装antlr插件

  1. 打开goland,File --> Settings --> Plugins, 搜索antlr,安装 antlr4
    在这里插入图片描述

  2. 插件安装完成后,可以看到ANTLR Preview窗口,一会我们可以在这个窗口进行简单的语法校验。
    在这里插入图片描述

3. 编写语法校验规则

  1. 创建工程,引入包

    go get -u github.com/antlr/antlr4/runtime/Go/antlr/v4
    
  2. 在工程中新建一个antlr目录,创建一个后缀名为 .g4 的文件,作为规则文件。此处我们创建Rule.g4

    // 定义语法名称,需要和文件名匹配
    grammar Rule;
    
    // DECIMAL, IDENTIFIER, COMMENTS, WS are set using regular expressions
    // key 为表达式中可支持的检索字段,可以是固定值(每个值中间用 | 隔开,是”或“的意思),也可以是正则表达式
    // value 使用正则表达式
    KEY : 'ip' | 'port' | 'protocol';
    //VALUE :'"' ( '""' | ~["\r\n] )* '"' ;
    //KEY : ('A' .. 'Z' | 'a' .. 'z' |  '_') + ;
    VALUE :'"' ( '\\"' | ~["] )* '"' ;
    
    // COMMENT and WS are stripped from the output token stream by sending
    // to a different channel 'skip'
    
    COMMENT : '//' .+? ('\n'|EOF) -> skip ;
    
    WS : [ \r\t\u000C\n]+ -> skip ;
    
    
    /* Parser rules */
    // 语法校验的入口
    start : logicalExpr* EOF;
    
    // 语法支持的结构
    logicalExpr
        : comparisonExpr // 示例: key == value 表示支持 == 和 != 的表达式
        | logicalExpr operator logicalExpr // 示例: key1 == value1 && key2 != value2 表示支持 && 和 || 运算符连接表达式
        | lparen logicalExpr rparen // 示例: (key1 == value1 && key2 != value2) 表示支持 () 连接表达式
        ;
    
    comparisonExpr
        : KEY compare VALUE
        ;
    compare
        : '='
        | '!='
        ;
    operator
        : '&&'
        | '||'
        ;
    lparen
        :  '('
        ;
    rparen
        :  ')'
        ;
    
  3. 初始化校验语法

    1. 选中Rule.g4 文件,鼠标右键,选择 Configure ANTLR Tool…

    2. 配置输出路径,和Rule.g4 同目录;配置语言,使用Go
      在这里插入图片描述

    3. 选中Rule.g4 文件,鼠标右键,选择 Generate ANTLR Recognizer,完成规则初始化
      在这里插入图片描述

  4. 树状图校验
    在这里插入图片描述

4. 语法校验

  1. 自定义listener

    package parser
    
    import (
    	"github.com/antlr/antlr4/runtime/Go/antlr/v4"
    	"strings"
    )
    
    type MyRuleListener struct {
    	*BaseRuleListener
    	Queue    []interface{}
    	QueueStr []string
    }
    
    // 注意:方法名必须是这个名字
    func (s *MyRuleListener) EnterComparisonExpr(ctx *ComparisonExprContext) {
    	key := ctx.GetChild(0).(antlr.ParseTree).GetText()
    	operator := ctx.GetChild(1).(antlr.ParseTree).GetText()
    	value := ctx.GetChild(2).(antlr.ParseTree).GetText()
    	if strings.HasPrefix(value, "\"") {
    		value = value[1:]
    	}
    	if strings.HasSuffix(value, "\"") {
    		value = value[:len(value)-1]
    	}
    	keyValue := map[string]string{}
    	keyValue["key"] = key
    	keyValue["operator"] = operator
    	keyValue["value"] = value
    
    	s.PushStr(ctx.GetText())
    	s.Push(keyValue)
    }
    
    // EnterKeyValue is called when production KeyValue is entered.
    func (s *MyRuleListener) ExitOperator(ctx *OperatorContext) {
    	s.Push(ctx.GetText())
    	s.PushStr(ctx.GetText())
    }
    
    // EnterKeyValue is called when production KeyValue is entered.
    func (s *MyRuleListener) ExitLparen(ctx *LparenContext) {
    	s.Push(ctx.GetText())
    	s.PushStr(ctx.GetText())
    }
    
    // EnterKeyValue is called when production KeyValue is entered.
    func (s *MyRuleListener) ExitRparen(ctx *RparenContext) {
    	s.Push(ctx.GetText())
    	s.PushStr(ctx.GetText())
    }
    
    func (s *MyRuleListener) Push(i interface{}) {
    	s.Queue = append(s.Queue, i)
    }
    
    func (s *MyRuleListener) PushStr(i string) {
    	s.QueueStr = append(s.QueueStr, i)
    }
    
    
  2. 获取解析异常的错误信息

    package parser
    
    import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
    
    type RuleErrorListener struct {
    	antlr.ErrorListener
    	Msg string
    }
    
    func (l *RuleErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) {
    	l.Msg = msg
    }
    
    
  3. 校验

    package main
    
    import (
    	parser "antlr-demo/antlr"
    	"errors"
    	"fmt"
    	"github.com/antlr/antlr4/runtime/Go/antlr/v4"
    )
    
    func main() {
    	expre := "ip=\"192.168.1.3\" && (port=\"80\" || protocol=\"http\")"
    	err := checkExpre(expre)
    	if err != nil {
    		fmt.Println(err)
    	}
    }
    
    func checkExpre(expre string) error {
    	input := antlr.NewInputStream(expre)
    	var lexerErr parser.RuleErrorListener
    	lexer := parser.NewRuleLexer(input)
    	lexer.AddErrorListener(&lexerErr)
    	stream := antlr.NewCommonTokenStream(lexer, 0)
    	ruleParser := parser.NewRuleParser(stream)
    	ruleParser.BuildParseTrees = true
    	var ruleErr parser.RuleErrorListener
    	ruleParser.AddErrorListener(&ruleErr)
    	tree := ruleParser.Start()
    	listener := new(parser.MyRuleListener)
    	antlr.ParseTreeWalkerDefault.Walk(listener, tree)
    
    	if lexerErr.Msg != "" || ruleErr.Msg != "" {
    		return errors.New("输入的语法不正确")
    	}
    	expreList := listener.QueueStr
    	fmt.Println("expreList--->", expreList)
    	expreMap := listener.Queue
    	fmt.Println("expreMap--->", expreMap)
    	return nil
    }
    
    
  4. 结果验证

    1. 正确表达式
      在这里插入图片描述

    2. key不在支持的语法内
      在这里插入图片描述

    3. 缺少key
      在这里插入图片描述

    4. 运算符不在支持的语法内
      在这里插入图片描述

    5. 缺少括号
      在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux启动过程

theme: channing-cyan 两种启动方式 传统启动方式(LEGACYMBR) 指传统BIOS启动方式,存在一些不足:比如最大只支持2TB磁盘,磁盘最多四个分区,且不支持图形操作 UEFIGPT方式 是新式的启动方式&#xff0c…

数学小课堂:三次方程(定理发明的过程)

文章目录 引言I 一元三次方程1.1 通解发明权之争1.2 费拉里-塔尔塔利亚公式1.3 Mathematica1.4 数学定理发明的过程引言 学习数学,最重要的是把实际问题变成数学问题,然后知道如何利用各种软件工具来解决。 方程是一个能把具体问题,等量转化成类型问题的好工具。 一元三次方…

Jetson AGX Orin安装Anaconda、Cuda、Cudnn、Pytorch最全教程

文章目录一:Anaconda安装二:Cuda、Cudnn安装三:Pytorch安装一:Anaconda安装 Jetson系列边缘开发板,其架构都是arm64,而不是传统PC的amd64,深度学习的环境配置方法大不相同。想要看amd64的相关环…

智能家居项目(六)之摄像头模块

目录 一、树莓派mipg-streamer实现监控功能调试 1、实现基本思路 2、安装摄像头模块 2.1、在安装sudo apt-get install libv4l-dev 的命令时报错 3、开启摄像头 以下内容是针对树莓派是stretch版本的修改办法: 一、树莓派mipg-streamer实现监控功能调试 1、…

有哪些前端面试题是必须要掌握的

对浏览器的缓存机制的理解 浏览器缓存的全过程: 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用; 下一次加载资源时&#x…

Flow API搭建指南

搭建Flow API,首先需要安装知行之桥EDI系统,注意,Flow API为新增功能,仅在2022版本(8336)及以后支持,如果你发现正在使用的产品没有这个功能,可以在我们官网下载最新版本或者联系我们…

将企业文件共享解决方案与数据丢失防护配对

您的企业文件共享解决方案是否足够?企业文件共享解决方案已经是一种加密移动中敏感数据的好方法,但仅加密是不够的。 您能否确保不会意外传输敏感信息?您是否可以审核谁发送了什么?最后但并非最不重要的一点是,您是否…

【Autoware】2小时安装Autoware1.13(保姆级教程)

前言:ROS的出现使得机器人软件开发更加快速和模块化,在此基础上,Autoware.ai开源项目可以让我们很容易地将一套完整的自动驾驶软件部署到我们的测试车辆上,并见证它跑起来! 文章目录1.Autoware简介2.电脑软硬件配置要求…

爆肝更新 Python 100道基础入门练习题(附答案)

前言 大家早好、午好、晚好吖 ❤ ~ 更多精彩内容、资源皆可点击文章下方名片获取此处跳转 实例001:数字组合 题目: 有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少? 程序分…

合宙入门教程之luat开发教程

合宙入门教程准备工作连接电脑建工程与烧录测试demo之main.lua实验现象准备工作 1.开发板(1块) 2.Luatools_v2.exe (烧录固件软件) 3.USB驱动 跳转合宙官网链接 连接电脑 1.首先安装合宙开发工具,其次安装USB驱动。…

【JAVA】一个项目如何预先加载数据?

这里写目录标题需求实现AutowiredPostConstruct实例CommandLineRunner实例ApplicationListener实例参考需求 一般我们可能会有一些在应用启动时加载资源的需求,局部或者全局使用,让我们来看看都有哪些方式实现。 实现 Autowired 如果是某个类里需求某…

山东大学机器学习期末2022

接力:山东大学机器学习期末2021 本来是不想写的,因为不想回忆起考试时啥也不会的伤痛,没想到最后给分老师海底捞,心情好了一些,还是一块写完 备考建议:多看ppt,多看ppt,多看ppt 山东…

关于 Android 线程优化这些知识你都该了解

前言在实际项目开发中会频繁的用到线程,线程使用起来是很简单,但是滥用线程会带来性能问题, 比如启动一个线程至少 占用16kb的内存、线程过多会导致cpu的频繁切换而cpu切换成本是很高的、消耗大量用户电量等问题, 所以应该让app的…

Set集合、HashSet集合、LinkedHashSet集合

1、Set集合的特点 无序,不重复、无索引 Set集合的方法上基本和Collection的API一致 2、Set集合的实现类特点 HashSet:无序、不重复、无索引 LinkedHashList:有序、不重复、无索引 TreeSet:可排序、不重复、无索引 public s…

taobao.fulfillment.order.assemble( 拆合单结果回传接口 )

¥免费必须用户授权 拆合单结果回传接口 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, secr…

centos7合并home分区到root分区

最近在尝试通过物理机安装 CentOS,官方镜像默认安装时,如果没有手动分区,默认设置是会将 home 单独分区,系统分区默认为 50 GB,这里提供方法将 home 分区合并到 root 分区。 1.查看当前系统分区情况 输入命令&#x…

华为OD机试用Python实现 -【删除重复数字后的最大数字】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲删除重复数字后的最大数字题目输入输出示例一输入输出示例二输入输出Python 代码展示编码思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.ne…

RedisTemplate和StringRedisTemplate的区别

RedisTemplate和StringRedisTemplate的区别: 两者的关系是StringRedisTemplate继承RedisTemplate。两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。…

图解鼠标事件的 ScreenX ,LayerX,clientX,PageX,offsetX,X

前言: 完在上一篇文章 🎁如何实现原生 JS 的拖拽效果我中使用到了 MouseEvent 事件对象身上的 clienX 的属性,但同时我也注意到了事件对象身上关于 X 的相关属性还有很多,并且在移动端开发中,这些属性需要频繁的用到&a…

golang 实现链表爽不爽?

犹记得刚学 C 语言的时候,学到指针这一章,就会有让我们写链表的需求,头插法,尾插法,翻转链表,合并链表,约瑟夫环等等 学的不亦乐乎,但是 对于指针刚学的时候,真是摸不着…