从编译器原理的角度来看规则引擎开发 | 青训营笔记

108 阅读5分钟

从编译器原理的角度来看规则引擎开发 | 青训营笔记

前言

这是我在青训营学习发布的第六篇笔记,该笔记旨在通过规则引擎开发的视角介绍编译器原理的相关内容,并介绍编译原理的基本概念。

规则引擎

规则引擎的定义

业务规则引擎是一种软件系统,用于在运行时的生产环境中执行一个或多个业务规则。这些规则可能来自于法律法规(例如“员工可以因任何原因或无理由被解雇,但不能出于非法原因”)、公司政策(例如“所有一次花费超过 100 美元的客户将获得 10% 的折扣”)或其他来源。业务规则系统使得这些公司策略和其他操作决策能够独立于应用程序代码进行定义、测试、执行和维护。—— 来源:Wikipedia

思考以下例子:某商城开设一个活动,用户购买指定产品可以获得积分(对应某种业务逻辑),这在代码实现上似乎很简单。然而,如果积分获取规则不断更改(对应业务逻辑的更改),那么程序员就需要不断地修改代码以实现新的业务逻辑,这无疑增加了不必要的工作量。

规则引擎是一种嵌入在应用程序中的组件,它实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。它接受数据输入,解释业务规则,并据此进行业务决策。通过规则引擎,业务人员可以直接修改规则决策,而无需通过开发人员的干预,提高工作效率,并解耦了任务。

规则引擎的组成

规则引擎由三个主要组件构成:

  • 数据输入:接受预定义的语义编写的规则集作为策略集,例如 price > 500,并接受业务数据作为执行过程中的参数,例如价格、标签等。
  • 规则理解:能够根据预先定义的词法、语法、优先级、运算符等正确理解所表达的业务规则的语义。
  • 规则执行:根据输入的参数对策略集中的规则进行正确解释和执行,并对执行过程中的数据类型进行检查,确保执行结果的准确性。

规则引擎的应用领域

  • 风险控制
  • 活动策略运营
  • 数据分析和清洗
  • ……

编译原理的基本概念

词法分析(Lexical Analysis)

词法分析是将源代码字符串转换为词法单元(Token)的过程。

例如,将表达式 price > 500 && (isNewUser || userLevel > 5) 分割为 price, >, 500, &&, (, isNewUser, ||, userLevel, >, 5, ) 几个部分(Token)。

我们可以使用有限自动机(Finite-State Automaton)来识别 Token。有限自动机是一种状态机,它的状态数量是有限的,通过基于输入字符的状态转换来处理任何状态中的状态转移。

词法分析完成后,我们可以通过语法分析来解析表达式。

语法分析(Syntax Analysis)

语法分析是在词法分析的基础上识别表达式的语法结构的过程。

例如,上述的 price, >, 500, &&, (, isNewUser, ||, userLevel, >, 5, ) 可以被识别为以下几个部分:

price > 500,其中 > 是操作符,price 是左操作数,500 是右操作数; isNewUser || userLevel > 5,其中 || 是操作符,isNewUser 是左操作数,userLevel > 5 是右操作数; userLevel > 5,其中 > 是操作符,userLevel 是左操作数,5 是右操作数; price > 500 && ( isNewUser || userLevel > 5 ),其中 && 是操作符,price > 500 是左操作数,( isNewUser || userLevel > 5 ) 是右操作数。 通过语法分析,我们可以构建表达式的抽象语法树。

抽象语法树(Abstract Syntax Tree)

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST)是源代码语法结构的一种抽象表示。它以树状的形式表示编程语言的语法结构,树上的每个节点表示源代码中的一种结构。—— Wikipedia

抽象语法树大致的结构如下图所示:

600px-Abstract_syntax_tree_for_Euclidean_algorithm.svg_.png

上述抽象语法树表示了以下伪代码,通过辗转相除法求最大公约数:

while b0:
    if a > b:
        a := a - b
    else:
        b := b - a
return a
巴科斯范式(BNF)

对于上下文无关语法(Context-Free Grammar,可以独立地进行正确性判断的语句),可以使用巴科斯范式来表达:

exp : add;
add : add '+' mul | mul; // 加法表达式,可以匹配 a + b + c 或 a + b * c
mul : mul '*' pri | pri; // 乘法表达式,可以匹配 a * b * c
pri : string | bool | number | identifier; // 基础表达式,可以匹配 weight 或 20 或 "abcde"

上述每一行都是产生式的一种形式。它们表示可以通过已知的其他类型的表达式或符号推导出的表达式。

在这个例子中,pri(代表基础表达式)可以由常量(string, bool, number)或标识符(identifier)组成;mul(代表乘法表达式)可以由 pri 或 mul * pri 组成,依此类推。

递归下降算法(Recursive Descent Parsing)

我们可以使用递归下降算法构建语法树。该算法通过递归的方式,不断将 Token 进行语法展开,来构建抽象语法树。

类型检查
  • 类型推导:根据子表达式的类型构造父表达式的类型。例如,表达式 A+B 的类型是基于 A 和 B 的类型定义的。
  • 编译时检查和运行时检查。

引用

该笔记材料主要来源于: 后端 - 字节内部课 (juejin.cn)