第6步:單項加與單項減
減法運算的-運算子,不只可以像在5-3一樣寫在2項中間,也可以像-3一樣寫在單項前面。同理,+運算子也可以像+3這樣用在沒有左項的時候。像這樣只取單項的運算子稱為「單項運算子」(unary operator)。相對的,取2項的運算子就稱為「2項運算子」(binary operator)。
C語言中,除了+和-以外,也有取得指標位址的&和提取指標(dereference)的*這些單項運算子。不過這一步我們只會實作+和-這兩個而已。
單項+和單項-雖然和2項的+和-是一樣的符號,但是定義不同。2項的-的定義是從左邊減去右邊,但是單項根本沒有左邊,照搬2項-的定義的話會無法適用。C語言的單項-是定義為把右邊的正負號反轉的運算。而單項+則是把右邊直接傳回的運算子,這個運算子不是必要的,是和單項-成對存在的贈品。
我們可以合理想像,像+和-這樣,有單項和2項相似但不同定義的同名運算子有很多個。是單項還是2項則需要看前後文脈絡來區分。底下是包含單項+/-的新文法:
expr = mul ("+" mul | "-" mul)*
mul = unary ("*" unary | "/" unary)*
unary = ("+" | "-")? term
term = num | "(" expr ")"上述文法追加了unary這個非終端符號,mul不是用上term而是改為unary。X?是代表選擇性的,也就是說,X會出現0次或1次的 EBNF 語法。unary = ("+" | "-")? term這條規則的意思,是表示unary這個非終端符號,不論有或沒有1個+/-符號其後都會接上term。
我們來驗證-3或-(3+5)、-3*+5這類式子可以和新的文法對應。底下是-3*+5的語法樹:
我們來依照這個文法修改分析器。照慣例,只要把文法照搬成函式的呼叫我們的分析器應該就改好了。分析unary的函式如下所示:
Node *unary() {
if (consume('+'))
return term();
if (consume('-'))
return new_node(ND_SUB, new_node_num(0), term());
return term();
}在這個階段,分析器把+x用x替換、-x用0-x替換。所以在這一步我們不用修改指令產生器。
加上幾條測試、和加上單項+/-的程式碼一起 commit 後,這一步就做完了。寫測試的時候,要注意測試的結果要落在0~255之間。在這一步,請利用像-10+20這樣,用上了單項-但是整體的結果是正數的算式。
參考實作: bb5fe99dbad62c95
小知識:單項加減和文法的好壞
單項+運算子在原始的C編譯器裡並不存在,是在1989年 ANSI(美國國家標準協會)在制定C語言標準時,官方才加上去的。因為有單項-,確實有單項+會提升對稱性,但實際上單項+並沒有什麼用處。
同時,在文法裡加上單項+有其副作用。不習慣C語言的人可能會誤把+=運算子錯寫成i =+ 3。如果沒有單項+的話會回報語法錯誤,但是因為有單項+所以這會被解釋為i = +3,編譯器會以為這是正確的代入式默默地接受。像這樣,在擴充文法時發生沒想到的副作用是在設計語言時很常發生的事。
ANSI 制定C語言標準的小組,應該是了解上述問題的前提之下決定加上單項+的,不過讀者們會怎麼想呢?如果你是C語言標準小組的一員,你會贊成還是反對呢?
Last updated
Was this helpful?