golang 整合antlr语法校验

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
    golang 整合antlr语法校验_第1张图片

  2. 插件安装完成后,可以看到ANTLR Preview窗口,一会我们可以在这个窗口进行简单的语法校验。
    golang 整合antlr语法校验_第2张图片

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
      golang 整合antlr语法校验_第3张图片

    3. 选中Rule.g4 文件,鼠标右键,选择 Generate ANTLR Recognizer,完成规则初始化
      golang 整合antlr语法校验_第4张图片

  4. 树状图校验
    golang 整合antlr语法校验_第5张图片

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. 正确表达式
      golang 整合antlr语法校验_第6张图片

    2. key不在支持的语法内
      golang 整合antlr语法校验_第7张图片

    3. 缺少key
      golang 整合antlr语法校验_第8张图片

    4. 运算符不在支持的语法内
      golang 整合antlr语法校验_第9张图片

    5. 缺少括号
      golang 整合antlr语法校验_第10张图片

你可能感兴趣的:(golang,golang,正则表达式,开发语言)