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)