“Grammars.md”的版本间差异

来自osdev
跳到导航 跳到搜索
(创建页面,内容为“#语法结构 语法本质上是语法声明,后跟规则列表,但具有一般形式: ``` →‎* 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。 选项、导入和…”
 
第1行: 第1行:
{{MARKDOWN}}
#语法结构
#语法结构



2022年2月22日 (二) 08:48的版本

语法结构

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

/** 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按照嵌套G1G3G2的顺序检查语法。

“嵌套” 包括来自 “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” 中。