《ANTLR 4权威指南》笔记7 - 将语法从应用相关代码中解耦
解析器在解析语句时需要触发某些动作。phase-action对表示语法和语言应用之间的接口。
有listener和visitor两种构造方式。listener负责响应parse-tree walker触发的entry和exit事件。visitor除此以外还负责控制如何遍历解析树,必须显式调用函数来访问子节点。
7.1 将嵌入式的动作进化到listener⌗
grammar PropertyFile;
file : {«start file»} prop+ {«finish file»} ;
prop : ID '=' STRING '\n' {«process property»} ;
ID : [a-z]+ ;
STRING : '"' .*? '"' ;
{}
中的是嵌入的Java代码,非常严重的耦合。
更好一点的办法是在语法文件里写函数原型,再继承生成的解析器:
grammar PropertyFile;
@members {
void startFile() { } // blank implementations
void finishFile() { }
void defineProperty(Token name, Token value) { }
}
file : {startFile();} prop+ {finishFile();} ;
prop : ID '=' STRING '\n' {defineProperty($ID, $STRING)} ;
ID : [a-z]+ ;
STRING : '"' .*? '"' ;
继承:
语法文件中仍然有部分Java代码。
7.2 Listener⌗
7.3 Visitor⌗
7.4 规则标签⌗
原始语法不用标签:
grammar Expr;
s : e ;
e : e op=MULT e // MULT is '*'
| e op=ADD e // ADD is '+'
| INT
;
只能生成enterE
和exitE
两个方法:
需要判断+
或*
:
添加标签后:
e : e MULT e # Mult
| e ADD e # Add
| INT # Int
;
会根据每个标签生成一个方法:
7.5事件函数之间共享信息⌗
用Visitor遍历解析树⌗
用栈模拟返回值,在Listener中使用⌗
标记解析树 tree annotation⌗
简单的方法,直接在解析树中储存结果,在语法文件中写返回值,但是会导致代码耦合:
e returns [int value]
: e '*' e # Mult
| e '+' e # Add
| INT # Int
;
更好的方法:
e
: e MULT e # Mult
| e ADD e # Add
| INT # Int
;
在Listener中添加一个ParseTreeProperty
类型的成员变量,这个是比较通用的方法。
Python不需要ParseTreeProperty对象,直接dict就行了,非常方便。
三种共享方法比较⌗
- 原生Java调用栈:使用Visitor,需要显式调用,比较麻烦。
- 栈数据结构:必须把函数调用转换成栈,逻辑比较复杂,修改困难。
- 标记:比较通用的方法。