1. BNF定義
2.運算式解析
3. 尾碼運算式
4.尾碼運算式到中間代碼
5.中間代碼的表示
1. BNF定義
雖然不想多提理論知識,但是有些東西還是避免不了。在解析運算式的時候,我們必須知道它的BNF定義,這樣解析起來就非常方便了。所謂的BNF定義,相信大家看一眼就知道了:
exp_additive -> exp_multiplicative ( "+"|"-" ) exp_multiplicative
exp_multiplicative -> exp_cast ( "*"|"/"|"%" ) exp_cast
exp_cast -> ...
意思是:
加法運算式可以表示為 “乘法運算式 + 乘法運算式”
乘法運算式可以表示為 “類型轉換運算式 *或/或% 類型轉換運算式”
...
知道了整個C語言的BNF定義,我們就可以很簡單的按照這個定義來解析了。整個C的BNF定義可以查看以下的連結:
http://lists.canonical.org/pipermail/kragen-hacks/1999-October/000201.html
2. 運算式解析
知道了上面的BNF定義,那麼我們的解析代碼就可以這麼寫:
void exp_additive(){
char op;
exp_multiplicative();
while(
(op = OPERATOR( '+' )) ||
op = OPERATOR( '-' )) ){
get_token();
exp_multiplicative();
...
}
}
void exp_multiplicative(){
char op;
exp_cast();
while(
(op = OPERATOR( '*' )) ||
(op = OPERATOR( '/' )) ||
(op = OPERATOR( '%' )) ){
get_token();
exp_cast();
...
}
}
過程是這樣的:
a. 調用exp_additive時,先調用exp_multiplicative
b. 然後判斷後面是否是 + 或 -,如果是,再次調用exp_multiplicative
這樣就完成了加法運算式的解析。如果非要問為什麼這麼寫就能解析出運算式,那麼我們可以舉個例子:
a = a * b + c * d;
那麼,他的文法樹應該是這樣的:
(圖4.2 文法樹)
我們向下遞迴調用的過程,其實就是構造這個文法樹的過程。但是我們不會真的建立出這個文法樹,而是儲存了一個與它等價的一種形式--尾碼運算式,其實尾碼運算式就是文法樹的後續遍曆。
3. 尾碼運算式
什麼是尾碼運算式?我們還是從例子出發,上面的運算式,轉化成尾碼運算式就是這樣子的:
a a b * c d * + =
為什麼要寫成這種奇怪的形式?我們不是吃飽了撐著,從左往右分別查看這個運算式您就知道原因了。
a
a
b
* 得到*號,那麼拿前面的兩個變數a b求和
c
d
* 得到*號,那麼拿前面的兩個變數c d求和
+ 的到+號,擷取前面的兩個變數 a*b c*d 的結果,求和
= 得到=號,將前面的結果賦給a
為了產生尾碼運算式,我們要改造上面的解析函數。
void exp_additive(){
char op;
exp_multiplicative();
while(
(op = OPERATOR( '+' )) ||
op = OPERATOR( '-' )) ){
get_token();
exp_multiplicative();
EXP_OPR( op ); <--將運算子入棧
}
}
void exp_multiplicative(){
char op;
exp_cast();
while(
(op = OPERATOR( '*' )) ||
(op = OPERATOR( '/' )) ||
(op = OPERATOR( '%' )) ){
get_token();
exp_cast();
EXP_OPR( op ); <--將運算子入棧
}
}
那麼解析完成以後,我們的棧中就會形成尾碼運算式了。有了運算式的尾碼形式,我們就可以很輕鬆的產生尾碼運算式的中間代碼了。
4.尾碼運算式到中間代碼
首先我們先說明一下我們的中間代碼是怎樣的一種形式,這裡暫且叫它為三元運算式,是因為這個種中間代碼的形式是固定的。例如,緊接上節的例子,運算式 a = a * b + c * d;的中間代碼最終應該是這樣子的:
@1 = a * b;
@2 = c * d;
@3 = @1 + @2;
@4 = a = @3;
其中以@開頭的都是我們為之產生的中間變數。產生上述的中間代碼後,將會對我們後續的解析提供很大的協助,應為它結構固定,所以我們不用再去解析來源程式,而是通過這個中間代碼產生最終的執行代碼。這裡先聲明下,我所說的執行代碼,不是真正意義上得可執行代碼,而是能夠被我的軟體解析的命令序列。其實它已經非常接近彙編代碼。但是我們的目標是解析執行,並不產生彙編代碼,所以產生簡單的命令序列已經可以完成目標了。
我們前面解析運算式,產生尾碼形式,為的就是生產這種中間運算式。運算式"a = a * b + c * d;"的尾碼形式是"a a b * c d * + =;" 我們要根據這個尾碼形式產生中間代碼的過程如下:
5.中間代碼的表示
typedef struct _code code_t;
typedef struct _code * pcode_t;
struct _code{
char opr;
struct{
int i, n, t;
}lab;
v_t var[4];
code_t * next;
};
它是一個鏈表,每個節點儲存了一個形如"@1 = a * b;"的中間代碼。其中,opr表示運算子"*";lab表示該節點為一個LAB,留到後面章節講解;var表示運算變數,如上面運算式的"@1, a, b"。
這樣子,當一個運算式解析完成後,會產生一個鏈表,表示該運算式的中間代碼。