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:ガッ