c++parsingboostboost-spiritboost-spirit-x3

Cleanest way to handle both quoted and unquoted strings in Spirit.X3


Buon giorno, I have to parse something such as:

foo: 123
"bar": 456

The quotes should be removed if they are here. I tried:

((+x3::alnum) | ('"' >> (+x3::alnum) >> '"'))

But the parser actions for this are of type variant<string, string> ; is there a way to make it so that the parser understands that those two are equivalent, and for my action to only get a single std::string as argument in its call?

edit: minimal repro (live on godbolt: https://gcc.godbolt.org/z/GcE8Pj4r5) :

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

using namespace boost::spirit;
// action handlers
struct handlers {
  void create_member(const std::string& str) { }
};

// rules
static const x3::rule<struct id_obj_mem> obj_mem = "obj_mem";

#define EVENT(e) ([](auto& ctx) { x3::get<handlers>(ctx).e(x3::_attr(ctx)); })

static const auto obj_mem_def = ((
    ((+x3::alnum) | ('"' >> (+x3::alnum) >> '"'))
     >> ':' >> x3::lit("123"))[EVENT(create_member)] % ',');
BOOST_SPIRIT_DEFINE(obj_mem)

// execution
int main()
{
  handlers r;
  std::string str = "foo: 123";
  auto first = str.begin();
  auto last = str.end();
  bool res = phrase_parse(
      first,
      last,
      boost::spirit::x3::with<handlers>(r)[obj_mem_def],
      boost::spirit::x3::ascii::space);
}

Solution

  • I too consider this a kind of defect. X3 is definitely less "friendly" in terms of the synthesized attribute types. I guess it's just a tacit side-effect of being more core-language oriented, where attribute assignment is effectively done via default "visitor" actions.

    Although I understand the value of keeping the magic to a minimum, and staying close to "pure C++", I vastly prefer the Qi way of synthesizing attributes here. I believe it has proven a hard problem to fix, as this problem has been coming/going in some iterations of X3.

    I've long decided to basically fix it myself with variations of this idiom:

    template <typename T> struct as_type {
        auto operator()(auto p) const { return x3::rule<struct Tag, T>{} = p; }
    };
    static constexpr as_type<std::string> as_string{};
    

    Now I'd write that as:

    auto quoted  = '"' >> +x3::alnum >> '"';
    auto name    = as_string(+x3::alnum | quoted);
    auto prop    = (name >> ':' >> "123")[EVENT(create_member)] % ',';
    

    That will compile no problem:

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <iomanip>
    #include <iostream>
    
    namespace x3 = boost::spirit::x3;
    
    struct handlers {
        void create_member(std::string const& str) {
            std::cerr << __FUNCTION__ << " " << std::quoted(str) << "\n";
        }
    };
    
    namespace Parser {
        #define EVENT(e) ([](auto& ctx) { get<handlers>(ctx).e(_attr(ctx)); })
    
        template <typename T> struct as_type {
            auto operator()(auto p) const { return x3::rule<struct Tag, T>{} = p; }
        };
        static constexpr as_type<std::string> as_string{};
    
        auto quoted  = '"' >> +x3::alnum >> '"';
        auto name    = as_string(+x3::alnum | quoted);
        auto prop    = (name >> ':' >> "123")[EVENT(create_member)] % ',';
    
        auto grammar = x3::skip(x3::space)[prop];
    } // namespace Parser
    
    int main() {
        handlers          r;
        std::string const str = "foo: 123";
    
        auto first = str.begin(), last = str.end();
        bool res = parse(first, last, x3::with<handlers>(r)[Parser::grammar]);
    
        return res ? 1 : 0;
    }
    

    Prints

    create_member "foo"
    

    Interesting Links

    etc.