書接上回,塵界程式碼彼此依賴,如樹、如網。要想脫出這張樹網,自成一個世界,固然可以一切用組合語言(下稱真言)寫就,但常人負荷不了如此巨大的工作量,借鑑塵界軟體的發展史,先造出一門高階語言,再以此撰寫程式,能大幅加快構造世界的速度。
自舉高階語言編譯器
X 語言的編譯器,未必就要用 X 語言來寫就,但如果能夠 X 語言來寫自己的編譯器,就象徵了 X 語言已經成熟到一定程度,其自成一格,不需要依賴另一種語言。
但若欲「離塵」,就需要去除掉塵界的所有依賴,就斷不可用塵界的語言來寫新世界語言的編譯器了。自舉不再是可選項,而是唯一的方法。
然而,還沒有可執行的 X 語言編譯器時,就算把 X 語言編譯器源碼寫好了,要怎麼編譯出可執行的 X 語言編譯器呢?這是一個雞生蛋蛋生雞的問題,在不同條件下各有做法。
洪荒年代
在還沒有高階語言的年代(195X 之前),要創造高階語言就只能由真言寫起了。流程如下:
- 以真言寫出 X 語言零號編譯器 X0。
- 以 X 語言寫出 X 語言初號編譯器 X1 源碼。
- 用 X0 編譯 X1 源碼,得到 X1。
或者也可以選擇:
- 用 X 語言來寫 X 語言的編譯器 X1 。(此時還沒有可執行的 X 語言編譯器)。
- 人肉將編譯器 X1 的源碼翻譯成真言,得到可執行的 X 語言編譯器 X1-0。
- 用 X1-0 編譯自身原始碼,得到非人肉翻譯的編譯器 X1-1。
這兩種流程其實大同小異,終究都是用人肉寫出第一版編譯器,不過第二種流程在生成第一版編譯器時,是根據 X 語言的源碼爲藍本逐句生成的,較有所憑依,算是流程上的一點優化。
近現代
軟體發展至今,早已有幾十種甚至數百種成熟可靠的語言可用,自舉之初,不必像洪荒年代直接手寫真言,而可以採用現成語言來寫零號編譯器。假設我們採用成熟的 C 語言來輔助自舉,流程如下:
- 用 A 語言來寫 X 語言的零號編譯器 X0 。
- 以 X 語言寫出 X 語言初號編譯器 X1 源碼。
- 用 X0 編譯 X1 源碼,得到 X1。
舉個實際例子, Rust 一開始用 Ocaml 來寫前幾版 Rust 編譯器,待 Rust 編譯器成熟後,再以 Rust 語言重寫一份 Rust 編譯器。此時 X = Rust , A = Ocaml
迭代
寫出完整的 X 語言未必是一蹴而就,畢竟,設計新語言需要反覆實驗,幾乎不可能在零號或初號編譯器就定完 X 語言而毫無問題,很有可能在自舉的過程中才會發現設計缺失。所以初號編譯器所能編譯的語言已經與零號編譯器有所不同了,在語言特性尚未穩定之前,每一版的編譯器都可能不相互兼容。
在洪荒年代,更有可能的狀況是,手寫真言實在太累了。因此零號編譯器只有十分簡單的功能,它僅能編譯 X 語言的子集 X--,例如,僅支援迴圈、分支、函數這幾個最簡單的語言特性。得到了 X-- 編譯器之後,用 X-- 寫一個 X- 編譯器(X- 是能力介於 X-- 與 X 之間的語言),再將 X-- 寫就的 X- 編譯器擴展為用 X- 寫就的 X 編譯器,如此反覆迭代,才最終得到原先定義好的 X 於言編譯器。