読者です 読者をやめる 読者になる 読者になる

qi::auto_で簡易パース

C++

qi::auto_は、各データごとに特殊化された方法で構文解析するパーサーです。
たとえば、intは以下のようにしてパースできます。

#include <boost/assert.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

template <class Rule, class Result>
bool parse(const std::string& s, const Rule& rule, Result& result)
{
    std::string::const_iterator first = s.begin();
    return qi::parse(first, s.end(), rule, result);
}

int main()
{
    int n;
    parse("123", qi::auto_, n);

    BOOST_ASSERT(n == 123);
}

ただ、これだけであればlexical_castとかを使えばいいので特にありがたみは感じられません。
qi::auto_は主に、これらの基本データ型のパースを基礎として、複合データ型やデータ構造をパースするのに使用します。


std::vectorにパースしてみましょう。

#include <iostream>
#include <vector>
#include <boost/spirit/include/qi.hpp>
#include <pstade/oven/identities.hpp>
#include <pstade/oven/io.hpp>

namespace qi = boost::spirit::qi;
namespace oven = pstade::oven;

template <class Rule, class Result>
bool parse(const std::string& s, const Rule& rule, Result& result)
{
    std::string::const_iterator first = s.begin();
    return qi::parse(first, s.end(), rule, result);
}

int main()
{
    {
        std::vector<int> v;
        parse("1 2 3", qi::auto_ % ' ', v);

        std::cout << (v | oven::identities) << std::endl;
    }
    {
        std::vector<int> v;
        parse("1,2,3", qi::auto_ % ',', v);

        std::cout << (v | oven::identities) << std::endl;
    }
    {
        std::vector<double> v;
        parse("1.2 2.3 3.4", qi::auto_ % ' ', v);

        std::cout << (v | oven::identities) << std::endl;
    }
}
{1,2,3}
{1,2,3}
{1.2,2.3,3.4}

区切り文字を指定するだけで、内部に入ってる要素を自動的に推論し、簡単にパースできています。
同じ方法で、std::listにもパースすることができます。


次に、ユーザー定義の型をパースしてみます。
ちょっとめんどくさいです。

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/struct.hpp>

namespace qi = boost::spirit::qi;

struct person {
    int id;
    std::string name;
    int age;
};

BOOST_FUSION_ADAPT_STRUCT(
    person,
    (int, id)
    (std::string, name)
    (int, age)
)

namespace boost { namespace spirit { namespace traits {
    template <>
    struct create_parser<person> {
        typedef proto::result_of::deep_copy<
            BOOST_TYPEOF(qi::int_ >> ',' >> *(qi::char_ - ',') >> ',' >> qi::int_)
        >::type type;

        static type call()
        {
            return proto::deep_copy(qi::int_ >> ',' >> *(qi::char_ - ',') >> ',' >> qi::int_);
       }
    };
}}}

template <class Rule, class Result>
bool parse(const std::string& s, const Rule& rule, Result& result)
{
    std::string::const_iterator first = s.begin();
    return qi::parse(first, s.end(), rule, result);
}

int main()
{
    person p;
    parse("123,Akira,25", qi::auto_, p);

    std::cout << p.id << std::endl;
    std::cout << p.name << std::endl;
    std::cout << p.age << std::endl;
}
123
Akira
25

ここでは、以下のことを行っています。

1. ユーザー定義型の定義
2. Qiのoperator>>()でパースするために型をFusionのシーケンスにアダプトする
3. boost::spirit::traits::create_parserを特殊化し、型のパース方法を定義する

この方法は、何度もパースが必要になるような場合や、ライブラリとして提供する場合に必要になるでしょう。



参照:
boost::qi::auto_