Grammars.md
Grammar Structure 语法结构
Grammar文件本质上是语法声明,后跟Rule列表,具有一般形式:
/** Optional javadoc style comment */
grammar Name; ①
options {...}
import ... ;
tokens {...}
channels {...} // lexer only
@actionName {...}
rule1 //parser和lexer rule,可能混合在一起
...
ruleN
包含grammarX
的文件名必须称为X.g4
。 您可以按任意顺序指定options、imports、token规范和actions。 options、imports和token规格最多可以有一个。 除header ①外,所有这些元素都是可选的,至少有一条Rule。 Rule采用基本形式:
ruleName : alternative1 | ... | alternativeN ;
Parser rule名称必须以小写字母开头,而lexer rule必须以大写字母开头。
在grammar
头上没有前缀的grammar是组合combined grammars,可以包含lexical和parser rule。 要创建只允许parser rules的parser grammar,请使用以下头。
parser grammar Name;
...
而且,对应的,纯lexer grammar看起来像这样:
lexer grammar Name;
...
只有lexer语法可以包含mode
规范。
只有lexer grammars可以包含自定义channels规范
channels {
WHITESPACE_CHANNEL,
COMMENTS_CHANNEL
}
然后可以像lexer rule中的枚举一样使用这些channels:
WS : [ \r\t\n]+ -> channel(WHITESPACE_CHANNEL) ;
第15.5节,Lexer Rules和第15.3节, Parser Rules 包含有关rule语法的详细信息。 15.8节,Options描述了grammar options,15.4节,Actions和Attributes提供了有关grammar-level actions的信息。
Grammar Imports 语法导入
语法imports
允许您将grammar分解为逻辑块和可重用的块,正如我们在[Importing Grammars]中看到的那样(http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference). ANTLR对待导入的grammars非常类似于面向对象的编程语言对待父类。 grammar继承了导入grammar中的所有rules,tokens specifications和已命名的actions。 “main grammar”中的Rules覆盖导入grammars中的Rule以实现继承。
可以将import
看作是一条智能的include语句(导入是会主动不包含已经定义的Rule)。 所有导入的结果都合并成单一的combined grammar; ANTLR代码生成器看到了完整的grammar,并且不知道有导入的grammar。
为了处理主grammar,ANTLR工具将所有导入的grammar加载到subordinate grammar objects中。 然后,它将rules、token types和已命名的actions从导入的grammar合并到主grammar中。 在下图中,右侧的grammar说明了在grammar MyELang
中导入grammar 'ELang' 的效果。
MyELang
继承规则stat
、WS
和ID
,但重写ruleexpr
并添加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}
如果主语法或任何导入的语法中存在modes,那么导入过程将导入这些modes,并在它们不是覆盖的时候合并它们的Rules。 如果有任何因为其所有Rule都已被该mode外的Rule覆盖,该mode变为空, 则该Mode将被丢弃。
如果有任何 tokens
specifications,则主要语法将合并token集。 如果有任何channel
specifications,主语法将合并channel集。 任何已命名的actions(如@embers
)都将被合并。 通常,您应该避免在导入语法中避免named actions和actions的rules,因为这限制了它们的重用。 ANTLR也会忽略导入语法中的任何options。
导入的语法也可以导入其他语法。 ANTLR以深度优先的方式寻找所有导入语法。 如果两个或多个导入的语法定义了rule r
,ANTLR将选择它找到的“r”的第一个版本。(译者注:从根开始遇到的第一个) 在下图中,ANTLR按照Nested
、G1
、G3
、G2
的顺序检查语法。
“Nested” 包括来自 G3
的 rule r
,因为它在G2
中的 r
之前看到该版本。
并非每种语法都能导入其他语法:
- Lexer grammars可以导入lexers,包括包含modes的lexers。
- Parsers可以导入parsers。
- Combined grammar可以导入没有modes的parsers或lexers。
ANTLR在主要lexer grammar中将导入的Rule添加到Rule列表的末尾。 这意味着主grammar中的lexer rules优先于导入rules。 例如,如果主grammar定义了rule IF : ’if’ ;
导入的grammar定义了 rule ID : [a-z]+ ;
(它还识别if
),导入的ID
不会隐藏主grammar的IF
Token定义。
Tokens Section (Tokens分段)
tokens
分段的目的是定义没有相关lexical Rule的grammar所需的token types。基本语法是:
tokens { Token1, ..., TokenN }
大多数情况下,tokens section用于定义语法中的actions所需的token类型,如第10.3节所示。 Recognizing Languages whose Keywords Aren’t Fixed:
// 显式定义关键字token类型,避免隐式定义警告
tokens { BEGIN, END, IF, THEN, WHILE }
@lexer::members { // lexer中用于分配token类型的关键字映射
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
语法层面的Action
当前,在语法Rule之外仅使用两个定义的named actions (用于Java目标): header
和 members
。 前者在识别器类定义之前将代码注入生成的识别器类文件,后者将代码作为字段和方法注入识别器类定义。
对于combined grammars,ANTLR将操作注入parser和lexer。 要将action限制为生成的parser或lexer,请使用 @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
中。