精讀《手寫 SQL 編譯器 - 詞法分析》

1 引言

由於工做關係,須要開發支持衆多方言的 SQL 編輯器,因此複習了一下編譯原理相關知識。前端

相比編譯原理專家,咱們只須要了解部分編譯原理便可實現 SQL 編輯器,因此這是一篇寫給前端的編譯原理文章。git

解析 SQL 能夠分爲以下四步:github

  1. 詞法分析,將 SQL 字符串拆分紅包含關鍵詞識別的字符段(Tokens)。
  2. 語法分析,利用自頂向下或自底向上的算法,將 Tokens 解析爲 AST,能夠手動,也能夠自動。
  3. 錯誤檢測、恢復、提示推斷,都須要利用語法分析產生的 AST。
  4. 語義分析,作完這一步就能夠執行 SQL 語句了,不過對前端而言,不須要深刻到這一步,能夠跳過。

2 精讀

詞法分析就像刀削麪的過程,拿着一段字符串(麪條)一端不斷下刀,當面條被切完也就完成了詞法分析,因此詞法分析是 字符串 -> 一堆字符段 的過程。算法

流程很簡單,難點就在下刀的分寸了,每次砍幾釐米呢?sql

回到詞法分析,爲了準備切分,咱們須要定義 SQL 的 Token 有哪些類型,即 Token 分類。typescript

Token 分類

SQL 的 Token 能夠分爲以下幾類:編輯器

  • 註釋。
  • 關鍵字(SELECTCREATE)。
  • 操做符(+->=)。
  • 開閉合標誌((CASE)。
  • 佔位符(?)。
  • 空格。
  • 引號包裹的文本、數字、字段。
  • 方言語法(${variable})。

能夠看到,在詞法分析階段,咱們的 Tokens 不須要關心關鍵詞是什麼,只要識別是否是關鍵詞便可,由於關鍵詞的辨認會留到語法分析時處理。涉及到語意處理就要考慮上下文,而這都不是詞法分析階段要考慮的。函數

一樣,操做符、空格、文本、佔位符等構成了 SQL 語句的其餘部分,最後經過開閉合標誌好比左括號和右括號,讓 SQL 支持子語句。spa

再強調一次,雖然 SQL 支持子語句,但並非放在任何位置都是合理的,其餘類型 Token 同理,可是詞法分析不須要考慮 Token 是否合理,只要切分便可。rest

用正則逐段分詞

像大多數語言同樣,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

若是你想參與討論,請點擊這裏,每週都有新的主題,週末或週一發佈。

相關文章
相關標籤/搜索