|
|
ツリー |
![]() |
![]() |
![]() |
構文解析木は、文法に一致した構造で入力をメモリ内に表現したものである。
セマンティックアクションの代わりに構文解析木を用いると、以下のような利点がある:
例
ツリーを使ってみようと思われたところで、どのように使うのか例を挙げよう。 如何に容易に使えるかが分かるだろう。 それでは、伝統(と、この文書の他の部分)に倣って、電卓を作っていこう。 以下はその文法である:
integer
= lexeme_d[ token_node_d[ (!ch_p('-') >> +digit_p) ] ]
;
factor
= integer
| '(' >> expression >> ')'
| ('-' >> factor)
;
term
= factor
>> *( ('*' >> factor)
| ('/' >> factor)
)
;
expression
= term
>> *( ('+' >> term)
| ('-' >> term)
)
;
さて、この文法でただ一つ他と異なるのは token_node_d ディレクティブであることに気づくだろう。 このディレクティブは、 integer ルールがその全ての入力を一つのノードに集めるようにする。 token_node_d がなければ、それぞれの文字はそれ自身のノードになる。 すぐあとで見ることになるが、すべての入力が一つのノードにある場合、その入力を int に変換するのは容易である。 ツリーを作成するために parse は以下のようになる:
tree_parse_info<> info = pt_parse(first, expression);
pt_parse() は parse() に似ている。 4つのオーバーロードがある: 二つは first と last のイテレータの組のためので、もう二つは文字列用である。 そのうち二つはスキップパーサを取り、他の二つは取らない。
tree_parse_info 構造体は、 trees と呼ばれる一つの追加のデータメンバがある以外は parse_info 構造体と同じ情報を持っている。 構文解析が完了すると、 trees は構文解析木を持つことになる。
そのツリーで入力を評価するには以下のようにする:
if (info.full)
{
cout << "parsing succeeded\n";
cout << "result = " << evaluate(info) << "\n\n";
}
evaluate() はどこから出てきたかって? Spirit の一部なのか? 残念ながら、違う。 evaluate() はサンプルの一部でしかない。以下に示す:
long evaluate(const tree_parse_info<>& info)
{
return eval_expression(info.trees.begin());
}
これでお分かりだろうが、 evaluate() は単に info.trees の begin() イテレータを渡して eval_expression() を呼んでいるだけだ。 さて以下は例の残りである:
// これらは話を単純にするための typedef である
typedef char const* iterator_t;
typedef parse_tree_match<iterator_t> parse_tree_match_t;
typedef parse_tree_match_t::const_tree_iterator iter_t;
// これらは我々が用いる関数のプロトタイプである。 各文法ルールについて関数一つ。
long evaluate(const tree_parse_info<>& info);
long eval_expression(iter_t const& i);
long eval_term(iter_t const& i);
long eval_factor(iter_t const& i);
long eval_integer(iter_t const& i);
// i はその expression ルールによって作成されたノードを指す
long eval_expression(iter_t const& i)
{
// 最初の子は term を指しているので、 eval_term が呼び出される
iter_t chi = i->children.begin();
long lhs = eval_term(chi);
for (++chi; chi != i->children.end(); ++chi)
{
// 次のノードは演算子を指す。 演算子のテキストは
// ( vector<char> である) value に格納される
char op = *(chi->value.begin());
++chi;
long rhs = eval_term(chi);
if (op == '+')
lhs += rhs;
else if (op == '-')
lhs -= rhs;
else
assert(0);
}
return lhs;
}
long eval_term(iter_t const& i)
{
// ... 完全な例は parse_tree_calc1.cpp を参照
// ( eval_expression() と非常によく似ている) ...
}
long eval_factor(iter_t const& i)
{
// ... ここも、全ての詳細を知りたいなら parse_tree_calc1.cpp を参照すること...
}
long eval_integer(iter_t const& i)
{
// string に対する範囲コンストラクタを使用
string integer(i->value.begin(), i->value.end());
// string を整数に変換
return strtol(integer.c_str(), 0, 10);
}
で、ご覧になったものはお気に召しただろうが、おそらく構文解析木は扱いが難しすぎると考えたのではないだろうか? もうあと二つか三つのディレクティブを使って抽象構文木(ast)を生成し、評価するコードの総量を少なくとも 50% 削減できる。 だからぐずぐずせずに、 ast による電卓の文法を示そう:
integer
= leaf_node_d[ lexeme_d[ (!ch_p('-') >> +digit_p) ] ]
;
factor
= integer
| inner_node_d[ch_p('(') >> expression >> ch_p(')')]
| (root_node_d[ch_p('-')] >> factor)
;
term
= factor
>> *( (root_node_d[ch_p('*')] >> factor)
| (root_node_d[ch_p('/')] >> factor)
)
;
expression
= term
>> *( (root_node_d[ch_p('+')] >> term)
| (root_node_d[ch_p('-')] >> term)
)
;
構文解析木文法と異なる箇所は赤い太字でハイライトされている。 式を評価するとき、実際には括弧は考慮しないので、 inner_node_d ディレクティブを使ってその内側のパーサが生成した first と last のノードを破棄された状態にする。 root_node_d ディレクティブが ast 生成の鍵である。 root_node_d の内側のパーサが生成したノードはルートノードとしてマークされる。 ルートノードが生成されると、それは同じルールによって生成された他のノードのルートまたは親ノードとなる。
構文解析を開始し ast を生成するためには、 pt_parse 関数によく似た ast_parse 関数を用いなければならない。
tree_parse_info<> info = ast_parse(first, expression);
以下は eval_expression 関数である ( ast を処理するために必要な関数は4つではなく1つだけである点に注目して欲しい):
long eval_expression(iter_t const& i)
{
if (i->value.id() == parser_id(&integer))
{
// string を整数に変換
string integer(i->value.begin(), i->value.end());
return strtol(integer.c_str(), 0, 10);
}
else if (i->value.id() == parser_id(&factor))
{
// factor は単項マイナスだけができる
return - eval_expression(i->children.begin());
}
else if (i->value.id() == parser_id(&term))
{
if (*i->value.begin() == '*')
{
return eval_expression(i->children.begin()) *
eval_expression(i->children.begin()+1);
}
else if (*i->value.begin() == '/')
{
return eval_expression(i->children.begin()) /
eval_expression(i->children.begin()+1);
}
}
else if (i->value.id() == parser_id(&expression))
{
if (*i->value.begin() == '+')
{
return eval_expression(i->children.begin()) +
eval_expression(i->children.begin()+1);
}
else if (*i->value.begin() == '-')
{
return eval_expression(i->children.begin()) -
eval_expression(i->children.begin()+1);
}
}
return 0;
}
動作する例の全体は ast_calc.cpp (libs/spriti/example/fundamenal/calc ディレクトリ)である。 あなたのツリーに対する食欲を刺激するのに十分であることを祈る。 より本質的な詳細については、この章の残りを読み進めて欲しい。
構文木を作成するために、以下の4つの非メンバ関数の一つを呼ぶことができる:
template <typename IteratorT, typename ParserT, typename SkipT>
tree_parse_info<IteratorT>
pt_parse(
IteratorT const& first_,
IteratorT const& last_,
parser<ParserT> const& parser,
SkipT const& skip_);
template <typename IteratorT, typename ParserT>
tree_parse_info<IteratorT>
pt_parse(
IteratorT const& first_,
IteratorT const& last,
parser<ParserT> const& parser);
template <typename CharT, typename ParserT, typename SkipT>
tree_parse_info<CharT const*>
pt_parse(
CharT const* str,
parser<ParserT> const& parser,
SkipT const& skip);
template <typename CharT, typename ParserT>
tree_parse_info<CharT const*>
pt_parse(
CharT const* str,
parser<ParserT> const& parser);
抽象構文木(短く言えば ast )を作成するために、4つの非メンバ関数の一つを呼び出すことができる:
template <typename IteratorT, typename ParserT, typename SkipT>
tree_parse_info<IteratorT>
ast_parse(
IteratorT const& first_,
IteratorT const& last_,
parser<ParserT> const& parser,
SkipT const& skip_);
template <typename IteratorT, typename ParserT>
tree_parse_info<IteratorT>
ast_parse(
IteratorT const& first_,
IteratorT const& last,
parser<ParserT> const& parser);
template <typename CharT, typename ParserT, typename SkipT>
tree_parse_info<CharT const*>
ast_parse(
CharT const* str,
parser<ParserT> const& parser,
SkipT const& skip);
template <typename CharT, typename ParserT>
tree_parse_info<CharT const*>
ast_parse(
CharT const* str,
parser<ParserT> const& parser);
pt_parse と ast_parse から返される tree_parse_info 構造体には構文解析に関する情報が収められている:
template <typename IteratorT = char const*>
struct tree_parse_info
{
IteratorT stop;
bool match;
bool full;
unsigned length;
typename tree_match<IteratorT>::container_t trees;
};
| tree_parse_info | |||||||||
| stop | 構文解析した最後の位置を指す(すなわち、構文解析処理は入力をこの位置まで処理した)。 | ||||||||
| match | 構文解析が成功したならば真。これは完全一致(パーサは入力を全て消費した)か 部分一致(パーサは入力の一部のみを消費した)のいずれかである。 | ||||||||
| full | 完全一致した場合に真(すなわち、パーサは入力を全て消費した)。 | ||||||||
| length | パーサによって消費された文字数。これは(部分または完全を問わず)一致した場合のみ有効である。 | ||||||||
| trees | ツリーのルートノードを持つ。 | ||||||||
Spirit がツリーを生成している場合、パーサの parse() メンバ関数は一致判定オブジェクトの代わりに tree_mach オブジェクトを返す。 tree_match は三つのテンプレートパラメータを持つ。 第一はイテレータ型であり、デフォルトでは char const* である。 第二はノードファクトリであり、デフォルトでは node_val_data_factory である。 第三はその一致判定オブジェクト(match)に格納された属性の型である。 tree_match は tree_node オブジェクトのコンテナ( std::vector )であるメンバ変数 trees を持っている。 効率の理由から、 tree_match がコピーされる時 trees はコピーされず新しいオブジェクトに移され、ソースオブジェクトには空のツリーコンテナが残る。 tree_match は match クラスと同じインタフェースをサポートする。 つまり、それは operator bool() を持っているので一致判定に成功したかどうかチェックできる。もし一致したなら、 length() 関数によって一致した長さを問い合わせることができる。 クラスは以下のインタフェースを持つ:
template <typename IteratorT = char const*, typename NodeFactoryT = node_val_data_factory<> >
struct tree_match
{
typedef typename NodeFactoryT::template factory<IteratorT> node_factory_t;
typedef typename node_factory_t::node_t parse_node_t;
typedef tree_node<parse_node_t> node_t;
typedef typename node_t::children_t container_t;
typedef typename container_t::iterator tree_iterator;
typedef typename container_t::const_iterator const_tree_iterator;
tree_match();
tree_match(unsigned length, parse_node_t const& n);
tree_match(tree_match const& x);
explicit tree_match(match const& x);
tree_match& operator=(tree_match const& x);
void swap(tree_match& x);
operator bool() const;
int length() const;
container_t trees;
};
parse が成功して完了したとき、 trees データメンバはそのツリーのルートノードを持つ。
それでなぜ vector なのか?と不思議に思うかもしれない。 その答えは、一つには実装の目的のためであり、 またもし文法でルールを使わないなら trees は子を持たないノードのシーケンスを持つことになるからである。 |
spirt がツリーを作ってしまえば、あとは通常の parse と同様である:
tree_match<> hit = expression.parse(tree_scanner);
if (hit)
process_tree_root(hit.trees[0]); // そのツリーで何かする
pt_parse か ast_parse を呼んでツリーを作成し、 そのツリーのルートノードを持つ tree_parse_info を得たなら、次はそのツリーで何かする必要がある。 tree_parse_info のデータメンバ trees は std::vector<tree_node> である。 tree_node はツリー構造を提供する。 そのクラスは T という名前のひとつのテンプレートパラメータを持つ。 tree_node は型 T のインスタンスを持つ。 また、そのノードの子である std::vector<tree_node<T> > も持っている。 そのクラスは次のように見える:
template <typename T>
struct tree_node
{
typedef T parse_node_t;
typedef std::vector<tree_node<T> > children_t;
typedef typename children_t::iterator tree_iterator;
typedef typename children_t::const_iterator const_tree_iterator;
T value;
children_t children;
tree_node();
explicit tree_node(T const& v);
tree_node(T const& v, children_t const& c);
void swap(tree_node<T>& x);
};
このクラスは単に、そのツリーに格納されているデータからツリーフレームワークを分けるために用いられる。 型汎用(generic)のノードであり、あらゆる型がその内部に保持され、データメンバ value によってアクセスされる。 T のデフォルトの型は node_val_data である。
node_val_data クラスは各ノードに関する実際の情報を持つ。 これは、構文解析されたテキストまたはトークンシーケンスと、 そのノードを作成したルールを示す id と、 そのノードがルートノードとしてマークされたかどうかを示すブール型(boolean)のフラグと、 オプショナルなユーザ定義値とを含む。 これがそのインタフェースである:
template <typename IteratorT = char const*, typename ValueT = nil_t>
struct node_val_data
{
typedef typename std::iterator_traits<IteratorT>::value_type value_type;
typedef std::vector<value_type> container_t;
typedef typename container_t::iterator iterator_t;
typedef typename container_t::const_iterator const_iterator_t;
node_val_data();
node_val_data(IteratorT const& _first, IteratorT const& _last);
template <typename IteratorT2>
node_val_data(IteratorT2 const& _first, IteratorT2 const& _last);
void swap(node_val_data& x);
container_t::iterator begin();
container_t::const_iterator begin() const;
container_t::iterator end();
container_t::const_iterator end() const;
bool is_root() const;
void is_root(bool b);
parser_id id() const;
void id(parser_id r);
ValueT const& value() const;
void value(ValueT const& v);
};
あるノードがあるルールによって生成されると、それは id の集合を持つ。 各々のルールはある id を持つ。それはそのルールによって生成された全てのノードの集合である。 id の型は parser_id である。 各ルールのデフォルトの id はそのルールのアドレス(を整数に変換したもの)に設定される。 しかし、ツリーを処理するコードはそのルールにアクセスしないかも知れないので、これが常に一番便利だとは限らない。 そこで、ルールの set_id(parser_id) を呼び出して各ルールで用いられるデフォルトの id を上書きすることができる。 その後、ツリーを処理する際には、そのノードを作成したのがどのルールか知るために node_val_data::id() を呼び出すことができる。
ツリーはルールによって構成される。 それぞれのルールはツリーの新しいレベルを作成する。 あるルールに接続された全てのパーサは一致判定に成功した場合にノードを作成する。 これらのノードは、そのルールによって作成されたノードの子となる。 従って、次のコード:
rule_t myrule = ch_p('a') >> ',' >> *ch_p('b');
char const* input = "a,bb";
scanner_t scanner(input, input + strlen(input));
tree_match<> m = myrule.parse(scanner);
が実行されると、このコードは tree_math のインスタンス m を返す。 m.trees[0] は以下のようなツリーを持つ:
![]() |
ルートノードはテキストを持たず、 myrule のアドレスが id として設定される。 それは四つの子を持つ。 各々の子の id は myrule のアドレスに設定され、図に示すようなテキストを持ち、 そして子を持たない。
ast_parse が呼び出される時、 ツリーは違った方法で生成される。 それはほとんどにおいて構文解析木を生成する時と同様に動作する。 違いは、あるルールが一つのサブノードのみを生成した際に生じる。 新しいレベルを作成する代わりに、 ast_parse はただ既存のノードを取り去る。 従って、次のコード:
rule_t myrule = ch_p('a');
char const* input = "a";
ast_scanner_t scanner(input, input+strlen(input));
tree_match<> m = myrule.parse(scanner);
は 'a' を含む単一のノードを生成する。 もし ast_match_policy の代わりに tree_match_policy が 用いられていたなら、そのツリーは次のように見えるだろう:
![]() |
ast_match_policy は、単なる通り抜けルール(pass-through rule)である中間ルールレベルを除去する効果がある。 抽象構文木を生成するにはこれだけでは十分でなく、 root_node_d も必要となる。 root_node_d については後に説明する。
もしあなたのアプリケーションで構文解析木と ast の振る舞いを混ぜて一致判定したいなら、 gen_pt_node_d[] ディレクティブと gen_ast_node_d[] ディレクティブを使うことが出来る。 構文解析処理が gen_pt_node_d ディレクティブを通過すると、 構文解析木を作成する振る舞いが起動する。 gen_ast_node_d ディレクティブが使われた場合は、その内部のパーサは ast の振る舞いを用いるツリーが生成される。 もしこれらのディレクティブの内側でルールを使うなら、 そのルールがどのように宣言されているかに注意を払わなければならない。 スキャナのマッチポリシーは期待する振る舞いと合致していなければならない。 ルールを避けてプリミティブパーサや文法を使うなら、問題はない。
ツリーの生成を制御するために使われるディレクティブがもういくつかある。
これらのディレクティブはツリーの生成にのみ効果を持つ。
それ以外には、効果がない。
このディレクティブは、 その内側のパーサに使われるスキャナのマッチポリシーを更新するという点で、 gen_pt_node_d や gen_ast_node_d とよく似ている。 その名が示すように、それはツリーの生成をせず、完全に停止する。 文法の一部がツリーに必要でないなら、これは便利である。 例えばキーワード、演算子( * 、 - 、 && 等)。 これらをツリーから削除することで、 メモリ使用量と構文解析時間の両方を低くすることが出来る。 このディレクティブは gen_pt_node_d や gen_ast_node_d が従うルールと同じ要求事項を持つ。 no_node_d[] の使い方の例については、 使用例のファイル xml_grammar.hpp ( libs/spirit/example/application/xml ディレクトリにある) をご覧頂きたい。
このディレクティブは no_node_d と似た目的を持つが、異なる動作をする。 それはスキャナのマッチポリシーを切り替えないので、 その内側のパーサはノードを生成し続ける。 生成されたノードは破棄され、ツリーの中には現れない。 discard_node_d を使うと no_node_d より遅くなるが、 その内側のどんなルールに対しても異なるルール型を指定しなければならないという欠点に悩まされることはない。
leaf_node_d と token_node_d はどちらも同じ動作をする。 それらは、その内部のパーサによって生成された全てのノードをグループにまとめる。 これらのディレクティブの内側ではルールを使われるべきでない点に注意。
以下のルールは
rule_t integer = !ch_p('-') >> *(range_p('0', '9'));
整数の id と構文解析された各文字に対する子ノードを持ったルートノードを生成する。
以下は
rule_t integer = token_node_d[ !ch_p('-') >> *(range_p('0', '9')) ];
整数全体を含むただ一つの子ノードだけを持つルートノードを生成する。
これはリストから区切り文字を削除するのに便利である。 それは偶数位置にある全てのノードを破棄する。従って以下のルールは
rule_t intlist = infix_node_d[ integer >> *(',' >> integer) ];
コンマノードをすべて破棄し、整数ノードは全て残す。
これは最初に生成されたノードを破棄する。
これは最後に生成されたノードを破棄する。
これは最初と最後に生成されたノードを破棄する。
root_node_d ディレクティブは ast の生成を助ける。 構文解析木を生成する際には影響しない。 あるパーサが root_node_d の内側にあるとき、 それが生成するノードはルートノードとしてマークされる。 これは、生成されているノードのリストにそれが追加される際の扱われ方に影響を及ぼす。 以下は一例である:
rule_t add = integer >> *(root_node_d[ ch_p('+') ] >> integer);
5+6 を構文解析すると、以下のツリーが生成される:
![]() |
1+2+3 を構文解析すると、以下が生成される:
![]() |
新しいノードが作成される際には、どのようにツリーを生成するか決定するために以下のようなルールが用いられる:
a は先に生成されたノードとする。
b は新しいノードとする。
もし b がルートならば、
b の子は a + b の前の子となる。
a は b のあたら最初の子となる。
もし a がルートノードでかつ b がそうでないならば、
b は a の最後の子となる。
どちらでもなければ、
a と b は兄弟となる。
構文解析処理が現在のルールを離れた後、頂上のノードのルートノードフラグは降ろされる。 これは root_node_d ディレクティブが現在のルールに対してのみ影響を及ぼすことを意味している。
例 ast_calc.cpp ( libs/spirit/example/fundamental/calc ディレクトリにある)は
root_node_d と ast_parse の使い方を例示している。
parse_tree_iterator クラスは spirit を用いてツリーを構文解析できるようにする。 このクラスは、リーフノードのトークンを作成されたのと同じ順に舐める。 parse_tree_iterator は ParseTreeMatchT でテンプレート化されている。 それはツリーのコンテナと、開始すべき位置から構築される。 以下は使い方の例である:
rule_t myrule = ch_p('a');
char const* input = "a";
// 構文解析木の生成
tree_parse_info<> i = pt_parse(input, myrule);
typedef parse_tree_iterator<tree_match<> > parse_tree_iterator_t;
// ツリーから離れて働く first と last のイテレータを作成
parse_tree_iterator_t first(i.trees, i.trees.begin());
parse_tree_iterator_t last();
// ツリーの構文解析
rule<parse_tree_iterator_t> tree_parser =...
tree_parser.parse(first, last);
parse_tree_iterator の具体的な使い方の例については、 例の cpp ファイル: cpp_expression_grammar.cpp と cpp_macromap.cpp を見て欲しい。
node_val_data は値を持つ。 デフォルトではそれは空のクラス void_t を持つ。 テンプレートパラメータを用いて型を指定することができ、その後あらゆるノードにその型で格納される。 型は、デフォルトコンストラクト可能かつ代入可能でなければならない。 その値を取得・設定するには
ValueT node_val_data::value();
および
void node_val_data::value(Value const& value);
を用いる。
その値の型を指定するには、 デフォルトのものとは異なる node_val_data ファクトリを用いなければならない。 node_val_data ファクトリはスキャナポリシーのテンプレートパラメータの一つなので、 カスタムスキャナを使うことになる。 pt_parse と ast_parse は設定済みのスキャナを使うので、自作したものを指定しなければならない。 以下は 各 node_val_data に double を格納し、アクセスするやり方の例である。
typedef node_val_data_factory<double> factory_t;
typedef tree_match<iterator_t, factory_t> match_t;
typedef ast_match_policy<iterator_t, factory_t> match_policy_t;
typedef scanner<iterator_t,scanner_policies<iter_policy_t,match_policy_t>>scanner_t;
typedef rule<scanner_t> rule_t;
rule_t r =...;scanner_t scan = scanner_t(first, last);match_t hit = r.parse(scan);
// ルートノードの double にアクセス:
double d = hit.trees.begin()->value.value();
さてさて、「それぞれのノードに値を持たせられるのは素晴らしいけど、 それを設定する方法なんて知らないよ?」と戸惑っていらっしゃるだろう。 そう、 access_node_d はまさにその為のものである。 access_node_d はディレクティブであり、次のようにアクションを接続できる:
access_node[...some parsers...][my_action()]
接続されたアクションは三つのパラメータ: そのパーサによって生成されたルートノードへの参照、 現在の first および last イテレータ、を渡される。 そのアクションはノードの値を設定することが出来る。
ファクトリを設定することで、どの型のノードをどのように作成するか制御することができる。 三つのファクトリ: node_val_data_factory 、 node_all_val_data_factory それに node_iter_data_factory があらかじめ定義されている。 独自のノード型をサポートする独自のファクトリを作成することも出来る。
以下はファクトリの使い方の例である:
typedef spirit::void_t value_t;
typedef node_val_data_factory<value_t> factory_t;
typedef tree_match<iterator_t, factory_t> match_t;
typedef ast_match_policy<iterator_t, factory_t> match_policy_t;
typedef scanner<iterator_t,scanner_policies<iter_policy_t,match_policy_t>>scanner_t;
typedef rule<scanner_t> rule_t;
rule_t r =...;scanner_t scan = scanner_t(first, last);match_t hit = r.parse(scan);
これはデフォルトのファクトリである。 node_val_data ノードを作成する。 リーフノードは一致したテキストのコピーを持ち、中間ノードは持たない。 node_val_data_factory は一つのテンプレートパラメータ: ValueT を持つ。 ValueT は node_val_data に格納される値の型を指定する。
このファクトリも node_val_data を作成する。 node_val_data_factory との違いは あらゆる ノードが、それに至るすべてのテキストを持つ、ということである。 これは、ルートノードは構文解析された入力シーケンス全体のコピーを持つということである。 node_all_val_data_factory は一つのテンプレートパラメータ ValueT を持つ。 ValueT は node_val_data に格納される値の型を指定する。
このファクトリは parse_tree_iter_node を作成する。 このノードはコピーを作る代わりに入力シーケンスへのイテレータを保持する。 使用するメモリはずっと少ない。 しかしながら、入力シーケンスはツリーの寿命の間有効であり続けなければならなず、 この型のノードで multi_pass イテレータを使うことは良い考えとは言えない。 ツリーの全てのレベルが begin と end のイテレータを持つ。 node_iter_data_factory は一つのテンプレートパラメータ: ValueT を持つ。 ValueT は node_val_data に格納される値の型を指定する。
独自のファクトリを作成することができる。 それはこのように見えるべきである:
class my_factory
{
public:
// この内部クラスはファクトリがテンプレートテンプレートパラメータ
// を模倣できるようにする為のものである
template <typename IteratorT>
class factory
{
public:
// これがあなたのノード型である
typedef my_node_type node_t;
static node_t create_node(
IteratorT const& first, IteratorT const& last, bool is_leaf_node)
{
// ノードを作成して返す。
}
// この関数は leaf_node ディレクティブと token_node ディレクティブに利用される。
// それらを使わないなら、この関数は実装せずに
// 放っておいてもよい。
template <typename ContainerT>
static node_t group_nodes(ContainerT const& nodes)
{
// 全てのノードを一つのグループにまとめて返す。
}
};
};
// my_factoryを使うための typedef
typedef my_factory factory_t;
typedef tree_match<iterator_t, factory_t> match_t;
typedef tree_match_policy<iterator_t, factory_t> match_policy_t;
// 文法の代わりにルールを使うための typedeftypedef scanner<iterator_t,scanner_policies<iter_policy_t,match_policy_t>>scanner_t;
typedef rule<scanner_t> rule_t;
![]() |
![]() |
![]() |
Copyright © 2001-2002 Daniel C. Nuffer
Permission to copy, use, modify, sell and distribute this document
is granted provided this copyright notice appears in all copies. This document
is provided "as is" without express or implied warranty, and with
no claim as to its suitability for any purpose.
Japanese Translation Copyright © 2003-2004 Kent.N
オリジナルの、及びこの著作権表示が全ての複製の中に現れる限り、この文書の複製、利用、変更、販売そして配布を認める。このドキュメントは「あるがまま」に提供されており、いかなる明示的、暗黙的保証も行わない。また、いかなる目的に対しても、その利用が適していることを関知しない。
このドキュメントの対象: Boost Version 1.30.0
最新版ドキュメント(英語)