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