Maikel Maikel - 1 year ago 85
C++ Question

Parsing pair of strings fails. Bad spirit x3 grammar

I would like to parse key-value pairs, mapping strings to strings. Since i want to support blocks of

{ ... }
for right hand sides i came up with a simple grammar to start with

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/std_pair.hpp>

namespace grammar::map
using namespace boost::spirit::x3;
auto expression = rule<class expression, std::pair<std::string, std::string>>{"expression"};

auto lhs = *(~char_('='));
auto rest = *(~char_(';'));
auto block = '{' >> *expression >> '}';

auto expression_def = lhs >> '=' >> (block | rest) >> ';';

But it fails to compile in combination, unless i change the attribute of expression to be

//! Transform a string into a key-value map of strings.
template <class M, class R>
requires InputRange<R>() && _ContainerLike<M>
&& Same<value_type_t<M>, std::pair<const std::string, std::string>>()
// && Assignable<std::pair<std::string, std::string>, value_type_t<M>>()
M parse(R&& range)
auto begin = rng::begin(range);
auto end = rng::end(range);
auto map = M{};
auto ret = x3::phrase_parse(begin, end, *grammar::map::expression, x3::space, map);
if (!ret)
throw std::runtime_error{"parse error"};
return map;

I get the error

boost/spirit/home/x3/support/traits/move_to.hpp:62:18: error: cannot convert ‘std::remove_reference<std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >&>::type {aka std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >}’ to ‘char’ in assignment
dest = std::move(src);

located from

boost/spirit/home/x3/support/traits/move_to.hpp: In instantiation of ‘void boost::spirit::x3::traits::detail::move_to_plain(Source&&, Dest&, mpl_::false_) [with Source = std::pair<std::__cxx11::basic_string<char>, std::__cxx11::basic_string<char> >; Dest = char; mpl_::false_ = mpl_::bool_<false>]’:

Same thing happens if try the following expressions

auto pair = std::pair<std::string, std::string>{};
auto ret = x3::phrase_parse(begin, end, grammar::map::expression, x3::space, map);

I am coming to it since a couple of days and can not find out how to do it properly. I appreciate any help... :^)

Tested it with boost-1.60{-62} and gcc 6.1.1, 6.2 and a more less recent trunk version.

jv_ jv_
Answer Source

Your problem is that you have define expression to have an attribute of pair<string,string>, but the synthesized attribute of lhs >> '=' >> (block|rest) >> ';' is synt_attr=tuple<string,variant<vector<synt_attr>,string>>>> which is basically synt_attr=pair<string,variant<map_of_value_type_synt_attr,string>>>. So you have at least two options depending on the result you want:

  • Change the synthesized attribute to be pair<string,string>. This is quite easy using the x3::raw directive (running on WandBox):

    auto expression_def = lhs >> '=' >> (raw[block] | rest) >> ';';
  • Change your definition of expression to have an attribute compatible with the synthesized attribute. This requires the use of a recursive variant and complicates the way you need to access the data in the parsed map since you'll need to create a visitor (running on WandBox).

    //A value is either a `string` or a `map<string,Value>`
    using Value = boost::make_recursive_variant<std::string,std::map<std::string,boost::recursive_variant_>>::type;
    struct printer : boost::static_visitor<void>
        printer(int indent) :indent(indent) {}
        void operator()(const std::string& val) const
            std::cout << std::string(indent, ' ') << val << std::endl;
        void operator()(const std::map<std::string,Value>& val) const
            for (const auto& pair : val)
                std::cout << std::string(indent, ' ') << pair.first << ':' << std::endl;
                boost::apply_visitor(printer(indent + 4), pair.second);
                std::cout << std::string(indent, ' ') << std::endl;
        int indent;
    void print_map(const Value& val)
        boost::apply_visitor(printer(0), val);