剖析器產生器(parser generator)
手寫剖析器既乏味又容易出錯,尤其當語言成長時。剖析器產生器是一個你交給它一份文法、它便替你寫出剖析器的程式,就像 3D 印表機接受一個設計檔、產出實體物件一樣。你描述語言的形狀;工具產出能辨認它的剖析器原始碼。
你餵給產生器一份接近 BNF 表示法的文法,每條規則可選擇地附上一個語意動作,一段在該規則歸約時要執行的程式碼,通常用來建出抽象語法樹的一個節點。經典工具 yacc 與其開源後繼 bison 從你的文法算出一張 LALR(1) 由下而上剖析表,並產出一個以 C 寫成、快速的表驅動剖析器;ANTLR 走由上而下的路線,產生一個預測式(類 LL,帶擴充前瞻)的遞迴下降剖析器。關鍵是,剖析器產生器通常與掃描器產生器(lex 或 flex)搭配,後者把正規表示式的詞符定義變成詞法分析器,落實標準分工:產生出的掃描器產出詞符,產生出的剖析器消耗詞符並建出樹。
剖析器產生器之所以重要,是因為它把數十年的自動機理論變成一個你按下去的按鈕。你不再手寫易錯的移位-歸約表,而是維護一份可讀的文法,並在語言改變時重新產生剖析器。誠實的限制是:產生出的剖析器只會和你的文法一樣好:若文法歧義或落在工具的類別之外,產生器會回報移位-歸約或歸約-歸約衝突,你必須理解並化解,通常用優先序與結合性宣告,或藉改寫規則。工具自動化的是機制,而非文法設計。
一個 bison 文法片段:expr : expr '+' expr { $$ = makeAdd($1, $3); } | NUM { $$ = $1; } ; 大括號裡的文字是建出 AST 節點的語意動作。bison 把這組規則編譯成 LALR(1) 剖析器;對應的 flex 規格則把 NUM、+ 等變成詞符。
yacc/bison 產生由下而上的 LALR 剖析器;ANTLR 產生由上而下的類 LL 剖析器。
剖析器產生器自動化的是剖析器,而非文法。它無法替你消除真正的歧義;歧義或超出類別的文法會產生你必須化解的衝突,乾淨的文法仍是工程師的責任。