-ANTLR参考手册,-ANTLR

格式文件 16
参考手册 献给 项目领导和最高导师TerenceParr旧金山大学支持站点 YourViewoftheJavaUniverse初期代码获益于 JohnLilly,EmpathySoftwareC++代码生成器 PeterWells和RicKlarenC#代码生成 MichealJordan,KunleOdutola和AnthonyOguntimehin。
Python's方面的扩展来自于 WolfgangHäfelingerandMarqKole基础软件支撑来自Perforce: 世界上最好的源码控制系统之一感谢以下朋友贡献了他们的聪明才智 LoringCraymerMontyZukowski JimCokerScottStanchfield JohnMitchellChapmanFlack(UNICODE,流部分)关于Eclipse和NetBeans方面的源码改进来自于MarcovanMeegenandBrianSmithANTLR2.7.5版2004年12月22日 目录 前言ANTLR是什么.........................................................................................................................5
第1章ANTLR规范:元语言(Meta-Language).........................................................................6 1.1
元语言词汇表(Meta-LanguageVocabulary).................................................................61.2Header段(HeaderSection).........................................................................................12
1.3语法分析类的定义(PaserClassDefinitions)............................................................121.4词法分析类定义(LexcalAnalyzerClassDefinitions)...................................................131.5树分析类定义(Tree-parserClassDefinitions).............................................................141.6选项段(OptionSection).......................................................................................14
1.7记号段(TokensSection).......................................................................................14
1.8语法继承(GrammarInheritance)............................................................................161.9规则定义(RuleDefinitions).......................................................................................161.10原子的产生式元素(AtomicProductionElements)................................................191.11简单的产生式元素(SimpleProductionElements)................................................211.12产生式元素操作符(ProductionElementOperators)................................................221.13记号类............................................................................................................................24
1.14谓词................................................................................................................................24
1.15元素标签.........................................................................................................................25
1.16扩展的BNF规则元素(EBNFRuleElements).........................................................251.17语义动作的解释(InterpretationOfSemanticActions)..........................................261.18语义谓词(SemanticPredicates)...............................................................................261.19语法谓词(SyntacticPredicates).................................................................................28 1.19.1
固定深度的超前预测分析和语法谓词(Fixeddepthlookaheadandsyntacticpredicates)...................................................................................................................29
1.20ANTLR元语言文法(ANTLR-metaLanuageGrammar)...........................................30第2章使用ANTLR进行词法分析(LexicalAnalysiswithANTLR)..........................................302.1词法规则(LexicalRules)..............................................................................................31
2.1.1跳过字符(Skippingcharacters)......................................................................322.1.2词法分析规则的区别(Distinguishingbetweenlexerrules)..........................322.1.3返回值(Returnvalues).....................................................................................33
2.2含谓词的LL(k)词法分析..................................................................................................34
2.3关键字和字面值(Keywordsandliterals)....................................................................372.4常见的前缀(Commonprefixes)..................................................................................372.5记号定义文件(Tokendefinitionfiles).........................................................................382.6字符类(Characterclasses)...........................................................................................38
2.7记号属性(TokenAttributes)........................................................................................382.8词法超前分析和记号结束符(Lexicallookaheadandtheend-of-tokensymbol).......382.9扫描二进制文件(ScanningBinaryFiles).....................................................................43第3章ANTLR的树分析器...........................................................................................................44
3.1什么是树分析器?..........................................................................................................45
3.2可以分析什么类型的树?..............................................................................................45
3.3树的语法规则..................................................................................................................46
3.4句法断言..........................................................................................................................47 3.5
语义断言..........................................................................................................................48
3.6一个树遍历器的例子......................................................................................................48
3.7翻译.................................................................................................................................51
3.8一个树翻译的例子..........................................................................................................51
3.9检查/调试AST.................................................................................................................53
第4章记号流(TokenStreams)...............................................................................................54
4.1引言..................................................................................................................................54
4.2自由通过记号流...............................................................................................................55
4.3记号流过滤.......................................................................................................................56
4.4记号流分离.......................................................................................................................57 4.4.1
例子........................................................................................................................58
4.4.2过滤器实现............................................................................................................59
4.4.3如何使用这个过滤器............................................................................................60
4.4.4树的创建................................................................................................................61
4.4.5垃圾回收................................................................................................................62
4.4.6附注........................................................................................................................62
4.5记号流多路技术(又叫"词法分析器多状态").............................................................634.5.1多词法分析器........................................................................................................63
4.5.2词法分析器共享同一字符流................................................................................66
4.5.3分析多元记号流....................................................................................................66
4.5.4多记号流超前扫描的效果....................................................................................68
4.5.5多词法分析器vs调用另一条词法规则...............................................................684.6TokenStreamRewriteEngine简单的语法制导翻译........................................................704.7未来.................................................................................................................................70
第5章记号(token)词汇表......................................................................................................71
5.1引言..................................................................................................................................71
5.1.1ANTLR如何决定哪个词法符号是什么记号类型?
...............................................725.1.2为什么记号类型从4开始....................................................................................725.1.3ANTLR生成什么样的词汇表相关的文件............................................................725.1.4ANTLR怎样同步在同一文件和不同文件里文法的符号类型映射.....................725.2文法继承和词汇表...........................................................................................................74
5.3识别器生成顺序...............................................................................................................75
5.4词汇表的一些使用技巧...................................................................................................76
第6章错误处理及恢复...............................................................................................................78
6.1、ANLTR的异常体系结构................................................................................................78
6.2借助文法来修改默认的错误消息.................................................................................81
6.3解析异常处理.................................................................................................................81
6.4指定解析异常处理方法.................................................................................................82
6.5Lexer中的默认异常处理..............................................................................................83
第7章JavaRuntimeModel.......................................................................................................85
第8章C++RuntimeModel........................................................................................................85
第9章C#RuntimeModel...........................................................................................................85
第10章PythonRuntimeModel.................................................................................................85
第11章ANTLR树构建...................................................................................................................85 11.1
注释................................................................................................................................86
11.2控制AST构建...............................................................................................................86
11.3构建AST的语法注释...................................................................................................86 11.3.1
叶节点................................................................................................................86
11.3.2根节点...............................................................................................................86
11.3.3关闭标准树的构建...........................................................................................87
11.3.4树节点构建........................................................................................................88
11.3.5ASTAction换化..............................................................................................88
11.4执行解析创建树...........................................................................................................90
11.5AST工厂........................................................................................................................90
11.6异类ASTs.......................................................................................................................92
11.6.1一棵表达式树例子...........................................................................................93
11.6.2使用语法描述异构树.....................................................................................100
11.7AST(XML)序列化.....................................................................................................101
11.8AST枚举......................................................................................................................102
11.9一些例子.....................................................................................................................102
11.10标签子规则...............................................................................................................103
11.11引用节点...................................................................................................................107
11.12必需的AST功能与形式...........................................................................................107
第12章语法继承(GrammarInheritance)............................................................................110
12.1语法继承(GrammarInheritance)...........................................................................11012.2功能(Functionality).................................................................................................113
12.3父语法(Supergrammar)可以放置的位置..............................................................11512.4错误信息(ErrorMessages).....................................................................................116
第13章选项(Options)..........................................................................................................116
13.1文件、语法和规则的选项(File,Grammar,andRuleOptions)..............................11613.1.1ANTLR中支持的选项(OptionssupportedinANTLR)..................................11813.1.2language:设置生成的目标语言......................................................................12113.1.3k:设置lookahead(前瞻)的深度................................................................12113.1.4importVocab:初始化语法词汇表....................................................................12213.1.5exportVocab:指定导出词汇表的名称............................................................12313.1.6testLiterals:是否生成常量检测代码...............................................................12413.1.7defaultErrorHandler:设置默认的错误处理器................................................12513.1.8codeGenMakeSwitchThreshold:控制代码的生成...........................................12613.1.9codeGenBitsetTestThreshold:控制代码的生成...............................................12613.1.10buildAST:自动创建抽象语法树(AST).......................................................12713.1.11ASTLabelType:设置节点类型........................................................................12713.1.12charVocabulary:设置词法分析器的字符表..................................................12813.1.13warnWhenFollowAmbig...................................................................................129
13.2命令行选项(CommandLineOptions)....................................................................131 前言ANTLR是什么 ANTLR,语言识别的另一个工具(ANotherToolforLanguageRecognition),(前身是PCCTS)是一种语言工具,它提供了一个框架,可以通过包含Java,C++,或C#动作(action)的语法描述来构造语言识别器,编译器和解析器。
计算机语言的解析已经变成了一种非常普遍的工作。
传统的计算机语言的编译器和工具(如C或Java)仍旧需要被构造,它们的数量与需要开发的那些成千上万的小语言的识别工具和解析工具相比是相形见拙。
程序员为了解析数据格式,图形文件(如,PostScript,AutoCAD),文本文件(如,HTML,SGML等)而需要构造解析器。
ANTLR被设计出来处理所有这些转换工作。
TerenceParr从1989年就和他的同事开始了ANTLR方面的工作,在编译理论和语言工具构造方面做出了巨大的贡献,引发了基于LL(k)文法识别工具的苏醒。
这儿有一个按年份排列的软件历史和ANTLR/PCCTS。
的贡献者的列表。
这儿是ANTLR的软件授权。
可以获得入门教程,从ANTLRFAQat可以找到你的一些问题的答案。
可以参见和术语表。
第1章ANTLR规范:元语言(Meta-Language) ANTLR接受3类语法规范——语法分析器(parsers),词法分析器(lexers),和树分析器(tree-parsers)(也叫树遍历器tree-walkers)。
由于ANTLR使用LL(k)分析所有的3种语法变型,并且语法说明相似,因而产生的lexers和语法分析程序也很类似。
另外产生的识别程序可读性很好,你可以查看输出的内容来明白很多关于ANTLR的机理。
1.1元语言词汇表(Meta-LanguageVocabulary) 空格(Whitespace) 空格、tab符号和换行符是分隔符,在ANTLR中可以分隔诸如标识符这样的词汇符号,但除此之外,它们会被忽略。
例如,“FirstNameLastName”对ANTLR来说是两个记号引用(tokenreference)序列而不是一个记号(token),空格,然后再接着一个记号(token)。
注释(Comments) ANTLR接受C语言风格的块注释和C++风格的行注释。
在语法类和规则中,Java风格的文档注释也是可以接受的,在需要的时候,这些注释可以被传递给生成的输出文件。
例如: /**此文法识别简单的表达式*@作者TerenceParr*/ classExprParser; /**匹配因子*/factor:...; 字符集(Characters) 字符常量像Java中那样被确定。
它们包含八进制转义字符集(e.g.,'\377')、Unicode字符集(e.g.,'\uFF00')和能被Java识别的常用的字符转义('\b','\r','\t','\n','\f','\'','\\')。
在词法分析器规则中,单引号代表一个可以在输入字符流中能得到匹配的字符。
单引号的字符在语法分析器中是不被支持的。
文件结束标志(EOF) EOF记号(Token)可以用如下语法分析器规则自动生成:rule:(statement)+EOF; 你可以在词法分析器规则的动作(action)中检测EOF_CHAR://makesurenothingbutnewlineor//EOFispastthe#endifENDIF{booleaneol=false;} :"#endif"(('\n'|'\r'){eol=true;})?
{if(!
eol){if(LA
(1)==EOF_CHAR){error("EOF");}else{error("Invalidchars");}}} ;同时你可以把文件结束符当作一个字符来检测,但它实际上并不是一个字符,而是一种条件。
你应该在你的词法分析器语法中重载CharScanner。
uponEOF()函数: /**此方法由YourLexer.nextToken()当文法分析器*遇到EOF条件时调用。
*EOF并不是字符。
*当在处理语法谓词或一般的词法规则时到达EOF,并不会调用此方法,*因为可能会抛出IOException。
这是通常EOF条件的陷阱。
*在全部对先前所有的记号求值后,并且当分析程序请求在EOF后的*非EOF记号时,uponEOF()方法会被调用。
*你也许希望抛出记号或字符流异常,可能因为这是一个过早的EOF,*即事实上并未到达文件结尾,或者到达文件结尾后,想回到文件开始*重新引用文件。
*/ publicvoiduponEOF()throwsTokenStreamException,CharStreamException {} 文件结束的情形有点让人困惑(从2.7.1版本开始),因为Terence将-1当作一个字符而不是一个整数(-1是‘\uFFFF’...)。
字符串(Strings) 字符串常量是一个由双引号括起来的字符序列。
字符串中的字符可以是字符集中合法的转义字符(八进制,Unicode等)。
目前ANTLR并不允许Unicode出现在字符串常量中(你不得不用转义符),这是因为在anglr.g文件中设定charVocabulary选项为ascii。
在词法分析规则中,字符串被理解为在输入流中将要进行匹配的字符序列(例如:“for”等效于‘f’‘o’‘r’)。
在语法分析规则中,字符串代表记号(tokens),并且每个唯一的字符串被分派给一个记号类型。
然而,ANTLR并不创建词法分析规则来匹配字符串。
相反,ANTLR将这些字符串输入到一张与词法分析器相关联的字符表中。
在将记号传送给语法分析器前,ANTLR将产生检测代码来检测字符表中的每个记号的内容,每遇到一个匹配都会修改记号的类型。
你也可以执行手动检测――自动代码的生成由词法分析器选项控制。
你也许想在你的动作(action)中使用某个字符的记号类型值,例如在错误处理的同步部分。
对于那些只由字母字符组成的字符串来说,这些字符串的值将是一个形如LITERAL_xxx的常量值,这里xxx是这个记号的名字。
例如,字符串“return”将有一个LITERAL_return值与之关联。
你也可以为记号节(tokenssection)中使用的字符分派一个特定的标号。
记号引用(Tokenreferences) 以大写字符开头的标识符称为记号引用。
接下来的字符可以是任意字符、数字或下划线。
在语法分析规则中一个记号引用将引起匹配特定的记号。
在词法分析规则中的一个记号引用将引起一个匹配记号的字符的词法规则的调用。
换句话说,词法分析器中的记号引用被看作是一个规则引用。
记号定义(Tokendefinitions) 在词法分析器中的记号定义与语法规则的语法定义是相同的,但是指向记号而不是语法规则。
例如: classMyParserextendsParser;idList:(ID)+;//解析规则定义 classMyLexerextendsLexer; ID:('a'..'z')+;//记号定义 规则引用(Rulereferences) 以小写字母开头的标识符是对ANTLR的语法规则的引用。
接下来的字符可以是任意字母,数字或下划线。
词法规则不能引用语法规则。
动作(action)(Actions) 在花括号中的字符序列(可能是嵌套的)是语义动作(action)。
在字符串和字符中的花括号并不是动作(action)分隔符。
动作(action)参数(ArgumentsActions) 在方括号中的字符序列(可能是嵌套的)是动作(action)参数。
在字符串和字符中的方括号不是动作(action)分隔符。
在[]中的参数是用生成语言的语法定义的,并且用逗号分开。
codeBlock [intscope,Stringname]//输入参数 returns[intx] //返回参数 :...; //pass2args,getreturn testcblock {inty;} : y=cblock[
1,"John"] ; 许多人倾向于我们使用普通的括号来表示参数,但是括号在EBNF中已经被很好的用来定义语法组符号(grammaticalgroupingsymbols)。
符号(Symbols) 下面的表统计了在ANTLR中使用的标点符号和关键字。
符号 描述 (...)(...)*(...)+(...)?
{...}[...]{...}?
(...)=> |..~.=:;<...>classextendsreturnsoptions 子规则闭包子规则(零和多个)正闭包子规则(一个和多个)可选(零个和一个)语义动作(action)规则参数语义谓词语法谓词可选符范围符非通配符赋值标号符,规则开始规则结束元素选项语法类指定语法基类指定规则返回类型options段 tokensheadertokens tokens段header段token定义段 1.2Header段(HeaderSection) 一个header段包含了任何由ANTLR生成的代码在被输出到语法分析器前需要被替换的源码(译者注:形为类似include、import)。
这个主要用在C++的输出中,因为C++需要一些元素在引用之前必须被声明。
在Java中,这可以用来为最后的语法分析指定一些包文件。
一个header段看起来像下面这样:header{ sourcecodeinthelanguagegeneratedbyANTLR;}header段是语法文件的第一节。
根据选择的目标语言的不同,会有不同类型header段。
请参考相应的附录。
1.3语法分析类的定义(PaserClassDefinitions) 所有的语法规则必须与一个语法分析类关联。
一个语法文件(.g)只包含一个语法分析类的定义(以及词法分析程序和树分析程序),一个语法分析类的定义先于其选项(options)和规则定义。
语法文件中的语法分析类的定义通常如下所示:{optionalclasscodepreamble}classYourParserClassextendsParser;optionstokens{optionalactionforinstancevars/methods}parserrules... 当在面向对象语言中生成代码时,语法分析类将在输出中生成一个类,规则都会变成这个类的成员函数。
在C中,类将生成一个结构,一些名字分配(name-mangling)的算法将使生成的规则函数是全局唯一的。
前面的可选类可以是包含在{}中的任意文本。
前面的可选类,如果存在的话,将被直接输出到生成类文件中,并且在类定义之前。
封闭的花括号不能用来分隔类,因为它很难将一个文件底部的左花括号与这个文件顶部的花括号联系起来。
然而,一个语法分析类被认为是连续的,直到遇到下一个类的语句。
你可以指定语法分析器的基类,它将作为语法分析器中生成代码所需的基类。
这个基类必须完全可信并在双引号中,它自己必须是ANTLR.LlkParser的子类。
例如: classTinyCParserextendsParser("ANTLR.debug.ParseTreeDebugParser"); 1.4词法分析类定义(LexcalAnalyzerClassDefinitions) 一个语法分析器类将产生一个将相关语法结构应用于输入流中的记号集的语法分析对象。
为了执行词法分析,你需要指定一个词法分析类,它描述了如何将字符输入流分解成记号流。
它的语法类似于语法分析类: {optionalclasscodepreamble}classYourLexerClassextendsLexer;optionstokens{optionalactionforinstancevars/methods}lexerrules... 词法分析类中的词法规则成为生成类中的成员方法。
每个语法文件(.g)只包含一个词法分析类。
语法分析类和词法分析类可以以任意顺序出现在语法文件中。
前面的可选类(optionalclasscodepreamble)是在{}中的任意文本。
前面部分的可选类,如果存在,将输出到生成类的文件中,在类定义的之前。
你可以定义一个词法分析类的超类,它可以被用来作为生成词法分析类的超类。
这个超类必须是完全可信的(fully-qualified),并且在双引号中,而它本身是ANTLR.CharScanner子类。
1.5树分析类定义(Tree-parserClassDefinitions) 一个树分析器就像一个语法分析器,不同的是树分析器处理的是二维的由结点组成的抽象语法树(AbstractSyntaxTree),而不是处理由记号组成的记号流。
树分析器定义类似于语法分析类,不同的是规则定义中可能包含特殊形式来指示其递归下降树。
同样,一个特定的语法文件(.g)中只能包含一个树分析器。
{optionalclasscodepreamble}classYourTreeParserClassextendsTreeParser;optionstokens{optionalactionforinstancevars/methods}treeparserrules... 你可以定义一个树分析器的超类,它可以被用来作为生成树解析器的超类。
这个超类必须是完全可信的(fully-qualified),并且在双引号中,它本身是ANTLR.TreeParser子类。
1.6选项段(OptionSection) 并不是让程序员给分析程序生成器指定多个的命令行参数,文法中的选项段本身就可以达到此目的。
这种方法更受欢迎,因为它将需要的选项关联到文法而不ANTLR的调用。
这部分以options关键字开关,包含多个的选项/值赋值语句。
可以为每个文件、每个文法、每个规则和每个子规则指定一个选项段。
同时你也可以为一个元素指定一个选项段,例如记号引用。
1.7记号段(TokensSection) 如果你需要定义一个“虚拟”的记号,也即没有对应实际输入的符号与其关联,可以使用记号段来定义它们。
虚拟记号通常用于标识树结点,该类树节点用于标记或组织根据实际 输入生成的子树。
例如,你可能希望让EXPR结点成为每一个子树表达式的根结点,DECL表示子树的声明,这样在树的遍历时更容易引用它们。
因为EXPR没有对应的输入符号,你就不能在文法中通过引用来隐含地定义它。
使用如下方法来定义那些虚拟的记号。
tokens{EXPR;DECL; } 通常的语法是: tokenSpec:"tokens"LCURLY(tokenItemSEMI)+RCURLY ; tokenItem:TOKENASSIGNSTRING(tokensSpecOptions)?
|TOKEN(tokensSpecOptions)?
|STRING(tokensSpecOptions)?
; tokensSpecOptions:"<"idASSIGNoptionValue(SEMIidASSIGNoptionValue)*">"; 在token段中你还可以定义字面值,更重要的是,给它们赋与一个有效的标签,如下例所示。
tokens{KEYWORD_VOID="void";EXPR;DECL; INT="int";}以这种方式定义的字符串会被认为你已经在分析程序中对它们进行了引用。
如果文法导入了包含一个记号的词汇表,比如记号
T,然后你可以简单地通过在该文法的记号段中添加表达式T=“字符串常量”来将一个字符串常量关联到该记号类型(也即T)。
类似地,如果导入的词汇表中定义了一个字面值,比如"_int32",但没有相关联的标签,你可以在记号段中关联一个标签,例如INT32="_int32"。
你可以为在记号段中定义的记号定义选项。
目前可用的选项仅有AST=class-type-to-instantiate。
//定义需要创建的多个AST结点//可以在文法中实际引用时重载tokens{ PLUS;STAR;} 1.8语法继承(GrammarInheritance) 面向对象编程语言,例如C++和Java,允许你定义一个新的对象,当它与已经存在的对象有区别时,这种方法提供了很多好处。
“根据差异编程”节省了开发/测试的时间,并且将来对基类的修改也会自动传递给子类。
ANTLR支持语法继承,也就是基于一个基类来创建新的文法类的机制。
文法相关的语法结构和动作(action)均可以单独被修改。
1.9规则定义(RuleDefinitions) 因为ANTLR把词法分析看作是对字符流的分析,所以词法分析规则的语法规则可以同时讨论。
一般讨论规则时,我们使用术语atom代表输入流中的一个元素(可能是字符或记号)。
输入流中atoms的结构通过多个互相引用的规则来指定。
每一个规则有一个名字,一些可选的参数,一个可选"throws"子句,一个可选的初始化动作(action)(init-action),一个可选的返回值,和一个或多个可选项。
ANTLR规则的基本形式为: rulename:alternative_1|alternative_2...|alternative_n; 如果规则需要参数,使用如下形式: rulename[formalparameters]:...;如果你希望从规则返回一个值,使用returns关键字:rulenamereturn[typeid]:...;这里type是一个生成语言的类型指定符,id是生成语言的一个有效标识符。
Java中一个 单一的类型指定符能够满足大部分的应用,但是例如返回一个字符串的数组将需要一对方括号: idreturn[String[]s]:(ID{...})*; 同样,如果生成C++,返回类型可能会很复杂,例如: idreturn[char*[]s]:...;return语句的id会传递给输出代码。
动作(action)可能直接对此id赋值来设置返回值。
不要在动作(action)中使用return指令。
为了指明你的分析器(或树分析规则)可以抛出非ANTLR指定的异常,使用异常子句。
例如:下面是一个简单的通过规则指定的分析程序,该分析程序抛出MyException: classPextendsParser; athrowsMyException:A; ANTLR为规则a生成如下代码: publicfinalvoida()throwsRecognitionException,TokenStreamException,MyException {try{match(A);}catch(RecognitionExceptionex){reportError(ex);consume();consumeUntil(_tokenSet_0);} } 词法规则可能并不指定异常。
初始化动作(action)(Init-actions)在冒号前指定。
初始化动作(action) (Init-actions)与一般的动作(action)不同,因为它们总会执行,并推测模式(guess
mode)无关。
另外,它们适合于局部变量的定义。
rule{ init-action} :...; 词法分析规则(Lexerrules)。
词法分析文法中定义的规则必须有一个以大写字母开头的名字。
这些规则隐含地匹配输入流的字符,而不是记号流中的记号。
被引用的文法元素包括记号引用(tokenreferences)(隐含的词法分析规则引用),字符和字符串。
词法分析规则按照与语法分析规则完全相同的方式被处理,可能会指定参数和返回值,未来,词法分析 规则同样可以使用局部变量和递归使用。
更多关于词法规则请参考第2章ANTLR的词法分析。
语法分析规则(Parserrules)。
语法规则将结构应用于记号流,而词法规则将结构应用于字符流。
语法分析规则不能引用字符的字面值。
语法分析规则中双引号括起的字符会被认为是记号引用(tokenreferences)和迫使ANTLR将字符串常量存储在表中,该表可以由相关词法分析程序中的动作(action)来检查。
所有的语法分析规则必须以小写字母开头。
树分析规则(Tree-parserrules)。
树分析规则中,一个额外的特殊的语法允许被用来指定二维结构的匹配。
一个语法分析规则类似: rule:ABC; 意思是“依次匹配ABC”。
一个树分析规则可能会使用如下语法: rule:#(ABC); 意思是“匹配一个类型A的结点,然后下降它的子结点列表,匹配B和C”。
这个符号可以任意嵌套,可以在EBNF结构能够使用的地方使用#(…),例如: rule:#(AB#(CD(E)*)); 1.10原子的产生式元素(AtomicProductionElements) 字数常量(Characterliteral)。
字数常量仅仅可以在词法分析规则中被引用。
单个的字符会在字符输入流被匹配。
不需要转义正则表达式中的元符号,因为正则表达式并不是用来匹配词法原子符号的。
例如,当你指定字面字符来匹配时,‘{’并不需要转义符。
字数常量和字符串常量外的元符号被用来指定词法结构。
你所引用的所有字符会隐含地添加到全局字符词汇表中(具体请参考charVocabulary节)。
当你引用通配符时,如‘.’或‘~c’(除c外的任意字符),词汇表此时就会起作用。
你不需要特别地处理Unicode字符。
例如,下面是一个名为LETTER的规则,此规则匹配被认为是Unicode字母的字符: protectedLETTER :'\u0024'| '\u0041'..'\u005a'|'\u005f'|'\u0061'..'\u007a'|'\u00c0'..'\u00d6'|'\u00d8'..'\u00f6'|'\u00f8'..'\u00ff'|'\u0100'..'\u1fff'|'\u3040'..'\u318f'|'\u3300'..'\u337f'|'\u3400'..'\u3d2d'|'\u4e00'..'\u9fff'|'\uf900'..'\ufaff'; 你可以在其它规则中引用上述规则: ID:(LETTER)+; ANTLR将生成代码来检查输入字符而不是lexer对象生成的字符集。
字符串常量(Stringliteral)。
语法分析规则中对字符串常量的引用会为此字符串常量定义一个记号类型,并且导致字符串常量在相关lexer的哈希表中被替换。
相关的lexer将会自动检查每一个被匹配的记号,以查看该记号是否匹配一个字面值。
如果匹配,此记号的记号类型会被设为从语法分析程序(parser)导入的为该字面值定义的记号类型。
你可以关掉自动检查,然后在一个类似ID的简单规则手动检查。
在语法分析程序中对字符串常量的引用会被添加一个元素类型的后缀,具体参考下面的记号引用章节。
词法规则中字符串的引用会特定的字符序列,是一种简写方式。
例如,考虑下面的词法规则定义: BEGIN:"begin"; 这个规则可以以另外一种功能相同的方式重写: BEGIN:'b''e''g''i''n'; 没有必要转义正则表达式中的符号,因为正则表达式并不是用来匹配词法分析程序(lexer)中字符。
记号引用(TokenReference)。
语法分析规则中的记号引用意味着你希望使用特定的记号类型来识别一个记号。
实际上这并不会调用相关的词法规则——词法分析阶段将记号流传递给语法分析程序(parser)。
词法分析规则中的记号引用意味着对该规则的一个调用方法,执行与语法分析程序中的规则引用相同的语义分析。
这样的话,你可以指定规则参数和返回值。
详情请参考下一规则引用章节。
你同样可以指定记号引用上的选项。
例如,下面的规则指引ANTLR从INT的引用创建INTNode对象: i:INT; 该语法的选项为: 通配符(Wildcard)。
语法分析规则(parserrule)中的‘.’通配符代表任意一个记号;在词法分析规则(lexerrule)中,它代表任意一个字符。
例如,‘.’代表任意一个在B和C之间的记号: r:AB.C; 1.11简单的产生式元素(SimpleProductionElements) 规则引用(Rulereference)。
对规则的引用意味着在语法分析程序中该位置处对该规则的
个方法调用。
你可以传递参数和获取返回值。
例如,形参和实参在方括号中被指定: funcdef:typeID"("; block[intscope]:"begin"...; args")"block[1]{/*useargscope/*} "end" 存储在变量中的返回值使用简单的赋值符返回: set{Vectorids=null;}//init-action :"("ids=idList")";idListreturns[Vectorstrs]{strs=newVector();}//init-action :id:ID{strs.appendElement(id.getText());}(","id2:ID{strs.appendElement(id2.getText());})* ; 语义动作(Symaticaction)。
动作(action)是括在花括号(curlybraces)中的源代码块(以目标语言来表示)。
这段代码会在前面的产生元素已经识别之后,后续元素识别之前执行。
动作(action)通常被用来产生输出,构造树或者修改符号表。
动作(action)的位置决定了它什么时候被识别,相对于周围的文法元素。
如果动作(action)是产生式的第一个元素,它将在此产生式中任何其它元素之前被执行,除非此产生式由超前查看(lookahead)预测。
EBNF子规则的第一个动作(action)后面可能紧跟着‘:’。
这样做是为了指定此动作(action)是一个初始化动作(init-action),把它与子规则关联成为一个整体,而不是任意的产生式。
一旦进入子规则,它就会被执行——在超前查看(lookahead)为子规则替换而进行预测之前——并且即使中预测过程中(检查语谓词)也会执行。
例如: ({init-action}:{actionof1stproduction}production_
1 |{actionof2ndproduction}production_2)?
不管可选的子规则中将匹配什么,初始化动作(init-action)都会执行。
初始化动作放置中在为子规则(...)+和(...)*生成的循环中。
1.12产生式元素操作符(ProductionElementOperators) 元素求反(plement)。
取反一元操作符’~’只能用于原子元素,比如记号标识符。
对一些原子的记号(token)
T,~T将匹配除文件结束符(end-of-file)和T以外的任何记号。
有词法分析规则(lexerrules)中,~’a’将匹配任何非’a’字符。
”~.”(不是任何东西)毫无意义,同时也是不允许的。
词汇表中的空格对取反操作符来说很重要。
在语法分析程序(parser)中,完整的记号类型列表对ANTLR来说是已知的,于是,ANTLR简单地设置和清除标记的元素。
对字符来说,如果你想使用取反操作符,你必须指定字符的词汇表。
注意对类似Unicode字符块的庞大的词汇表来说,最坏情况下,对一个字符的取反意味着创建2^16(2的16次方)个元素集(大 约8k)。
字符的词汇表是charVocabulary选项指定的词汇表与所有在词法分析规则(lexerrules)中引用的字符的并集。
下面是一个字符词汇表选项的简单使用例子:classLextendsLexer;options{charVocabulary='\3'..'\377';}//LATIN DIGIT:'0'..'9';SL_COMMENT:"//"(~'\n')*'\n';(译者注:单行注释的文法) 集合取反(plement)。
通过对其它集合取反,非操作符(notoperator)同样可以用来构造一个记号集或字符集。
最大的用处的就是当你希望匹配多个记号或多个字符,直到遇到特定的分隔符。
并不是为这类集合引入特殊的语法,ANTLR允许将~放在仅由简单元素且没有动作构成的子规则前,以此来生成这类集合。
在这类特定的情况下,ANTLR并不会生成子规则,而是创建一个集合匹配。
简单的元素可以是记号引用,记号范围,字数常量,或者字符范围。
例如: classPextendsParser;r:T1(~(T1|T2|T3))*(T1|T2|T3); classLextendsLexer;SL_COMMENT:"//"(~('\n'|'\r'))*('\n'|'\r); STRING:'"'(ESC|~('\\'|'"'))*'"';protectedESC:'\\'('n'|'r'); 范围操作符(Rangoperator)。
范围二元操作符意味着一定范围内的原子元素可能被匹配。
词法分析程序中的表达式’c1’..’c2’匹配包含在此范围内(包括’c1’和’c2’)的所有字符。
语法分析程序中的表达式T..U匹配任何记号类型包含在此范围内(包含T和U)的记号,该范 围是不确定的值,除非记号类型是在外部生成的。
抽象语法树根结点操作符(ASTrootoperator)。
当生成抽象语法树(ASTs)时,以根结点操 作符”^”为后缀的记号引用将此结点强制生成并添加为当前树的根结点。
这个符号仅仅当 buildAST选项设置时有效。
更多关于ASTs的信息是可以得到的,请参考后面相关的章节。
AST排除操作符(ASTexcludeoperator)。
当生成抽象语法树(ASTs)时,以排除操作符"!
" 为后缀的记号引用并不会包含在为相应规则而构造的抽象语法树(AST)中。
规则引用同样也可以以排除操作符为后缀,这意味着当为引用的规则构造树时,它并不会链接到为引用的 规则构造的树。
同样,这个符号仅仅当buildAST选项设置时有效。
更多关于ASTs的信息是可以得到的,请参考后面相关的章节。
1.13记号类 通过使用范围操作符、非操作符、或者仅仅由原子的元素构成的子规则,你可以隐含地定义匿名的记号或字符类——具有很好时间和空间效率的集合。
例如,你可以如下地定义一个词法分析规则: OPS:(PLUS|MINUS|MULT|DIV); 或 WS:(''|'\n'|'\t'); 这些单独地描述了记号和字符集合,这种集合很容易被优化为简单、单一的位的集合,而不是一系列的记号和字符的比较。
1.14谓词 语义谓词(Semanticpredicate)。
语义谓词是在分析能够继续传递它们之前必须满足的条件。
语义谓词的功能会在接下来的章节中详细地说明。
语义谓词的语法就是以问号符(?
)为后缀的语义动作: {表达式}?
其中的表达式不能有副作用,求值必须能够得到true或者false(Java中的boolean值或者C++中的bool值)。
既然语义谓词能够在预测时执行,它们不应该依赖动作的返回值或规则的参数。
语法谓词(Syntacticpredicate)。
语法谓词指定了被用来预测可替代项的超前预测分析语言(lookaheadlanguage)。
语法谓词的功能会在接下来的章节中详细地说明。
语法谓词 的语法形式为以=>操作符为后缀的子规则:(lookahead-language)=>production 这里的超前预测分析语言(lookaheadlanguage)可以是任何有效的ANTLR结构,包括对其它规则的引用。
尽管如此,在语法谓词求值过程中,动作并不会被执行。
1.15元素标签 任何原子的或规则引用的产生式元素可以用标识符进行标识(大小写并有重要)。
在原子的 元素带标签的情况,标识符在语义动作中被使用,以此来访问相关的Token对象或者字符。
例如: assign: ; v:ID"="expr";"{System.out.println( "assignto"+v.getText());} 在动作中对标签的引用并不需要"$"操作符,与PCCTS1.xx版本中一样。
在动作中,一个记号引用可以这样被访问,就像通过标签访问Token对象,或通过#标签访问为该记号生成的AST。
为一个规则引用生成的AST结点在动作中可以以#标签来访问。
记号引用的标签同样可以在关联的语法分析异常处理中使用,来指定当记号不能被匹配时做什么。
规则引用的标签同样也可以在关联的语法分析异常中使用,因此任何在执行标识的规则时产生的异常能够被捕获到。
1.16扩展的BNF规则元素(EBNFRuleElements) ANTLR支持与下面四个子规则语法或语法图相应的扩展的BNF符号: (P1|P2|...|Pn) (P1|P2|...|Pn)?
(P1|P2|...|Pn)* (P1|P2|...|Pn)+ 1.17语义动作的解释(InterpretationOfSemanticActions) 语义动作被逐字的复制到输出的语法分析程序中适当的位置,并且可能会抛出ASTactiontranslation异常。
没有从PCCTS1.xx开始的$-变量符号($-variablenotation)引入到ANTLR中。
1.18语义谓词(SemanticPredicates) 语义谓词指定了在分析能够继续处理之前必须满足的条件(运行时)。
我们需要区别两种类型的语义谓词:(i)确认(validating)谓词,如果在分析产生式时条件没有得到满足,就 抛出异常的谓词(类似断言assert);(ii)消除歧义(disambiguating)的谓词,提升到相关产 生式的谓词汇表达式中的谓词。
从语法上来说,语义谓词就是带有问号标记符为后缀的语义 动作: {语义谓词汇表达式}?({
semantic-predicate-expression}?
) 此处的表达式可以使用任何程序员提供的或者ANTLR生成的符号,表达式在输出中出现的地 方可用的符号。
谓词在产生式中的位置决定了它是哪种类型。
例如,考虑下面的确认谓词(出现在任何非左 边的位置),该谓词确保一个标识符号语法上是一种类型名: decl:
"var"ID":"t:ID{isTypeName(t.getText())}?
; 当确认谓词失败时,会产生语法分析异常。
抛出的异常是SemanticException。
你可以在异 常处理者(exceptionhandler)中捕获此异常和其它的异常。
消除歧义的谓词在一个产生式中总是第一个元素,因为它们不能提升到动作、记号、规则引 用之上。
例如下述规则的第一个产生式有一个消除歧义的谓词,可以提升到谓词汇表达式中, 作为第一个可供选择的: stat: |
; //declaration"typevarName;" {isTypeName(LT
(1))}?
IDID";" ID"="expr";" //assignment 如果我们将此文法限制为LL
(1),从语法上来说,它是不确定的,因为常见的左前缀:ID。
尽管如此,语义谓词正确地提供附加的信息来消除分析决策时的歧义。
分析逻辑将是: if(LA
(1)==ID&&isTypeName(LT
(1))){matchproductionone }elseif(LA
(1)==ID){ matchproductionone}elseerror 通常,在PCCTS1.xx中,语义谓词代表了一个产生式的语义上下文。
如此,语义和语法上 下文(超前预测分析)能够被提升到其它规则中。
在ANTLR中,谓词并不会被提升到包含它 们的规则之外。
因此,类似下面的规则: type:{isType(t)}?
ID; 毫无意义。
换句话说,这种语义上下文的特点给许多PCCTS1.xx的版本产生了不可忽视的 歧义。
1.19语法谓词(SyntacticPredicates) 偶尔会有通过有限的预测不能呈现为确定的语法分析决策。
例如: a:(A)+B|(A)+C; 在k为任何值的LL(k)情况下,通常的左前缀会造成两个产生式不确定。
明显的是,这两个 产生式可以从左因式分解为(left-factored): a:(A)+(B|C); 而不改变已经识别的语言。
尽管如此,当动作嵌入在文法中时,从左因式分解(left-factoring) 并不总是可能的。
进一步来说,从左因式分解和其它文法上的处理不会产生自然(可读的) 文法。
解决方法是在少数有限的
LL(k)(k>1)不足够的情况下,简单地使用任意的超前预测分析。
ANTLR允许你通过可能的无限字符串来以下述语法来指定超前预测分析语言: (predictionblock)=>production 例如,考虑下面的规则,该规则区分集合(逗号分隔的单词列表)和并列赋值(一个列表赋 值给另外一个): stat:|; (list"=")=>list"="listlist 如果一个紧跟着一个赋值符的列表在输入流在被发现,第一个产生式被预测。
如果不是,会 尝试第二个可供选择的产生式。
语法谓词是一种选择性的可返回(selectivebacktracking)的形式,因此,当对一个语法 谓词求值时,动作会被关掉,所以动作没必要是未完成的。
语法谓词是使用目标语言中的异常来实现的,如果存在异常的话。
当生成C代码(C中没有 异常)时,会使用longjmp来实现。
对任何在文法中发现的非LL(k)决策,我们本可以选择简单地使用任意的超前预测分析。
尽 管如此,在文法中显示地使用任意超前预测分析很有用,因为你不必去猜测语法分析程序在 做什么。
更重要的是,存在模棱两可的语言结构,因为存在非确定的文法!例如,声名狼藉 的
if-then-else结构对任何k都没有LL(k)文法。
现在的文法是模棱两可的,不确定的: stat:| "if"expr"then"stat("else"stat)?
... ; 在一个非确定的决策中,给定在两个产生式中的一个选择,我们简单地选择第一个。
在大部分情况下,这样工作得很好。
强制这个决策使用任意的超前预测分析会降低分析的效率。
1.19.1固定深度的超前预测分析和语法谓词(Fixeddepthlookaheadandsyntacticpredicates) ANTLR并不能确保哪种超前预测分析可以跟在语法谓词后面(唯一的逻辑可能性是不管什么 都可以跟在谓词预测的可选择项后,但是错误的输入等使之更复杂),ANTLR假设什么都可 以跟在语法土谓词后。
这种情形类似于当遇到记号规则定义结束时的词法超前预测分析的计 算。
考虑带(...)*的谓词,其隐含的退出分支强行计算什么跟在循环的后面,这种情况下是语法谓 词的末尾。
class
parseextendsParser; a : (A(P)*)=>A(P)* |
A ; 超前预测分析在退出分支时人为地设为“任意的记号”。
通常P与这“任意的记号”会产生 冲突,但是ANTLR知道你的意思是匹配一系列的P记号,如果它们同时出现,并不产生警 告。
在任何一个决策中如果不止一条路径能够通向谓词的结尾,ANTLR会产生一个警告。
下面的 规则会产生两个警告。
classparseextendsParser; a : (A(P|)*)=>A(P)* |
A ; 空的可选项可以间接地成为这个循环的开始,与P相冲突。
进一步来说,ANTLR检测到了这 个问题,就是有两路径可以到达谓词的结尾。
生成的语法分析程序会发出警告但从不会终止 (P|*)循环。
k>1的超前预测分析中,情况会更复杂。
当第n个超前预测分析到达谓词结尾时,它会记录 原因,然后代码生成器会忽略此深度的超前预测分析。
classparseextendsParser;options{ k=2; } a : (A(PB|P)*)=>A(P)* |
A ; ANTLR从谓词(..)*里生成如下形式的一个决策: if((LA
(1)==P)&&(LA
(2)==B)){match(P);match(B); }elseif((LA
(1)==P)&&(true)){ match(P);}else{ break_loop4;} 这种计算在所有的文法类型中都会起作用。
1.20ANTLR元语言文法(ANTLR-metaLanuageGrammar) 请参考antlr/antlr.g来了解文法,此文法描述ANTLR语言本身中的输入文法的语法。
Version:$Id://depot/.ANTLR/release/ANTLR-2.7.6/doc/metalang.html#1$ 第2章使用ANTLR进行词法分析(LexicalAnalysiswithANTLR) 词法分析器(通常称为扫描器)将输入的字符流分解为词汇表中的一个个的符号,然后输出到语法分析器,语法分析器将语法结构应用于那些符号流。
因为ANTLR为词法分析、语法分析和树分析引入了相同的识别机制,ANTLR生成的词法分析器比基于DFA词法分析器更强大,比如DLG和lex生成的词法分析器。
词法分析能力的提高是在一些词法分析器规范上的不方便所引起的花费,以及确实要求一个严格地关于词法分析的思维转变。
请参考关于LL(k)和基于DFA的词法分析的比较。
ANTLR生成超前预测分析LL(k)的词法分析器,这意味着你可以有一些语义和语法的谓词,并且可以使用k>1的超前预测分析。
其它的优点在于: 你可以阅读和调试输出代码,因为它与你手工创建的很相似。
指定词法结构的语法对词法分析器(lexers)、语法分析器(parsers)和树分析器 (treeparsers)来说都是相同的。
在识别单个记号的过程中,你可以让动作执行。
你可以识别复杂的记号,比如HTML标记,或者“可运行的”注释,像在/**...*/ 注释中javadoc@-tags。
词法分析器有一个堆栈,不像DFA那样,所以你可以匹配 嵌套的结构,比如嵌套的注释。
一个词法分析器的总体结构如下: classMyLexerextendsoptions{ someoptions}{ lexerclassmembers}lexicalrules Lexer; 2.1词法规则(LexicalRules) 在一个词法分析器文法中定义的规则必须有一个以大写字母开关的名字。
这些规则隐示地匹 配输入流的字符,而不是记号流中的记号。
引用的文法元素包括记号引用(隐示地词法分析 规则引用)、字符和字符串。
词法分析规则按照与语法分析规则完全相同的方式处理,可以 指定参数和返回值;更进一步说,词法分析规则同样可以有局部变量和使用递归。
下面的规 则定义了一个名为
ID的规则,该规则名作为一个记号类型在语法分析器是可用的。
ID:('a'..'z')+; 此规则将成为最终的词法分析器的一部分,并将以一个名为mID()的方法出现,类似如下 方法: publicfinalvoidmID(...)throwsRecognitionException,CharStreamException,TokenStreamException {..._loop3:do{if(((LA
(1)>='a'&&LA
(1)<='z'))){matchRange('a','z');} }while(...);...} 熟悉ANTLR的输出是一个好主意——生成的词法分析器是可读的,并使很多概念变得更加清晰。
2.1.1跳过字符(Skippingcharacters) 为了使被某个规则匹配的字符被忽略掉,设置记号类型为Token.SKIP。
例如: WS:(''|'\t'|'\n'{newline();}|'\r')+{$setType(Token.SKIP);} ; 被跳过的记号迫使词法分析器复位并尝试其它的记号。
被跳过的记号永远不会传递给语法分析器。
2.1.2词法分析规则的区别(Distinguishingbetweenlexerrules) 与大部分类似lex的词法分析器生成器一样,你只需简单地列表匹配记号的词法规则的集合。
工具会自动地生成代码来将下一个输入字符映射到规则可能匹配的字符。
因为ANTLR生成递归下降的词法分析器,就像它对语法分析器和树分析器做的一样,ANTLR自动地为一个假想的规则生成一个称为nextToken的方法,以通过查看超前预测分析的字符来预测你的词法分析规则将匹配的字符。
你可以把这方法想像成一个大的"switch"语句,其路径识别流向合适的规则(尽管其代码可能比一个简单的switch语句复杂很多)。
nextToken方法是TokenStream(在Java中)的唯一方法:publicinterfaceTokenStream{ publicTokennextToken()throwsTokenStreamException;}语法分析器填充超前预测分析的缓冲区,并且缓冲区来自任何TokenStream。
考虑如下两个词法分析规则:INT:('0'..'9')+;WS:''|'\t'|'\r'|'\n'; 你将会在ANTLR生成的词法分析器中看到一些如下的类似方法: publicTokennextToken()throwsTokenStreamException{... for(;;){Token_token=null;int_ttype=Token.INVALID_TYPE;resetText();...switch(LA
(1)){case'0':case'1':case'2':case'3':case'4':case'5':case'6':case'7':case'8':case'9':mINT();break;case'\t':case'\n':case'\r':case'':mWS();break;default://error}... }} 当相同的字符预测到不止一个词法规则时会怎样?在冲突的规则之间,ANTLR产生一个非确 定的警告,指明你需要确保你的规则之间没有相同的左前缀。
ANTLR并不遵循常见地"第
个定义优先"词法分析规则(尽管如此,规则中的可供选择的项之间依然遵循此规则)。
相反, 足够地权力被赋于给处理两种最常见模棱两可的情况,也就是“关键字vs标识符”以及“常 见的前缀”;对于特别恶心的情况,你可以使用语法或语义谓词。
如果你希望将一个复杂的规则定义分解为多条规则,该怎样?这种情况下,你肯定不希望 每条规则都产生一个完整的
Token对象。
一些规则仅仅是用来帮助其它规则构造记号。
为了 区分那些“协助”规则与产生记号的规则,使用protected修饰符。
这重载的Java权 限访问控制术语出现了,因为如果这规则是不可见的,那它就不能被语法分析器 “看到”。
请参考什么是受保护的词法分析规则。
另外一个更实用的看待这种情况的方法是注意仅仅非受保护的规则由 nextToken
来调用,也就是仅仅非受保护的规则能产生可传递到通向TokenStream的管道的记号。
2.1.3返回值(Returnvalues) 所有的规则都会自动返回记号对象,此对象至少包含为规则匹配的文字和它的记号类型。
为 了指定一个用户自定义的返回值,可以定义一个返回变量,然后在动作中设置其值: protected INT
returns[intv]:(‘0’..’9’)+{v=Integer.valueOf($getText);}; 注意仅仅受保护的规则可以有一个返回类型,因为正则词法分析规则通常是由nextToken() 调用的,并且语法分析器不能访问返回值,这会导致冲突。
2.2含谓词的LL(k)词法分析 词法分析规则允许你的语法分析器匹配输入字符流中的上下文无关结构,而不是更弱的正则 结构(使用DFA-确定的有限状态自动机)。
例如,考虑下面的情况,使用DFA来匹配嵌套 的花括号可能使用计数器来实现,而嵌套的花括号是很平凡地被上下文无关文法所匹配 ACTION:; '{'(ACTION|~'}')*'}' 从ACTION规则到ACTION的递归当然是一个死循环,并不是一个普通的词法分析规则。
因为同样的算法被用来分析词法分析规则和语法分析规则,词法分析规则可能使用不止一个 超前预测分析的符号,可以使用语义谓词,并且也可以语法谓词来进行任意地超前查看,也 就是,提供了在
LL(k)语言外、上下文相关的识别能力。
下面是一个简单的要求k>1的超前 预测分析: ESCAPE_CHAR:'\\''t'//twocharoflookaheadneeded,|'\\''n'//duemonleft-prefix; 为了说明为词法分析规则的语法谓词使用,考虑Pascal中浮点数和范围的区分问题。
输入 3..4极可能被分解成3个记号:INT,RANGE,接下来是INT。
另一方面,输入3.4,极可能 作为一个REAL发送到语法分析器。
麻烦在于第一个‘.’前的数字序列可以是任意长。
扫描 器必须消耗掉第一个‘.’来下一个字符是不是一个‘.’也就暗示了它必须回退,并把第
个数字序列当作是一个整数。
使用不能回退跟踪的词法分析器使这个任务变得非常困难:没 有回退跟踪,你的词法分析器必须一次能够响应不止一个的记号。
尽管如此,一个语法谓词 可以被用来指定何种任意的超前预测分析是需要的: class
PascalextendsParser; prog: INT(RANGEINT {System.out.println("INT..INT");} |EOF{System.out.println("plainoldINT");} )|REAL{System.out.println("tokenREAL");}; classLexPascalextendsLexer; WS:||| ; ('''\t''\n''\r')+{$setType(Token.SKIP);} protectedINT:('0'..'9')+ ; protectedREAL:INT'.'INT ; RANGE:".."; RANGE_OR_INT :(INT"..")=>INT{$setType(INT);} |(INT'.')=>REAL{$setType(REAL);} |INT {$setType(INT);} ; ANTLR词法分析规则甚至能够处理FORTRAN的赋值语句以及其它复杂的词法结构。
考虑下面 的DO循环: DO100I=1,10如果中间的逗号替换成句点,循环语句将成为一个对一个称为"DO100I"的超乎寻常的变量 的赋值语句: DO100I=1.10 下面的规则正确地区别了这两种情况: DO_OR_VAR:(DO_HEADER)=>"DO"{$setType(DO);}|VARIABLE{$setType(VARIABLE);} ;protectedDO_HEADERoptions{ignore=WS;} :"DO"INTVARIABLE'='EXPR','; protectedINT:('0'..'9')+; protectedWS:''; protectedVARIABLE :'A'..'Z'('A'..'Z'|''|'0'..'9')*{/*stripspacefromend*/} ; //justanintorfloatprotectedEXPR :INT('.'(INT)?
)?
; 前面的例子讨论了如何区分词法规则与大量超前预测分析(固定k或任意)。
还有你需要打 开和关闭特定的词法分析规则(使特定记号有效和失效)的其它情形,依赖于前面的上下文 内容或语义信息。
一个最好的例子是匹配一个记号,仅仅当它从一行的左边开始(也就是第 一列)。
如果不能检测词法分析器的列计数器,你就无法很好地完成此项工作。
下面是一个 简单的
DEFINE规则,仅仅当语义谓词为真时才被匹配。
DEFINE:; {getColumn()==1}?
"#define"ID 在单个可供选择的词法规则左边的语义谓词被提升进nextToken的预测机制。
将谓词添加 到一个规则使其不是一个识别的候选项,直至谓词为真。
这种情况下,为DEFINE产生的方 法将永远不会进入,即使当列数大于1时,超前预测分析预测到#define。
另一个有用的例子包括上下文相关识别,比如当你希望仅仅当你的词法分析器在一个特定的 的上下文中时才匹配一个记号(例如,词法分析器先前匹配的一些触发序列)。
如果你正在 匹配分隔数据行的记号,比如"----",你可能仅仅希望当“开始表(begin
table)”序列已 经被找到时才匹配这个记号。
BEGIN_TABLE :'['{this.inTable=true;}//进入表上下文; ROW_SEP:{this.inTable}?
"----"; END_TABLE:']'{this.inTable=false;}//退出表上下文; 这种谓词提升能力是一种对基于DFA的、类似lex的词法分析器生成器的仿真,虽然谓词 更强大。
(你甚至可以根据分析的阶段打开特定的规则)。
:) 2.3关键字和字面值(Keywordsandliterals) 许多语言有一个通用的“标识符”识别词法规则,关键字是标识符模式的特例情况。
一个典型的标识符如下定义: ID:LETTER(LETTER|DIGIT)*; 这通常与关键字相冲突。
ANTLR通过让你把关键字放在一个字面值表中来解决这个问题。
在每个记号被匹配后,会检查字面值表(在词法分析器中通常以hash表来实现),所以字面值能够有效地覆盖更普通的标识符模式。
字面值以下面两种方法中的一种来创建。
首先,任何在语法分析器使用的双引号括起来的字符串自动地政相关词法分析器的字面值表。
其次,通过literal选项(literaloption)的方式在词法分析规则中指定字面值。
另外,testLiterals选项(testLiteralsoption)能够让你精切地控制字面值测试代码的生成。
2.4常见的前缀(Commonprefixes) 通过增加词法分析器超前预测分析的深度,词法分析规则中固定长度的常见前缀能够很好地被处理。
例如,一些来自Java的操作符: classMyLexerextendsLexer;options{ k=4;}GT:">";GE:">=";RSHIFT:">>";RSHIFT_ASSIGN:">>="; UNSIGNED_RSHIFT:">>>";UNSIGNED_RSHIFT_ASSIGN:">>>="; 2.5记号定义文件(Tokendefinitionfiles) 通过记号定义文件的方式,记号定义能够从一种文法被转移到另一文法。
这是通过importVocab和exportVocab选项实现的。
2.6字符类(Characterclasses) 使用~操作符来对一个字符或字符集取反。
例如,为了匹配任何除换行符外的其它字符,下面的规则引用了~'\n'。
SL_COMMENT:"//"(~'\n')*'\n'; ~操作符同样可以被用来对一个字符集取反: NOT_WS:~(''|'\t'|'\n'|'\r'); 范围操作符可以被用来创建一系列的序列字符集合: DIGIT:'0'..'9'; 2.7记号属性(TokenAttributes) 请参考下一章节。
2.8词法超前分析和记号结束符(Lexicallookaheadandtheend-of-tokensymbol) 当分析词法的文法时,一个独特的情况会出现,类似于在分析正则文法时的文件结束符条件。
考虑为分析在如下规则B中的子规则('b'|),你将如何计算超前预测分析集合: classLextendsLexer; A:B'b'; protected//仅仅通过其它lex规则调用B:'x'('b'|) ; 子规则的第一个可供选择的项的超前预测分析很清楚的是‘b’。
第二个可供选择的项为空, 超前预测分析集合是所有能够跟在子规则的引用后面的字符的集合,此子规则是规则B的 follow集合。
这种情况中,字符‘b’跟在B的引用后,所以是空可选项的间接的超前预测 分析集合。
因为‘b’开始于两个可供选择的项,此子规则的分析决策是我们有时说的非确 定或模棱两可的。
ANTLR会正确地产生一个对此规则的警告(除非你使用了 warnWhenFollowAmbig选项)。
现在,如果规则A并不存在,规则B也不是protected(它是一个完整的记号而不是一个“子 记号”),超前预测分析会有什么意义: B: 'x'('b'|); 这种情况中,空的可选择项仅仅查找到规则的结束作为超前预测分析,并且没有其它的规则 引用它。
更糟糕的情况中,任何字符可以跟在此规则后(也就是,下一记号或错误序列的开 始)。
所以那么空的可选择项的超前预测分析就不应该整个字符词汇表?以及这不应该产生 一个非确定性的警告,因为它肯定与‘b’可选项冲突?从概念上来说,两个问题的答案都 是肯定的。
尽管如此,从一个实际的立场来说,你会很清楚地说:“嗯,在记号
B的结束处 匹配‘b’,如果你找到一个的话。
”我讨论过不应该产生警告,ANTLR匹配元素的策略会尽 快做到这点。
另外一个不把超前预测分析表现为整个词汇表的原因是,'\u0000'..'\uFFFF'的词汇表实在太 庞大了(一个2的16次方再除以32个长字的内存集合)。
任何在其超前预测分析集合中含 '<标记结束符(end-of-token)>'的可选择项将被代码生成器压入ELSE或DEFAULT从句中, 因此庞大的位集可以避免。
总结是单纯由遇到词法分析规则结束而得到的超前预测分析不能是导致非确定的一个原因。
下表总结了一系列的情况,有助于帮助你弄明白何时ANTLR将抱怨,何时不会。
X:'q'('a')?
('a')?
第一个子规则是不确定的,因为第二个子规则(标记结束符)里的 ; 'a'在退出分支(...)?
的超前预测分析中。
X:'q'('a')?
('c')?
; 确定的。
Y:'y'X'b'; protectedX:'b'|; 规则X中存在非确定性。
X:'x' 没有非确定性,因为循环的退出分支查看单纯根据标记结束符计算 ('a'|'c'|'d')+|'z'得到的超前预测分析。
('a')+; Y:'y'('a')+('a')?
(...)+中的'a'和退出分支之间存在非确定性,因为退出时能够看到可 ; 选子规则的'a'。
即使('a')?
简单地是'a',这也将是一个问题。
(...)*会 产生相同的问题。
X:
'y'('a''b')+'a''c' ; 在k=1时,对来说(...)?
,这是一个非确定的,因为'a'能够预测继续循环和退出循环。
在k=2时,没有非确定性。
Q:'q'('a'|)?
; 这里,在一个可选的子规则中存在一个空的可供选择的项。
会报告存在一个非确定性,因为两条路径都可能预测标记结束符。
你也许想知道为什么下面的第一个子规则是模棱两可的: ('a')?
('a')?
答案是NFA到DFA的转换会导致含‘a’的转移的一个DFA合并到一个单独的状态转移中去。
对一个除了在一个完整的匹配后,你不能有动作(action)的DFA来说,这样没问题。
记住ANTLR允许你如下使用规则: ('a'{do-this})?
('a'{do-that})?
另外还有一件其它的事情知道很重要。
在词法分析规则中的可选项的重新调用会根据它们超前预测分析的要求重新排序,从最高到最低。
A:'a'|'a''b'; 在k=2时,ANTLR可以看到第一个可选项的‘a’后面跟着‘<标记结束符(end-of-token)>’,以及第二个可选项的‘a’后面跟着‘b’。
对第一个可选项深度为2的超前预测分析是‘<标记结束符(end-of-token)>’并抑制了一个警告,深度为2能够匹配第一个可选项的任意字符。
当没有警告产生时,为了行为自然和生成好的代码,ANTLR对可选项重新排序,所以生成的代码类似如下代码: A(){} if(LA
(1)=='a'&&LA
(2)=='b'){//可选项2match('a');match('b'); }elseif(LA
(1)=='a'){//可选项1 match('a')}else{error;} 注意可选项1的深度为2的超前预测分析的缺失。
当出现一个空的可选项时,ANTLR将其移到末尾。
例如:
A : 'a' | | 'a''b' ; 产生的类似如下的代码: A(){} if(LA
(1)=='a'&&LA
(2)=='b'){//alt2match('a');match('b'); }elseif(LA
(1)=='a'){//alt1 match('a')}else{} 注意这里无法出现词法分析错误(这样做有意义,因为此规则是可选的——虽然这个规则仅仅当是protected时有意义)。
当可选项根据超前预测分析的深度排序时,语义谓词会与其相关的可选项一起移动。
如果一个{true}?
谓词(隐示地存在于每一个可选项)的增加改变了词法分析器识别的内容,这会很诡异。
下列规则被重新排序,所以可选项2首先被检测。
B:{true}?
'a'|'a''b'; 语法谓词不会被重新排序。
说起规则后的谓词,它与结果在不明确性上存在冲突,比如此条规则中: F:'c'|('c')=>'c'; 尽管如此,其它的可选项会关于语法谓词重新排序,即使为LL
(1)组件生成了switch语句并语法谓词被压入default语句中。
下面的规则解释了这点。
F:'b'|{/*empty-path*/}|('c')=>'c'|'c'|'d'|'e'; 规则F的决策会生成为如下所示: switch(la_1){case'b':{ match('b');break; }case'd':{ match('d');break;}case'e':{match('e');break;}default:booleansynPredMatched15=false;if(((la_1=='c'))){ int_m15=mark();synPredMatched15=true;guessing++;try{ match('c');}catch(RecognitionExceptionpe){ synPredMatched15=false;}rewind(_m15);guessing--;}if(synPredMatched15){match('c');}elseif((la_1=='c')){match('c');}else{if(guessing==0){ /*empty-path*/}}} 注意在检测‘c’可选项后,空路径是如何被移动的? 2.9扫描二进制文件(ScanningBinaryFiles) 字符常量并不限于可打印的ASCII字符。
为了说明这个概念,假如你想解析一个包含字符串和短整型整数的二进制文件。
为了区分它们,根据下列格式使用了的标记字节: 格式'\0'高位低位'\1'非'\2'的字符串字符'\2' 描述短整型字符串 简单的输入(274后面接着是“atest”)可能如下十六进制所示(UNIX命令od–h的输出): 00000000000001120161207465737402 或者以字符形式查看: 0000000000\0001022001a test002 语法分析器,很一般地,仅仅就是一个关于两种输入标记类型的(...)+: classDataParserextendsParser; file:; (sh:SHORT{System.out.println(sh.getText());} |st:STRING{System.out.println("\""+st.getText()+"\"");} )+ 所有有趣的事情发生在词法分析器中。
首先,定义类并且设置词汇表为所有的8位二进制值: classDataLexerextendsLexer;options{ charVocabulary='\u0000'..'\u00FF';} 然后,根据说明定义两个标记,字符串带有多个标记字节,短整型前有一个标记字节: SHORT: ; //matchthemarkerfollowedbyany2bytes'\0'high:.lo:.{//packthebytesintoatwo-byteshortintv=(((int)high)<<8)+lo;//makeastringoutofthevalue$setText(""+v);} STRING: ; '\1'!
//beginstring(discard)(~'\2')*'\2'!
//endstring(discard) 为了调用语法分析器,使用如下类似的程序: importjava.io.*; classMain{publicstaticvoidmain(String[]args){try{//useDataInputStreamtograbbytesDataLexerlexer=newDataLexer(newDataInputStream(System.in));DataParserparser=newDataParser(lexer);parser.file();}catch(Exceptione){System.err.println("exception:"+e);}} } Version:$Id://depot/.antlr/release/antlr-2.7.6/doc/lexer.html#1$ 第3章ANTLR的树分析器 曾经的SORCERER 在ANTLR2。
xx版本中,只要增加一些树操作符,就可以帮助你建立一种中间形式的树结构(抽象语法树)来重写语法规则和语义动作(action)。
ANTLR同样允许你去指定AST树的文法结构,因此,可以通过操作或简单遍历树结点的方式来进行文法翻译。
以前,树分析器用一个单独的工具SORCERER来生成,但是ANTLR已经取代了它的功能。
ANTLR现在可以为字符流,记号流,以及树结点来建立识别器。
3.1什么是树分析器? 分析是将语法结构应用于输入的记号流的过程。
ANTLR在这方面比大多数工具考虑的都要深,它把一颗树看作是二维的结点流。
实际上,在ANTLR中,对记号流进行分析和对树的进行分析生成的代码生成过程来说,真正仅有的区别就变成了对超前扫描,规则方法定义头部的检测,以及对二维树结构代码生成模板的指定上。
3.2可以分析什么类型的树? ANTLR树分析器可以遍历实现了AST接口的任何树。
AST接口是一种基于类似儿子-兄弟结点的树通用结构,有如下重要的制导方法: getFirstChild:返回第一个子结点的引用。
getNextSibling:返回下一个兄弟结点的引用。
每一个AST结点有一个子女列表,一些文本和一个"记号类型"。
每个树的结点都是一棵树,因此我们说树是自相似的(也即树是递归定义的:译者注)。
AST接口的完整定义如下:/**最小AST结点接口用于ANTLR的AST成生和树遍历 */publicinterfaceAST{ /**添加一个子结点到最右边*/publicvoidaddChild(ASTc);publicbooleanequals(ASTt);publicbooleanequalsList(ASTt);publicbooleanequalsListPartial(ASTt);publicbooleanequalsTree(ASTt);publicbooleanequalsTreePartial(ASTt);publicASTEnumerationfindAll(ASTtree);publicASTEnumerationfindAllPartial(ASTsubtree);/**得到第一个子结点;如果没有子结点则返回null*/publicASTgetFirstChild(); /**得到本结点的下一个兄弟结点*/publicASTgetNextSibling();/**得到本结点的记号文本*/publicStringgetText();/**得到本结点的记号类型*/publicintgetType();/**得到本结点的子结点总数;如果是叶子结点,返回0*/publicintgetNumberOfChildren();publicvoidinitialize(intt,Stringtxt);publicvoidinitialize(ASTt);publicvoidinitialize(Tokent);/**设置第一个子结点。
*/publicvoidsetFirstChild(ASTc);/**设置下一个兄弟结点。
*/publicvoidsetNextSibling(ASTn);/**设置本结点的记号文本*/publicvoidsetText(Stringtext);/**设置本结点的记号类型*/publicvoidsetType(intttype);publicStringtoString();publicStringtoStringList();publicStringtoStringTree();} 3.3树的语法规则 正如PCCTS1。
33的SORCERER工具和ANTLR记号语法中所看到的,树语法是一个嵌入语义动作(action),语义断言和句法断言的EBNF规则的集合。
规则:| 可选产生式1可选产生式
2 ...|; 可选产生式n 每一个可选的产生式都是由一个元素列表所组成,列表中的元素是加入了树模式的ANTLR正则表达式语法中的项,有如下的形式: #(根结点子结点1子结点2...子结点n) 例如:下列的树模式匹配一个以PLUS为根结点,并有两个INT子结点的简单树结构: #(PLUSINTINT) 树模式的根结点必须是一个记号引用,但是子结点元素不限于此,它甚至可以是子规则。
例如,一种常见结构是if-then-else树结构,其中的else子句声明的子树是可选的: #(IFexprstat(stat)?
) 值得一提的是,当指定树模式和树语法后,通常,会进行满足条件的匹配而不是精确的匹配。
一旦树满足给定的模式,不管剩下多少没有分析,都会报告一次匹配。
例如,#(AB),对于像#(A#(BC)D)这样有相同结构的树,不管有多长,都会报告一次匹配。
3.4句法断言 ANTLR树分析器在工作时仅使用一个单独的超前扫描记号,这在通常情况下不是一个问题,因为这种中间形式被明确设计成利于遍历的结构。
然而,偶尔也需要区别出相似的树结构。
句法断言就是被用来克服有限确定的超前扫描所带来的限制。
例如:在区分一元和二元减号时,可以为每一种类型的减号都创建不同记号的操作结点,但赋与相同的根结点,这样的处理方法可以工作的很好。
使用句法断言可以区分以下结构: expr:(#(MINUSexprexpr))=>#(MINUSexprexpr)|#(MINUSexpr) ...; 赋值的次序很重要,因为第二个可选产生式是第一个可选产生式的“子集”。
3.5语义断言 在可选产生式开始部分的语义断言,只是简单地与可选断言表达式合成一体,就像合成正则文法一样。
产生式中间的语义断言,当失败时,也会像正则文法一样抛出异常。
3.6一个树遍历器的例子 考虑一下如何去建立一个简单的计算器。
一个方法是建立一个分析器,识别输入并计算表达式的值。
为了说明这种方法,我们将会建立一个分析器来为输入的表达式创建一棵树,并把表达式以这种中间形式表示,然后树分析器遍历这个中间表达式,并计算出结果。
我们的识别器,CalcParser,通过如下的代码来定义: classCalcParserextendsParser;options{ buildAST=true;////默认使用CommonAST}expr:mexpr(PLUS^mexpr)*SEMI!
;mexpr :atom(STAR^atom)*;atom:INT; PLUS和STAR记号是操作符,因此把它们作为子树的根结点,在它们后面注释上字符'^'。
SEMI记号后缀有字符'!
',表明它不应该被加入到树中。
这个计算器的词法分析器定义如下: classCalcLexerextendsLexer; WS: ('' | '\t' | '\n' | ;LPAREN: ;RPAREN: ;STAR: ;PLUS: ;SEMI: ;INT ; '\r'){_ttype=Token。
SKIP;} '(' ')' '*' '+' ';' : ('0'..'9')+ 识别器生成的树是一棵简单的表达式树。
例如,输入"3*4+5"将产生形如#(+(*34)5)的树。
为了给这种形式的树建立树遍历器,你必须要为ANTLR递归地描述树的结构: classCalcTreeWalkerextendsTreeParser; expr: #(PLUSexprexpr)//PLUS为根结点,两个expr分别为左右子结点 | #(STARexprexpr) | INT ; 一旦指定了结构,你就可以嵌入语义动作(action)来计算正确的结果。
一个简单的实现办法就是使expr规则返回一个整型的值,然后让每一条可选产生式来计算每个子树的值。
下面的树文法和动作(action)达到了我们期望的效果: classCalcTreeWalkerextendsTreeParser;exprreturns[intr]{ inta,b; r=0; } : #(PLUSa=exprb=expr){r=a+b;} | #(STARa=exprb=expr){r=a*b;} | i:INT {r=Integer。
parseInt(i。
getText());} ; 注意到当计算表达式值得时候,没有必要指定优先级,因为它已经隐含在树的结构中了。
这也解析了为什么以中间树形式的表示比以树的形式复制输入的表示要重要。
输入的记号确实作为结点储存在树结构中,而且这种结构隐含了结点之间的关系。
要想执行分析器和树遍历器,还需要以下的代码: importjava.io.*;importANTLR.CommonAST;importANTLR.collections.AST; classCalc{publicstaticvoidmain(String[]args){try{CalcLexerlexer=newCalcLexer(newDataInputStream(System。
in));CalcParserparser=newCalcParser(lexer);//分析输入的表达式Parser.expr();CommonASTt=(CommonAST)parser.getAST();//以LISP记号的形式输出树System.out.println(t.toStringList());CalcTreeWalkerwalker=newCalcTreeWalker();//遍历由分析器建立的树intr=walker.expr(t); System.Out.println("valueis"+r);}catch(Exceptione){ System.err.println("exception:"+e);}}} 3.7翻译 树分析器对检查树或者从一棵树产生输出来说是非常有用,但必须要为它们添加处理树转换的代码。
就像正则分析器一样,ANTLR树分析器支持buildAST选项,这类似于SORCERER的翻译模式。
不需要程序员的参与,树分析器自动把输入树拷贝到结果的树中。
每一个规则都隐含(自动定义的)一颗结果树。
通过getAST方法,我们可以从树分析器中获得此树的开始记号。
在一些可选产生式和文法元素后面注释上"!
",将意味着不被自动输出到输出树。
部分或全部子树都可以被重写。
嵌入到规则中的语义动作(action)可以根据测试和树结构来对结果树进行设置。
参考文法动作(action)翻译章节。
3.8一个树翻译的例子 再来看一下上面提到的简单计算器的例子,我们可以执行树翻译来代替计算表达式的值。
下面树文法中的动作(action)优化了加法的恒等运算(加0)。
classCalcTreeWalkerextendsTreeParser;options{ buildAST=true;//"翻译"模式} expr:!
#(PLUSleft:exprright:expr)//'!
'关闭自动翻译{ //x+0=xif(#right.getType()==INT&& Integer.parseInt(#right.getText())==0){ #expr=#left;}//0+x=xelseif(#left.getType()==INT&& Integer.parseInt(#left.getText())==0){ #expr=#right;}//x+yelse{ #expr=#(PLUS,left,right);}}|#(STARexprexpr)//使用自动翻译|i:INT; 执行分析

标签: #转换成 #打不开 #三维图 #文件 #怎么改 #上有 #缓慢 #面积