C++で構文解析 Boost.Spirit.Qi #3
今回はQiの使いやすさを向上するものを紹介します。
構造体をtuple
として扱えるようにする
パーサに>>
演算子を適用すると、属性がtuple<A, B>
になることは前回説明しました。でもtuple
だと後々ゴチャゴチャしすぎるので、
struct Result {
A a;
B b;
};
みたいなのに突っ込めたらとても分かりやすくなってGoodです。Boost.Fusionを使うと出来ます。
// 構造体定義(グローバルでなくてもOK) struct Result { int x; double y; }; // Fusionのマクロ適用(グローバルスコープで行うこと) BOOST_FUSION_ADAPT_STRUCT(Result, x, y)
BOOST_FUSION_ADAPT_STRUCT
マクロには、最初に構造体、それ以降にメンバ名を指定します。これを使うと、Qiが構造体をtuple
とみなして扱ってくれます(正確には「構造体をFusionシーケンスに適応させる」が正しい。詳しく知りたい人はググってください)。
std::string str("12345, 67.89"); auto itr = str.begin(), end = str.end(); Result result; // 結果の格納先 bool success = qi::phrase_parse(itr, end, qi::int_ >> qi::lit(",") >> qi::double_, qi::space, result); if (success && itr == end) { std::cout << result.x << std::endl; // 構造体メンバでアクセスできる std::cout << result.y << std::endl; }
こんな風に結果の変数にアクセスできます。メンバには当然名前がついているのでパースした後の処理がとてもやりやすくなります。
余談: 本当は、属性tuple
はFusionコンテナであるboost::fusion::vector
のことです。前回std::tuple
使用時にインクルードしたboost/fusion/include/std_tuple.hpp
ファイルは、Fusionシーケンスに適応させるためのヘッダファイルとなっています。
rule
とgrammar
rule
を使用すると、文法に名前をつけることができます。また、grammar
を使用するとrule
をまとめることができます。
まず、rule
の使用例から。
qi::rule<std::string::iterator, int(), qi::space_type> int_rule; int_rule = qi::int_; std::string str("12345, 67.89"); auto itr = str.begin(), end = str.end(); int result; qi::phrase_parse(itr, end, int_rule, qi::space, result);
qi::rule
のテンプレート引数には、パースする文字列のイテレータの型、属性の型、スキップするパーサの型を指定します。属性の型は、後ろに括弧が必要です。qi::space_type
は、qi::space
パーサがgrammar
(後述)の中に定義されたものだと思ってください。スキップしない(qi::phrase_parse()
を使わない)ならこれは省略可能です。
これでint_rule
と名前のついたパーサができました。これは以下のように
int_rule >> qi::lit(",") >> int_rule
既存のものと組み合わせて使用することができます。
これらrule
をまとめるgrammar
というものがあります。qi::grammar
を継承した構造体(クラス)を作成してまとめ上げます。
// 結果の型の定義とマクロ適用 struct Result { int x; double y; }; BOOST_FUSION_ADAPT_STRUCT(Result, x, y) // grammar定義 template <typename Iterator> struct MyGrammar : public qi::grammar<Iterator, Result(), qi::space_type> { MyGrammar() : MyGrammar::base_type(expr) { expr = int_parser >> qi::lit(",") >> double_parser; int_parser = qi::int_; double_parser = qi::double_; } qi::rule<Iterator, Result(), qi::space_type> expr; qi::rule<Iterator, int(), qi::space_type> int_parser; qi::rule<Iterator, double(), qi::space_type> double_parser; }; // パース std::string str("12345, 3.14"); Result result; MyGrammar<std::string::iterator> parser; auto itr = str.begin(), end = str.end(); qi::phrase_parse(itr, end, parser, qi::space, result); std::cout << result.x << ", " << result.y << std::endl;
基底クラスqi::grammar
のテンプレート引数は、qi::rule
と同じです。イテレータの型はよくテンプレート化されます。ローカル変数にqi::rule
を宣言して、コンストラクタ内に文法定義を書きます。基底クラスのコンストラクタの引数は、最初にパースするべき文法を指定します(文法のルートのものとか、最初にパースする非終端記号とか言ったほうがわかりやすい?)。
パースするときに、このgrammar
のインスタンスを作成して、qi::parse()
またはqi::phrase_parse()
に渡すとパースできます。
次回
セマンティックアクションやります。