1 引言
重回 “手寫 SQL 編輯器” 系列。之前幾期介紹了 詞法、文法、語法的解析,以及回溯功能的實現,這次介紹如何生成語法樹。
基於 《回溯》 一文介紹的思路,我們利用 JS 實現一個微型 SQL 解析器,並介紹如何生成語法樹,如何在 JS SQL 引擎實現語法樹生成功能!
解析目標是:
select name, version from my_table;
複製程式碼
文法:
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 更多討論
如果你想參與討論,請點選這裡,每週都有新的主題,週末或週一釋出。