gUnitに挑戦。
まぁ、何かテキトーに動きそうなツリーが出来たトコロで、
エラー処理辺りを作りこもうかな…と言う気分なのだけど、
そもそも良く分かってない部分があるので、細かい単位で動かせないと駄目だよね…と言う訳。
で、ANTLRには、gUnitと言うテスティングツールと言うかフレームワークと言うかがあるので、使ってみる事に。
gUnit is an unit testing framework for ANTLR grammars. It provides a simple way to write and run automated tests for grammars in a manner similar to what jUnit does for unit testing.
こいつを動かしているうちに、ANTLRへの理解がもっと深まったらいいな…とか。
examples/java/LL-star/SimpleC.g
最初のコイツは、何箇所かテストでコケるのだけど、テストが通る様にしてみる。
examples/java/LL-star/SimpleC.gに対するテストコードとして、SimpleC.testsuiteを作る。
中身はwikiに書いてある通りコピペ。
gunit SimpleC; variable: //test rule:variable with 2 tests "int x" FAIL //expect failure, because of missing ';' in the input string "int x" -> "x" //expect standard output "x" from rule functionHeader: //test rule:functionHeader with 1 test "void bar(int x)" returns ["int"] //expect a return string "int" from rule program: //test rule:program with 3 tests, input starts immediately after the initial << so the first test is a blank line << char c; int x; >> OK //expect success (no error messages from ANTLR) input OK //expect success input -> "" //expect standard output "" from rule
テストを実行すると、ドキュメント通りに、3か所コケる。
こんな感じ。
-------------------------------------------------------------------------------- executing testsuite for grammar:SimpleC with 6 tests -------------------------------------------------------------------------------- 3 failures found: test2 (variable) - expected: x actual: BR.recoverFromMismatchedToken line 0:-1 mismatched input '<EOF>' expecting ';' test3 (functionHeader) - expected: int actual: bar test6 (program) - expected: actual: bar is a declaration foo is a definition Tests run: 6, Failures: 3
test2とか、test3とか、最初何を言っているのか良く分からなかったけど、
テストコードを上から数えて何番目って事らしい。
上からやっつける。
まずtest2。こいつは、何やらやっかいな感じ。
エラー自体は、スゲェ簡単。入力に、セミコロンがねぇぞ、ゴルァ!とただそれだけ。
入力にセミコロンを付けてみる。
"int x;" -> "x"
結果はかくの如し
test2 (variable) - expected: x actual: null
null返ってきてんのか…。特に何も起きないという事かな。
じゃあ、こうする。
"int x;" OK
通った。
んで、test3。これは、楽勝。でも念の為、SimpleC.gを確認。
functionHeader returns [String name] @init { name=null; // for now you must init here rather than in 'returns' } : type ID '(' ( formalParameter ( ',' formalParameter )* )? ')' {$name = $ID.text;} ;
何か見た事無い様な事が色々書いてあるけど、つまりは、パースした結果として、関数名が返ってくるっつう事だに。
と言う訳で、barがreturnされるでよ、と言う風に。
"void bar(int x)" returns ["bar"]
オケ。
最後に、test6。何も無い事もなくて、何か返ってきてるでよ。と言うテストシパーイ。
どこが何を出しているのか…と思ったら、
declaration : variable | functionHeader ';' {System.out.println($functionHeader.name+" is a declaration");} | functionHeader block {System.out.println($functionHeader.name+" is a definition");} ;
こんなんなっている。actionちうトコロで、標準出力している訳だな。
と言うか、テストコードで、標準出力を拾えるのか。これはこれで凄い話だ。
同じディレクトリ内に、「output」なんてファイルがあったので、使おうとしてみる。
line 21:9 no viable alternative at input 'output' BR.recoverFromMismatchedToken line 0:-1 mismatched input '<EOF>' expecting ':' java.lang.NullPointerException at org.antlr.gunit.gUnitExecuter.execTest(gUnitExecuter.java:159) at org.antlr.gunit.Interp.exec(Interp.java:83) at org.antlr.gunit.Interp.main(Interp.java:66)
うぼぁばー…。ダメか。入力はテキトーなテキストファイルから出来るけど、
出力をテキストファイルと比較するのは無理らしい。
仕方がないので、「expected multiple-line output string」と言うのを試してみる。
input -> <<bar is a declaration foo is a definition >>
こんな感じ。改行コードが微妙極まりない感じに嫌がらせをしてくるので注意。
SimpleC.testsuiteを保存する時の改行コードをLFにしておかないと、テストが成功しない。
出来あがったテストコードは、こんな感じ。
gunit SimpleC; variable: //test rule:variable with 2 tests "int x" FAIL //expect failure, because of missing ';' in the input string "int x;" OK //expect standard output "x" from rule functionHeader: //test rule:functionHeader with 1 test "void bar(int x)" returns ["bar"] //expect a return string "int" from rule program: //test rule:program with 3 tests, input starts immediately after the initial << so the first test is a blank line << char c; int x; >> OK //expect success (no error messages from ANTLR) input OK //expect success input -> <<bar is a declaration foo is a definition >> //expect standard output "" from rule
examples/java/simplecTreeParser/SimpleC.g
ドキュメントがちょっと読み辛くて、どこで切り替わったのか分り辛いのだけど、
途中から別のgrammarに対するテストになっているので注意。
例によってコケるテストコードをコピペ。
gunit SimpleC; /** only test AST output in this testsuite */ variable: "int x;" -> (VAR_DEF int x) //test1 declaration: "void bar(int);" -> () //test2 "int foo(int y, char d) {}" -> () //test3 program: input -> () //test4
出てくるエラーメッセージは、
-------------------------------------------------------------------------------- executing testsuite for grammar:SimpleC with 4 tests -------------------------------------------------------------------------------- 3 failures found: test2 (declaration) - expected: () actual: line 1:12 no viable alternative at input ')' java.lang.NullPointerException test3 (declaration) - expected: () actual: (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) BLOCK) test4 (program) - expected: () actual: (VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5))))) Tests run: 4, Failures: 3
こんなん。test2のメッセージが微妙に違う気がするけど、まぁ、いいか。っつうか、何だ?ヌルポて*1。
今度のテストコードは、コメントでテスト番号が書いてあるから、ちょっと読み易いね。
っつうか、gUnitって、テスト失敗時のメッセージ読み辛くねぇか?今更だけど。
ルール単位にテストを書くのは、まぁ、良いとしても、メッセージのテストの番号が上から順なおかげで、
似たような感じのテストコード山ほど書いたらどこでコケたか分からないじゃん…。
サテ、test2がコケるのは、何でだろ…。「)」が変なトコロにあるぞ、ゴルァ。と言っている。
関係がありそうな部分をgrammarからコピペ。
declaration : variable | functionHeader ';' -> ^(FUNC_DECL functionHeader) | functionHeader block -> ^(FUNC_DEF functionHeader block) ; functionHeader : type ID '(' ( formalParameter ( ',' formalParameter )* )? ')' -> ^(FUNC_HDR type ID formalParameter+) ; formalParameter : type declarator -> ^(ARG_DEF type declarator) ;
おけ。仮引数名っぽいのが無いからダメらしい。
じゃあ、こうする。
"void bar(int a);" -> ()
まぁ、この状態で、空っぽの何かが返ってくる訳無いんだけどさ。
案の定、怒らりる。
test2 (declaration) - expected: () actual: (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int a)))
何か、token的なアレが塊になっている。ここで、ANTLRのアロー演算子が何なのか悟る。
置き換えをしてくれるんだに。なるほど。で、主にフラグ立てるのに使いますよ、とそういう事デスカ。
test3は、まぁ、同じなので省略。
最後、test4。
test4も同じだろうと思って、actualに出力されている内容を、エイっとコピペ。
input -> (VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5))))) //test4
こんなんなった。で、テスト実行。
-------------------------------------------------------------------------------- executing testsuite for grammar:SimpleC with 4 tests -------------------------------------------------------------------------------- 1 failures found: test4 (program) - expected: (VAR_DEF char c) actual: (VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5))))) Tests run: 4, Failures: 1
は?どういう事?何か、expectedで用意した値のうち、最初の半角スペースより後ろが全く評価されてないんですけども。
じゃあ、こうする。
input -> ((VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5))))) )//test4
で、実行。
-------------------------------------------------------------------------------- executing testsuite for grammar:SimpleC with 4 tests -------------------------------------------------------------------------------- 1 failures found: test4 (program) - expected: ((VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5)))))) actual: (VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5))))) Tests run: 4, Failures: 1
うがー!!括弧の分だけ余分、とかそういう事ですか?そうなんですか?ふざけとる…。
超手詰まった。
そうだ、gUnitには、JUnitのテストコードを生成する機能もあるのだった。
ちょっとやってみるか。
んで、できたコードの当該部分。
public void testProgram1() throws Exception { // test input: "input" Object retval = execParser("program", "input", true); Object actual = examineParserExecResult(10, retval); Object expecting = "(VAR_DEF char c)"; assertEquals("testing rule "+"program", expecting, actual); }
あ?なんじゃこりゃ…。単に文字列として比較しとる。と、言う事は…。
input -> "(VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5))))) "//test4
ダブルクォートすれば、いいんじゃないか。と言う思いつき。
おおぅ、テストが通ってしまった。まぁ、いいか。
っつうかね、多分、gUnitのgrammarがどうなってるかしんないけど、exptectedの終端を表す記号を使わない様にしたのが失敗なんじゃね?とか。
で、文字列比較しちゃえば、まぁ、それはそれで、テスト出来ちゃうから、いいか…みたいな話かね。
で、分かった事は、grammarの中でtoStringメソッドを自分のデバッグ用に書き換えちゃったりすると、
gUnitは動かなくなるかもね…と、そういう感じ。
忘れてたけど、通る様になったテストコード。
gunit SimpleC; /** only test AST output in this testsuite */ variable: "int x;" -> (VAR_DEF int x) //test1 declaration: "void bar(int a);" -> (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int a))) //test2 "int foo(int y, char d) {}" -> (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) BLOCK) //test3 program: input -> "(VAR_DEF char c) (VAR_DEF int x) (FUNC_DECL (FUNC_HDR void bar (ARG_DEF int x))) (FUNC_DEF (FUNC_HDR int foo (ARG_DEF int y) (ARG_DEF char d)) (BLOCK (VAR_DEF int i) (for (= i 0) (< i 3) (= i (+ i 1)) (BLOCK (= x 3) (= y 5)))))" //test4
と言う所まで来て、重大な事に気付いた。
パースエラー時のメッセージをテストコード内でバリデーション出来ないじゃん。
「FAIL」しかない。これって、JUnitでテストコード書けって事なのかね。何という事だ…。
gUnitって、実はあんまり便利でもない…とか、そういうオチなの?もしかして。
生成されるJUnitのコード見る限り、Javaでテストコード書いてもそんなに手間でも無い様な感じだし。
ANTLRWorksからテストを実行出来る訳でも無いし、ううむ…。
gUnitが生成するテストコード内に固定的に出力されている、「execParser」メソッドだけパクれば、あんま用なし?とか。
そういう感じかなぁ…。
*1:ガッ