前言
近几年, 随着前后端 (或者说整个应用程序开发技术) 的提升. Low-code 概念开始冒出来了.
Low 的意思是 low level, 也就是懂一些 coding 能力就可以做出很有用的程序.
这不容易, 只有工业化到一定程度才会出现这种现象. 比如很多公司现在都可以做手机了. 因为行业分工细, 你做一个手机类似与组装一个手机.
软件开发也是同理, 只要把各种 SASS 组装到 PAAS 上, 一个程序就开始贡献了.
于是我遇到了一些这样的需求. 用户想通过简单的表达式来实现对系统的权限限制.
比如说, 一个 create invoice 的权限, 但是 invoice amount 必须小于 1000.
以前处理这类需求一般上都是 hardcode. 但最近我接触比较多表达式树概念后. 我发现这些需求并不需要 hardcode.
让用户输入一个简单的表达式, 然后解释执行就可以做到验证权限的效果了.
比如上面这个 case, 表达式就是 'invoice amount' < 1000, 类似与 excel advanced filter 的使用难度.
有了这样一个功能, 系统就不需要担心后续的扩展了. 这个表达式还可以用在许多地方. 比如 notification. 细节我就不多说了.
解析器与编辑器
参考:
先聊聊什么是解析器, 和我们日常用到的哪些东西有关联.
c, c++ 是语言, 写完以后要怎么在 OS 里跑起来呢 (控制 CPU 等)?
需要一个编辑过程, 针对不同的 OS, CPU 需要编程成不同的可执行物 (机器码). OS 只认机器码.
javascript 是语言, 你写完以后不需要去编辑它, 直接丢给游览器就可以了. 因为游览器负责了把它变成可执行物, 职责转移了, 这种就叫解析.
所以它们很多时候很难去分辨, 比如对于 Angular 来说, 你写好了 Angular 是需要有一个编辑过程的, 编辑成 javascript.
不用纠结太多, 我们写业务的懂个大概就可以了.
解析器
参考:
Let’s Build A Simple Interpreter. Part 1
解析就是把一个语言, 变成可执行物. 比如 'invoice amount' < 100, 它就可以是一个语言, 任何一个可以表达事物的东西都可以称作语言 (当然这里讲的是字符串而不是声音).
解析语言就是把 string 变成可执行物. 我们读过书的都知道, 一门语言它一定是有词汇, 语法 (grammer) 这些冬冬的, 抽象点说就是它有一些定义, 然后有一些规则.
‘invoice amount’ < 100 里面就出现了 symbol, string, number. Symbol 就有它的定义, 它们被一些规则所限制, 比如顺序, 分割等等.
因为有了规则, 才变得可以被解析, 因为有了语义才知道要怎么执行.
上面这段想表达的就是判断 invoice amount (这个属性值), < 小于, 100 一百块钱
术语和流程
第一步 – 词法分析 (lexical analysis)
先把 string 切分出来, 变成一个一个有意义的东西 (它们叫 token), 这个过程叫词法分析, 做这件事的人叫词法分析器 (lexer)
它的工作就是一个一个字符串就 read 出来 (通常是配上正则), 然后把它们归类. 比如 invoice amount 是一个读属性的意思, < 是一个操作符, 100 是一个数目
这样就分成了 3 块有点意义的东西了, 而不只是一个 string.
第二部 – 语法分析 (syntactic analysis, or parsing)
做这件事的人叫语法分析器 (parser), 有了那些 token 还不足够执行, 因为执行顺序并不是左到右的. 它还有其它规则, 比如先乘除后加减, 先 and 后 or, 括弧等 参考: Order of operations
于是 parser 需要把 token 变成一个有顺序, 可以容易被遍历的结构.
这就是大名鼎鼎的 Abstract syntax tree (AST 抽象語法樹)
在之前我写 C# 反射 & 表达式树 的时候介绍过 Expression Tree, 它就算是一种 AST.
总结
于是...都连起来了.
EF Core
Ef Core 的职责是吧 Expression Tree 变成 SQL query string. 一般上我们都是 hardcode 去写这些 expression 的.
所以它没有用到任何解析器的东西. 它是 Expression Tree -> SQL query string
Dynamic LINQ
Dynamic LINQ 的职责是把 string expression 变成 Expression Tree, 然后交由 LINQ 去执行. 它就用到了解析器, string expression to Expression Tree
OData
我介绍过 OData – Query to Expression, 它的职责是把 odata query string 变成 AST (它自己的) 然后在转成 Expression Tree 让 EF Core 去执行.
它也用到了解析器, odata query string -> AST -> Expression Tree -> EF Core -> SQL query string.
OData.NxT 还希望也把反向给做了, Expression Tree -> AST -> odata query string.
一开始我搞不同为什么 odata 不把 query string 直接解析成 Expression Tree, 而是搞了一个自己的 AST. 但后来我发现这样的好处是不依赖 C#。
如果想在其它平台, 比如 Node.js 去做解释, 就可以比较容易的 port 过去了.
目前的计划
本来想用 odata 作为 string expression, 这样就可以利用它的解析器. 但后来想想, 这套方案在前端就不起作用了. 想找一个 string expression 符合我的业务需求 (不复杂, 但又有一些些 customize, odata 不支持 addDays 这种操作)
支持 C# 和 Javascript 环境的, 最终没有找到. 于是才有了自己写的念头, research 完了以后发现, 确实是工程不小啊. 还是再观察看看吧.