- 本章重點在前端,特別是詞法分析,語法分析和中間代碼生成
首先建立一個將中綴算術表達式轉換成后綴表達式的語法制導翻譯器,然后我們擴展這個翻譯器,將某些程序片段轉換為如圖所示三地址代碼
{ int i; int j; float[100] a; float v; float x; while(true){ do i = i + 1; while(a[i] < v); do j = j + 1; while(a[j] > v); if(i >= j) break; x = a[i]; a[i] = a[j]; a[j] = x; } }
編譯器在分析階段把一個源程序划分成各個組成部分
引言
- 編譯器在分析階段把一個源程序划分成各個組成部分,並生成源程序的內部表示形式,這種內部表現形式稱為中間代碼
- 編譯器在合成階段將這個中間代碼翻譯成目標程序
- 分析階段的工作是圍繞着待編譯語言的"語法"展開的
- 語法(syntax),描述了該語言的程序的正確形式;語義(semantics),定義了程序的含義,每個程序時應該做什么事
- 上下文無關文法,不僅可以描述一個語言的語法,還可以知道程序的翻譯過程

- 編譯前端的模型
詞法單元
- 詞法分析器,使得翻譯器可以處理由多個字符組成的構造,標識符由多個字符組成,在語法分析階段被當作一個單元進行處理
中間代碼形成
- 中間代碼形成,抽象語法樹(abstract syntax tree),或簡稱語法樹(syntax tree)
- 語法分析器生成一個語法樹,它又被進一步翻譯為三地址代碼,有些編譯器會將語法分析和中間代碼形成合並為一個組件

- 三地址指令,顯示了更加完整的示例,這個中間代碼形式的名字源於它的指令形式: x = y op z,op是一個二元運算符
- y和z是運算分量的地址,x是運算結果的存放地址

語法定義
- 描述程序設計語言語法的表示方法--"上下文無關文法",文法自然地描述了大多數程序設計語言構造地層次化語法結構
- if (expression) statement else statement 表示為 stmt→if (expr) stmt else stmt
- 箭頭 →,可以讀作"可以具有如下形式",這樣的規則稱為產生式(production)
- 像關鍵字 if 和括號這樣的詞法元素稱為終結符號(terminal),像 expr 和 stmt 這樣的變量表示終結符號的序列,稱為 非終結符號(nonterminal)
文法定義
- 一個上下文無關文法(context-free grammar)由四個元素組成
- 一個終結符號集合,有時稱為"詞法單元",終結符號是該文法所定義的語言的基本符號的集合
- 一個非終結符號集合,有時稱為"語法變量",每個非終結符號表示一個終結符號串的集合
- 一個產生式集合,每個產生式包括一個稱為 產生式頭 或 左部 的非終結符號,一個箭頭,和一個稱為 產生式體 或 右部 的由終結符號及非終結符號組成的序列
- 產生式用來表示某個構造的某種書寫形式,如果產生式頭非終結符號代表一個構造,該產生體代表了該構造的一種書寫方式
- 指定一個非終結符號為開始符號
詞法單元
- 由兩個部分組成:名字和屬性值
- 詞法單元的名字,是語法分析器進行語法分析時使用的抽象符號,我們常常把這些詞法單元名字稱為 終結符號
- 我們假設數位、符號(如 < ,<=)和黑體字符串(如while)都是終結符號
- 斜體字符串表示非終結符號,所有非斜體的名字或符號都可以看作是 終結符號
- 以同一個非終結符號為頭部的多個產生式的體可以放在一起表示,不同體之間用符號 | 分隔
例2.1
- 使用由數位和+、-符號組成的表達式,比如 9 - 5 + 2、3 - 1 或 7。兩個數位之間必須出現 + 或 - ,我們把這樣的表達式稱為 "由 +、- 號分隔的數位序列"
- 文法的產生式包括: list→list + digit,list→list - digit,list→digit,digit→ 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
- 以非終結符號list為頭部的三個產生式可以等價地組合為 list→list + digit | list - digit | digit
- 根據我們的習慣,該文法的終結符號包括,+ - 0 1 2 3 4 5 6 7 8 9
- 該文法的非終結符號是斜體名字 list 和 digit,list是該文法的開始符號
- 若某個非終結符號是某個產生式的頭部,就說該產生式是該非終結符號的產生式
- 一個終結符號串是由零個或多個終結符號組成的序列
- 空串(empty string),零個終結符號組成的串,記為 ε
推導
- 從開始符號推導得到所有終結符號串的集合,稱為該文法定義的語言(language)
例2.2
- 非終結符號digit的10個產生式使得digit可以表示0、1、...、9中的任意數位,單個數位本身是一個list
- 任何列表后跟一個符號 + 或 - 以及另一個數位可以構成一個新的列表,按照如下方法推導處 9 - 5 + 2 是一個list
- list→list + digit,list→list - digit,list→digit,digit→ 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
- 因為9是digit,根據產生式可知9是list
- 因為5是digit,且9是list,9 - 5也是list
- 因為2是digit,9 - 5 是list,9 - 5 + 2 也是list
例2.3
- 另有一種不同的列表是函數調用中的參數列表
- 在Java中,參數是包括在括號中的,例如max(x, y)表示使用參數x和y調用函數max,這種列表的微妙之處是終結符號"(" 和 ")"之間的參數列表可能是空串
- 我們可以為這樣的序列構造具有如下產生式的文法
- call → id(optparams)
- optparams → params | ∈
- params → params,param | param
- 注意,在optparams(可選參數列表)的產生式中,第二個可選擇體是∈,它表示空的符號串
- 也就是說,optparams可以被替換為空串,params的產生式和上式的list產生式類似,只是將算術運算符 + 或 - 換成了逗號,並將digit換成param
- 函數參數實際上可以是任意表達式,但是在這里我們沒有給出param的產生式
語法分析(parsing)的任務
- 接受一個終結符號串作為輸入,找出從文法的開始符號推導出這個串的方法
- 如果不能從文法的開始符號推導得到該終結符號串,報告該終結符號串中的語法錯誤
- 語法分析是所有編譯過程中最基本的問題之一
- 一般情況下,一個源程序中會包含由多字符組成的詞素,這些詞素由詞法分析器組成詞法單元,而詞法單元的第一個分量就是被語法分析器處理的終結符號
語法分析樹
- 語法分析樹用圖形方式展現了從文法的開始符號推導出相應語言中的符號串的過程,如果非終結符號A有一個產生式A→XYZ,那么在語法分析樹中就可能有一個標號為A的內部結點,該結點有三個結點,從左向右的標號分別為X、Y、Z

給定一個上下位無關文法,該文法的一棵語法分析樹(parse tree)具有以下性質
- 根結點的標號為文法的開始符號
- 每個葉子結點的標號為一個終結符號或∈
- 每個內部結點的標號為一個非終結符號
- 如果非終結符號A是某個內部結點的標號,並且它的子結點的標號從左至右分別為X1,X2,...,Xn,那么必然存在產生式A→X1X2...Xn,其中X1,X2,...,Xn既可以是終結符號,也可以是非終結符號。作為一個特殊情況,如果A→∈是一個產生式,那么一個標號為A的結點可以只有一個標號為∈的子結點
例題2.4
例題2.2中 9 - 5 + 2 的推導可以用下圖的樹來演示
根結點的子結點的標號從左向右分別為list、+和digit, list→list + digit

- 一棵語法樹的葉子結點從左向右構成了樹的結果(yield),也就是從這棵語法樹的根結點上的非終結符號推導得到的符號串
- 所有的葉子結點都放在底層,我們不一定會把葉子結點按照這種方法排列,任何樹的葉子結點都有一個自然的從左到右的順序
- 如果X和Y是同一個父結點的子結點,並且X在Y的左邊,那么X的所有后代結點都在Y的后代結點的左邊
- 一個文法的語言的另一個定義是指任何能夠由某棵語法分析樹生成的符號串的幾何
- 為一個給定的終結符號串構建一棵語法分析樹的過程稱為對該符號進行語法分析
二義性
-
一個文法可能具有多棵語法分析樹能夠生成同一個給定的終結符號串,這樣的文法稱為具有二義性(ambiguous)
- 要證明一個文法具有二義性,只需要找到一個終結符號串,說明它是兩棵以上語法分析樹的結果
例2.5
- 假如我們使用一個非終結符號string
- string → string + string | string - string | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
- 將符號digit和list合並為非終結符號string是有一些意義的,因為單個的digit是list的一個特例

- 這兩棵語法分析樹對應於兩種帶括號的表達式:(9 - 5) + 2 和 9 - (5 + 2)
- 第二種方法給出的表達式時意想不到的2,而不是通常的值6
運算符的結合性
- 9 + 5 + 2 等價於 (9 + 5)+ 2,9 - 5 - 2 等價於(9 - 5)- 2
- 當一個運算分量,的左右兩側都有運算符時,我們需要一些規則來決定哪個運算都應用於該運算分量
- 我們說運算符"+"是左結合的,因為當一個運算分量左右兩側都有"+"號時,它屬於其左邊的運算符
- 加、減、乘、除四種算術運算符都是左結合的
- 某些常用運算符是右結合的,比如指數運算,C語言中的賦值運算符"="及其后裔(+=、-=)也是右結合的
- 對表達式a=b=c的處理和對表達式 a =(b=c)的處理相同
- 左結合運算符的語法分析樹和一個右結合運算符的語法分析樹
- 9 - 5 -2(左結合)的語法分析樹向左下端延伸,而a=b=c(右結合)的語法分析樹則向右下端延伸
運算符的優先級
- 如果*先於+獲得運算分量,我們就說*比+具有更高的優先級
- 通常的算術中,乘法和除法比加法和減法具有更高的優先級
例2.6
- 算術表達式的文法可以根據表示運算符結核性和優先級的表格來構建
- 運算符按照優先級遞增的順序排列,同一行上的運算符具有相同的結合性和優先級
- 左結合: + - 左結合: * /
- 創建兩個非終結符號expr和term,分別對應於這兩個優先級層次,並使用另一個非終結符號factor來生成表達式中的基本單元
- factor → digit | (expr)
- 生產式和左結合列表的產生式類似:
- term → term * factor
- | term / fatoer
- | fator
- 類似地,expr生成由加減運算符分隔的term列表
- expr → expr + term
- | expr - term
- | term
最終得到的文法:
- expr → expr + term + expr - term | term
- term → term * factor | term / factor | factor
- factor → digit | (expr)
factor
- 因子(factor)是不能被任何運算符分開的表達式,原子性
- 一個項(term)是一個可能被高優先級的運算符*和/分開的,但不能被低優先級運算符分開的表達式
- 一個表達式可能被任何一個運算符分開
例2.7

- 在stmt的第一個產生式中,終結符號id表示任意標識符
- 非終結符號expression的產生式還沒有給出
- 第一個產生式描述的賦值語句復合Java的語法,雖然Java將 = 號看作是咳出現在表達式內部的賦值運算符
- 非終結符號stmts產生一個可能為空的語句列表
- stmts的第二個產生式生成一個空列表 ε
- 第一個產生式生成的是一個可能為空的列表再跟上一個語句
- 分號的放置方式很微妙,它們出現在所有不以stmt結尾的產生式的末尾
- 可以避免在if或while的語句后面出現多余的分號,因為if和while語句的最后一個嵌套的子語句
- 當嵌套子語句是一個賦值語句或do-while語句時,分號將作為這個子語句的一部分被生成
