Java實現C語言語義分析(遞歸下降)

說起這次的語義分析,不得不說的是我的重大的改變。上一次的語法分析是利用了預測分析法來實現的,經過多方考證,發現用預測分析法的語法分析器基礎來實現語義分析的困難重重,例如在語法指導翻譯的時候那個棧的變化和各種屬性的傳遞就已經讓我頭暈腦脹了。無奈之下,只好重寫語法分析,用了遞歸下降來實現語法分析進而實現我的語義分析。

使用遞歸下降的最大好處就是思路特別清晰,一旦開始寫了,就特別明確接下來要做什麼。這就是我選擇遞歸下降的原因。

簡單的說一下,遞歸下降語法分析的思想。一個遞歸下降的語法分析程序由一堆的過程組成,每個非終結符號都有一個對應的過程。程序的執行從開始符號對應的過程開始,如果這個過程的過程體掃描了整個輸入串,它就停止執行,並且語法分析結束。

而要在遞歸下降語法分析的過程中進行翻譯:在語法分析中對應於每個非終結符A都有一個函數A,在函數A的函數體中,可以進行語法分析和處理繼承屬性和綜合屬性。

1.決定用哪一個產生式來展開A

2.當需要讀入一個終結符號時,在輸入中檢查這些符號是否出現。

3.在局部變量中保存所有必要的屬性值。這些屬性可以用來計算產生式體中的非終結符號的繼承屬性,或產生式左部的非終結符號的綜合屬性。

4.調用對應於被選定產生式體中的非終結符號的函數,向他們提供正確的參數。


實現語義分析很關鍵的一點是語義動作的書寫。我的語義動作都嵌入到了每個非終結符所對應的類中的某些方法來實現的。

例如在類Expression中有如下幾個方法:

	/**
	 * 返回一個可以成爲某個三地址指令的右部
	 * @return 一個項
	 */
	public Expression gen() {
		return this;
	}
	
	/**
	 * 把一個表達式規約成一個地址
	 * @return 一個地址
	 */
	public Expression reduce(){
		return this;
	}
	
	/**
	 * 調用jumps()方法,生成跳轉代碼
	 * @param t true出口
	 * @param f false出口
	 */
	public void jumping(int t,int f){
		jumps(toString(),t,f);
	}
	
	/**
	 * 生成跳轉代碼
	 * @param string
	 * @param t
	 * @param f
	 */
	public void jumps(String string,int t,int f){
		if (t != 0 && f != 0){
			emit("if " + string + " goto L" + t);
			emit("goto L"+ f);
		}else if(t != 0){
			emit("if " + string + " goto L" + t);
		}else if (f != 0){
			emit("iffalse " + string + " goto L" + f);
		}
	}

在Expression的子類中通常會實現gen()方法和reduce()方法。

 

而在類Or中,也有jumps()方法與此相關,如下:

	/**
	 * 爲布爾表達式B = B1 || B2生成跳轉代碼
	 * 如果B1爲真,那麼B必然爲真,所以B1的true出口是B的true出口
	 * 如果B1爲假,那麼B1的false出口則是B2的第一條指令
	 * B2的true出口和false出口與B的相同
	 */
	public void jumping(int t,int f) {
		int label = t != 0 ? t : newLabel();
		expression1.jumping(label, 0);
		expression2.jumping(t, f);
		if (t == 0){
			label(label);
		}
	}

在類And中,也有也有jumps()方法與此相關,如下:

	/**
	 * 爲布爾表達式B = B1 & B2生成跳轉代碼
	 * 如果B1爲假,那麼B必然爲假,所以B1的false出口是B的false出口
	 * 如果B1爲真,那麼B1的true出口則是B2的第一條指令
	 * B2的true出口和false出口與B的相同
	 */
	public void jumping(int t,int f) {
		int label = f != 0 ? f : newLabel();
		expression1.jumping(0,label);
		expression2.jumping(t, f);
		if (f == 0){
			label(label);
		}
	}

還有一些方法,如label()和newLabel()方法也都與語義動作相關,newLabel()方法是用來生成一個新的標號,label()方法是用給接下來的一句三地址碼標號。


關於符號表的處理:

我的符號表是一個HashMap,其定義是HashMap<String, ID>。其中key是變量名,value是一個ID類的對象。在ID類中,保存了變量的名字,類型,類型的長度,偏移量,入口地址等多個屬性。如果變量是一個數組,則包含更加複雜的屬性,如數組元素個數,數組元素的類型等等。

在變量聲明階段,程序每讀到一個新的聲明語句,都會將其加入到符號表中。當在賦值語句中使用這些變量的時候,首先從符號表中尋找是否已經存在這樣一個已經被聲明過的變量,否則不可使用。

我的測試程序如下:


根據這個測試程序,生成的符號表如下:



簡要的說一下我的語義分析可識別的錯誤類型:

1.if或while的條件必須是布爾語句

2.變量必須要先聲明後使用

3.相同變量名的變量不得重複聲明

好像主要能識別的錯誤就是以上的三類了,其中還要說明的一點是,我的語義分析支持簡單的類型轉換,例如把float類型的值賦給int型變量是不會出錯的。


總體來說,這次的語義分析實現的不大好。沒有堅持用預測分析的方法來實現語義分析一直是我的一個遺憾,卻轉向較爲簡單遞歸下降的實現。並且在遞歸下降的實現中仍然有缺陷,例如不支持函數調用,符號表對於函數沒有處理,指針運算的不支持,數組賦值只適用於a[i] = x這類的賦值,錯誤處理不完備等等。

最後,語義分析結果(仍然是針對上述測試程序)如下:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章