翻译:原文
语法本质上是一个语法声明,后面是规则列表,但具有以下一般形式:
/** Optional javadoc style comment */
grammar Name; ①
options {...}
import ... ;
tokens {...}
channels {...} // lexer only
@actionName {...}
rule1 // parser and lexer rules, possibly intermingled
...
ruleN
包含语法X的文件名必须称为X.g4。您可以按任何顺序指定选项,导入,令牌规范和操作。选项,导入和令牌规范中最多可以有一个。所有这些元素都是可选的,但标题①和至少一个规则除外。规则采用基本形式:
ruleName : alternative1 | ... | alternativeN ;
解析器规则名称必须以小写字母开头,而词法分析器规则必须以大写字母开头。
在语法标头上没有前缀定义的语法是可以包含词汇规则和解析器规则的组合语法。要创建仅允许解析器规则的解析器语法,请使用以下标头。
parser grammar Name;
...
而且,自然的,纯词法语法看起来像这样:
lexer grammar Name;
...
只有词法分析器语法可以包含mode specifications。
只有词法分析器语法可以包含自定义channels specifications。
channels {
WHITESPACE_CHANNEL,
COMMENTS_CHANNEL
}
然后可以将这些通道像词法分析器规则中的枚举一样使用:
WS : [ \r\t\n]+ -> channel(WHITESPACE_CHANNEL) ;
第15.5节“ Lexer规则”和第15.3节“解析器规则”包含有关规则语法的详细信息。第15.8节“选项”描述了语法选项,而第15.4节“actions和attributes”提供了有关grammar-level动作的信息。
Grammar Imports
语法imports使您可以将语法分为逻辑块和可重用块,如在导入语法中所见。 ANTLR对待导入的语法非常类似于面向对象的编程语言对待超类。语法从导入的语法继承所有规则,标记规范和命名操作。 “主语法”中的规则会覆盖导入语法中的规则以实现继承。
将导入更像是一个聪明的include语句(其中不包含已经定义的规则)。所有导入的结果是一个单一的组合语法; ANTLR代码生成器看到了完整的语法,却不知道有导入的语法。
要处理主语法,ANTLR工具会将所有导入的语法加载到从属语法对象中。然后,它将规则,标记类型和命名操作从导入的语法合并到主语法中。在下图中,右侧语法说明了MyELang语法导入ELang语法的效果。
MyELang继承规则stat,WS和ID,但覆盖规则expr并添加INT。这是一个示例构建和测试运行,显示MyELang可以识别整数表达式,而原始ELang无法识别。第三个错误的输入语句触发一条错误消息,该错误消息还表明解析器正在寻找MyELang的expr而非ELang的表达式。
$ antlr4 MyELang.g4
$ javac MyELang*.java
$ grun MyELang stat
=> 34;
=> a;
=> ;
=> EOF
<= line 3:0 extraneous input ';' expecting {INT, ID}
如果主语法或任何导入的语法中存在模式,则导入过程将导入这些模式并在不覆盖它们的情况下合并其规则。如果任何模式变为空,因为其所有规则都已被该模式之外的规则覆盖,则该模式将被丢弃。
如果有任何令牌规范,则主要语法将合并令牌集。如果有任何通道规范,则主要语法将合并通道集。任何已命名的动作(例如@members)都将被合并。通常,应避免在导入的语法中使用命名动作和规则中的动作,因为这样做会限制重用。 ANTLR还忽略导入语法中的任何选项。
导入的语法也可以导入其他语法。 ANTLR以深度优先的方式学习所有导入的语法。如果两个或多个导入的语法定义了规则r,则ANTLR将选择找到的r的第一个版本。在下图中,ANTLR按照嵌套,G1,G3,G2的顺序检查语法。
嵌套包含来自G3的r规则,因为它可以在G2中的r之前看到该版本。
并非每种语法都可以导入其他每种语法:
词法分析器语法可以导入词法分析器,包括包含模式的词法分析器。
解析器可以导入解析器。
组合语法可以导入没有模式的解析器或词法分析器。
ANTLR在主词法语法中将导入的规则添加到规则列表的末尾。这意味着主语法中的词法分析器规则优先于导入的规则。例如,如果主语法定义了规则IF:'if';导入的语法定义了规则ID:[a-z] +; (也可以识别),导入的ID不会隐藏主语法的IF token定义。
Tokens Section
tokens section的目的是定义没有相关词汇规则的语法所需的标记类型。基本语法为:
tokens { Token1, ..., TokenN }
大多数时候,tokens section用于定义语法中的动作所需的tokens section,如第10.3节“识别关键字未固定的语言”中所示:
// explicitly define keyword token types to avoid implicit definition warnings
tokens { BEGIN, END, IF, THEN, WHILE }
@lexer::members { // keywords map used in lexer to assign token types
Map<String,Integer> keywords = new HashMap<String,Integer>() {{
put("begin", KeywordsParser.BEGIN);
put("end", KeywordsParser.END);
...
}};
}
tokens section实际上只是定义了一组token,以添加到整个集合中。
$ cat Tok.g4
grammar Tok;
tokens { A, B, C }
a : X ;
$ antlr4 Tok.g4
warning(125): Tok.g4:3:4: implicit definition of token X in parser
$ cat Tok.tokens
A=1
B=2
C=3
X=4
Actions at the Grammar Level
当前,在语法规则之外仅使用了两个已定义的命名操作(用于Java目标):header和members。前者将代码注入到识别器类定义之前的生成的识别器类文件中,后者将代码作为字段和方法注入到识别器类定义中。
对于组合语法,ANTLR将动作同时注入解析器和词法分析器。要将操作限制为生成的解析器或词法分析器,请使用@parser::name或@lexer::name。
这是一个示例,其中语法为生成的代码指定了一个包:
$ cd foo
$ antlr4 Count.g4 # generates code in the current directory (foo)
$ ls
Count.g4 CountLexer.java CountParser.java
Count.tokens CountLexer.tokens
CountBaseListener.java CountListener.java
$ javac *.java
$ cd ..
$ grun foo.Count list
=> 9, 10, 11
=> EOF
<= 3 ints
Java编译器期望软件包foo中的类位于目录foo中。