本章翻译人 CowNew开源团队 周晓
记号词表
每一个文法都指定了带有规则(子结构)和词表语言结构。这些符号在运行时被转换成整型的"记号类型"从而可以有效的比较。定义从符号到记号类型的映射的文件对ANTLR和ANTLR生成的分析器来说是基础。 这份文档描述了ANTLR使用和生成的这类文件,还介绍了用于控制词表的选项。
导言
在分析时,一个语法分析器文法通过符号在它的词表里引用记号要符合由词法分析器或其他几号流生成的Token对象。记号类型储存在记号对象中,每种符号的值是唯一的,分析器比较这些整数来判断记号类型。如果分析器期望下一个记号类型是23,但发现第一个超前扫描记号类型,
LT(1).getType(),不是23,这时分析器抛出
MismatchedTokenException异常。
一个文法可能有一个导入词表,经常也会有一个导出词表,用于被其他文法引用。导入的词表从未被修改,通过它可以知道词表的"初始条件"。不要和导入词汇混淆了。
下面列出了最常见的问题:
ANTLR如何决定哪一个词法符号是什么记号类型?
每个文法有一个记号管理器来管理文法的导出词表。从文法的importVocab选项,记号管理器以 符号/记号类型 的形式被预载。这个选项强制ANTLR寻找有如下映射关系的文件:
PLUS=44
没有importVocab选项,文法的记号管理器是空的(稍后会看见一个警告)。
你的文法中的任意记号没有预设值,它们被按照遇到的顺序赋值。例如,在下面的文法中,记号A和B分别是4和5:
class P extends Parser; a : A B ;
词法文件以如下形式命名:
Name
TokenTypes.txt.
为什么记号类型从4开始?
因为ANTLR在分析过程中需要一些特殊的记号类型,用户定义的记号类型必须在3后开始。
ANTLR生成什么样的词表文件?
ANTLR为词表
V生成
V
TokenTypes.txt和
V
TokenTypes.java,
V是文法的名字或者是exportVocab选项指定的名字。文本文件有点像一个简化的记号管理器,保存着ANTLR需要的一些信息,供定义在其他文件中的文法查看其词表信息等等。Java文件是是一个包含了记号类型常量定义的接口。ANTLR生成的分析器实现了其中的一个接口,获得所需要的记号类型定义。
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 $