C編譯器入門~想懂低階系統從自幹編譯器開始~
  • 譯者序
  • 前言
    • 符號與規範
    • 本書的開發環境
    • 關於作者
    • 結束前言之前
  • 機械語言與組譯器
    • CPU 與記憶體
    • 什麼是組譯器
    • C程式和所對應的組合語言
      • 簡單的範例
      • 包含呼叫函式的範例
    • 本章小結
  • 創造計算機等級的語言
    • 第1步:創造能編譯1個整數的語言
    • 第2步:製作可以算加減法的編譯器
    • 第3步:加入標記解析器(tokenizer)
    • 第4步:改良錯誤訊息
    • 文法的記法與遞迴下降分析法
      • 將文法結構表示為樹(tree)
      • 以生成規則定義文法
      • 以 BNF 描述生成規則
      • 簡單的生成規則
      • 以生成規則描述運算子的優先順序
      • 包含遞迴的生成規則
      • 遞迴下降語法分析
    • 堆疊機
      • 堆疊機的概念
      • 編譯成堆疊機指令
      • 以x86-64實作堆疊機的方法
    • 第5步:製作可進行四則運算的編譯器
    • 第6步:單項加與單項減
    • 第7步:比較運算子
      • 修改標記解析器
      • 新的文法
      • 產生組合語言指令
  • 分離編譯與連結
    • 分離編譯
      • 分離編譯與其必要性
      • 標頭檔的必要性與其內容
      • 連結錯誤
      • 全域變數的宣告與定義
    • 第8步:分割檔案與修改 Makefile
      • 分割檔案
      • 修改 Makefile
  • 函式與區域變數
    • 第9步:1個字的區域變數
      • 堆疊上的變數空間
      • 修改標記解析器
      • 修改分析器
      • 左邊值與右邊值
      • 從任意的記憶體位址取得其值
      • 修改指令產生器
      • 修改主函式
    • 第10步:複數文字的區域變數
    • 第11步:return
    • 1973年的C編譯器
Powered by GitBook
On this page

Was this helpful?

  1. 創造計算機等級的語言

第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, "不是數值"),這一步就完成了。

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

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

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

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

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

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

Previous第3步:加入標記解析器(tokenizer)Next文法的記法與遞迴下降分析法

Last updated 5 years ago

Was this helpful?

參考實作:

c6ff1d98a1419e69