如何使用qi创建通用解析器?

时间:2016-08-10 12:26:38

标签: c++ boost-spirit boost-spirit-qi

我正在尝试使用qi创建通用解析器元素,因为我不幸(必须支持MSVC)不能使用X3。 想法是有一个模板结构:

template<class T> struct parse_type;

我可以这样使用:

template<class T> T from_string(std::string const& s)
{
    T res;
    parse_type<T> t;
    ...
    if (phrase_parse(...,parse_type<T>(),...,t))
}

或像这样专注

template<class T,class Alloc> 
struct parse_type<std::vector<T,Alloc>>
{
    // Parse a vector using rule '[' >> parse_type<T> % ',' > ']';
}

主要目的是允许简单地解析例如std :: tuple,boost :: optional和boost :: variant(由于qi的贪婪性,最后一个不能自动完成)。

我希望得到关于如何处理此问题的反馈。目前我的结构基于qi :: grammar,但X3不支持语法,我想在MSVC编译时使用X3,而且我也不得不提供队长。 另一种方法是在parse_type中使用一个返回适当规则的静态函数。我在考虑这是否更清洁?

我们将不胜感激。

Update2:将code-snippet替换为在运行时失败的可编译示例。这是代码:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>
#include <string>
#include <iostream>
#include <iostream>

//  Support to simplify
using iter = std::string::const_iterator;
void print(std::vector<int> const& v)
{
    std::cout << '[';
    for (auto i: v) std::cout  << i << ',';
    std::cout << "]";
}

namespace qi = boost::spirit::qi;

//  My rule factory - quite useless if you do not specialise
template<class T> struct ps_rule;

//  An example of using the factory
template<class T>
T from_string(std::string const& s)
{
    T result;
    iter first { std::begin(s) };
    auto rule = ps_rule<T>::get();
    phrase_parse(first,std::end(s),rule,qi::space,result);
    return result;
}

//  Specialising rule for int
template<>
struct ps_rule<int> 
{
    static qi::rule<iter,int()> get() { return qi::int_; } 
};

//  ... and for std::vector (where the elements must have rules)
template<class T,class Alloc>
struct ps_rule<std::vector<T,Alloc>>
{
    static qi::rule<iter,std::vector<T,Alloc>()> get()
    {
        qi::rule<iter,std::vector<T,Alloc>()> res;
        res.name("Vector");
        res =
                qi::lit('{')
            >>  ps_rule<T>::get() % ','
            >>  '}';
        return res;
    }
};

int main()
{
    //  This one works like a charm.
    std::cout << ((from_string<int>("100") == 100) ? "OK\n":"Failed\n");

    std::vector<int> v {1,2,3,4,5,6};

    //  This one fails
    std::cout << ((from_string<std::vector<int>>("{1,2,3,4,5,6}") == v) ? "OK\n":"Failed\n");
}

代码在boost / function_template.hpp第766行中失败:

result_type operator()(BOOST_FUNCTION_PARMS) const
{
  if (this->empty())
    boost::throw_exception(bad_function_call());

  return get_vtable()->invoker
           (this->functor BOOST_FUNCTION_COMMA BOOST_FUNCTION_ARGS);
}

此代码是boost :: function4中的成员函数                ,boost :: fusion :: vector0&gt; &安培;                ,boost :: spirit :: unused_type const&amp;&gt; 问题是get_vtable返回一个无效的指针。

1 个答案:

答案 0 :(得分:2)

您的主要问题是qi::rule的复制构造函数会引用原始规则,在您的情况下,它是一个局部变量。避免此问题的一种方法是使用qi::rule copy成员函数,但这需要稍微更改ps_rule专业化的返回类型。

static typename boost::proto::terminal<qi::rule<iter,std::vector<T,Alloc>()>>::type get()
{
    //[...] (same as before)
    return res.copy();
}

一旦你这样做,你的ps_rule<int>就会出现同样的问题,即使它似乎是孤立的。你可以做类似的事情,但在这种情况下,规则不是必需的,只是使用类似的东西会更好(甚至从性能的角度来看):

static qi::int_type get() { return qi::int_; }

Full Sample (Running on WandBox)

#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>

//  Support to simplify
using iter = std::string::const_iterator;
void print(std::vector<int> const& v)
{
    std::cout << '[';
    for (auto i: v) std::cout  << i << ',';
    std::cout << "]";
}

namespace qi = boost::spirit::qi;

//  My rule factory - quite useless if you do not specialise
template<class T> struct ps_rule;

//  An example of using the factory
template<class T>
T from_string(std::string const& s)
{
    T result;
    iter first { std::begin(s) };
    auto rule = ps_rule<T>::get();
    qi::phrase_parse(first,std::end(s),rule,qi::space,result);
    return result;
}

//  Specialising rule for int
template<>
struct ps_rule<int> 
{
    static qi::int_type get() { return qi::int_; } 
};


//  ... and for std::vector (where the elements must have rules)
template<class T,class Alloc>
struct ps_rule<std::vector<T,Alloc>>
{
    static typename boost::proto::terminal<qi::rule<iter,std::vector<T,Alloc>()>>::type get()
    {
        qi::rule<iter,std::vector<T,Alloc>()> res;
        res.name("Vector");
        res =
                qi::lit('{')
            >>  ps_rule<T>::get() % ','
            >>  '}';
        return res.copy();
    }
};

int main()
{
    //  This one works like a charm.
    std::cout << ((from_string<int>("100") == 100) ? "OK\n":"Failed\n");

    std::vector<int> v {1,2,3,4,5,6};

    std::cout << ((from_string<std::vector<int>>("{1,2,3,4,5,6}") == v) ? "OK\n":"Failed\n");

    std::vector<std::vector<int> > vv {{1,2,3},{4,5,6}};

    std::cout << ((from_string<std::vector<std::vector<int>>>("{{1,2,3},{4,5,6}}") == vv) ? "OK\n":"Failed\n");

}

PS:如果您在主模板中使用Spirit's own machinery to create parsers automatically,则可以节省大量专精。 Here is an example