重回 “手写 SQL 编辑器” 系列。以前几期介绍了 词法、文法、语法的解析,以及回溯功能的实现,此次介绍如何生成语法树。git
基于 《回溯》 一文介绍的思路,咱们利用 JS 实现一个微型 SQL 解析器,并介绍如何生成语法树,如何在 JS SQL 引擎实现语法树生成功能!github
解析目标是:sql
select name, version from my_table;
文法:typescript
const root = () => chain(selectStatement, many(";", selectStatement)); const selectStatement = () => chain("select", selectList, fromClause); const selectList = () => chain(matchWord, many(",", matchWord)); const fromClause = () => chain("from", matchWord); const statement = () => chain( "select", selectList, "from", chain(tableName, [whereStatement, limitStatement]) );
这是本文为了方便说明,实现的一个精简版本。完整版见咱们的开源仓库 cparser。
root
是入口函数,many()
包裹的文法能够执行任意次,因此数组
chain(selectStatement, many(";", selectStatement));
表示容许任意长度的 selectStatement
由 ;
号链接,selectList
的写法也同理。性能优化
matchWord
表示匹配任意单词。编辑器
语法树是人为对语法结构的抽象,本质上,若是咱们到此为止,是能够生成一个 基本语法树 的,这个语法树是多维数组,好比:函数
const fromClause = () => chain("from", matchWord);
这个文法生成的默认语法树是:['from', 'my_table']
,只不过 from
my_table
具体是何含义,只有当前文法知道(第一个标志无含义,第二个标志表示表名)。工具
fromClause
返回的语法树做为结果被传递到文法 selectStatement
中,其结果多是:['select', [['name', 'version']], ['from', 'my_table']]
。性能
你们不难看出问题:当默认语法树汇集在一块儿,就没法脱离文法结构单独理解语法含义了,为了脱离文法结构理解语法树,咱们须要将其抽象为一个有规可循的结构。
经过上面的分析,咱们须要对 chain
函数提供修改局部 AST 结构的能力:
const selectStatement = () => chain("select", selectList, fromClause)(ast => ({ type: "statement", variant: "select", result: ast[1], from: ast[2] }));
咱们能够经过额外参数对默认语法树进行改造,将多维数组结构改变为对象结构,并增长 type
variant
属性标示当前对象的类型、子类型。好比上面的例子,返回的对象告诉使用者:“我是一个表达式,一个 select 表达式,个人结果是 result,个人来源表是 from”。
那么,chain
函数如何实现语法树功能呢?
对于每一个文法(每一个 chain
函数),其语法树必须等待全部子元素执行完,才能生成。因此这是个深度优先的运行过程。
下图描述了 chain
函数执行机制:
生成结构中有四个基本结构,分别是 Chain、Tree、Function、Match,足以表达语法解析须要的全部逻辑。(不包含 可选、多选 逻辑)。
每一个元素的子节点所有执行完毕,才会生成当前节点的语法树。实际上,每一个节点执行完,都会调用 callParentNode
访问父节点,执行到了这个函数,说明子元素已成功执行完毕,补全对应节点的 AST 信息便可。
对于修改局部 AST 结构函数,需等待整个 ChainNode
执行完毕才调用,并将返回的新 AST 信息存储下来,做为这个节点的最终 AST 信息并传递给父级(或者没有父级,这就是根结点的 AST 结果)。
本文介绍了如何生成语法树,并说明了 默认语法树 的存在,以及咱们之因此要一个定制的语法树,是为了更方便的理解含义。
同时介绍了如何经过 JS 运行一套完整的语法解析器,以及如何提供自定义 AST 结构的能力。
本文介绍的模型,只是为了便于理解而定制的简化版,了解所有细节,请访问 cparser。
最后说一下为什么要作这个语法解析器。现在有许多开源的 AST 解析工具,但笔者要解决的场景是语法自动提示,须要在语句不完整,甚至错误的状况,给出当前光标位置的全部可能输入。因此经过完整重写语法解析器内核,在解析的同时,生成语法树的同时,也给出光标位置下一个可能输入提示,在通用错误场景自动从错误中恢复。
目前在作性能优化,通用 SQL 文法还在陆续完善中,目前仅可当学习参考,不要用于生产环境。
讨论地址是: 精读《手写 SQL 编译器 - 语法树》 · Issue #99 · dt-fe/weekly
若是你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。