「2週間でできるスクリプト言語の作り方」の4日目「プログラムを表すオブジェクト」と5日目「構文解析器を作る」。 この本ではyaccとか再帰下降法じゃなくてパーサコンビネータを使って構文木を作ってる。
2項演算の優先順位はoperatorsへの登録時に指定していて、実際のパースはParser.Expr.parse()
でまず factor.parse()
でパースして、続く演算子がある間(nextOperator(lexer) != null
) doShift()
を呼び出す。doShift()
の呼び出しは右結合の処理、またはより優先順位の高い演算子が続く場合(rightIsExpr()
)。
パースに失敗した時のバックトラックというか、トークンの消費はどうやってるのかは、Parser.match()
で Lexer.peek()
を使って条件に合うかを調べて、Parser.parse()
で Lexer.read()
を使って消費する?よくわかってない。
構文に合った場合にどうやって木を作るか。構文を定義している BasicParser
にはアクションは全く書いてない。Javaのリフレクションを使っている。rule(ASTree
の子クラス).somerule() としたときに、そのルールにマッチした場合にその子クラスのインスタンスが作られる。
Factory.get()
の中であれこれやっていて、渡されたクラスに(静的メソッドの) create()
というメソッドがあればそれを、なければコンストラクタを呼び出す。PrimaryExprはcreate()
を定義していて、インスタンスを作るか省略するかを選択している。
複雑な部分はパーサコンビネータのソース Parser.java に押し込んでいて、自分が定義するパーサ BasicParser.java はシンプルになってるけど、あまりよく動作は飲み込めていない。
Javaのリフレクションを知らなかったのでテストした。例えば
class Test { |
とかいうクラスがあったときに、
Method m = Test.class.getMethod("test", null); |
で test
メソッドが取り出せる。引数のあるメソッドは
Method m2 = Test.class.getMethod( |
と引数の型の配列を渡す。
指定のメソッドがない場合には NoSuchMethodException
例外が発生する。
取り出したメソッドの呼び出しは
m.invoke(t); |
など。動くコードはこちら:http://ideone.com/jmpKh