新的文法

我們來考慮一下加入了比較運算子的文法會變成什麼樣子吧。至今為止出現的運算子的優先順序由低至高表列如下:

  1. == !=

  2. < <= > >=

  3. + -

  4. * /

  5. 單項+ 單項-

  6. ()

把優先順序分別對應到不同的終端符號,就可以用生成文法來表現優先順序。像exprmul一樣的方法來思考,加上比較運算子的文法如下所示:

expr       = equality
equality   = relational ("==" relational | "!=" relational)*
relational = add ("<" add | "<=" add | ">" add | ">=" add)*
add        = mul ("+" mul | "-" mul)*
mul        = unary ("*" unary | "/" unary)*
unary      = ("+" | "-")? term
term       = num | "(" expr ")"

equality代表 ==!=relational代表<<=>>=。這類非終端符號運用左結合運算子的分析模式,可以直接對應成函式。

除此之外,為了以equality表示算式整體,把exprequality分離。也可以把equality的右邊直接寫在expr,但是上述的寫法應該比較好讀。

小知識:單純但冗長的程式碼和進階的簡潔程式碼

遞迴下降分析法基本上是照搬生成規則寫成程式碼,所以根據相同規則分析的函式看起來會一模一樣。至今為止寫的relationalequalityaddmul應該就是長得一樣的函式。

看到像這樣在不同函式出現同樣的模式,應該很自然會想要利用C的巨集(macro)或C++的模板(template)、高階函式(higher-order function)或程式碼產生(code generation)等超程式設計(meta-programming,也譯作元程式設計)手法來進行抽象化吧。事實上是可以這樣做的,但本書刻意不這樣處理。其理由如下:

單純的程式碼就算有些冗長,也還是很好理解。就算事後要在這些很像的函式重複加上一樣的修改,其實也不用花太大的功夫。此外,高度抽象化的程式碼,要先知道其抽象化的原理,再進而搞懂該怎麼使用他們,就會變得較難以理解。舉例來說,本輸如果用超程式設計的手法來產生遞迴下降分析的程式碼開始講解,這本書應該會變得更難讀吧。

不需要隨時都以寫出簡潔寫技巧精練的程式碼為目標。如果這樣做,連本不該變難的程式都會變得很難寫。

自己讀自己寫的程式,和他人讀同樣的程式的感受是差很多的。寫程式的本人因為是這份程式碼的專家,會以專家的視角認為簡潔沒有多餘的程式碼才是好的程式碼。但是大部分的讀者並無法共感,而且說實話也不需要精通到那個程度,所以作為作者,要懷疑作為該部分專家的自己的感性。

就像文章是為了寫給讀者,程式碼也為了讀者而寫吧!不要忘了作為讀者讀程式碼的感受,在必要時去寫「應該有更好的寫法但單純的程式碼」,是做出易讀易維護的程式的重要技巧。

Last updated