プログラミングガイド


パーサの生成

PEG文法定義ファイルからパーサを生成するには、houkenコマンドに定義ファイルを与える。
houken sample.peg
すると、5つのファイルが生成される。
sample.tab.h 自動生成されたパーサのヘッダファイル
sample.tab.cpp 自動生成されたパーサのソースファイル
sample.tab.tmpl ユーザが実装すべきアクション関数のテンプレート。これをコピーして中身を実装していくと良い。
sample.tab.up A <% e ルールにより自動生成を停止された、ユーザが実装すべきパーサ関数のテンプレート。これをコピーして中身を実装し ていくと良い。
sample.tab.ua 宝剣ソースコード中で使用するアクション関数の宣言をまとめたヘッダファイル。これを userActionFuncDef.h にコピーすると良い。

これらを元に実装していく。
この他に、システム依存の処理を行う sysDep.hsysDep.cpp を用意する。

parse関数

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


アクション関数

peg文法定義ファイル中で $action にて指定したアクション関数をユーザは実装する。

$action  action(Application*)
Number <- [0-9]+

というルールを記述すると、SyntaxTreeクラスにメンバ関数
virtual void SyntaxTree::action(Application*)
が宣言される。
Parserクラスを継承したP_Numberク ラスと、SyntaxTreeクラスを継承したST_Numberク ラスが定義され、メンバ関数
void ST_Number::action(Application*)

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

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

アクション関数は、パースが正常終了したらその結果のSyntaxTreeから呼び出すようにして使う。
void ST_Number::action(Application* ap) {
  // 処理して結果を ap に入れる
}

SyntaxTree* st = p_Number->parse();  // p_Number からは ST_Number オブジェクトが生成される
if (st->isValidTree()) {
  Application app;
  st->action(&app);    // ST_Number::action() が呼び出される
  // 結果が app に入っている
}

errorMessage関数

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

A <- B $3 C

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


uParse関数


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

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"))

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

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



Token使用時の注意

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


メインプログラムの流れ

  1. InputBufferを生成し、Houken::gpInp に代入する。
  2. Parser::initialize()を呼ぶ
  3. パースしたいルールの parse() を呼ぶ
    結果が
  4. SyntaxTreeオ ブジェクトのアクション関数を呼ぶ。
  5. 不要になった SyntaxTree やユーザーが使用したオブジェクトは delete する。
  6. 最後に Parser::finalize() と SyntaxTree::finalize() を呼ぶ。これで、生成された ParserSyntaxTree は全て delete される。ユーザーは自分で new したものであっても Parser を自分で delete してはいけない。

サンプル

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