Search code examples
c++boostc++14boost-spiritboost-spirit-x3

Parsing map of variants with Boost Spirit X3


I am trying (and failing) to parse a map<int, variant<string, float>> using Boost Spirit X3, with the following code:

#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <map>
#include <variant>
#include <string>

using namespace std;

namespace x3 = boost::spirit::x3;

int main() {
    auto variantRule = x3::rule<class VariantClass, x3::variant<std::string, float>>() = (*x3::alnum | x3::float_);

    auto pairRule = x3::rule<class PairClass, pair<int, x3::variant<std::string, float>>>() = x3::int_ >> ":" >> variantRule;

    auto mapRule = x3::rule<class MapClass, map<int, x3::variant<std::string, float>>>() = pairRule >>  * ( "," >> pairRule );

    string input = "1 : 1.0, 2 : hello, 3 : world";

    map<int, x3::variant<std::string, float>> variantMap;

    auto success = x3::phrase_parse(input.begin(), input.end(), mapRule, x3::space, variantMap);

    return 0;
}

For some reason I can't parse maps of pair<int, variant<string, float>>. I was able to parse a vector of variants though, it's only when I try to parse maps of variants that my code fails. It's worth mentioning that I also went through the X3 tutorials. Any help would be greatly appreciated.

Edit 1

Taking into account @liliscent's answer, and a few other changes, I was finally able to get it working, here's the correct code:

#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <map>
#include <variant>
#include <string>

using namespace std;

namespace x3 = boost::spirit::x3;

int main() {
    auto stringRule = x3::rule<class StringClass, string>() = x3::lexeme[x3::alpha >> *x3::alnum];

    auto variantRule = x3::rule<class VariantClass, x3::variant<std::string, float>>() = (stringRule | x3::float_);

    auto pairRule = x3::rule<class PairClass, pair<int, x3::variant<std::string, float>>>() = x3::int_ >> ':' >> variantRule;

    auto mapRule = x3::rule<class MapClass, map<int, x3::variant<std::string, float>>>() = pairRule % ",";

    string input = "1 : 1.0, 2 : hello, 3 : world";

    map<int, x3::variant<std::string, float>> variantMap;

    auto bg = input.begin(), ed = input.end();

    auto success = x3::phrase_parse(bg, ed, mapRule, x3::space, variantMap) && bg == ed;

    if (!success) cout<<"Parsing not succesfull";

    return 0;
}

Solution

  • If you want spirit to recognize std::pair and std::map, you need to include the fusion adaptor for std::pair:

    #include <boost/fusion/adapted/std_pair.hpp>
    

    This should fix your compilation problem. But there are other problems in your code, this rule (*x3::alnum | x3::float_); won't do what you want, because the left part could directly match empty. You need to rethink how to define this identifier.

    Also, better to write pairRule % "," than pairRule >> * ( "," >> pairRule );.

    And you should pass the input begin as an lvalue iterator, because then it will be advanced during parsing, so that you can check whether the parser has prematurely terminated.