1

I am attempting to create generic parser-elements using qi as I unfortunately (MSVC must be supported) can not use X3. The idea is to have a templated struct:

template<class T> struct parse_type;

Which I could use like this:

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

or specialise like this

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

The primary purpose is to allow for easy parsing of e.g. std::tuple, boost::optional and boost::variant (The last one can not be automatic due to the greedy nature of qi).

I would appreciate feedback as to how approach this. Currently I base my struct on qi::grammar, but grammar is not supported in X3 and I would like to use X3 when MSVC compiles this, and I am also a little bit uncomfortable with having to provide the skipper. An alternative would be to have a static function in parse_type that returns the appropriate rule. I am considering if this is a cleaner approach?

Any feedback will be appreciated.

Update2: Replaced code-snippet with compilable example that fails at runtime. Here is the code:

#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");
}

The code fails in boost/function_template.hpp line 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);
}

This code is a member function in boost::function4 ,boost::fusion::vector0 > & ,boost::spirit::unused_type const&> and the problem is that get_vtable returns an invalid pointer.

user3721426
  • 273
  • 2
  • 8

1 Answers1

3

Your main problem is that the copy constructor for qi::rule takes a reference to the original rule, which in your case is a local variable. One way you can avoid this problem is by using qi::rule's copy member function but this requires changing slightly the return type of your specialization of ps_rule.

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

Once you do that, the same problem arises with your ps_rule<int> even though it seemed to work in isolation. You could do something analogous but in this case the rule is not required, it would be better (even from a performance point of view) to just use something like:

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: You can save lots of specializations if you use Spirit's own machinery to create parsers automatically in your primary template. Here is an example.

llonesmiz
  • 155
  • 2
  • 11
  • 20
  • Great! Thank you very much for your help. I had looked into copy, but did not get the return type correct. – user3721426 Aug 11 '16 at 10:15
  • I also was sort of aware of the auto parser, but only skimmed the documentation and thus did not realise that I could specialise them myself. I will pursue this approach and remove the ps_rule.unless I get problems wrt some partial specialisations. – user3721426 Aug 11 '16 at 10:26