ANTLR 入门(二)
ANTLR基本语法
前面已经简单介绍了ANTLR以及怎么安装和测试。
同学们应该大概清楚ANTLR的使用场景,但是对于关键步骤,怎么编写一个语法文件并没有详细介绍,这篇笔记主要详细讲解一下ANTLR的语法。
在过去的几十年内人类发明了很多种编程语言,现在还在持续增加。而ANTLR的语法就是要把任意的编程语言的语法规则通过自身的语法描述文件来定义。好消息是,这么多的编程语言,相对而言,基本的语言模式并不多。之所以这样,其实原因也很简单。因为我们在设计编程语言时,倾向于将语言设计的和脑海中的自然语言相类似。我们期望看到有序的词法符号,也期望同词法符号的依赖关系。比如不会有任何语言出现{(})这种语法,大家会用数学符号,标识符,字符串。
总结下来,所有编程语言的语言模式可以抽象成四类:
序列:一列元素,类似数组里面的值
选择:在多种可选方案中做选择,eg:if else
词法符号依赖:符号是成对出现,eg:左右括号
嵌套结构: 自相似的语言结构,eg:编程语言中的一个语法嵌套另一个语法。
ANTLR实际就是基于以上的原则进行语法的设计,达到定义一种语法的作用。下面我们一个个详细说明一下这四种语言模式
序列
序列模式是最常见的一种模式。简单来说把一连串的词法按顺序排列就是一种序列,所有的指令也是一个序列。
然后ANTLR结合正则表达式,就可以快速的描述出多个元素的序列模式,
INT+表示一个或多个整数,INT\*表示零或多个整数,INT?表示零个或一个整数。
CSV这个文件为例,CSV的语法用ANTLR描述出来就是
选择
如果一个编程语言只有一种语句,就太无聊了,也做不了什么。选择模式就是表示一个地方可能支持多种有效的语句。
在ANTLR中使用|来表达选择模式,选择模式在语法中随处可见。
上面的例子,CSV的字段肯定不一定都是字符串,那么我们应该改成
注意的是在ANTLR中是按顺序来匹配解析规则,所以多个|分割的规则顺序是有意义的。
词法符号依赖
词法符号依赖最常见的用法还是定义括号的限制,规定括号必须成对出现。
还是那上面的字段举例子,比如我们要限制CSV的字段必须用“包括。那可以改成
嵌套结构
嵌套词组是一种自相似的语言结构,即它的子词组也遵循相同的规则。表达式是一种典型的自相似语言结构,它包含多个嵌套的,以运算符分割的子表达式。类似于我们程序中的递归。
我们看一下简单wihle循环怎么定义
其实上面这种直接递归是比较难理解,一般的写法会写成间接递归
常用语法
前面已经介绍了常用的四种语法模式,下面列一下ANTLR常用的语法标记,后面可以当作写语法文件的字典。
JSON的语法
下面通过分析一下大家常用的json的语法来加强理解。
上面是json格式最核心的语法定义,回顾一下,这里其实用到了前面说的全部四种模式,下面一一讲解。
这里其实是定义了一个json的基础,json基础规则就由一个value规则组成。
然后这里定义了value的规则, 可以看到这里用到了选择模式。 json的value可以是字符串,数字,true,false,null, 这四个其实是传统定义json格式“值”部分能够使用的基本类型。然后除了基本类型,value还可以是obj和arr。
arr这个规则其实就是一个json数组,它由多个value通过,分割的数组,或者是一个空数组。 这里用到了间接嵌套模式。 统通过这个规则,json的某一个值可以是另一组json格式
obj是由一个json对象,它由多个pair通过,分割。或者可以是一个空的{}。然后其中的pair则是一个最基础的key-val的格式,这也是json最基础的语法。可以看出一点json定义中key必须是字符串。
这里其实就是json最核心的一些定义,大家可以回想一下json格式的规则是不是就是这样的。然后再加上一下针对不同格式的正则要求,就完成了json的ANTLR语法定义。下面附带了完整的文件,有兴趣可以自己结合之前的分享,读一下这文件。
[JSON的ANTLR语法文件](https://github.com/antlr/grammars-v4/blob/master/json/JSON.g4)
语言类程序
在之前的学习中,我们已经知道了如何使用ANTLR来定义一种语言,现在进行一些深入的研究。通常单独的语法并没实际作用,应该有一个语法分析器才能帮助我们实现一些具体功能,才能开发一个语言类的程序。
语法分析器除了能够解析语法外,应该还能在遇到特定的语句,词组,或者语法符号时触发一些特定的行为。这样的语法和特殊行为的集合就构成了语言类程序。
ANTLR中为了实现这种功能,引入了访问器和监听器。
访问器可以通过显式触发的方式访问特定的节点。
监听器能够对特定规则的进入和退出事件作出响应。
下面详细介绍一些访问器和监听器能做什么,还是以之前学习的简单赋值语法做例子
ParseTreeVisitor(访问器)
根据语法生成的访问器接口
从这个接口可以看到
ANTLR的访问器的顶级接口是ParseTreeVisitor
访问器中会生成每个规则的visit方法,Hello里面只有statement这个规则,所以只有一个方法
接下来我们可以实现一个访问器的实现类,进行相关逻辑处理
visit方法的参数是一个\*\*Context的类,每个规则会有一个\*\*Context的类,这个类里面包含了规则里面的词组。
可以提前规则里面的词组进行处理,这里就有了实现逻辑代码的空间。
ParseTreeListener(监听器)
ANTLR的监听器的顶级接口是ParseTreeListener
访问器中会每个规则的一组方法。enterStatement和exitStatement,分别在分析树在进入规则和退出规则时触发。
和访问器类似,我们也可以通过实现接口来添加代码逻辑。
这里有一点需要注意,在enterStatement方法时, 因为时刚进入该规则触发,所以这时还获取不到Statement里面的ID和NUM。如果需要用到这两个,只能在exitStatement方法类处理。
使用方式
在写好了访问器和监听器后,如何在ANTLR解析中使用?
这两个的使用方法有一定区别。
监听器是在解析前通过addParseListener方法添加到Parser里面。
访问器是通过Context调用accept方法来指定访问规则。
思考
介绍了访问器和监听器使用后,大家可以思考一下分别使用这个两个来做一个简单的计算器。
版权声明: 本文为 InfoQ 作者【zane】的原创文章。
原文链接:【http://xie.infoq.cn/article/a087bc9d8f28e5f6836822759】。文章转载请联系作者。
评论