プログラミングガイド


宝剣で生成したパーサを動作させるためには、解析結果の SyntaxTree を受け取ってそれを処理するメソッドを用意する必要がある。

parse関数


ルールから生成されたクラスは全て Parser クラスの子クラスであり、メンバ関数 SyntaxTree* parse(void) を持つ。
例えばルールの名前が Number であれば、クラス名は P_Number というように、頭に P_ がついたものとなる。
そのクラスのインスタンスが1つ自動生成される。こちらは頭に p_ がついたものとなる。
P_Number クラスなら p_Number という名前のインスタンスとなる。
Number の解析をしたいのであれば、
SyntaxTree* st = p_Number->parse();
とする。


action関数


Number <- [0-9]+

というルールを記述すると、P_Numberクラスが定義され、メンバ関数
void P_Number::action(SyntaxTree*, Application*)

void P_Number::actionAtParse(SyntaxTree*)
が宣言のみで実装は無いまま残される。
ユーザはこれを実装する。

actionAtParse() は、パースが成功した時点で呼ばれる。
これはパーサ自身の動作を変更したい場合に使用すると良い。
宝剣では、 $ctype を実現するために使用している。
他にもC言語の #include のような動作をさせるために使用できるだろう。

action() は、パースが正常終了したらその結果を渡して呼び出す。
実際には、SyntaxTree::action() から呼ばれる場合がほとんどで、その方が便利。
SyntaxTree::action(Application* ap) の中身は
parser->action(this, ap) という処理になっているので、SyntaxTreeを生成したParserのaction()が呼ばれる。

SyntaxTree* st = p_Number->parse();
if (st->isValidTree()) {
  Application app;
  st->action(&app);
  // 結果が app に入る
}

errorMessage関数

ErrorCut ($数字) を実行した後にパース失敗すると、エラーとなって異常終了するが、その時に出力する
メッセージをこの関数で決める事ができる。

A <- B $3 C

というルールがあり、これを元にパースし、B が成功して $3 を通過した後 C が失敗すると、
エラーとなり、A の errorMessage関数が呼ばれる。
引数として、3 と、パースに失敗した位置(この場合は C の位置)が渡される。


uParse関数


A <% e
というルールを定義した場合、これはパーサをC++で作成するという事になる。
SyntaxTree* P_A::uParse(void)
という関数が宣言され、未実装のまま残される。
プログラマは uParse() 関数を実装しなければならない。
actionAtParse()関数は宣言されない。

SyntaxTree


パースされた結果は SyntaxTree オブジェクトに格納される。
SyntaxTreeは、
を持つ。

Number <- [0-9]+
でパースした結果であれば、Numberの子は複数の [0-9] となる。
123abNumberでパースすると、消 費した入力は 123 となる。
子が生成したSyntaxTreeは3つとなり、最初の子は 1 を消費し、
最後の子は 3 を消費している。

Number <- %[0-9]+
とすると、% の機能により子が生成したSyntaxTreeは 全部捨てられ、0個になる。



中置演算子左結合と中置演算子右結合では、それぞれのSyntaxTreeは3つの子
を持つような形で結合順の通りに整理されている

"+"infixl"**"infixr の場合、入力とそのパース結果のSyntaxTreeをおおまかに示すと以下の ようになる。

1+2+3  →  (("1","+","2"),"+","3")
1**2**3  →  ("1","**",("2","**","3"))

これを元に演算を実行する場合、各子の action() を呼んで、その結果を使って自分の演算をするような action() をプログラムすれば良い。

SyntaxTreeには特殊な値が存在する。
真となる判定関数 意味 定義済み値
正常値 isValidTree() 成功したパース結果を保持する。
結果を削除 == _NO_SYNTAX_TREE 成功だが、値を捨てた状態。 _NO_SYNTAX_TREE
パース失敗 isFail(), isFailNotError() パースに失敗した状態。 _PARSE_FAILED
エラーカット isErrorCut() ErrorCutを示す。この後にパース失敗するとそれはエラーとなるように内部的に処理される。ErrorCut番号 を保持する。
エラー isFail(), isError() ErrorCut機能によりパース失敗がエラーに変換されたもの。ErrorCut番号を保持する。内部的に ErrorMessage()を呼び出し、その後は致命的エラー状態になる。
致命的エラー isFail(), isError(), isFatalError() パースもエラー処理もこれ以上行わないで終了させる状態。 _FATAL_PARSER_ERROR

Application


Applicationクラスはユーザーが任意に定義する。
action()SyntaxTree を処理した結果を Application オブジェクトに格納する。

Number で解析したものを整数にしたければ、
struct Application {
   int x;
};
として
void P_Number::action(SyntaxTree* st, Application* ap)
{
    (整数に変換する処理)
    ap->x = (結果の整数)
}
とすれば良いだろう。



Token使用時の注意


Token機能('token')を使う場合は、ユーザーが NotTokenPred ルールを定義しなければならない。
1文字先読みして、それがTokenの一部ではない事を示すルールを定義する。
入力を消費しないようにする事。 !& を使えば入力を消費しない。
以下の定義が多くの場合に有用であろう。
NotTokenPred <- !([a-z]/[A-Z]/[0-9]/"_")


全体の流れ


  1. Applicationクラスを定義する
  2. InputBufferを生成する
  3. Parser::initialize()を呼ぶ
  4. パースしたいルールの parse() を呼ぶ
    結果が
  5. Applicationオブジェクトを用意し、SyntaxTreeオ ブジェクトの action() を呼ぶと、対応するパーサの action() が呼ばれる。
    結果が Application オブジェクトに格納される。
  6. 不要になった SyntaxTreeApplication オブジェクトは delete する。
  7. 最後に Parser::finalize() を呼ぶ。これで、生成された Parser は全て delete される。逆にユーザーは自分で new したものであっても Parser を自分で delete してはいけない。

サンプル


簡単な整数の四則演算ができるサンプルを sample 以下に置いたので、詳しくは sample/calc.peg と sample/calc.cpp を見て欲しい。
宝剣自身も参考になる。houken/peg.peg と houken/PegParserAction.cpp, houken/main.cpp を見ると良い。