Grammars.md

来自osdev
跳到导航 跳到搜索

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