“Grammars.md”的版本间差异
第1行: | 第1行: | ||
{{MARKDOWN}} | {{MARKDOWN}} | ||
#语法结构 | # Grammar Structure 语法结构 | ||
Grammar文件本质上是语法声明,后跟Rule列表,具有一般形式: | |||
``` | ``` | ||
第14行: | 第14行: | ||
@actionName {...} | @actionName {...} | ||
rule1 // | rule1 //parser和lexer rule,可能混合在一起 | ||
... | ... | ||
ruleN | ruleN | ||
``` | ``` | ||
包含grammar`X`的文件名必须称为`X.g4`。 您可以按任意顺序指定options、imports、token规范和actions。 options、imports和token规格最多可以有一个。 除header ①外,所有这些元素都是可选的,至少有一条Rule。 Rule采用基本形式: | |||
``` | ``` | ||
第25行: | 第25行: | ||
``` | ``` | ||
Parser rule名称必须以小写字母开头,而lexer rule必须以大写字母开头。 | |||
在`grammar`头上没有前缀的grammar是组合combined grammars,可以包含lexical和parser rule。 要创建只允许parser rules的parser grammar,请使用以下头。 | |||
``` | ``` | ||
第34行: | 第34行: | ||
``` | ``` | ||
而且,对应的,纯lexer grammar看起来像这样: | |||
``` | ``` | ||
第41行: | 第41行: | ||
``` | ``` | ||
只有lexer语法可以包含 | 只有lexer语法可以包含`mode`规范。 | ||
只有lexer grammars可以包含自定义channels规范 | |||
``` | ``` | ||
第52行: | 第52行: | ||
``` | ``` | ||
然后可以像lexer rule中的枚举一样使用这些channels: | |||
``` | ``` | ||
第58行: | 第58行: | ||
``` | ``` | ||
第15.5节,[Lexer Rules](http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference)和第15.3节, [Parser Rules](http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference) 包含有关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语句( | 可以将`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' 的效果。 | |||
<img src=images/combined.png width=400> | <img src=images/combined.png width=400> | ||
`MyELang`继承规则 | `MyELang`继承规则`stat`、`WS`和`ID`,但重写rule`expr`并添加`INT`。 下面的示例构建和测试运行表明,`MyELang`可以识别整数表达式,而原始的`ELang`不能。 第三个错误的输入语句会触发一条错误消息,该消息还表明解析器正在寻找 `MyElang` 的expr而不是 `Elang`。 | ||
``` | ``` | ||
第83行: | 第83行: | ||
``` | ``` | ||
如果主语法或任何导入的语法中存在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`的顺序检查语法。 | ||
<img src=images/nested.png width=350> | <img src=images/nested.png width=350> | ||
“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 Section (Tokens分段) | ||
`tokens`分段的目的是定义没有相关lexical Rule的grammar所需的token types。基本语法是: | |||
``` | ``` | ||
第109行: | 第109行: | ||
``` | ``` | ||
大多数情况下,tokens section用于定义语法中的actions所需的token类型,如第10.3节所示。 [Recognizing Languages whose Keywords Aren’t Fixed](http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference): | |||
``` | ``` | ||
第115行: | 第115行: | ||
tokens { BEGIN, END, IF, THEN, WHILE } | tokens { BEGIN, END, IF, THEN, WHILE } | ||
@lexer::members { // | @lexer::members { // lexer中用于分配token类型的关键字映射 | ||
Map<String,Integer> keywords = new HashMap<String,Integer>() {{ | Map<String,Integer> keywords = new HashMap<String,Integer>() {{ | ||
put("begin", KeywordsParser.BEGIN); | put("begin", KeywordsParser.BEGIN); | ||
第124行: | 第124行: | ||
``` | ``` | ||
`tokens`section实际上只是定义了一组要添加到整个集合中的Token。 | |||
``` | ``` | ||
第142行: | 第142行: | ||
##语法层面的Action | ##语法层面的Action | ||
当前,在语法Rule之外仅使用两个定义的named actions (用于Java目标): `header` 和 `members`。 前者在识别器类定义之前将代码注入生成的识别器类文件,后者将代码作为字段和方法注入识别器类定义。 | |||
对于combined grammars,ANTLR将操作注入parser和lexer。 要将action限制为生成的parser或lexer,请使用 `@parser::name` 或 `@lexer::name`。 | |||
下面是一个语法为生成的代码指定包的示例: | 下面是一个语法为生成的代码指定包的示例: | ||
第185行: | 第185行: | ||
``` | ``` | ||
Java编译器希望包 | Java编译器希望包 `foo` 中的类位于目录 `foo` 中。 |
2022年2月23日 (三) 02:58的最新版本
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
中。