# 第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`的語法樹：

![-3\*+5的語法樹](https://416395721-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LjokrNdcE1H2E8hE2Dc%2F-LotN135iFxM5kE7DVh3%2F-LotQPNixZeJ5NV7RcOm%2Findex.svg?alt=media\&token=ba182db7-5108-47c1-bbab-af90930f8981)

我們來依照這個文法修改分析器。照慣例，只要把文法照搬成函式的呼叫我們的分析器應該就改好了。分析`unary`的函式如下所示：

```c
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](https://github.com/rui314/chibicc/commit/bb5fe99dbad62c9516ec6a4bc64e444d09115e6d)

{% hint style="info" %}

#### 小知識：單項加減和文法的好壞

單項`+`運算子在原始的C編譯器裡並不存在，是在1989年 ANSI（美國國家標準協會）在制定C語言標準時，官方才加上去的。因為有單項`-`，確實有單項`+`會提升對稱性，但實際上單項`+`並沒有什麼用處。

同時，在文法裡加上單項`+`有其副作用。不習慣C語言的人可能會誤把`+=`運算子錯寫成`i =+ 3`。如果沒有單項+的話會回報語法錯誤，但是因為有單項`+`所以這會被解釋為`i = +3`，編譯器會以為這是正確的代入式默默地接受。像這樣，在擴充文法時發生沒想到的副作用是在設計語言時很常發生的事。

ANSI 制定C語言標準的小組，應該是了解上述問題的前提之下決定加上單項`+`的，不過讀者們會怎麼想呢？如果你是C語言標準小組的一員，你會贊成還是反對呢？
{% endhint %}
