這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文在此。
————翻譯分隔線————
編譯器(5)-語言規格說明書
第一部分:介紹
第二部分:編譯、轉譯和解釋
第三部分:編譯器設計概覽
第四部分:語言設計概述
這是最後一部分關於 Calc 的設計規格!
設計語言
我希望盡量讓語言保持簡單。我管這個語言叫 Calc。很明了,就是用於計算機(calculator)。聰明嗎,聰明嗎?好,嗯,繼續!
我還希望有一個單一的基礎類型。我決定做一些與這個語言名字一樣聰明的事情,就叫它 Integer(整數)。我知道,聰明絕頂。你的讚賞將被載入史冊。為了簡單期間,我決定避免處理浮點數,以及二進位、八進位、十六進位還有科學計數法。我將添加其他計數方式作為練習留給你。
我們還需要表示檔案結尾、注視和數學符號。
在開始解析和執行我們的語言之前,還有一個需要確定的基本內容是運算子優先順序和封裝方式。祝福也好,詛咒也罷,這次我決定不在運算子優先順序上把事情搞砸了。所以我決定使用預定義的符號和 Lisp 樣式的運算式。也就是說運算子將在參數前,並由左右括弧封裝。二加三的例子如下:
例子 1 (簡單運算式):
(+ 2 3)
Result: 5
這樣有兩個好處:1)明確了運算子優先順序;以及,2)我們可以讓運算子處理任意值的數字。
例子 2 (運算子優先順序):
(* (+ 3 1) (/ 4 2))
Result: 8
這裡沒有什麼運算子以什麼樣的順序執行的問題。在計算乘法之前,加法和除法是分別進行計算的。沒有算術運算子優先順序(BEDMAS)!
例子 3 (多值):
(* 1 2 3 4 5)
Result: 120
在這個情況下,我們對錶達式的每個元素從左至右的進行處理。所以也可以將這個函數用下面的等價的形式編寫:
(*5(*4(*3(*2 1)))) == ((((1*2)*3)*4)*5) == 1*2*3*4*5
因此,我們需要定義左右(開閉)括弧的標識符,和每個想要使用的數學符號。
最後,還應當具有注釋。我們將僅增加單行注釋,不過多行應該也不會很難添加。讓我們使用一個分號來表示一段注釋吧。
所需要用到的標識符
- 左括弧
- 右括弧
- 加號
- 減號
- 乘號
- 除號(商)
- 模數(餘)
- 整數
- 分號(注釋)
這就是開始詞法分析前所需要的所有的東西了。有心人應當已經留意到,我並未對任何關鍵字、內建函數或變數定義標識符。因為我們簡單的語言並未有任何這些東西。
這一系列最初的目的並不是教你設計一個圖靈完備的,功能完整的程式設計語言。核心目的只是提供一些基礎的計數來編寫一個初級的、可以工作的編譯器。
在系列接下來的內容中,我會描述如何添加這些特性。
空白字元
接下來要決定的事情就是如何處理空白字元。在像 Lisp 或 Go 這樣的語言中,空白字元通常被忽略。我說通常是因為並不是完全被忽略。字串和注釋不會忽略空白字元。同時也被用於間隔例如數字這樣的元素。然而,從被忽略就能看出它對於一個語言的文法或者語句來說並不是十分的重要。
順便說一下,空白字元通常是空格、tab 或換行。在 Windows 上,你還應當包括斷行符號等等。基本上,任何能通過鍵盤輸入的不直接顯示出來的字元都算是。
當前來說,你的語言也可以將空白字元納入考慮之中。例如在 Python 中,它們就非常重要,作為語句的標示分隔。Calc 不會這樣做。
文法
我們已經列出了代碼中將含有的所有元素。現在需要給它們賦予各自的含義。
由於想要用於表達數學方程。我們對語言中的運算式給與了巨大的關注。但是沒有涉及語句(一個糟糕的笑話,我希望有人能看懂)。
每個運算式必須在括弧之間,從運算子開始,並且需要兩個或兩個以上的運算對象。一個例子:
“(“ operator operand1 operand2 [… [operandN]] “)”
我將括弧放在引號內來表示他們是文本,是必須的元素,而不是可擴充的語言規則。運算子和兩個運算對象也是必須的元素。方括弧表示額外的運算子是可選的。
我們的運算子可以是什嗎?它們只能是以下符號之一:+ – * / %
那麼運算對象又如何呢?好吧,現在事情開始變得有趣起來,不過我覺得咱們可以處理得了。運算對象可以是數字或運算式。這樣允許我們通過運算式的嵌套來進行複雜的計算。
我更喜歡用另外一種形式來表達文法。這是一種用於定義某些事情的,叫做上下文無關文法。這種特殊的形式與傳統的巴科斯—諾爾運算式(BNF)不同。用這種記號法,可以讓我們的語言表達為這樣:
file ::= expr
expr ::= “(“ operator whitespace expr (whitespace expr)+ ”)” | integer
operator ::= [+-*/%]
whitespace ::= [ \t\n\r]+
integer ::= digit+
digit ::= [0-9]
我並不是上下文無關文法的專家,所以希望我沒有把它搞錯。如同你已經看到的那樣,它把上面的所需要的清單表達得相當簡潔,也會讓我們的解析器設計起來更加容易。
後記
我們的語言非常簡單,不過在文法的協助下,我們期望會更加清晰。並且編寫掃描器和解析器會更加容易。
例如整數的定義,我們需要掃描什麼一目瞭然。我們需要掃描一位或者多位從零到九的數字。對於空白符號也是一樣,只要有一個或者多個空格、tab 或換行。
這樣我們就知道了實際上該解析什麼。程式是由一個運算式組成。每個運算式都包裹在括弧內,並且運算式的第二個元素一定是一個運算子。運算對象可以是運算式或數字。至少需要兩個運算對象。