“Grammars.md”的版本间差异

来自osdev
跳到导航 跳到搜索
 
第1行: 第1行:
{{MARKDOWN}}
{{MARKDOWN}}
#语法结构
# Grammar Structure 语法结构


语法本质上是语法声明,后跟规则列表,但具有一般形式:
Grammar文件本质上是语法声明,后跟Rule列表,具有一般形式:


```
```
第14行: 第14行:
@actionName {...}
@actionName {...}
   
   
rule1 // parser and lexer rules, possibly intermingled
rule1 //parser和lexer rule,可能混合在一起
...
...
ruleN
ruleN
```
```


包含语法“X”的文件名必须称为“X.g4”。 您可以按任意顺序指定选项、导入、token规范和actions。 选项、导入和token规格最多可以有一个。 除标题外,所有这些元素都是可选的① 至少有一条Rule。 Rule采用基本形式:
包含grammar`X`的文件名必须称为`X.g4`。 您可以按任意顺序指定options、imports、token规范和actions。 options、imports和token规格最多可以有一个。 除header ①外,所有这些元素都是可选的,至少有一条Rule。 Rule采用基本形式:


```
```
第25行: 第25行:
```
```


解析器规则名称必须以小写字母开头,而lexer规则必须以大写字母开头。
Parser rule名称必须以小写字母开头,而lexer rule必须以大写字母开头。


在“grammar”头上没有前缀的语法是组合语法,可以包含词法规则和语法分析器规则。 要创建只允许解析器规则的解析器语法,请使用以下标头。
在`grammar`头上没有前缀的grammar是组合combined grammars,可以包含lexical和parser rule。 要创建只允许parser rules的parser grammar,请使用以下头。


```
```
第34行: 第34行:
```
```


而且,自然地,纯词法语法看起来像这样:
而且,对应的,纯lexer grammar看起来像这样:


```
```
第41行: 第41行:
```
```


只有lexer语法可以包含'mode'规范。
只有lexer语法可以包含`mode`规范。


只有词法分析器语法可以包含自定义通道规范
只有lexer grammars可以包含自定义channels规范


```
```
第52行: 第52行:
```
```


然后可以像lexer规则中的枚举一样使用这些通道:
然后可以像lexer rule中的枚举一样使用这些channels:


```
```
第58行: 第58行:
```
```


Sections 15.5, [Lexer Rules](http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference) and Section 15.3, [Parser Rules](http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference) 包含有关规则语法的详细信息。 15.8节,选项描述了语法选项,15.4节,操作和属性提供了有关语法级操作的信息。
第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”允许您将语法分解为逻辑和可重用的块,正如我们在[Importing Grammars]中看到的那样(http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference). ANTLR对待导入的语法非常类似于面向对象的编程语言对待超类。 语法继承了导入语法中的所有规则,令牌规范和命名操作。 “主语法”中的规则覆盖导入语法中的规则以实现继承。
语法`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语句(它不包含已经定义的规则)。 所有导入的结果都是单一的组合语法; ANTLR代码生成器看到了完整的语法,并且不知道有导入的语法。
可以将`import`看作是一条智能的include语句(导入是会主动不包含已经定义的Rule)。 所有导入的结果都合并成单一的combined grammar; ANTLR代码生成器看到了完整的grammar,并且不知道有导入的grammar。


为了处理主语法,ANTLR工具将所有导入的语法加载到从属语法对象中。 然后,它将规则、令牌类型和命名操作从导入的语法合并到主语法中。 在下图中,右侧的语法说明了语法 'MyELang' 导入语法 'ELang' 的效果。
为了处理主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`继承规则'stat''WS''ID',但重写规则'expr'并添加'INT'。 下面的示例构建和测试运行表明,`MyELang`可以识别整数表达式,而原始的`ELang`不能。 第三个错误的输入语句会触发一条错误消息,该消息还表明解析器正在寻找 'myelang' 的expr而不是 'elang'
`MyELang`继承规则`stat``WS``ID`,但重写rule`expr`并添加`INT`。 下面的示例构建和测试运行表明,`MyELang`可以识别整数表达式,而原始的`ELang`不能。 第三个错误的输入语句会触发一条错误消息,该消息还表明解析器正在寻找 `MyElang` 的expr而不是 `Elang`


```
```
第83行: 第83行:
```
```


如果主语法或任何导入的语法中存在模式,那么导入过程将导入这些模式,并在不覆盖它们的地方合并它们的规则。 如果任何模式变为空,因为其所有规则都已被该模式外的规则覆盖,则该模式将被丢弃。
如果主语法或任何导入的语法中存在modes,那么导入过程将导入这些modes,并在它们不是覆盖的时候合并它们的Rules。 如果有任何因为其所有Rule都已被该mode外的Rule覆盖,该mode变为空, 则该Mode将被丢弃。


如果有任何 “令牌” 规范,则主要语法将合并令牌集。 如果有任何“通道”规范,主语法将合并通道集。 任何命名的操作(如`@embers`)都将被合并。 通常,您应该避免在导入语法中的规则中指定操作和操作,因为这限制了它们的重用。 ANTLR也会忽略导入语法中的任何选项。
如果有任何 `tokens` specifications,则主要语法将合并token集。 如果有任何`channel` specifications,主语法将合并channel集。 任何已命名的actions(如`@embers`)都将被合并。 通常,您应该避免在导入语法中避免named actions和actions的rules,因为这限制了它们的重用。 ANTLR也会忽略导入语法中的任何options。


导入的语法也可以导入其他语法。 ANTLR以深度优先的方式追求所有进口语法。 如果两个或多个导入的语法定义了规则“r”,ANTLR将选择它找到的“r”的第一个版本。 在下图中,ANTLR按照`嵌套`、`G1`、`G3`、`G2`的顺序检查语法。
导入的语法也可以导入其他语法。 ANTLR以深度优先的方式寻找所有导入语法。 如果两个或多个导入的语法定义了rule `r`,ANTLR将选择它找到的“r”的第一个版本。(译者注:从根开始遇到的第一个) 在下图中,ANTLR按照`Nested`、`G1`、`G3`、`G2`的顺序检查语法。


<img src=images/nested.png width=350>
<img src=images/nested.png width=350>


“嵌套” 包括来自 “G3” “r” 规则,因为它在 “G2” 中的 “r” 之前看到该版本。
“Nested” 包括来自 `G3` rule `r` ,因为它在`G2` 中的 `r` 之前看到该版本。


并非每种语法都能导入其他语法:
并非每种语法都能导入其他语法:


*词法分析器语法可以导入词法分析器,包括包含模式的词法分析器。
* Lexer grammars可以导入lexers,包括包含modes的lexers。
* 解析器可以导入解析器。
* Parsers可以导入parsers。
*组合语法可以导入没有模式的解析器或词法分析器。
*Combined grammar可以导入没有modes的parsers或lexers。


ANTLR在主要词法分析器语法中将导入的规则添加到规则列表的末尾。 这意味着主语法中的词法规则优先于导入规则。 例如,如果主语法定义了规则'if:'if';'导入的语法定义了规则“ID:[a-z]+;”(它还识别'if'),导入的'ID'不会隐藏主语法的'if'标记定义。
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”部分的目的是定义没有相关词汇规则的语法所需的标记类型。基本语法是:
`tokens`分段的目的是定义没有相关lexical Rule的grammar所需的token types。基本语法是:


```
```
第109行: 第109行:
```
```


大多数情况下,令牌部分用于定义语法中的操作所需的令牌类型,如第10.3节所示。 [Recognizing Languages whose Keywords Aren’t Fixed](http://pragprog.com/book/tpantlr2/the-definitive-antlr-4-reference):
大多数情况下,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中用于分配令牌类型的关键字映射
@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行:
```
```


The `tokens` section really just defines a set of tokens to add to the overall set.
`tokens`section实际上只是定义了一组要添加到整个集合中的Token。


```
```
第142行: 第142行:
##语法层面的Action
##语法层面的Action


当前,在语法规则之外仅使用两个定义的命名操作 (用于Java目标): “头” “成员”。 前者在识别器类定义之前将代码注入生成的识别器类文件,后者将代码作为字段和方法注入识别器类定义。
当前,在语法Rule之外仅使用两个定义的named actions (用于Java目标): `header` `members`。 前者在识别器类定义之前将代码注入生成的识别器类文件,后者将代码作为字段和方法注入识别器类定义。


对于组合语法,ANTLR将操作注入解析器和词法分析器。 要将操作限制为生成的解析器或词法程序,请使用 “@ parser:: name” 或“ @ lexer:: name”。
对于combined grammars,ANTLR将操作注入parser和lexer。 要将action限制为生成的parser或lexer,请使用 `@parser::name` 或 `@lexer::name`。


下面是一个语法为生成的代码指定包的示例:
下面是一个语法为生成的代码指定包的示例:
第185行: 第185行:
```
```


Java编译器希望包 “foo” 中的类位于目录 “foo” 中。
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继承规则statWSID,但重写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按照NestedG1G3G2的顺序检查语法。

“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的IFToken定义。

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);
    ...
}};
}

tokenssection实际上只是定义了一组要添加到整个集合中的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目标): headermembers。 前者在识别器类定义之前将代码注入生成的识别器类文件,后者将代码作为字段和方法注入识别器类定义。

对于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 中。