ANTLR(3) - ASTの構築(1)

入力から構文木を作るところまで一気に。

や、これは便利ですわ。

文法

grammar ExprAst;

options {
	output=AST;
	ASTLabelType=CommonTree;
}

tokens {
	NEGATE;
}

@header {
package introduction;
}

@lexer::header {
package introduction;
}

start	: expr ';' EOF -> expr
	;
expr	: term (( '+'^ | '-'^ ) term)*
	;
term	: unary	(( '*'^ | '/'^ ) unary)*
	;
unary	: factor	-> factor
	| '+' factor	-> factor
	| '-' factor	-> ^(NEGATE factor)
	;
factor	: NUMBER	-> NUMBER
	| '(' expr ')'	-> expr
	;

NUMBER	: ('0'..'9')+ ( '.' ('0'..'9')+ )?
	;
SKIP	: ( ' ' | '\t' | '\r' | '\n' )+ { $channel = HIDDEN; }
	;
  • optionsの中にオプションを書ける
    • output=AST; でASTを作る文法
    • ASTLabelType=CommonTree; でライブラリが持ってるASTノードを使える。比較的便利。
  • tokens { ... } はトークンに別名を付ける。
    • 中置 -(マイナス) と前置 - が混同しそうなので、前置 - に NAGATE というラベルを導入
    • tokensは@headerよりも上で定義しないとだめぽい
  • -> でASTを書き換えて戻す
    • expr ';' EOF -> expr で、';', EOF を無視して expr 自体を結果として返す
    • -> を書かないと、そのものを返す感じ?
    • rewrite-rule で ^(
  • ^ とすることで、^をラベルとしたツリーを構築
    • term (( '+'^ | '-'^ ) term)* で、+または-をラベルとしたツリー
    • 微妙にルールがはっきりしない。1 + 2 + 3 => (+ (+ 1 2) 3)

はっきりしないけど、とりあえず次に使う部分には問題なさそう。いずれしっかり把握。

確認コード

public class ExprAstTest {
  public static void main(String[] args)
      throws IOException, RecognitionException {
    ANTLRInputStream in
        = new ANTLRInputStream(System.in, "UTF-8");
    Lexer lexer = new ExprAstLexer(in);
    CommonTokenStream tokens
        = new CommonTokenStream(lexer);
    ExprAstParser parser = new ExprAstParser(tokens);
    CommonTree root = parser.start().tree;
    System.out.println(root.toStringTree());
  }
}
  • 戻り値の.tree というフィールドに org.antlr.runtime.CommonTree という型の要素が
    • #toStringTree() でダンプ出力

実行結果

> echo 1 + 2 * 3;
| java -cp bin;lib\antlr-runtime-3.0.1.jar introduction.ExprAstTest
(+ 1 (* 2 3))

> echo (1 + 2) * 3;
| java -cp bin;lib\antlr-runtime-3.0.1.jar introduction.ExprAstTest
(* (+ 1 2) 3)