《ANTLR 4权威指南》笔记10 - 属性和动作
使用parse-tree walker的结果是总是在解析完成后执行代码。有些语言需要一边解析一边执行代码,所以需要将代码片段actions
嵌入到解析器中。
为了让语法和目标语言解耦,一般不使用嵌入actions
。但是为了某些原因,仍然可以使用actions
:
- 简化逻辑,希望避免创建
listener
或visitor
。 - 提升效率,不希望消耗内存构造解析树。
- 谓词解析
语法规则外的动作(actions)⌗
g4
文件结构如下:
grammar GrammarName;
@header{
...
}
@members{
...
}
parserRule: ...;
LEXER_RULE: ...;
生成的Java文件如下:
<header>
public class <grammarName>Parser extends Parser {
<members>
...
}
<header>
public class <grammarName>Lexer extends Lexer {
<members>
...
}
header
块一般用来写import
等相关语句。members
块一般用来写解析器的成员变量,其中的代码会写入到lexer和parser文件中。package
语句则可以用antlr
命令的-package
参数指定,不需要写在语法文件里。
@parser::name
和@lexer::name
用于指定parser或lexer的代码。比如用@parser::header
、@parser::members
,代码只写入到parser文件,用@lexer::header
、@lexer::members
则代码只写入到lexer文件。
例子,成员方法:
@parser::members {
/** "memory" for our calculator; variable/value pairs go here */
Map<String, Integer> memory = new HashMap<String, Integer>();
int eval(int left, int op, int right) {
switch ( op ) {
case MUL : return left * right;
case DIV : return left / right;
case ADD : return left + right;
case SUB : return left - right;
}
return 0;
}
}
**注意:**如果只想指定package,可以用antlr
命令的-package
参数代替,更加通用。
嵌入规则中的动作(actions)⌗
例子,在match后执行的action:
stat: e NEWLINE {System.out.println($e.v);}
| ID '=' e NEWLINE {memory.put($ID.text, $e.v);}
| NEWLINE
;
其中$e.v
表示规则e
的属性v
,在下面的语法中v
定义为e
的返回值。
语法规则的返回值:rule returns [<return value 1>,<return value 2>,...]
// 返回单个值:
e returns [int v]
...;
// 返回多个值,用逗号分隔
returnStmt
returns[int i, String s]: RETURN DIGITS;
只有parser rule可以添加返回值,lexer token不可以。注意Python不需要类型。
例子,返回值,规则的label:
e returns [int v]
: a=e op=('*'|'/') b=e {$v = eval($a.v, $op.type, $b.v);}
| a=e op=('+'|'-') b=e {$v = eval($a.v, $op.type, $b.v);}
| INT {$v = $INT.int;}
| ID
{
String id = $ID.text;
$v = memory.containsKey(id) ? memory.get(id) : 0;
}
| '(' e ')' {$v = $e.v;}
;
如果一条规则中有多个同类的语句,需要用label区分,其中a=e
和b=e
表示规则e
的两个标签,可以在action中使用标签名。当action有某个label的引用$label
时,ANTLR会在目标代码中生成以下成员变量,否则即使添加了label也不会生成成员变量:
public static class EContext extends ParserRuleContext {
public int v; // rule e return value from "returns [int v]"
public EContext a; // label a on (recursive) rule reference to e
public Token op; // label on operator sub rules like ('*'|'/')
public EContext b; // label b on (recursive) rule reference to e
public Token INT; // reference to INT matched by 3rd alternative
public Token ID; // reference to ID matched by 4th alternative
public EContext e; // reference to context object from e invocation
...
}
更复杂的动作可以看文档。
规则可以有参数、返回值、局部变量,完整的语法如下:
rule[param1,param2,...] returns [ret1,ret2,...] locals [var0=0,var1=0,...]
例子:
row[columns] returns [values] locals [col=0]
交互式计算器主程序:
package tools;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Calc {
public static void main(String[] args) throws Exception {
String inputFile = null;
if ( args.length>0 ) inputFile = args[0];
InputStream is = System.in;
if ( inputFile!=null ) {
is = new FileInputStream(inputFile);
}
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String expr = br.readLine(); // get first expression
int line = 1; // track input expr line numbers
ExprParser parser = new ExprParser(null); // share single parser instance
parser.setBuildParseTree(false); // don't need trees
while ( expr!=null ) { // while we have more expressions
// create new lexer and token stream for each line (expression)
ANTLRInputStream input = new ANTLRInputStream(expr+"\n");
ExprLexer lexer = new ExprLexer(input);
lexer.setLine(line); // notify lexer of input position
lexer.setCharPositionInLine(0);
CommonTokenStream tokens = new CommonTokenStream(lexer);
parser.setInputStream(tokens); // notify parser of new token stream
parser.stat(); // start the parser
expr = br.readLine(); // see if there's another line
line++;
}
}
}
访问token和rule属性⌗
局部变量:rule locals [<var 1>, ...]
file
locals [int i=0]
: hdr ( rows+=row[$hdr.text.split(",")] {$i++;} )+
{
System.out.println($i+" rows");
for (RowContext r : $rows) {
System.out.println("row token interval: "+r.getSourceInterval());
}
}
;
@init
在匹配任何一条选择之前执行,@after
在匹配到任意一条选择之后执行,普通action可以放在规则的任何地方,比如规则row
。
grammar CSV;
@header {
import java.util.*;
}
/** Derived from rule "file : hdr row+ ;" */
file
locals [int i=0]
: hdr ( rows+=row[$hdr.text.split(",")] {$i++;} )+
{
System.out.println($i+" rows");
for (RowContext r : $rows) {
System.out.println("row token interval: "+r.getSourceInterval());
}
}
;
hdr : row[null] {System.out.println("header: '"+$text.trim()+"'");} ;
/** Derived from rule "row : field (',' field)* '\r'? '\n' ;" */
row[String[] columns] returns [Map<String,String> values]
locals [int col=0]
@init {
$values = new HashMap<String,String>();
}
@after {
if ($values!=null && $values.size()>0) {
System.out.println("values = "+$values);
}
}
// rule row cont'd...
: field
{
if ($columns!=null) {
$values.put($columns[$col++].trim(), $field.text.trim());
}
}
( ',' field
{
if ($columns!=null) {
$values.put($columns[$col++].trim(), $field.text.trim());
}
}
)* '\r'? '\n'
;
field
: TEXT
| STRING
|
;
TEXT : ~[,\n\r"]+ ;
STRING : '"' ('""'|~'"')* '"' ; // quote-quote is an escaped quote
处理关键字不固定的语言⌗
显式声明tokens
:
tokens { BEGIN, END, IF, THEN, WHILE }