《編程語言實現模式?可以理解為編程語言的《設計模式》,這本書的中文翻譯通俗易懂,非常適合沒有基礎的人閱讀。 本節主要介紹第一部分,詞法分析和句法分析。
1.為什么需要學習這些模式
因為需要自定義DSL(領域自定義語言).
人的智能非常強大,能夠靈活地處理各種問題。計算機雖然迅速,但遠遠不及人類靈活。因此才有編程語言作為橋梁,建立人和機器的溝通方式。
然而,通用語言功能強大,但針對特定的應用環境,可能不夠簡潔,同時有很多噪音,也可能難以被領域專家理解。更誇張的情況是,一些方法用通用語言非常難實現,而使用DSL則容易得多,比如正則表達式。
在我們還不能構造出足夠智能的機器前,使用DSL簡潔流暢地反映人的算法和概念,便是一種折中的選擇。
即使不去設計DSL,能理解DSL的原理和方法,對編程水平的提高都非常有幫助。
設計DSL看起來有難度,但當有足夠的經驗之后,就會發現其實有很多固定的模式可以借鑒,通過學習和熟悉這些模式,就能夠更快速,方便,精確的構造自己的DSL和語言應用。
2.詞法分析
字母的組合構成單詞,單詞的組合構成句子,所有正確的句子的組合則構成一門語言。
對語言來說,如何判定句子是否正確?規定句子是否正確的規則稱為文法。
為了能夠正確理解句子,就需要先將句子拆分成多個單詞。對自然語言這叫做分詞;對編程語言,則叫做詞法分析。
通常來說,一個TOKEN可能是數字,符號,單詞,或是字符串。因而通過正則表達式,可以流式的切分並確定TOKEN的類型。
值得指出,TOKEN的類型由於二義性,並不能在詞法分析,而需要在語法分析時確定。
3.語法分析
在完成詞法分析后,就是語法分析的階段。語法分析的目標是將文本翻譯為句法樹。
LL(1)模式
語法分析就像貪吃蛇,詞法分析得到的單詞就像一顆顆的糖豆,最簡單的語法分析可以理解為一次吃一顆糖豆。
如果沒有遞歸子結構,形成的結果更像一個鏈表,而非樹結構。
一旦語言支持嵌套結構,就需要遞歸下降語法分析器。如果使用本模式,生成的將是直接調用自身而無限遞歸的函數。
遞歸下降的問題是,無法識別左遞歸,否則將陷入無限循環。另外,由於只向前看一個單元,可能並不能直接判斷出當前的文法。
LL(k)模式
為了解決這個問題,可能需要向前多看幾個單元。往前看的單元越多,貪吃蛇就在遇到交叉時順着不同的路徑看的更遠,從而越知道該選擇哪條路徑。而做解析決策的能力越強,語言就更容易編寫。
最簡單的方式,就是使用數組來緩沖所有的輸入符號,以整數輸入作為下標的指針。使用環形緩沖區能夠方便地保存數據。
回溯解析器
有時,當文法要求語言能夠支持任意多的重復結構時,要求LL(k)中的緩沖要無限大,這是不可能的。為了解決這個問題,需要采用回溯解析。
其概念就像走迷宮,既然不能只看不走,那么就嘗試去走每一個可能的選項,直到找到合適的為止。這樣等價於任意地向前看。
回溯可以設計為遇到成功的,就不嘗試其他路徑;也可以不論是否成功,都嘗試全部路徑,最后選擇最長,最短,或其他自定義的篩選方法。
回溯解析的性能遠遠不及LL(k),其中一大原因是重復。可能兩個路徑A和B, B路徑中又包含了A路徑。這樣A路徑就可能探查兩遍。為了避免重復,可以通過消耗少量的內存引入記憶機制。
記憶解析器
記憶解析器使用了類似動態規划的概念,記錄在某一位置時,使用某條文法的嘗試結果,如成功,失敗或其他的匹配特征。這樣就能大大減少重復,使得回溯解析能盡可能地達到LL(k)模式的性能。
謂詞解析器
有時,通過純粹的文法很難判斷選擇哪一條匹配路徑,因而可以通過嵌入判斷邏輯,借助運行時信息調整解析策略。
謂詞解析的常用實現,是在文法中嵌入代碼(如通用語言),解析引擎在運行時執行這些代碼,來調整決策。
3.語言優化
生成AST結構之后,就可以對樹進行操作了。主要的操作有兩部分,遍歷和規約。
節點結構本身的設計值得考量,基本上有兩種風格:
- 同構弱類型:所有的節點類型一致,只通過標識符區分操作,通過列表保存子節點。 優點是開發方便,缺點是少了運行時檢查。
- 異構強類型:節點類型不一致,但都繼承於一個基類。如果方法很多,會產生大量的節點類型。對於編程的工作量可能較大。另外,也不適合自動工具生成時嵌入自定義代碼。
遍歷
所謂遍歷,看似很容易。但有一些需要注意的點。
- 訪問和執行是其實是分離的,先訪問不一定代表先執行。根據執行代碼相對於訪問代碼的位置,可以生成前序,中序和后序遍歷。
- 遍歷中肯定要執行操作,對操作的描述可以放在節點定義文件中,也可以設計外部訪問器
規約
在生成句法樹后,可能一些樹結構是冗余或無效的,也可能可以被優化為更好的結構,例如: 0*(x+5)
由於任何數字乘以0都為0,所以應該直接被規約為0。
規約涉及到兩個問題,首先要識別特定的樹結構,這就需要對樹進行模式匹配,ANLNR已經有現成的工具和語法支持這類操作。