ANTLR 4 实践上篇 - grammar

前言

要学习ANTLR4,首先需要知道antlr是干什么的?

ANTLR(全称:ANother Tool for Language Recognition)是目前非常流行的语言识别工具,使用Java语言编写,基于LL(*)解析方式,使用自上而下的递归下降分析方法。通过输入语法描述文件来自动构造自定义语言的词法分析器、语法分析器和树状分析器等各个模块。ANTLR使用上下无关文法描述语言,文法定义使用类似EBNF的方式。

所有编程语言的语法,都可以用ANTLR来定义。ANTLR提供了大量的官方grammar示例,包含了各种常见语言,比如Java、SQL、Javascript、PHP等等。ANTLR的应用非常广泛,比如Hive、Presto和SparkSQL等的SQL Parser模块都是基于ANTLR构建的。

LL是自顶向下(top-down)的语法分析方法,其中的第一个L表示分析器从左(Left)至右单向读取每行文本,第二个L表示最左派生(Leftmost derivation),ANTLR生成的就是LL分析器。

ALL全称:ALL Adaptive LL(*),是一种名为自适应的 LL(*)的新技术。ALL(*)是 ANTLR3 中 LL(*)的扩展,在实际生成的语法分析器执行之前,它能够在运行时以动态方式对语法执行分析,而非先前的静态方式。

一、Grammar语法定义

  • 语法名称XXX必须和文件XXX.g4一致。
  • 分为词法规则和语法(文法)规则:文法规则和词法规则可以同时存在一个文件中,但文法以小写开头,词法以大写开头。
  • 我们在构建一个语法文件时,一般是先考虑文法,再考虑词法。就像是我们造句,先会考虑句子的结构,再往里面填词。但是实际语法分析的过程是:输入流先经过词法分析器生成匹配的词法符号流,词法符号流经过语法分析器生成匹配的语法结构。
  • 必须有个名称(grammar Name;)和最少一个语法规则。
  • 可以无序指定options、import、token和Actions,但是只能最多一次。
  • 如果grammar没有前缀lexer 或者parser ,这个语法文件可以包含词法和语法规则。
  • 如果前缀是lexer这个文件只能包含词法规则,如果是parser这个文件只能包含语法规则

例如语法规则名:parser grammar Name;

例如词法规则名:lexer grammar Name;

  • antlr4中支持的关键字:import, fragment, lexer, parser, grammar, returns, locals, throws, catch, finally, mode, options, tokens。

二、ANTLR4的改进

  1. 语法上的改进,ALL(*)(读作“all star”)是ANTLR3中的LL(*)的扩展,在实际生成的语法分析器执行前,它能够在运行时以动态方式对语法执行分析,而非先前的静态方式
  2. ANTLR4极大的简化了匹配某些句法结构(如编程语言中的算术表达式)所需的语法规则
  3. 最大的改变是ANTLR4降低了语法中内嵌动作(代码)的重要性,取而代之的是监听器和访问器
  4. 由于ANTLR能够自动生成语法分析树和树的遍历器,在ANTLR4中,你无需再编写树语法,取而代之的是一些广为人知的设计模式,如访问者模式
  5. ANTLR3的LL(*)语法分析策略不如ANTLR4的ALL(*)强大,所以ANTLR3位了能够正确识别输入的文本,有时候不得不进行回溯,回溯的存在使得语法的调试格外困难

三、常用的ANTLR语言模式

  • 序列模式,即一系列元素
file : (row '\n')* ;       // 以一个'\n'作为终止符的序列
row  : field (',' field)*; // 以一个','作为终止符的序列
field: INT;                // 假设字段都是整数
  • 选择模式,在多个可选方案中做出选择:一条规则往往有多个分支,我们可以采用|符号
stmt: node_stmt
	| edge_stmt
	| attr_stmt
	| id '=' id
	|subgraph
	;
  • 词法符号以来,一个词法符号需要和某处另一个词法符号配对:非常常见,]需要匹配之前的[,}需要匹配之前的{。这种符号之前的依赖在ANTLR是自动完成的,我们只需要在文法规则中通过单引号支出即可。
expr: expr '(' exprList? ')'   // 类似f(), f(x), f(1,2) 的函数调用
	| expr '[' expr ']'			//类似a[i], a[i][j]的数组索引
	...
	;
  • 嵌套结构,一种自相似的语言结构:往往与之前的词法符号依赖一起出现,因为嵌套本身是要通过各种括号实现
expr: ID '[' expr ']'   // a[1], a[b[1]], a[(2*b[1])]
	| '(' expr ')'      // (1), (a(1)), (((1))), (2*a[1])
	| INT               // 1, 323
	;

四、语法解读原则

最左匹配原则

如果如下g4语法

...
expr:  expr '*' expr   # MulDiv   
| expr '+' expr        # AddSub
| INT                           # int
;
INT : [0-9]+ ; 

提问:上面的规则合理吗?

答:看起来合理,但是如果你把你的表达式放进去套用,发现上面的规则存在歧义。像1+2这种简单的整数表达式和单运算符表达式上,是绝对没有问题的,因为它只存在一种方式去匹配,只能用第二个备选分支去匹配,如图 4-1最左侧的语法分析树。但是对于1+2*3这种输入而言,上述规则能够用两种方式解释它,如图 4-1中间和右侧的语法分析树所示。

其实这就是运算符优先级带来的问题,传统的语法是无法指定优先级的。大多数语法工具,使用额外的标记来指定运算符优先级。但是,我们ANTLR通过优先选择位置靠前的备选分支来解决歧义问题,乘法规则在加法规则之前,所以ANTLR在解决1+2*3的歧义问题时会优先处理乘法。

图 4-1 按照不同方式解释的语法分析树

但是如果我们将语法的备选分支交换一下位置呢?我们再输入1+2*3还能按照我们希望的优先级执行吗?

另外,如果我们遇到了指数运算符这样的计算呢?如果还按照从左到右的将运算符结合,明显是不行的。

这个时候就要在这样的运算符上使用assoc选项手工指定结合性,这样假设输入2^3^4才能够正确的被解释为2^(3^4):

expr:  expr '^' <assoc=right> expr  // 在ANTLR4.2之后<assoc=right>放到备选分支最左侧,否则会收到警告:<assoc=right> expr '^'expr
	| INT
	;
INT : [0-9]+ ;

虽然ANTLR4已经能够处理直接左递归,但是它还无法处理间接左递归,所以我们无法将expr规则分解为下列规则,虽然语义上等价:

expr : expo
     | ...
     ;
expo : expr '^'<assoc=right> expr;

五、如何使用

1、安装Java包(要装JDK,而非JRE。java version "20.0.2" 2023-07-18)

2、安装antlr4(version 4.7.2)

$ cd /usr/local/lib
$ sudo curl -O https://www.antlr.org/download/antlr-4.7.2-complete.jar
  
// 设置环境变量
$ vim .bash_profile 
$ export CLASSPATH=".:/usr/local/lib/antlr-4.7.2-complete.jar:$CLASSPATH"
$ alias antlr4='java -jar /usr/local/lib/antlr-4.7.2-complete.jar'
$ alias grun='java org.antlr.v4.gui.TestRig'  //  antlr4.0版本时用 org.antlr.v4.runtime.misc.TestRig
// TestRig  调试工具
$ wq!  // 保存
$ source .bash_profile

$ antlr4

3、新建一个hello.g4文件,执行

grammar hello;
@header { package com.pioneeryi.learn.antlr; }

s : 'hello' ID ;
ID : [a-z]+ ;
WS : [ \t\r\n]+ -> skip ;

||

||

\ /

$ antlr4 Hello.g4  // 生成语法分析器和词法分析器

||

||

\ /

||

||

\ /

$ javac Hello*.java  // 编译antlr生成的Java代码

||

||

\ /

||

||

\ /

||

||

\ /

||

||

\ /


以上是一个示例以及解析出来的语法树

参考文献:

antlr4官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值