本章翻譯人 CowNew開源團隊 周曉
每一個文法都指定了帶有規則(子結構)和詞表語言結構。這些符號在運行時被轉換成整型的"記號類型"從而可以有效的比較。定義從符號到記號類型的映射的文件對ANTLR和ANTLR生成的分析器來說是基礎。 這份文檔描述了ANTLR使用和生成的這類文件,還介紹了用于控制詞表的選項。
在分析時,一個語法分析器文法通過符號在它的詞表里引用記號要符合由詞法分析器或其他幾號流生成的Token對象。記號類型儲存在記號對象中,每種符號的值是唯一的,分析器比較這些整數來判斷記號類型。如果分析器期望下一個記號類型是23,但發現第一個超前掃描記號類型,LT(1).getType(),不是23,這時分析器拋出MismatchedTokenException異常。
一個文法可能有一個導入詞表,經常也會有一個導出詞表,用于被其他文法引用。導入的詞表從未被修改,通過它可以知道詞表的"初始條件"。不要和導入詞匯混淆了。
下面列出了最常見的問題:
每個文法有一個記號管理器來管理文法的導出詞表。從文法的importVocab選項,記號管理器以 符號/記號類型 的形式被預載。這個選項強制ANTLR尋找有如下映射關系的文件:
PLUS=44
沒有importVocab選項,文法的記號管理器是空的(稍后會看見一個警告)。
你的文法中的任意記號沒有預設值,它們被按照遇到的順序賦值。例如,在下面的文法中,記號A和B分別是4和5:
class P extends Parser;
a : A B ;
詞法文件以如下形式命名: NameTokenTypes.txt.
因為ANTLR在分析過程中需要一些特殊的記號類型,用戶定義的記號類型必須在3后開始。
ANTLR為詞表V生成VTokenTypes.txt和VTokenTypes.java,V是文法的名字或者是exportVocab選項指定的名字。文本文件有點像一個簡化的記號管理器,保存著ANTLR需要的一些信息,供定義在其他文件中的文法查看其詞表信息等等。Java文件是是一個包含了記號類型常量定義的接口。ANTLR生成的分析器實現了其中的一個接口,獲得所需要的記號類型定義。
一個文法的導出詞表必須是另一個文法的導入詞表或者2個文法必須共享一個公共的導入詞表。
設想p.g中有一個語法分析器P:
// yields PTokenTypes.txt
class P extends Parser;
// options {exportVocab=P;} ---> default!
decl : "int" ID ;
l.g中有一個詞法分析器L
class L extends Lexer;
options {
importVocab=P; // reads PTokenTypes.txt
}
ID : ('a'..'z')+ ;
即使L主要是P的詞表中的值,但ANTLR生成的是LTokenTypes.txt和LTokenTypes。
不同文件中的文法必須共享同樣的記號類型空間,使用importVocab選項去預載同樣的詞表。
如果這些文法在同一個文件中,ANTLR會用同樣的方法處理它。然而,你可以通過設置它們的導出詞表到同一個文件來使這2個文法共享同一個詞表。例如,如果P和L在一個文件中,你可以這樣做:
// yields PTokenTypes.txt
class P extends Parser;
// options {exportVocab=P;} ---> default!
decl : "int" ID ;
class L extends Lexer;
options {
exportVocab=P; // shares vocab P
}
ID : ('a'..'z')+ ;
如果你沒有為L指定詞表,它將會選擇共享文件中導出的第一個詞表;在這里,它將共享P的詞表。
// yields PTokenTypes.txt
class P extends Parser;
decl : "int" ID ;
// shares P's vocab
class L extends Lexer;
ID : ('a'..'z')+ ;
記號類型映射文件像這樣:
P // exported token vocab name
LITERAL_int="int"=4
ID=5
文法繼承父文法的規則,動作和選項,但是子文法使用什么詞表和記號詞表呢?ANTLR對子文法的處理就好像你復制粘貼父文法的所有非重載規則到子文法。因此,子文法記號的集合就是父文法和子文法的交集。所有的文法都導出詞表所以子文法導出并使用一個和父文法不同的詞表文件。除非你使用importVocab選項重載,否則子文法導入父文法的詞表。
文法Q繼承P,會預先設置好P的詞表,就好像Q使用了importVocab=P選項。例如,下面的文法有2個記號符號。
class P extends Parser;
a : A Z ;
子文法Q,最初和父文法有相同的詞表,但隨后會增加一些符號。
class Q extends P;
f : B ;
在上面的情況,Q定義了幾個符號,B使得Q的詞表為{A,B,C}.
一個子文法的詞表一般是父文法詞表的父集。注意重載規則不影響最初的詞表。
如果你的子文法需要父文法未使用過的新詞法結構,你或許需要子語法分析器使用一個子詞法分析器。使用importVocab選項指定子詞法分析器的詞表來重載它初始的詞表。例如,假設語法分析器P使用詞法分析器PL。不用importVocab重載,Q的詞表將使用P的詞表,即PL的詞表。如果你想讓Q使用另一個詞法分析器的記號類型,比如說使用QL,那么做下面的事情:
class Q extends P;
options {
importVocab=QL;
}
f : B ;
Q的詞表現在和QL的詞表相同或者是QL詞表的父集。
如果你所有的文法在一個文件中,你就不用擔心ANTLR將會最先處理哪一個文法文件,不過你仍要擔心ANTLR處理文件中文法的次序。如果你嘗試去導入一個會被另一個文法導出的詞表,ANTLR將提示它不能讀取這個文件。下面的文法文件會造成ANTLR出錯:
class P extends Parser;
options {
importVocab=L;
}
a : "int" ID;
class L extends Lexer;
ID : 'a';
ANTLR在文法文件中還沒有發現文法L,所以它將提示不能發現LTokenTypes.txt。另一方面,如果LTokenTypes.txt存在(比如說在文法文件中還沒有P文法的時候ANTLR運行生成的),ANTLR將為P讀取這個文件,然后在處理L文法的時候覆蓋掉它。ANTLR不知道它要處理的文法恰好在一個文件中,所以它假設是要讀取從另一個文件生成的詞表。
一般的,如果你想讓文法B使用文法A的記號類型(無論什么文法類型),你必須首先對A運行ANTLR。例如,一個樹文法用到了分析器文法的詞表,應該在ANTLR生成了分析器之后再去處理樹文法。
例如,當你想讓一個詞法分析器和一個語法分析器共享同一個詞表空間的時候,你要做的就是去把它們放到同一個文件中,設置它們的導出詞表到同一個空間。如果它們在分開的文件中,把語法分析器的導入詞表選項設置為詞法分析器的導出詞表,除非記號都定義在語法分析器中,這時,換一下導入/導出的關系讓詞法分析器使用語法分析器的導出詞表。
如果你的文法在不同的文件中,你仍想讓它們共享全部或部分記號空間,該怎么辦。有2種解決方案:(1) 讓文法導入同樣的詞表 (2) 讓文法繼承同一個父文法,該父文法含有記號空間。
第一個方案在下面情況使用,你有2個詞法分析器和2個語法分析器,語法分析器必須要處理從根本上就不同的輸入部分。ANTLR 2.6.0發行版examples/java/multiLexer中的例子就屬于這種情況。javadoc注釋和Java代碼部分的詞法分析和語法分析過程都不一樣。javadoc詞法分析器有必要識別"*/"中止注釋的詞法結構,但它一般讓Java語法分析器用打開/關閉的記號引用來嵌套運行javadoc語法分析器:
javadoc
: JAVADOC_OPEN
{
DemoJavaDocParser jdocparser =
new DemoJavaDocParser(getInputState());
jdocparser.content();
}
JAVADOC_CLOSE
;
問題在于:javadoc詞法分析器定義了JAVADOC_CLOSE,即也定義了它的記號類型。不幸的是Java語法分析器的詞表基于Java詞法分析器而不是javadoc詞法分析器。 要讓javadoc詞法分析器和java詞法分析器都可以看到JAVADOC_CLOSE (并且有同樣的記號類型),2個詞法分析器都要導入含有這種記號類型定義的詞表。這里有DemoJavaLexer和DemoJavaDocLexer的頭部:
class DemoJavaLexer extends Lexer;
options {
importVocab = Common;
}
...
class DemoJavaDocLexer extends Lexer;
options {
importVocab = Common;
}
...
CommonTokenTypes.txt有:
Common // name of the vocab
JAVADOC_CLOSE=4
共享詞表的第二種方案在下面情況使用,你有1個語法分析器和3個不同的詞法分析器(比如說為不同類型的C)。如果你只想語法分析器空間利用率高,語法分析器必須可以訪問3個詞法分析器的詞表,去掉文法不用的結構(大概可以用語義斷言)。給出CLexer,GCCLexer和MSCLexer,CLexer作為父文法定義出所有記號的集合。例如,如果MSCLexer需要"_int32",那么在CLexer中定義一個所有詞法分析器可見的記號類型:
tokens {
INT32;
}
在MSCLexer中,你可以給它實際意義的字符。
tokens {
INT32="_int32"
}
用這種方法,3個詞法分析器共享同一個記號空間,允許你用一個語法分析器識別多種C的輸入。
Version: $Id: //depot/code/org.antlr/release/antlr-2.7.6/doc/vocab.html#1 $