第4步:改良錯誤訊息

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

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

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

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

// 輸入程式
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);
}

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

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

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

參考實作: c6ff1d98a1419e69

小知識:程式碼排版器(formatter)

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

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

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

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

Last updated