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()
に渡すとパースできます。
次回
セマンティックアクションやります。