つれづれなるままにANTLRをやる。 もう、インストール済みなので、 立ち上げると,[Grammar Name:]を聞かれる。 あ、何するかきめてなかった。 なにか目標をたてよう。 そうだ、 入力: y == x 出力 strcmp(x,y) これていいや、文法名はLesson01とかでいいかな、 とおもったけど、ファイル名に数字があると駄目っぽい。とりあえずGGGとかいう、てきとーな名前にした。 動作確認したいだけなので、名前はどーでもいいのである。 Lexical Itemsでチェックボックスを入れることができる。 Identifierは、たぶん、識別子だから、イコールとか使えるようになるのかな? 変数は、Characterかな?、不要なスペースもキャンセルしたいから、White spaceにもチェックを入れておけばいいのかな。 とりあえず、仮説をたててやってみると、いいかもね。 すると、デフォルトでコードがいくつか作られている。 ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ; WS : ( ' ' | '\t' | '\r' | '\n' ) {$channel=HIDDEN;} ; CHAR: '\'' ( ESC_SEQ | ~('\''|'\\') ) '\'' ; fragment HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ; fragment ESC_SEQ : '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\') | UNICODE_ESC | OCTAL_ESC ; fragment OCTAL_ESC : '\\' ('0'..'3') ('0'..'7') ('0'..'7') | '\\' ('0'..'7') ('0'..'7') | '\\' ('0'..'7') ; fragment UNICODE_ESC : '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT ; たしかに、初めて利用する場合、このように、よくある選択肢を選ぶようになっているのは、ありがたい。 とりあえず、デバックボタンを押してみる。 ありゃ、エラー [10:46:05] java.lang.NullPointerException at org.antlr.xjlib.foundation.XJUtils.concatPath(XJUtils.java:43) at org.antlr.xjlib.foundation.XJUtils.concatPath(XJUtils.java:54) 対策がネットに載っていた、コンパイラーが通っていないかららしい。 You can fix this by going to File > Preferences > Compiler and entering the path for the executable. って書いてあったが、Macの場合は[ANTLRWorks]-[環境設定]-[Preferrences] でたどれた。 コンソールにて which java で、どこにインストールされているか調べてみた。 /usr/bin/java ってでてきた。 それを入れれば、動いたけれども、なんだかうまく動かない。 antlr-3.2.jar をクラスパスに指定したりしてみたりしたら動いた。 とにかく、IDの書き方はいままで、 ID : 'a'..'z'+; しかしらんかったのだけど ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')* ; でもいけるということがわかった。 空白文字の書き方も WS : (' '|'\n'|'\r')+ {$channel=HIDDEN;}; しか知らなかったけど WS : ( ' ' | '\t' | '\r' | '\n' ) {$channel=HIDDEN;} ; と書けることもわかった。こっちの方が見やすい。 1改善づつできることを、地道に増やしていくのが、近道かもね。 IDって、複数回マッチした場合どうなるんだろう? もいちど、antlrの本家に立ち戻って、Getting Startを見てみる。 コメントをつかってLexerとPaserを区分けしている。 うむ、見やすい、採用。ということで、自分のコードは下記のようになった。 grammar GGG; /*------------------------------------------------------------------ * PARSER RULES *------------------------------------------------------------------*/ r : 'call' ID '==' ';' {System.out.println("invoke "+$ID.text);}; /*------------------------------------------------------------------ * LEXER RULES *------------------------------------------------------------------*/ ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*; WS : ( ' ' | '\t' | '\r' | '\n' ) {$channel=HIDDEN;} ; パーサ部分を下記のようにして実行してみた。 /*------------------------------------------------------------------ * PARSER RULES *------------------------------------------------------------------*/ r : 'call' ID '==' expr ';' {System.out.println("strcmp("+$ID.text+",");}; r : ID '==' expr ';' {System.out.println("strcmp("+$ID.text+",");}; expr : ID{System.out.println($ID.text+")");}; 結果 call hoge==fuga; という入力に対して fuga) strcmp(hoge, という出力が得られた。 あと一歩だ、トークンを解析した結果の文字列を保存できれば、なんとかなるかもしれない。 この問題を解決しそうなチュートリアルをさがすと、下記のURLのチュートリアルが一番近そうだ。 http://www.antlr.org/wiki/display/ANTLR3/Simple+tree-based+interpeter javaのコードを入れることが出来るようだ。 インポート文を入れれるし、メソッドも普通に書ける。 とりあえず、動作確認できたコードは下記のとおりである。 @header { import java.util.Map; import java.util.HashMap; import java.math.BigInteger; } @members { public String getString(String name) { return name; } } /*------------------------------------------------------------------ * PARSER RULES *------------------------------------------------------------------*/ r : 'call' ID '==' expr ';' {System.out.println("strcmp("+$ID.text+","+$expr.value+")");}; r : ID '==' expr ';' {System.out.println("strcmp("+$ID.text+","+$expr.value+")");}; expr returns [String value] : ID{$value = getString($ID.text);}; /*------------------------------------------------------------------ * LEXER RULES *------------------------------------------------------------------*/ ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*; WS : ( ' ' | '\t' | '\r' | '\n' ) {$channel=HIDDEN;} ; 末端で解析した結果は、上位のノードに結果を返すようにしている点が、改良点だ。 あと、実験的にJavaのメソッドで解析した値を、定義したメソッドを介して得られる仕組みをとった。 これは、ちゃんと動作した。新たに一歩前進したといえるだろう。 独学は、確実に動作するサンプルをもとに、小さな課題を設定してクリアしていくことが近道なのだ。