哈工大軟件學院編譯原理實驗2——語法分析

  這次實驗讓人煞費苦心啊,話說我已經寫了一天的C語言文法了,囧。

  總結一下,可以說:程序編寫很帥很順利,文法編寫很挫很糾結。我用的是LL(1)分析法(又叫預測分析法),開始的時候花了一段時間來理解LL(1)算法,後來到設計、實現、各種測試,可謂經歷了一番波折。記得剛開始寫的時候想用C++,後來發現竟然忘的差不多了,囧,於是索性挫到底——用Java實現,輕噴啊閉嘴

  這次實驗的內容就是讓你採用一種語法分析技術分析類高級語言中的基本語句,至少包括函數定義、變量說明、賦值、循環、分支等語句,同時還必須讓程序有一定的錯誤處理的機制。

  開始要精度ppt和龍書相關的章節,知道這個LL(1)分析法到底是個啥,具體的相關的知識我不再贅述,其實我弄得也不是特別透徹,首先得知道LL(1)分析器的系統結構吧:

  有一句話很重要,LL(1)分析器是模擬了最左推導的過程,這句話對理解LL(1)很有幫助。

  然後要理解LL(1)的通用控制算法:

   漂亮,其實說的通俗一點,LL(1)分析器有一個棧,用來存放推導式,在分析的過程中,由當前棧頂元素和當前輸入符號來決定下一步的操作(比如使用哪個產生式繼續往下推導,或者彈出棧頂元素前移輸入指針,或者報錯等操作),另外不要忘了,LL(1)分析法是在模擬最左推導

  根據這個算法,我們要做的事就是構造預測分析表,根據通用控制算法寫總控程序,此外,構造預測分析法要用到文法每個產生式的SELECT集,構造SELECT集需要求出每個終結符和非終結符的First集,還要求出每個非終結符的Follow集。

  由於代碼太多,這次不貼代碼了,只是大體說說我的數據結構的設計。

  我爲非終結符和終結符設計了一個基類(儘管很多情況下我設計的基類被人說成是雞肋,我還是喜歡這樣做):

public abstract class MyCharacter {
	public String what;
	public List<String> First;
	public MyCharacter() {
		First = new ArrayList<String>();
	}
}

  然後終結符和非終結符繼承這個基類:

public class TerminateCharacter extends MyCharacter {
	public TerminateCharacter(String what) {
		super();
		this.what = what;
	}
}
public class NonterminalCharacter extends MyCharacter {
	public List<String> Follow;
	public List<String> Sync;

	public NonterminalCharacter(String what) {
		super();
		this.what = what;
		Follow = new ArrayList<String>();
		Sync = new ArrayList<String>();
	}

	/**
	 * 根據一些啓發式的方法設置該終結符的同步記號集合,即把該終結符的FOLLOW集、FIRST集加入Sync集
	 */
	public void setSync() {
		Sync.addAll(Follow); // 添加Follow集
		for (String s : First) { // 添加First集
			if (!Sync.contains(s)) {
				Sync.add(s);
			}
		}
	}
}

  此外,產生式也作爲程序的一個實體類:

public class Production {
	// 產生式的左部
	private String Left;
	// 產生式的右部
	private ArrayList<String> right;
	// 產生式的Select集
	public ArrayList<String> Select;

	public Production(String left, ArrayList<String> right) {
		Left = left;
		this.right = right;
		this.Select = new ArrayList<String>();
	}

	public String getLeft() {
		return Left;
	}

	public void setLeft(String left) {
		Left = left;
	}

	public ArrayList<String> getRight() {
		return right;
	}

	public void setRight(ArrayList<String> right) {
		this.right = right;
	}

}

  由於語法分析的輸入是詞法分析的Token序列,因此,我爲讀到程序裏的Token也設計了一個實體類:

public class Token {
	private String token;
	private String value;
	public Token(String token, String value) {
		super();
		this.token = token;
		this.value = value;
	}
	public String getToken() {
		return token;
	}
	public void setToken(String token) {
		this.token = token;
	}
	public String getValue() {
		return value;
	}
	public void setValue(String value) {
		this.value = value;
	}
}

  當一個文法確定下來,這個文法的非終結符、終結符、產生式以及這個文法的預測分析表(前提是該文法必須是LL(1)文法,而且產生式已經消除了回溯和左遞歸)都會確定下來,他們的數據結構如下:

	/**
	 * 非終結符
	 */
	public static Map<String, MyCharacter> nCharacters = new HashMap<String, MyCharacter>();
	/**
	 * 終結符
	 */
	public static Map<String, MyCharacter> tCharacters = new HashMap<String, MyCharacter>();
	/**
	 * 產生式
	 */
	public static List<Production> productions;
	/**
	 * 預測分析表
	 */
	public static Map<String, HashMap<String, ArrayList<String>>> ForecastTable = new HashMap<String, HashMap<String, ArrayList<String>>>();

  終結符和非終結符用了一個Map,這個Map用字符本身作爲key,該字符同時爲這個字符new一個NonterminalCharacter或者TerminateCharacter的對象作爲相應的value;預測分析表用了一個二維的Hash表,在查表時可以通過ForecastTable.get(X).get(a)完成。

  在寫程序的時候要用到一個算法,判斷一個非終結符能不能經過N步推導推出空串,這個方法貼出來寫下:

	/**
	 * 遞歸判斷X可否推出空串
	 * 
	 * @param X
	 * @return
	 */
	public static boolean canLeadNull(String X) {
		// X是終結符,則X不可能推出空串
		if (isTCharacter(X)) {
			return false;
		}
		// X是非終結符
		else {
			// 查找Cache表
			if (GrammerAnalysis.canLeadNullList.contains(X)) {
				return true;
			}
			for (Production p : GrammerAnalysis.productions) {
				if (X.equals(p.getLeft())) {
					// 存在一個 X=>$ 的產生式,則說明X可以推出空串
					if ("$".equals(p.getRight().get(0))) {
						// 把X加入Cache
						GrammerAnalysis.canLeadNullList.add(X);
						return true;
					}
					// 當前產生式不是 X=>$ ,遞歸調用
					else {
						boolean flag = true;
						for (int i = 0; i < p.getRight().size(); i++) {
							// 當前產生式不能產生空串
							if (!canLeadNull(p.getRight().get(i))) {
								flag = false;
								break;
							}
						}
						if (flag == true) {
							// 把X加入Cache
							GrammerAnalysis.canLeadNullList.add(X);
							return true;
						}
					}
				}
			}
			return false;
		}
	}

  上面程序的Cache是個啥呢?我們知道,遞歸一般是很耗資源的,既然文法確定了,那麼一個終結符經過判斷如果能推出空串,就不會變了,我們把能推出空串的非終結符保存起來,下次直接去查表不就不用再遞歸判斷了?

	/**
	 * 爲了提高遞歸的效率, 如果某個終結符經過N步推導後能推出空串,則把這個非終結符放在這個集合中 相當於cache
	 */
	public static List<String> canLeadNullList = new ArrayList<String>();

  這個函數的編寫是根據一個“公理”,即:一個非終結符序列可以經過N步推導推出空串的充要條件是構成它的每一個非終結符都能經過N步推導推出空串,所以很顯然遞歸是方便快捷的方式。
  下面是總控程序:

	/**
	 * 根據當前文法分析句子,輸出分析結果
	 * 
	 * @param sentence
	 *            要分析的語句(Token表示)
	 * @param startChar
	 *            當前文法的起始符號
	 * @return 返回自頂向下推導序列
	 */
	public static ArrayList<Production> Analysis(ArrayList<String> sentence,
			String startChar) {
		ArrayList<Production> productionSequences = new ArrayList<Production>();
		Stack<String> prodChars = new Stack<String>();
		prodChars.push("#");
		prodChars.push(startChar);
		// sentence = sentence + "#";
		sentence.add("#");
		int currentIndex = 0; // 當前分析到的下標

		while (!"#".equals(prodChars.peek())) {
			String X = prodChars.peek();
			String a = "";
			if (currentIndex < sentence.size()) {
				a = sentence.get(currentIndex);
			}
			if (isTCharacter(X) || "#".equals(X)) {
				if (a.equals(X)) {
					if (!"#".equals(X)) {
						prodChars.pop();
						currentIndex++;
					}
				} else {
					String eStr = prodChars.pop();
					System.err.println("ERROR,Ignore Char : " + eStr);
					// break;
				}
			} else {
				ArrayList<String> item = GrammerAnalysis.ForecastTable.get(X)
						.get(a);
				if (item != null) {
					prodChars.pop();
					if (!"$".equals(item.get(0))) {
						for (int i = item.size() - 1; i > -1; i--) {
							prodChars.push(item.get(i));
						}
					}
					productionSequences.add(new Production(X, item));
					System.out.println(X + " -> " + item);
				} else {
					if (((NonterminalCharacter) GrammerAnalysis.nCharacters
							.get(X)).Sync.contains(a)) {
						String eStr = prodChars.pop();
						System.err
								.println("ERROR,Have Pop NCharacter: " + eStr);
					} else {
						String eStr = a;
						System.err.println("ERROR,-Ignore Char : " + eStr);
						currentIndex++;
					}
					// break;
				}

			}
		}
		return productionSequences;
	}


  至於C語言文法的編寫,我參考了網上流傳很廣的一份神文法寫的。寫的各種糾結,各種消除左遞歸,消除回溯。

  由於現在我的文法還有bug,故暫時不貼出來了,代碼和數據結構設計僅供參考。

  下週有用戶界面設計的考試,下下週有計算機安全概論和知識產權法的考試,任重而道遠啊。

  歡迎留言討論。
  ==================================================

  後記:

  唉,又調了一下午的文法,終於大部分都基本搞定了,不過這個文法還存在缺陷,還是貼出來吧,畢竟我花了好多心血在裏面,各種消除回溯,消除左遞歸:

  缺陷記錄:

  1.if-else有缺陷,其中selection_statement'的Select集竟然相交了,That is to say,if-else那部分不符合LL(1)文法,即,不支持else子句,求鄙視可憐

  2.函數調用只支持帶參數的函數調用。postfix_expression' -> ( const_expression_list )這一句有點猥瑣,如果這句支持無參函數調用的話又會出現if-else的情況。

  3.給數組的某一個具體項賦值時有缺陷,不能這樣複製b[3] = b[2]。

  上述缺陷如果有時間我還願意去調試,不過有點調不動了,僅供大家參考。

program -> external_declaration program'
program' -> external_declaration program' | $
#
external_declaration -> function_definition
#
function_definition -> type_specifier declarator_for_fun compound_statement
#
type_specifier -> CHAR | INT | FLOAT | CHAR*
#
declarator_for_fun -> IDN ( declarator_for_fun'
declarator_for_fun' -> ) | parameter_list )
#
declarator -> IDN declarator'
declarator' -> [ CONST_INT ] declarator' | $
#
#identifer_list -> IDN identifer_list'
#identifer_list' -> , IDN identifer_list' | $
#
parameter_list -> parameter_declaration parameter_list'
parameter_list' -> , parameter_declaration parameter_list' | $
parameter_declaration -> type_specifier IDN
#
compound_statement -> { compound_statement'
compound_statement' -> } | statement_list } | declaration_list statement_list }
#
declaration_list -> declaration declaration_list'
declaration_list' -> declaration declaration_list' | $
declaration -> type_specifier init_declarator declaration'
declaration' -> , init_declarator declaration' | $
init_declarator -> declarator init_declarator'
init_declarator' -> $ | = initializer
initializer -> assigment_expression | { const_expression_list }
#
statement_list -> statement statement_list'
statement_list' -> statement statement_list' | $
#
statement -> compound_statement | expression_statement | selection_statement | iteration_statement | jump_statement | declaration_list
#
expression_statement -> ; | expression ;
#
selection_statement -> IF ( expression ) statement selection_statement'
selection_statement' -> $ | ELSE statement
#
iteration_statement -> WHILE ( expression ) statement | FOR ( expression_statement expression_statement expression ) statement
#
jump_statement -> CONTINUE ; | BREAK ; | RETURN ; | RETURN expression ;
#
expression -> assigment_expression expression'
expression' -> , assigment_expression expression' | $
#
assigment_expression -> IDN assigment_expression'' | const_expression assigment_expression'
assigment_expression'' -> assigment_expression' | = logical_or_expression
assigment_expression' -> > logical_or_expression | < logical_or_expression | >= logical_or_expression | <= logical_or_expression | == logical_or_expression | != logical_or_expression | AND_OP logical_or_expression | OR_OP logical_or_expression | + logical_or_expression | - logical_or_expression | / logical_or_expression | * logical_or_expression | % logical_or_expression | $
logical_or_expression -> logical_and_expression logical_or_expression'
logical_or_expression' -> OR_OP logical_and_expression logical_or_expression' | $
#
logical_and_expression -> equality_expression logical_and_expression'
logical_and_expression' -> AND_OP equality_expression logical_and_expression' | $
#
equality_expression -> relational_expression equality_expression'
equality_expression' -> == relational_expression equality_expression' | $
equality_expression' -> != relational_expression equality_expression' | $
#
relational_expression -> shift_expression relational_expression'
relational_expression' -> > shift_expression relational_expression' | $
relational_expression' -> < shift_expression relational_expression' | $
relational_expression' -> >= shift_expression relational_expression' | $
relational_expression' -> <= shift_expression relational_expression' | $
#
shift_expression -> multiplicative_expression shift_expression'
shift_expression' -> + multiplicative_expression shift_expression' | $
shift_expression' -> - multiplicative_expression shift_expression' | $
#
multiplicative_expression -> cast_expression multiplicative_expression'
multiplicative_expression' -> % cast_expression multiplicative_expression' | $
multiplicative_expression' -> / cast_expression multiplicative_expression' | $
multiplicative_expression' -> * cast_expression multiplicative_expression' | $
#
cast_expression -> postfix_expression
#
postfix_expression -> primary_expression postfix_expression'
postfix_expression' -> [ expression ] postfix_expression' | $
postfix_expression' -> ( const_expression_list )
#
primary_expression -> IDN | const_expression | ( expression )
const_expression -> CONST_INT | CONST_FLOAT | CHAR* | CHAR
#
const_expression_list -> const_expression const_expression_list'
const_expression_list' -> , const_expression const_expression_list' | $



 

 

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