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

Time:10-12

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

CodePudding user response:

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.

  • Related