精读《手写 SQL 编译器 - 语法树》

1 引言

重回 “手写 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']]

你们不难看出问题:当默认语法树汇集在一块儿,就没法脱离文法结构单独理解语法含义了,为了脱离文法结构理解语法树,咱们须要将其抽象为一个有规可循的结构。

2 精读

经过上面的分析,咱们须要对 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 结果)。

3 总结

本文介绍了如何生成语法树,并说明了 默认语法树 的存在,以及咱们之因此要一个定制的语法树,是为了更方便的理解含义。

同时介绍了如何经过 JS 运行一套完整的语法解析器,以及如何提供自定义 AST 结构的能力。

本文介绍的模型,只是为了便于理解而定制的简化版,了解所有细节,请访问 cparser

最后说一下为什么要作这个语法解析器。现在有许多开源的 AST 解析工具,但笔者要解决的场景是语法自动提示,须要在语句不完整,甚至错误的状况,给出当前光标位置的全部可能输入。因此经过完整重写语法解析器内核,在解析的同时,生成语法树的同时,也给出光标位置下一个可能输入提示,在通用错误场景自动从错误中恢复。

目前在作性能优化,通用 SQL 文法还在陆续完善中,目前仅可当学习参考,不要用于生产环境。

4 更多讨论

讨论地址是:精读《手写 SQL 编译器 - 语法树》 · Issue #99 · dt-fe/weekly

若是你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。

相关文章
相关标签/搜索