Grammars.md
语法结构
语法本质上是语法声明,后跟规则列表,但具有一般形式:
/** Optional javadoc style comment */
grammar Name; ①
options {...}
import ... ;
tokens {...}
channels {...} // lexer only
@actionName {...}
rule1 // parser and lexer rules, possibly intermingled
...
ruleN
包含语法“X”的文件名必须称为“X.g4”。 您可以按任意顺序指定选项、导入、token规范和actions。 选项、导入和token规格最多可以有一个。 除标题外,所有这些元素都是可选的① 至少有一条Rule。 Rule采用基本形式:
ruleName : alternative1 | ... | alternativeN ;
解析器规则名称必须以小写字母开头,而lexer规则必须以大写字母开头。
在“grammar”头上没有前缀的语法是组合语法,可以包含词法规则和语法分析器规则。 要创建只允许解析器规则的解析器语法,请使用以下标头。
parser grammar Name;
...
而且,自然地,纯词法语法看起来像这样:
lexer grammar Name;
...
只有lexer语法可以包含'mode'规范。
只有词法分析器语法可以包含自定义通道规范
channels {
WHITESPACE_CHANNEL,
COMMENTS_CHANNEL
}
然后可以像lexer规则中的枚举一样使用这些通道:
WS : [ \r\t\n]+ -> channel(WHITESPACE_CHANNEL) ;
Sections 15.5, Lexer Rules and Section 15.3, Parser Rules 包含有关规则语法的详细信息。 15.8节,选项描述了语法选项,15.4节,操作和属性提供了有关语法级操作的信息。
语法导入
语法“imports”允许您将语法分解为逻辑和可重用的块,正如我们在[Importing Grammars]中看到的那样(http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference). ANTLR对待导入的语法非常类似于面向对象的编程语言对待超类。 语法继承了导入语法中的所有规则,令牌规范和命名操作。 “主语法”中的规则覆盖导入语法中的规则以实现继承。
可以将import
看作是一条智能的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}
如果主语法或任何导入的语法中存在模式,那么导入过程将导入这些模式,并在不覆盖它们的地方合并它们的规则。 如果任何模式变为空,因为其所有规则都已被该模式外的规则覆盖,则该模式将被丢弃。
如果有任何 “令牌” 规范,则主要语法将合并令牌集。 如果有任何“通道”规范,主语法将合并通道集。 任何命名的操作(如@embers
)都将被合并。 通常,您应该避免在导入语法中的规则中指定操作和操作,因为这限制了它们的重用。 ANTLR也会忽略导入语法中的任何选项。
导入的语法也可以导入其他语法。 ANTLR以深度优先的方式追求所有进口语法。 如果两个或多个导入的语法定义了规则“r”,ANTLR将选择它找到的“r”的第一个版本。 在下图中,ANTLR按照嵌套
、G1
、G3
、G2
的顺序检查语法。
“嵌套” 包括来自 “G3” 的 “r” 规则,因为它在 “G2” 中的 “r” 之前看到该版本。
并非每种语法都能导入其他语法:
*词法分析器语法可以导入词法分析器,包括包含模式的词法分析器。
- 解析器可以导入解析器。
- 组合语法可以导入没有模式的解析器或词法分析器。
ANTLR在主要词法分析器语法中将导入的规则添加到规则列表的末尾。 这意味着主语法中的词法规则优先于导入规则。 例如,如果主语法定义了规则'if:'if';'导入的语法定义了规则“ID:[a-z]+;”(它还识别'if'),导入的'ID'不会隐藏主语法的'if'标记定义。
Tokens Section
“tokens”部分的目的是定义没有相关词汇规则的语法所需的标记类型。基本语法是:
tokens { Token1, ..., TokenN }
大多数情况下,令牌部分用于定义语法中的操作所需的令牌类型,如第10.3节所示。 Recognizing Languages whose Keywords Aren’t Fixed:
// 显式定义关键字token类型,避免隐式定义警告
tokens { BEGIN, END, IF, THEN, WHILE }
@lexer::members { // lexer中用于分配令牌类型的关键字映射
Map<String,Integer> keywords = new HashMap<String,Integer>() {{
put("begin", KeywordsParser.BEGIN);
put("end", KeywordsParser.END);
...
}};
}
The tokens
section really just defines a set of tokens to add to the overall set.
$ 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
语法层面的Action
当前,在语法规则之外仅使用两个定义的命名操作 (用于Java目标): “头” 和 “成员”。 前者在识别器类定义之前将代码注入生成的识别器类文件,后者将代码作为字段和方法注入识别器类定义。
对于组合语法,ANTLR将操作注入解析器和词法分析器。 要将操作限制为生成的解析器或词法程序,请使用 “@ parser:: name” 或“ @ lexer:: name”。
下面是一个语法为生成的代码指定包的示例:
grammar Count;
@header {
package foo;
}
@members {
int count = 0;
}
list
@after {System.out.println(count+" ints");}
: INT {count++;} (',' INT {count++;} )*
;
INT : [0-9]+ ;
WS : [ \r\t\n]+ -> skip ;
语法本身应该在目录foo
中,以便ANTLR在同一个foo
目录中生成代码(至少在不使用`-o‘ANTLR工具选项时):
$ 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” 中。