精读《手写 SQL 编译器 - 词法剖析》

1 弁言

由于工作关系,须要开辟支撑浩瀚方言的 SQL 编辑器,所以温习了一下编译道理相干学问。

比拟编译道理专家,我们只须要相识部份编译道理即可完成 SQL 编辑器,所以这是一篇写给前端的编译道理文章。

剖析 SQL 能够分为以下四步:

  1. 词法剖析,将 SQL 字符串拆分红包括关键词识别的字符段(Tokens)。
  2. 语法剖析,应用自顶向下或自底向上的算法,将 Tokens 剖析为 AST,能够手动,也能够自动。
  3. 毛病检测、恢复、提醒揣摸,都须要应用语法剖析发作的 AST。
  4. 语义剖析,做完这一步就能够实行 SQL 语句了,不过对前端而言,不须要深切到这一步,能够跳过。

2 精读

词法剖析就像刀削面的历程,拿着一段字符串(面条)一端不停下刀,当面条被切完也就完成了词法剖析,所以词法剖析是 字符串 -> 一堆字符段 的历程。

流程很简朴,难点就在下刀的分寸了,每次砍几厘米呢?

回到词法剖析,为了预备切分,我们须要定义 SQL 的 Token 有哪些范例,即 Token 分类。

Token 分类

SQL 的 Token 能够分为以下几类:

  • 解释。
  • 关键字(SELECTCREATE)。
  • 操作符(+->=)。
  • 开闭合标志((CASE)。
  • 占位符(?)。
  • 空格。
  • 引号包裹的文本、数字、字段。
  • 方言语法(${variable})。

能够看到,在词法剖析阶段,我们的 Tokens 不须要体贴关键词是什么,只需识别是不是是关键词即可,由于关键词的识别会留到语法剖析时处置惩罚。涉及到语意处置惩罚就要斟酌上下文,而这都不是词法剖析阶段要斟酌的。

一样,操作符、空格、文本、占位符等构成了 SQL 语句的其他部份,末了经由过程开闭合标志比方左括号和右括号,让 SQL 支撑子语句。

再强调一次,虽然 SQL 支撑子语句,但并非放在任何位置都是合理的,其他范例 Token 同理,然则词法剖析不须要斟酌 Token 是不是合理,只需切分即可。

用正则逐段分词

像大多数言语一样,SQL 为了轻易人类浏览,采纳从左到右的誊写体式格局,因而分词方向也从左到右

我们为每一个 Token 范例写一个函数,比方婚配空格的婚配函数:

function getTokenWhitespace(restStr: string) {
  const matches = restStr.match(/^(\s+)/);

  if (matches) {
    return { type, value: matches[1] };
  }
}

restStr 示意掐去头部剩下的 SQL 字符串,一切婚配函数都拿 restStr 举行婚配,已婚配的不须要再处置惩罚。

经由过程正则 /^(\s+)/ 婚配到第一个以空格开首的空格(读起来有点别扭),婚配时必需保证以你要婚配的内容开首,而且只婚配一次,如许才不会在切词时发作脱漏。

同理婚配 /**/ 范例解释时,也能经由过程正则易如反掌的完成:

function getTokenBlockComment(restStr: string) {
  const matches = restStr.match(/^(\/\*[^]*?(?:\*\/|$))/);

  if (matches) {
    return { type, value: matches[1] };
  }
}

个中 (?:\*/\) 示意婚配到以 */ 末端处,而 (?:\*\/|$) 背面的 |$ 示意或许直接婚配到末端(假如一向没有碰到 */ 那背面悉数看成解释)。

所以只需 Token 分类妥当,而且能为每一个分类写一个头婚配正则,分词功用就完成了 90%。

方言拓展

为了支撑某些方言,须要从分词时就最先做斟酌。比方 ${variable} 作为一种变量用法时,我们须要在一般字段的正则婚配中,到场一项 \$\{[a-zA-Z0-9]+\} 婚配。

假如要支撑纯中文作为字段,能够再补充 |\u4e00-\u9fa5

分词主流程

有了一个个分词函数,再补充一个不停婚配、切割字符串、再婚配的主函数即可,这一步更简朴:

while (sqlStr) {
  token =
    getTokenWhitespace(sqlStr, token) | getTokenBlockComment(sqlStr, token);

  sqlStr = sqlStr.substring(token.value.length);

  tokens.push(token);
}

上面的函数每取一次 Token,都将取到的 Token 长度丢掉,继承婚配剩下的字符串,直到字符串被切分完为止。

有些特殊情况须要拿到上次的 Token 才推断下一个 Token 该怎样切割,所以将 Token 传给每一个下一步 Match 函数。

末了,实行这个主函数,分词就完成了!

3 总结

分词比较简朴,到这里就悉数完毕了。背面行将进入深水区语法剖析,敬请期待。

4 更多议论

议论地点是:
精读《手写 SQL 编译器 – 词法剖析》 · Issue #93 · dt-fe/weekly

假如你想介入议论,请点击这里,每周都有新的主题,周末或周一宣布。

    原文作者:黄子毅
    原文地址: https://segmentfault.com/a/1190000015568992
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞