> For the complete documentation index, see [llms.txt](https://koshizuow.gitbook.io/compilerbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://koshizuow.gitbook.io/compilerbook/calculator_level_language/step4.md).

# 第4步：改良錯誤訊息

到目前為止，我們做的編譯器在輸入的文法有錯誤時，只能知道有錯誤發生，但不知道在哪裡。這一步我們針對這個問題進行改良。具體來說，要顯示如下較為直覺的錯誤訊息：

```
$ ./9cc "1+3++" > tmp.s
1+3++
    ^ 不是數值

$ ./9cc "1 + foo + 5" > tmp.s
1 + foo + 5
    ^ 標記解析失敗
```

要想能顯示這樣的錯誤訊息，必須在錯誤發生時，知道式發生在輸入的第幾個位元才行。為此，我們來把作為輸入程式的文字列存成變數 `user_input`，定義一個新的函式可以接受只到其中某一個位置的指標，並顯示錯誤訊息。程式碼如下：

```c
// 輸入程式
char *user_input;

// 回報錯誤的位置
void error_at(char *loc, char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);

  int pos = loc - user_input;
  fprintf(stderr, "%s\n", user_input);
  fprintf(stderr, "%*s", pos, ""); // 輸出pos個空白
  fprintf(stderr, "^ ");
  vfprintf(stderr, fmt, ap);
  fprintf(stderr, "\n");
  exit(1);
}
```

&#x20;`error_at`所接受的指標，會指向指向輸入文字列的某個位置。計算該指標和輸入的開頭的差，就知道錯誤發生的位置，然後就可以在該位置顯示一個醒目的`^`符號。

把 `argv[1]`存到`user_input`變數，然後把程式碼中`error("不是數值")`改為 `error_at(token->str, "不是數值")`，這一步就完成了。

實用的編譯器也應該要針對輸入有錯誤時的行為寫測試，但現階段，輸出錯誤訊息只是為了幫助除錯，可以暫時不用寫測試沒關係。

> 參考實作： [c6ff1d98a1419e69](https://github.com/rui314/chibicc/commit/c6ff1d98a1419e69c31902447e2caa85af4e9844)

{% hint style="info" %}

#### 小知識：程式碼排版器（formatter）

就像讀自然語言的文章（編註：原文為日文，但一般語言也一樣）時，如果遇上句讀等在基本寫作層級發生錯誤的文章會很難讀下去，程式語言如果在縮排、有無空白等前後沒有一致的話，別說程式的內容了，根本稱不上是漂亮的程式碼。程式碼的排版就是在這些和功能無關的地方，機械化地套用某種規則，注意不要寫出讓人分心、難讀的程式碼。

如果很多人一起開發的時候，得好好商量一下究竟要採用哪種格式，但本書是是一個人在開發，可以在主流的格式中隨意選一個自己喜歡的。

在比較新出現的程式語言裡，為了消除「該選什麼樣的格式」這種只是主觀喜好而非必要的討論，有的語言有提供官方的格式。舉例來說像Go語言就有提供`gofmt`這個指令，可以幫忙把程式碼整型變漂亮。`gofmt`並沒有選擇格式風格的選項，也就是說只能整成「Go官方格式」這個唯一選擇。因為不給選擇的空間，Go完全解決了「該怎麼處理格式」的問題。

C或C++有 clang-format 這個排版器可以用，但本書並不想推薦要使用這類工具。與其在寫了之後把奇怪的程式碼用排版器整型，不如試著一開始寫的時候就注意寫長相前後一致的程式。
{% endhint %}
