摘要:一般用大寫的表示文法的開頭,稱為開始符號。更多討論討論地址是精讀手寫編譯器文法介紹如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。
1 引言
文法用來描述語言的語法規則,所以不僅可以用在編程語言上,也可用在漢語、英語上。
2 精讀我們將一塊語法規則稱為 產生式,使用 “Left → Right” 表示任意產生式,用 “Left => Right” 表示產生式的推導過程,比如對于產生式:
E → i E → E + E
我們進行推導時,可以這樣表示:E => E + E => i + E => i + i + E => i + i + i
也有使用 Left : Right 表示產生式的例子,比如 ANTLR。BNF 范式通過 Left ::= Right 表示產生式。
舉個例子,比如 SELECT * FROM table 可以被表達為:
S → SELECT * FROM table
當然這是最固定的語法,真實場景中,* 可能被替換為其他單詞,而 table 不但可能有其他名字,還可能是個子表達式。
一般用大寫的 S 表示文法的開頭,稱為開始符號。終結符與非終結符
下面為了方便書寫,使用 BNF 范式表示文法。
終結符就是語句的終結,讀到它表示產生式分析結束,相反,非終結符就是一個新產生式的開始,比如:
::= SELECT FROM ::= [ , ] ::= [ , ]
所有 ::= 號左邊的都是非終結符,所以 selectList 是非終結符,解析 selectStatement 時遇到了 selectList 將會進入 selectList 產生式,而解析到普通 SELECT 單詞就不會繼續解析。
對于有二義性的文法,可以通過 上下文相關文法 方式描述,也就是在產生式左側補全條件,解決二義性:
aBc -> a1c | a2c dBe -> d3e
一般產生式左側都是非終結符,大寫字母是非終結符,小寫字母是終結符。
上面表示,非終結符 B 在 ac 之間時,可以解析為 1 或 2,而在 de 之間時,解析為 3。但我們可以增加一個非終結符讓產生式可讀性更好:
B -> 1 | 2 C -> 3
這樣就將上下文相關文法轉換為了上下文無關文法。
上下文無關文法根據是否依賴上下文,文法分為 上下文相關文法 與 上下文無關文法,一般來說 上下文相關文法 都可以轉換為一堆 上下文無關文法 來處理,而用程序處理 上下文無關文法 相對輕松。
SQL 的文法就是上下文相關文法,在正式介紹 SQL 文法之前,舉一個簡單的例子,比如我們描述等號(=)的文法:
SELECT CASE WHEN bee = "red" THEN "ANGRY" ELSE "NEUTRAL" END AS BeeState FROM bees; SELECT * from bees WHERE bee = "red";
上面兩個 SQL 中,等號前后的關鍵字取決于當前是在 CASE WHEN 語句里,還是在 WHERE 語句里,所以我們認為等號所在位置的文法是上下文相關的。
但是當我們將文法粒度變細,將 CASE WHEN 與 WHERE 區塊分別交由兩塊文法解決,將等號這個通用的表達式抽離出來,就可以不關心上下文了,這種方式稱為 上下文無關文法。
附上一個 mysql 上下文無關文法集合。
左推導與右推導上面提到的推導符號 => 在實際運行過程中,顯然有兩種方向左和右:
E + E => ?
從最左邊的 E 開始分析,稱為左推導,對語法解析來說是自頂向下的方式,常用方法是遞歸下降。
從最右邊的 E 開始分析,稱為右推導,對語法解析來說是自底向上的方式,常用方法是移進、規約。
右推導過程比左推導過程復雜,所以如果考慮手寫,最好使用左推導的方式。
左推導的分支預測比如 select
::= , |
由于它可以展開:SelectList => SelectList , a => SelectList , b, a => c, b, a。
但程序執行時,讀到這里會進入死循環,因為 SelectList 可以被無限展開,這就是左遞歸問題。
消除左遞歸消除左遞歸一般通過轉化為右遞歸的方式,因為左遞歸完全不消耗 Token,而右遞歸可以通過消耗 Token 的方式跳出死循環。
Token 見上一期精讀 精讀《手寫 SQL 編譯器 - 詞法分析》
::= ::= , | null
這其實是一個通用處理,可以抽象出來:
E → E + F E → F
E → FG G → + FG G → null
不過我們也不難發現,通過通用方式消除左遞歸后的文法更難以閱讀,這是因為用死循環的方式解釋問題更容易讓人理解,但會導致機器崩潰。
筆者建議此處不要生硬的套公式,在套了公式后,再對產生式做一些修飾,讓其更具有語義:
提取左公因式::= | ,
即便是上下文無關的文法,通過遞歸下降方式,許多時候也必須從左向右超前查看 K 個字符才能確定使用哪個產生式,這種文法稱為 LL(k)。
但如果每次超前查看的內容都有許多字符相同,會導致第二次開始的超前查看重復解析字符串,影響性能。最理想的情況是,每次超前查看都不會對已確定的字符重復查看,解決方法是提取左公因式。
設想如下的 sql 文法:
::= as | as | |
其實 Text 本身也是比較復雜的產生式,最壞的情況需要對 Text 連續匹配六遍。我們將 Text 公因式提取出來就可以僅匹配一遍,因為無論是何種 Field 產生式,都必定先遇到 Text:
::= ::= | ::= as ::= |
和消除左遞歸一樣,提取左公因式也會降低文法的可讀性,需要進行人為修復。不過提取左公因式的修復沒辦法在文法中處理,在后面的 “函數式” 處理環節是有辦法處理的,敬請期待。
結合優先級對 SQL 的文法來說不存在優先級的概念,所以從某種程度來說,SQL 的語法復雜度還不如基本的加減乘除。
3 總結在實現語法解析前,需要使用文法描述 SQL 的語法,文法描述就是語法分析的主干業務代碼。
下一篇將介紹語法分析相關知識,幫助你一步步打造自己的 SQL 編譯器。
4 更多討論討論地址是:精讀《手寫 SQL 編譯器 - 文法介紹》 · Issue #94 · dt-fe/weekly
如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96170.html
摘要:返回的語法樹作為結果被傳遞到文法中,其結果可能是。每個元素的子節點全部執行完畢,才會生成當前節點的語法樹。更多討論討論地址是精讀手寫編譯器語法樹如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。 1 引言 重回 手寫 SQL 編輯器 系列。之前幾期介紹了 詞法、文法、語法的解析,以及回溯功能的實現,這次介紹如何生成語法樹。 基于 《回溯》 一文介紹的思路,我們利用 JS ...
摘要:經過連續幾期的介紹,手寫編譯器系列進入了智能提示模塊,前幾期從詞法到文法語法,再到構造語法樹,錯誤提示等等,都是為智能提示做準備。 1 引言 詞法、語法、語義分析概念都屬于編譯原理的前端領域,而這次的目的是做 具備完善語法提示的 SQL 編輯器,只需用到編譯原理的前端部分。 經過連續幾期的介紹,《手寫 SQL 編譯器》系列進入了 智能提示 模塊,前幾期從 詞法到文法、語法,再到構造語法...
閱讀 3137·2021-11-08 13:18
閱讀 2282·2019-08-30 15:55
閱讀 3607·2019-08-30 15:44
閱讀 3067·2019-08-30 13:07
閱讀 2782·2019-08-29 17:20
閱讀 1949·2019-08-29 13:03
閱讀 3410·2019-08-26 10:32
閱讀 3225·2019-08-26 10:15