語法和語義的區別
- 語法:描述該語言的程序的正確形式
- 語義:定義了程序的含義,即每個程序在運行時做什么
抽象語法樹和三地址指令
三地址指令可以理解為只有3個成分的指令:2個操作數和一個操作符,最多執行一個操作。恰好對應一顆二叉樹的2個子節點和其父親節點。
抽象語法樹如下:

筆記后續更新,可以關注github https://github.com/dslu7733/Compilers
對於抽象語法樹的“翻譯”,是從葉子節點開始向上翻譯的。
文法產生式
->表示推導,可由前者推導出后者,也可以理解為某種“等價替換”
- 加減運算的文法產生式 (且該文法是左遞歸的,且僅針對個位數加減)
//list叫開始符,總是從它開始
list -> list + digit
list -> list - digit
list -> digit
digit -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
左遞歸指,該文法可以一直向左邊推導,等價成一顆不斷向左子樹延申的抽象語法樹。
- 二義性
同樣,對於上面的例子,可能有人會想到下面的文法生成式
string -> string + string
string -> string - string
string -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
這種文法的缺點在於它對應的語法樹是二義性的,即存在2顆不同的語法樹對應着同一句話,在某些情況下,這是含糊的。

但是細心可以發現這2顆樹還是有所不同的,前一顆是左遞歸,后一顆是右遞歸。可以人為的賦予一層解釋給它們,子樹的運算級別更高,即添加了一層“隱性的括號”。
於是對於前一顆語法樹,它將被翻譯為 (9-5)+2 = 6
后一顆語法樹被翻譯為 9-(5+2) = 2
- 左結合與右結合
看了上面對於左遞歸和右遞歸的解釋,人們會想第一個例子能不能寫成右遞歸呢?答案是可以的
list -> digit suffix | digit suffix
suffix -> - digit suffix | + digit suffix | epsilon
digit -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
//epsilon表示空字符串, ’|‘表示‘或’
那它們有什么區別嗎?就像上面那個string的例子,解釋不同,對於右遞歸的文法,是右結合的。
例如加減運算是左結合的,所以3+2-1=(3+2)-1; 而等號是右結合的,所以a=b=2等同於a=(b=2)
- 函數調用中參數列表的文法產生式
call -> id (optparams)
optparams -> epsilon | params
params -> params , param | param
上面的參數列表文法產生式中用id表示標識符,epsilon表示空字符串,param表示形參。
這個文法產生式某種意義上來說是”不完整“的, 因為它沒有將id與params推導到字符級別,但有時候為了方便理解,就這么寫了。
除此之外,文法產生式是很嚴謹的,連參數之間的逗號和函數調用的括號都要考慮在內,因為param僅代表一個字符串,不包含逗號在內。
- 運算符的優先級
到現在可以看到文法生成式算得上是一種嚴謹的”數學推導“了,而且它還能表示運算符號的優先級。
考慮
+-:左結合
*/:左結合
優先級 */ > +-
expr -> expr - term | expr + term | term
term -> term * factor | term / factor | factor
factor -> digit | (expr)
語法樹的翻譯是從葉子節點開始的,因此可以區分出優先級,子樹的運算優先級比其父節點高。
factor是最小的運算單位,表示不能被其它任何運算符分開的表達式。
上面這個文法可以實現*/優先級比+-高,但是他也能表示2 *(1+3)這類語句。
- 后綴表達式
上面的文法都是中綴形式,后綴可以寫成E -> E E op | digit
C語句的子集的文法
說了這么多,來看一個涉及C語言部分語句的文法
stmt -> id = expression;
| if(expression) stmt
| if(expression) stmt else stmt
| do stmt while (expression);
| while(expression) stmt
| {stmts}
stmts -> stmts stmt | epsilon
上面用id表示標識符,expression表示實際程序語句。注意這個文法中的分號的使用,非常嚴謹,不多不少。
