本人於2023年暑期開始貢獻 Shecc ,從暑期至今都主要活躍於前端(包括 lexer 及 parser)的改進以及函式抽象化的優化(參考此處)。
然而後來開始著手處理解決 lexer 及 parser 對於 preprocessor directives 的 hack code 以及缺陷時,發現僅仰賴傳統作法的 lexer parser 對於某些具有上下文敏感性 preprocessor directive (如:#define
, function-like macro)的 syntax 上會受到諸多限制。
針對此狀況,我於2024年4月開始,利用 Rust 來快速概念驗證於腦中所構思的新技術:Regional Lexical Analyzer (a.k.a. Regional Lexer)。
本次快速概念驗證中挑選 Rust 作為實作語言的原因牽涉到諸多考量:
Iterator
)來簡化邏輯。筆者註:我在實作 Regional Lexer 的時候並無參考任何論文,若有相似的實作技巧歡迎留言!
Regional Lexer ,如字面上所述,藉由將原始碼分割並交由不同用途的特化 Lexer 進行 tokenize 的動作,達成消弭單一 Lexer 的過度特化。實作架構大致如下:
其中,regioanl_lexers
是一個 stack,每個 Regional Lexer 皆有各自負責的原始碼區域(區域可能是藉由 synthesize 產生而成的偽原始碼)。藉由分離某些特化的 Lexer 規格(如 skip_newline
、skip_backslash
),我們可以更有效的控制何種規則需要用於解析相對應的原始碼。
由於這樣的做法會有多個 Regional Lexer 需要維護,故我們需要一個 "Global" Lexer 來管理整體。而根據設計,"Global" Lexer 中始終會有一個 Regional Lexer 存在(等價於原先已經 source loader 展開過後的原始碼所使用的 Lexer)。
由於 Parser 是一個單純的語法規則處理單元,所以不需要做過多調整。而透過 "Global" Lexer 的包裝,我們可以有效的抽象化一些
共用邏輯(如:lex_token
)。
對於 Lexer 的邏輯,我們可以把 #define
所定義的 macro 或 alias 視為一段合成的程式碼,並當遇到對應的 aliased identifier 時,直接將 Lexer 的解析的位置重置至需要被置換的原始碼位置。由於不論是 macro 還是 alias,我們都需要在特定的位置再重置 Lexer 位置回到上一次跳轉的位置,這時候我們可以利用 Lexer::regional_lexers
其資料結構 Stack<_>
的特性:當子程式碼的位置超過合成程式碼的範圍,其會始終回傳 TokenType::TEof
,也就可以斷定該層的詞法分析已結束,將該 Regional Lexer 的資源及本身釋放,並回傳前一層 Regional Lexer 所分析的 Token 結果,即達成區域詞法分析的功能。
Lexer::regional_lexers
如此設計甚至對後續實作 Nested Macro Invocation 有諸多奇效!
筆者註:由於篇幅問題,這裡不會討論 Macro 的額外特殊判斷及行為。有興趣可以到 Tangle 參考實作邏輯。
雖然仍有兩個特性有待新增並驗證:
但該概念驗模型已解決先前 Shecc 所無法達成的目標,如:
此外,我將持續改進該概念驗證模型,由於目前所合成的程式碼可能會造成過多的字串複製,進而留下可觀的 memory footprint,因此,目前下一步需要概念驗證的是如何利用 Rust 的 std::slice
(也就是 &str
)來防止過多的字串複製以及確保原始碼的生命週期。