Home > Back-end >  Boost X3: Can a variant member be avoided in disjunctions?
Boost X3: Can a variant member be avoided in disjunctions?

Time:09-22

I'd like to parse string | (string, int) and store it in a structure that defaults the int component to some value. The attribute of such a construction in X3 is a variant<string, tuple<string, int>>. I was thinking I could have a struct that takes either a string or a (string, int) to automagically be populated:

    struct bar
    {
        bar (std::string x = "", int y = 0) : baz1 {x}, baz2 {y} {}

        std::string          baz1;
        int                  baz2;
    };

BOOST_FUSION_ADAPT_STRUCT (disj::ast::bar, baz1, baz2)

and then simply have:

    const x3::rule<class bar, ast::bar> bar = "bar";
    using x3::int_;
    using x3::ascii::alnum;

    auto const bar_def = ( (alnum) | ('(' >>  (alnum) >> ',' >> int_ >> ')')) >> ';';

    BOOST_SPIRIT_DEFINE(bar);

However this does not work:

/usr/include/boost/spirit/home/x3/core/detail/parse_into_container.hpp:139:59: error: static assertion failed: Expecting a single element fusion sequence
  139 |             static_assert(traits::has_size<Attribute, 1>::value,

Setting baz2 to an optional does not help. One way to solve this is to have a variant field or inherit from that type:

    struct string_int {
        std::string s;
        int i;
    };

    struct foo {
        boost::variant<std::string, string_int>  var;
    };

BOOST_FUSION_ADAPT_STRUCT (disj::ast::string_int, s, i)
BOOST_FUSION_ADAPT_STRUCT (disj::ast::foo, var)

(For some reason, I have to use boost::variant instead of x3::variant for operator<< to work; also, using std::pair or tuple for string_int does not work, but boost::fusion::deque does.) One can then equip foo somehow to get the string and integer.

Question: What is the proper, clean way to do this in X3? Is there a more natural way than this second option and equipping foo with accessors?

Live On Coliru

CodePudding user response:

Sadly the wording in the x3 section is exceedingly sparse and allows it (contrast the Qi section). A quick test confirms it:

Live On Coliru

#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;

template <typename Expr>
std::string inspect(Expr const& expr) {
    using A = typename x3::traits::attribute_of<Expr, x3::unused_type>::type;
    return boost::core::demangle(typeid(A).name());
}

int main()
{
    std::cout << inspect(x3::double_ | x3::int_) << "\n";       // variant expected
    std::cout << inspect(x3::int_ | "bla" >> x3::int_) << "\n"; // variant "understandable"
    std::cout << inspect(x3::int_ | x3::int_) << "\n";          // variant suprising:
}

Prints

boost::variant<double, int>
boost::variant<int, int>
boost::variant<int, int>

All Hope Is Not Lost

In your specific case you could trick the system:

auto const bar_def =                              //
    ( x3::alnum >> x3::attr(-1)                   //
    | '(' >>  x3::alnum >> ',' >> x3::int_ >> ')' //
    ) >> ';';

Note how we "inject" an int value for the first branch. That satisfies the attribute propagation gods:

Live On Coliru

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <iomanip>
namespace x3 = boost::spirit::x3;

namespace disj::ast {
    struct bar {
        std::string x;
        int         y;
    };
    using boost::fusion::operator<<;
} // namespace disj::ast

BOOST_FUSION_ADAPT_STRUCT(disj::ast::bar, x, y)

namespace disj::parser {
    const x3::rule<class bar, ast::bar> bar = "bar";

    auto const bar_def =                              //
        ( x3::alnum >> x3::attr(-1)                   //
        | '(' >>  x3::alnum >> ',' >> x3::int_ >> ')' //
        ) >> ';';

    BOOST_SPIRIT_DEFINE(bar)
}

namespace disj {
    void run_tests() {
        for (std::string const input : {
                 "",
                 ";",
                 "bla;",
                 "bla, 42;",
                 "(bla, 42);",
             }) {

            ast::bar val;
            auto f = begin(input), l = end(input);

            std::cout << "\n" << quoted(input) << " -> ";

            if (phrase_parse(f, l, parser::bar, x3::space, val)) {
                std::cout << "Parsed: " << val << "\n";
            } else {
                std::cout << "Failed\n";
            }

            if (f!=l) {
                std::cout << " -- Remaining " << quoted(std::string_view(f, l)) << "\n";
            }
        }
    }
}

int main()
{
    disj::run_tests();
}

Prints

"" -> Failed

";" -> Failed
 -- Remaining ";"

"bla;" -> Parsed: (bla -1)

"bla, 42;" -> Failed
 -- Remaining "bla, 42;"

"(bla, 42);" -> Parsed: (bla 42)

¹ just today

  • Related