使用parse-tree walker的结果是总是在解析完成后执行代码。有些语言需要一边解析一边执行代码,所以需要将代码片段actions嵌入到解析器中。

为了让语法和目标语言解耦,一般不使用嵌入actions。但是为了某些原因,仍然可以使用actions

  • 简化逻辑,希望避免创建listenervisitor
  • 提升效率,不希望消耗内存构造解析树。
  • 谓词解析

语法规则外的动作(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=eb=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 }