【ANTLR學習筆記】2:基本工作流程和歧義處理方法

1 解析的整體流程

首先是詞法分析器處理字符序列(對應CharStream類),生成Token流(對應TokenStream類,這是連接詞法分析和語法分析過程的橋樑)傳給語法分析器,語法分析器再用它檢查語法正確性,然後解析得到語法樹(葉子結點對應TerminalNode類,非葉子結點對應RuleNode類)。
在這裏插入圖片描述

在詞法分析之後,不僅要標記一個個Token,還需要記錄這些Token對應的具體內容(比如知道是一個變量,也要記錄變量名是什麼)。ANTLR的做法是不去記錄這個字符串,而是像上圖一樣,首先爲字符流記錄一個個位置號,然後在TokenStream裏記錄這個Token對應的字符開始和結束的索引

在生成的語法樹中,葉子結點TerminalNode就是要記錄這些Token的具體值(的起止索引)了,而對於非葉子結點RuleNode,則會根據不同的語法規則生成不同的子類,即上篇說到的形如*Context的類,*處是.g4文件中語法規則的名字首字母大寫。

例如,下圖是上面的賦值語句解析出的語法分析樹上各個結點的類。根節點是stat語法規則(表示語句)生成的StatContxt類,它的子結點是assign語法規則(表示賦值)生成的AssignContext類,它的四個孩子對應了assign所匹配的四個詞法符號:
assign : ID   =   expr   ; assign \ : \ ID \ \ \ = \ \ \ expr \ \ \ ;

對於標識符ID和具體符號=;都無法繼續展開,因此會作爲樹的葉子,並使用TerminalNode記錄。表達式expr可以再展開,在這裏爲了解析100,它選擇了其中一個與之匹配的分支(整形數值),因此它生成唯一孩子TerminalNode記錄這個100

在這裏插入圖片描述
這些生成的*Context類(作爲生成的語法Parser的靜態內部類)可以訪問它所對應的詞組中的所有元素(圖中它的子樹)。例如,上圖的AssignContext類就可以通過ID()方法訪問標識符子結點(返回值是TerminalNode類型),通過expr()方法訪問表達式子樹(返回值是ExprContext類型)。

2 語法分析器的工作過程

ANTLR根據給出的語法規則,生成一個遞歸下降的語法分析器,當待解析的語法規則有多條分支時,語法分析器會去前瞻詞法符號(不必是LL(1),可以前瞻若干個詞法符號),這個過程和手寫的Parser是類似的,對於:

stat: assign
	|	ifstat
	|	whilestat
	...
	;

這表示語句(stat)可以是賦值語句(assign),可以是IF語句(ifstat),可以是WHILE語句(whilestat)或者其它語句。僅就這三條而言,它們的第一個詞法符號分別是標識符、IF關鍵字、WHILE關鍵字,因此可以前瞻一個詞法符號解決,解析stat的邏輯是:

void stat() {
	switch(/*當前輸入的詞法符號*/) {
		case ID : assign(); break;
		case IF : ifstat(); break;
		case WHILE : whilestat(); break;
		...
		default: /*全都匹配不上,拋出異常*/
	}
}

3 歧義處理方法

如果可以通過多條分支解析輸入的文本,那麼就說明輸入文本是有歧義的,可以有多種語義去解釋。用戶提供的.g4文件中不論是Token的匹配還是語法的描述都可能存在歧義。

3.1 語法描述上的歧義

例如:

stat : expr ';'			// 表達式語句
	| ID '(' ')' ';'	// 函數調用語句
	;
expr : ID '(' ')'
	| INT
	;

對於輸入f();,可以走stat的第一條分支,將f()當作一個表達式expr來解析,此時f();被認爲是一個表達式語句。也可以走stat的第二條分支,此時f();被認爲是一個函數調用語句,其中標識符f被視爲函數名。
在這裏插入圖片描述
ANTLR解決語法歧義的方法是,匹配所有可匹配分支的第一條。因此對於剛剛的例子,會將f();作爲表達式解析。

3.2 Token匹配時的歧義

最常見的是語法關鍵字和標識符之間的歧義,例如:

BEGIN : 'begin';
ID : [a-z]+;

這表示BEGIN關鍵字匹配begin序列,標識符匹配一個至多個小寫字母序列。ANTLR解決Token歧義的方法是,匹配定義最靠前的語法規則。利用這一點可以自然的保證begin不能作爲標識符的問題,因爲BEGIN的聲明就在ID的前面。

詞法分析器在匹配Token時是貪婪模式的,即會儘可能匹配一個最長的字符串來生成Token,因此beginner會匹配爲ID,而不是BEGIN後面接名爲ner的標識符。

3.3 語言語法本身的歧義

這裏書中舉了兩個例子。其一是表達式優先級的歧義,如
1+23 1+2*3

在Smalltalk裏就是自左向右處理(因此計算出來是9),在其它語言裏*優先級高於+(因此計算出來是7)。因此如何隱式指定表達式運算符優先級是一個問題。

另一種是C語言裏的,如i*j;*是乘號還是指針符號,取決於i的Token是一個表達式還是一個類型(比如int*j;就是定義指針變量,8*j;則是一個表達式語句)。也就是說這類歧義要通過檢查上下文信息解決。

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