day12 规则引擎设计与实现(2) | 青训营笔记

78 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天,今天讲解如何简单实现一个规则引擎.

一. 设计一个规则引擎

1.前置准备

  • go语言环境
  • Windows Docker 安装
    • 其他linux版本可以在左面教程选择
  • Arch Linux Docker安装
    • 安装Docker
      sudo pacman -S docker
      
      如果安装了yay也可以
      yay docker
      
    • 启动Docker
      • 启动Docker
        sudo systemctl start docker.service
        
      • 设置开机启动
        sudo systemctl enable docker.service
        
      • 关闭开机启动
        sudo systemctl disable docker.service
        
      • 工作用户加入 docker 组 一般情况下,docker会自动创建docker,直接将当前工作用户加入即可
        sudo gpasswd -a $USER docker
        
        重启docker(restart)并重新登录该用户,或者重启即可可生效
        如果没有创建,需要手动创建,再加入工作组
        sudo groupadd docker
        
  • 安装docker-compose工具
  • 项目
    git clone https://github.com/qimengxingyuan/young_engine.git 
    chmod a+x ./setup.sh 
    ./setup.sh
    
    脚本执行成功,则环境可以支持项目的执行

2. 设计目标

设计一个规则引擎,支持特定的词法,运算符,数据类型,数据类型和优先级.并且支持基于以上预定义语法的规则表达式的编译和执行.

  • 词法(合法Token)

    • 参数 : 由字母数字下划线组成eg: _ab2,user_name
    • 布尔值 : true , false
    • 字符串 : "abcd" , 'abcd' , `abcd`
    • 十进制int : 1234
    • 十进制float : 123.5
    • 预定义运算符 : + -
  • 运算符

    • 一元运算符 : + -
    • 二元运算符 : + - * / % < > <= >= != ==
    • 逻辑运算符 : && || !
    • 括号 : ( )
  • 数据类型

    • 字符串 "abc" 'def'
    • 十进制int 123
    • 十进制float 123.4
    • bool true
    • 变量 id
  • 优先级

    • image.png

3.词法分析

image.png

  • 设计语法分析的状态机: image.png
    同心圆会向后再看一位

4.语法分析

expr : logOr EOF;
logOr : logOr '||' logAnd | logAnd;
logAnd : logAnd '&&' logNot | logNot;
logNot : '|' logNot | cmp;
cmp : cmp '>' add | cmp '>=' add | cmp '<' add | cmp '<=' add | cmp '==' add | cmp '!=' add | add ;
add : add '+' mul | add '-' mul | mul;
mul : mul '+' pri | mul '/' pri | mul '%' pri | pri;
pri : BooleanLiteral | IntengerLiteral | FloatLiteral | StringLiteral | Identifier | '('expr')' '
  • 优先级的表达
type precedence struct {
    validSymbols   []Symbol // 当前优先级支持的运算符类型
    nextPrecedence *precedence // 更高优先级的
    planner        planner // 当前优先级的处理函数
}
  • 语法树结构
    • 一元运算符 : 左子树为空,右子树为右操作符.
    • 二元运算符 : 左子树为左操作符,右子树为右操作符.
    • 括号 : 左子树为空,右子树为内部表达式的AST(抽象语法树).

5. 语法树执行与类型检查

  • 语法树的执行

    预先定义好每种操作符的执行逻辑.
    对抽象语法树进行后续遍历执行,即:

    • 先执行左子树,得到左节点的值
    • 再执行右子树,得到右节点的值
    • 最后根据根节点的操作符执行得到根节点的值
  • 类型检查

    检查时机: 执行时检查.
    检查方法: 在一个节点的左右子节点执行完成后,分别校验左右子节点的类型是否符合对应操作符的类型检查预设规则.
    例如:

    • '>' 符号要求左右子节点的值都存在且为 int 或者 float .
    • '|' 符号要求左节点为空且右节点的值为 bool .

二. 规则引擎的实现

  • 项目结构 image.png

  • 先定义词法(token):
    image.png...

  • 再进行词法分析(compiler/scanner.go):

    type Scanner struct {
       source   []rune // 规则表达式字符串
       position int    // 遍历规则表达式过程中的位置
       length   int    // 规则表达式字符串, 用于判断是否扫描结束
       ch       rune   // position 位置对应的字符
    }
    ...
    func (scanner *Scanner) Lexer() ([]token.Token, error) {
       tokens := make([]token.Token, 0)
    
       var err error
       var tok token.Token
       for {
          tok, err = scanner.Scan()
          tokens = append(tokens, tok)
          if err != nil || tok.Kind == token.Eof {
             break
          }
       }
    
       return tokens, err
    }
    ...
    

    Scan()为读取一个类型为Scanner的当前位置的字符,并写入tok

  • 然后就是语法分析(compiler/parser.go)

    type Parser struct {
       tokens      []token.Token
       index       int
       tokenLength int
    }
    ...
    func NewParser(tokens []token.Token) *Parser {
       return &Parser{
          tokens:      tokens,
          tokenLength: len(tokens),
       }
    }
    ...
    

    然后进行语法检查(checkBalance(),ParseSyntax() ),避免后续进行不必要的操作.
    例如类型'(a + (b > c)' ,括号不对称,是不合法的;'param1 + 100 param2' is illegal;'a + b +' is illegal.
    保证构建语法树不会受到干扰

  • 构建语法树(compiler/builder.go)

    type Builder struct {
       rootPlanner *precedence
       parser      *Parser
    }
    ...
    func (b *Builder) Build() (*executor.Node, error) {
       if b.parser == nil {
          return nil, errors.New("parse is nil")
       }
    
       if b.rootPlanner != nil {
          // TODO 树优化
          return b.rootPlanner.plan(b)
       }
    
       return nil, errors.New("build failed")
    }
    ...