Boost Spirit X3: Parse into Structs

Parsing variant of struct with a single member using Boost Spirit X3 and Fusion

I hate to say, but this is a well-known limitation and it keeps cropping up. I've kind of given up on trying to get it fixed. It's an edge case that is usually easy to work-around or avoid.

See e.g.

  • boost::spirit::x3 attribute compatibility rules, intuition or code?
  • How to move results parsed results into a struct using spirit x3
  • https://github.com/boostorg/spirit/pull/178

A longer list exists for Qi.

In your cause I would probably avoid parsing into TestStruct and rather parse into float. Otherwise, parse it and propagate using a semantic action instead of automatic attribute propagation.

I tried and found it pretty hard to get over the hurdle in this specific case (it appears to be the "inverse" problem, where an already correctly matched rule exposing TestResult is still posing a problem down the road. Clearly the attribute synthesis rules are wrong again).

Brute Force

auto assign = [](auto& ctx) { _val(ctx) = _attr(ctx); };

Doing the attribute propagation manually:

auto test_struct
= x3::rule<struct test_struct, TestStruct>{}
= x3::float_ >> ",";

auto test_variant
= x3::rule<struct test_variant, TestVariant>{}
= test_struct [assign] | "default" >> x3::attr("default"s)[assign];

Works: Live On Coliru

Indeed, the "official" propagation would look more like¹

auto assign = [](auto& ctx) {
x3::traits::move_to(_attr(ctx), _val(ctx));
};

And it appears to not-work: Live On Coliru


¹ but not exactly, there is a lot of meta-programming conditions before that

Boost Spirit x3: parse into structs

I love @llonesmiz's approach in the comment.

I "had to" try my favorite approach with X3 using functional composition too, though. Here's a sketch of the approach which does parse and propagate the values.

Missing are checks on property presence/uniqueness. (I think such a thing is doable using a x3::with<> context addition that basically contains a std::set<V T::*>. Of course such a thing needs (implementation dependent?) casts or an erasure wrapper).

For now, presented without comment:

Live On Coliru

#include <iostream>
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

struct LayerInfo
{
std::string layerName;
int layerNumber = 0;
std::string color;
bool visible = false;
};

namespace Parser {
namespace x3 = boost::spirit::x3;

// custom type parsers
auto quoted = rule<std::string>("quoted", x3::lexeme [ '"' >> *('\\' >> x3::char_ | ~x3::char_('"')) >> '"' ]);
struct colors_type : x3::symbols<char> {
colors_type() {
this->add("red")("blue")("green")("black");
}
} static const colors;

namespace detail {
template <typename T> auto propagate(T member) {
return [=](auto& ctx){ x3::traits::move_to(x3::_attr(ctx), x3::_val(ctx).*member); };
}

template <typename T> auto make_member_parser(int T::* const member) { return x3::int_ [propagate(member)]; }
template <typename T> auto make_member_parser(bool T::* const member) { return x3::bool_ [propagate(member)]; }
template <typename T> auto make_member_parser(std::string T::* const member) { return x3::raw[colors] [propagate(member)]; }

template <typename T = LayerInfo, typename P>
auto rule(const char* debug, P p) { return x3::rule<struct _, T> {debug} = x3::skip(x3::space)[p]; };

auto property = [](auto label, auto member) {
return rule(label, x3::as_parser(label) >> '=' >> make_member_parser(member));
};
}

using detail::rule;
using detail::propagate;
using detail::property;

auto name = rule("name", "Layer" >> quoted [propagate(&LayerInfo::layerName)]);

auto number = property("number", &LayerInfo::layerNumber);
auto color = property("color", &LayerInfo::color);
auto visible = property("visible", &LayerInfo::visible);

auto layer_info = name >> '{' >> +(number | color | visible) >> '}';

auto grammar = rule("layer_info", layer_info);
}

std::ostream& operator<<(std::ostream& os, LayerInfo const& li) {
return os << "LayerInfo \"" << li.layerName << "\"{"
<< "number=" << li.layerNumber << " "
<< "color=" << li.color << " "
<< "visible=" << std::boolalpha << li.visible
<< "}\n";
}

int main() {
std::string const sample = R"(Layer "L1" {
number = 23
color = green
visible = true
})";

LayerInfo v;
auto f = sample.begin(), l = sample.end();
bool ok = parse(f, l, Parser::grammar, v);

if (ok)
std::cout << "Parsed: " << v << "\n";
else
std::cout << "Parse failed\n";

if (f!=l)
std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
}

Prints

Parsed: LayerInfo "L1"{number=23 color=green visible=true}

Wit debug info: Live On Coliru

<layer_info>
<try>Layer "L1" {\n num</try>
<name>
<try>Layer "L1" {\n num</try>
<quoted>
<try> "L1" {\n number =</try>
<success> {\n number = 23\n </success>
<attributes>[L, 1]</attributes>
</quoted>
<success> {\n number = 23\n </success>
<attributes>LayerInfo "L1"{number=0 color= visible=false}
</attributes>
</name>
<number>
<try>\n number = 23\n </try>
<success>\n color = green\n </success>
<attributes>LayerInfo "L1"{number=23 color= visible=false}
</attributes>
</number>
<number>
<try>\n color = green\n </try>
<fail/>
</number>
<color>
<try>\n color = green\n </try>
<success>\n visible = true\n</success>
<attributes>LayerInfo "L1"{number=23 color=green visible=false}
</attributes>
</color>
<number>
<try>\n visible = true\n</try>
<fail/>
</number>
<color>
<try>\n visible = true\n</try>
<fail/>
</color>
<visible>
<try>\n visible = true\n</try>
<success>\n}</success>
<attributes>LayerInfo "L1"{number=23 color=green visible=true}
</attributes>
</visible>
<number>
<try>\n}</try>
<fail/>
</number>
<color>
<try>\n}</try>
<fail/>
</color>
<visible>
<try>\n}</try>
<fail/>
</visible>
<success></success>
<attributes>LayerInfo "L1"{number=23 color=green visible=true}
</attributes>
</layer_info>

Parsing Selector struct with alternating tokens using Boost Spirit X3

I've written similar answers before:

  • Parsing CSS with Boost.Spirit X3 (a treasure trove for more complete CSS parsing in both Qi and X3)
  • Using boost::spirit to parse named parameters in any order (Qi and X3 in the comments)
  • Boost Spirit x3: parse into structs
  • Combining rules at runtime and returning rules

I don't think you can directly fusion-adapt. Although if you are very motivated (e.g. you already have the adapted structs) you could make some generic helpers off that.

To be fair, a little bit of restructuring in your code seems pretty nice to me, already. Here's my effort to make it more elegant/convenient. I'll introduce a helper macro just like BOOST_FUSION_ADAPT_XXX, but not requiring any Boost Fusion.

Let's Start With The AST

As always, I like to start with the basics. Understanding the goal is half the battle:

namespace Ast {
using boost::optional;

struct Selector {
// These selectors always
// - start with 1 or no elements,
// - could contain 1 or no ids, and
// - could contain 0 to n classes.
optional<std::string> element;
optional<std::string> id;
std::vector<std::string> classes;

friend std::ostream& operator<<(std::ostream& os, Selector const&s) {
if (s.element.has_value()) os << s.element.value();
if (s.id.has_value()) os << "#" << s.id.value();
for (auto& c : s.classes) os << "." << c;
return os;
}
};
}

Note that I fixed the optionality of some parts to reflect real life.

You could use this to detect repeat-initialization of element/id fields.

Magic Sauce (see below)

#include "propagate.hpp"
DEF_PROPAGATOR(Selector, id, element, classes)

We'll dig into this later. Suffice it to say it generates the semantic actions that you had to tediously write.

Main dish

Now, we can simplify the parser rules a lot, and run the tests:

int main() {
auto name = as<std::string>[x3::alpha >> *x3::alnum];
auto idRule = "#" >> name;
auto classesRule = +("." >> name);

auto selectorRule
= x3::rule<class TestClass, Ast::Selector>{"selectorRule"}
= +( name [ Selector.element ]
| idRule [ Selector.id ]
| classesRule [ Selector.classes ]
)
;

for (std::string const& input : {
"element#id.class1.class2.classn",
"element#id.class1",
".class1#id.class2.class3",
"#id.class1.class2",
".class1.class2#id",
})
{
Ast::Selector sel;
std::cout << std::quoted(input) << " -->\n";
if (x3::parse(begin(input), end(input), selectorRule >> x3::eoi, sel)) {
std::cout << "\tSuccess: " << sel << "\n";
} else {
std::cout << "\tFailed\n";
}
}
}

See it Live On Wandbox, printing:

"element#id.class1.class2.classn" -->
Success: element#id.class1.class2.classn
"element#id.class1" -->
Success: element#id.class1
".class1#id.class2.class3" -->
Success: #id.class1.class2.class3
"#id.class1.class2" -->
Success: #id.class1.class2
".class1.class2#id" -->
Success: #id.class1.class2

The Magic

Now, how did I generate those actions? Using a little bit of Boost Preprocessor:

#define MEM_PROPAGATOR(_, T, member) \
Propagators::Prop<decltype(std::mem_fn(&T::member))> member { std::mem_fn(&T::member) };

#define DEF_PROPAGATOR(type, ...) \
struct type##S { \
using T = Ast::type; \
BOOST_PP_SEQ_FOR_EACH(MEM_PROPAGATOR, T, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
} static const type {};

Now, you might see that it defines static const variables named like the Ast types.

You're free to call this macro in another namespace, say namespace Actions { }

The real magic is Propagators::Prop<F> which has a bit of dispatch to allow for container attributes and members. Otherwise it just relays to x3::traits::move_to:

namespace Propagators {
template <typename F>
struct Prop {
F f;
template <typename Ctx>
auto operator()(Ctx& ctx) const {
return dispatch(x3::_attr(ctx), f(x3::_val(ctx)));
}
private:
template <typename Attr, typename Dest>
static inline void dispatch(Attr& attr, Dest& dest) {
call(attr, dest, is_container(attr), is_container(dest));
}

template <typename T>
static auto is_container(T const&) { return x3::traits::is_container<T>{}; }
static auto is_container(std::string const&) { return boost::mpl::false_{}; }

// tags for dispatch
using attr_is_container = boost::mpl::true_;
using attr_is_scalar = boost::mpl::false_;
using dest_is_container = boost::mpl::true_;
using dest_is_scalar = boost::mpl::false_;

template <typename Attr, typename Dest>
static inline void call(Attr& attr, Dest& dest, attr_is_scalar, dest_is_scalar) {
x3::traits::move_to(attr, dest);
}
template <typename Attr, typename Dest>
static inline void call(Attr& attr, Dest& dest, attr_is_scalar, dest_is_container) {
dest.insert(dest.end(), attr);
}
template <typename Attr, typename Dest>
static inline void call(Attr& attr, Dest& dest, attr_is_container, dest_is_container) {
dest.insert(dest.end(), attr.begin(), attr.end());
}
};
}

BONUS

A lot of the complexity in the propagator type is from handling container attributes. However, you don't actually need any of that:

auto name = as<std::string>[x3::alpha >> *x3::alnum];

auto selectorRule
= x3::rule<class selector_, Ast::Selector>{"selectorRule"}
= +( name [ Selector.element ]
| '#' >> name [ Selector.id ]
| '.' >> name [ Selector.classes ]
)
;

Is more than enough, and the propagation helper can be simplified to:

namespace Propagators {
template <typename F> struct Prop {
F f;
template <typename Ctx>
auto operator()(Ctx& ctx) const {
return call(x3::_attr(ctx), f(x3::_val(ctx)));
}
private:
template <typename Attr, typename Dest>
static inline void call(Attr& attr, Dest& dest) {
x3::traits::move_to(attr, dest);
}
template <typename Attr, typename Elem>
static inline void call(Attr& attr, std::vector<Elem>& dest) {
dest.insert(dest.end(), attr);
}
};
}

As you can see evaporating the tag dispatch has a beneficial effect.

See the simplified version Live On Wandbox again.

FULL LISTING

For posterity on this site:

  • test.cpp

    //#define BOOST_SPIRIT_X3_DEBUG
    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    #include <iomanip>

    namespace x3 = boost::spirit::x3;

    namespace Ast {
    using boost::optional;

    struct Selector {
    // These selectors always
    // - start with 1 or no elements,
    // - could contain 1 or no ids, and
    // - could contain 0 to n classes.
    optional<std::string> element;
    optional<std::string> id;
    std::vector<std::string> classes;

    friend std::ostream& operator<<(std::ostream& os, Selector const&s) {
    if (s.element.has_value()) os << s.element.value();
    if (s.id.has_value()) os << "#" << s.id.value();
    for (auto& c : s.classes) os << "." << c;
    return os;
    }
    };
    }

    #include "propagate.hpp"
    DEF_PROPAGATOR(Selector, id, element, classes)

    #include "as.hpp"
    int main() {
    auto name = as<std::string>[x3::alpha >> *x3::alnum];

    auto selectorRule
    = x3::rule<class selector_, Ast::Selector>{"selectorRule"}
    = +( name [ Selector.element ]
    | '#' >> name [ Selector.id ]
    | '.' >> name [ Selector.classes ]
    )
    ;

    for (std::string const& input : {
    "element#id.class1.class2.classn",
    "element#id.class1",
    ".class1#id.class2.class3",
    "#id.class1.class2",
    ".class1.class2#id",
    })
    {
    Ast::Selector sel;
    std::cout << std::quoted(input) << " -->\n";
    if (x3::parse(begin(input), end(input), selectorRule >> x3::eoi, sel)) {
    std::cout << "\tSuccess: " << sel << "\n";
    } else {
    std::cout << "\tFailed\n";
    }
    }
    }
  • propagate.hpp

    #pragma once
    #include <boost/preprocessor/cat.hpp>
    #include <boost/preprocessor/seq/for_each.hpp>
    #include <functional>

    namespace Propagators {
    template <typename F> struct Prop {
    F f;
    template <typename Ctx>
    auto operator()(Ctx& ctx) const {
    return call(x3::_attr(ctx), f(x3::_val(ctx)));
    }
    private:
    template <typename Attr, typename Dest>
    static inline void call(Attr& attr, Dest& dest) {
    x3::traits::move_to(attr, dest);
    }
    template <typename Attr, typename Elem>
    static inline void call(Attr& attr, std::vector<Elem>& dest) {
    dest.insert(dest.end(), attr);
    }
    };
    }

    #define MEM_PROPAGATOR(_, T, member) \
    Propagators::Prop<decltype(std::mem_fn(&T::member))> member { std::mem_fn(&T::member) };

    #define DEF_PROPAGATOR(type, ...) \
    struct type##S { \
    using T = Ast::type; \
    BOOST_PP_SEQ_FOR_EACH(MEM_PROPAGATOR, T, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
    } static const type {};
  • as.hpp

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

    namespace {
    template <typename T>
    struct as_type {
    template <typename...> struct tag{};
    template <typename P>
    auto operator[](P p) const {
    return boost::spirit::x3::rule<tag<T,P>, T> {"as"}
    = p;
    }
    };

    template <typename T>
    static inline const as_type<T> as = {};
    }

Boost X3 parse struct with containers

I fixed it by using rules:


namespace LayoutSymbolsParser
{
namespace x3 = boost::spirit::x3;

x3::rule<class SymbolsRule, LayoutSymbols> const symbolsRule = "symbols";
x3::rule<class LayoutsRule, std::vector<std::string>> const layoutsRule = "layouts";
x3::rule<class OptionsRule, std::vector<std::string>> const optionsRule = "options";

const auto symbolsRule_def = "pc_" >> layoutsRule >> -('_' >> optionsRule);
const auto layoutsRule_def = +x3::alpha >> *('_' >> +x3::alpha >> '_' >> x3::omit[x3::digit]);
const auto optionsRule_def = x3::lexeme[+(x3::char_ - ')') >> x3::char_(')')] % '_';

BOOST_SPIRIT_DEFINE(symbolsRule, layoutsRule, optionsRule);
}

Usage:

std::string test = "pc_us_ru_2_ua_3_inet(evdev)_capslock(grouplock)";
LayoutSymbols layoutSymbols;
x3::phrase_parse(test.begin(), test.end(), LayoutSymbolsParser::symbolsRule, x3::space, layoutSymbols);

Boost Spirit x3 parsing into a struct with single field

Yeah. This looks like unrelated to single-element sequences. Linker errors usually are :)

You could probably employ the techniques demonstrated

  • here: Boost spirit x3 example calculator (calc8, calc9) linker error
  • and here: linking errors while separate parser using boost spirit x3

To find the culprit. If you post a full, minimal, "working" example we can help.

Spirit.X3: passing local data to a parser

About the approaches:

1/ Yes, this is viable for small, contained parsers. Typically only used in a single TU, and exposed via non-generic interface.

2/ This is the approach for (much) larger grammars, that you might wish to spread across TUs, and/or are instantiated across several TU's generically.

Note that you do NOT need BOOST_SPIRIT_DEFINE unless you

  • have recursive rules
  • want to split declaration from definition. [This becomes pretty complicated, and I recommend against using that for X3.]

The Question

My question is: how to combine both (properly, without globals) ?

You can't combine something with namespace level declarations, if one of the requiremenents is "without globals".

My dream API would be to pass some argument to phrase_parse and then do some x3::_arg(ctx) but I couldn't find anything like this.

I don't know what you think x3::_arg(ctx) would do, in that particular dream :)

Here is for instance my parser: for now the actions are writing to std::cerr. What if I wanted to write to a custom std::ostream& instead, that would be passed to the parse function?

Now that's a concrete question. I'd say: use the context.

You could make it so that you can use x3::get<ostream>(ctx) returns the stream:

struct ostream{};

auto access_index_array = [] (const auto& ctx) { x3::get<ostream>(ctx) << "access_array: " << x3::_attr(ctx) << "\n" ;};
auto access_empty_array = [] (const auto& ctx) { x3::get<ostream>(ctx) << "access_empty_array\n" ;};
auto access_named_member = [] (const auto& ctx) { x3::get<ostream>(ctx) << "access_named_member: " << x3::_attr(ctx) << "\n" ;};
auto start_action = [] (const auto& ctx) { x3::get<ostream>(ctx) << "start action\n" ;};
auto finish_action = [] (const auto& ctx) { x3::get<ostream>(ctx) << "finish action\n" ;};
auto create_array = [] (const auto& ctx) { x3::get<ostream>(ctx) << "create_array\n";};

Now you need to put the tagged param in the context during parsing:

bool r = phrase_parse(
f, l,
x3::with<parser::ostream>(std::cerr)[parser::array_def | parser::sequence_def],
x3::space);

Live Demo: http://coliru.stacked-crooked.com/a/a26c8eb0af6370b9

Prints

start action
access_named_member: a
finish action
start action
access_named_member: b
start action
start action
access_array: 2
start action
access_named_member: foo
start action
access_empty_array
finish action
start action
access_named_member: c
finish action
create_array
true

Intermixed with the standard X3 debug output:

<sequence>
<try>.a|.b..[2].foo.[]|.c</try>
<action>
<try>.a|.b..[2].foo.[]|.c</try>
<success>|.b..[2].foo.[]|.c]</success>
</action>
<action>
<try>.b..[2].foo.[]|.c]</try>
<success>|.c]</success>
</action>
<action>
<try>.c]</try>
<success>]</success>
</action>
<success>]</success>
</sequence>

But Wait #1 - Event Handlers

It looks like you're parsing something similar to JSON Pointer or jq syntax. In the case that you wanted to provide a callback-interface (SAX-events), why not bind the callback interface instead of the actions:

struct handlers {
using N = x3::unused_type;
virtual void index(int) {}
virtual void index(N) {}
virtual void property(std::string) {}
virtual void start(N) {}
virtual void finish(N) {}
virtual void create_array(N) {}
};

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

const auto action_def =
+(x3::lit('.')[EVENT(start)] >> -((+x3::alnum)[EVENT(property)]) >>
*(('[' >> x3::int_ >> ']')[EVENT(index)] | x3::lit("[]")[EVENT(index)]));

const auto sequence_def = action[EVENT(finish)] % '|';
const auto array_def = ('[' >> sequence >> ']')[EVENT(create_array)];
const auto root_def = array | action;

Now you can implement all handlers neatly in one interface:

struct default_handlers : parser::handlers {
std::ostream& os;
default_handlers(std::ostream& os) : os(os) {}

void index(int i) override { os << "access_array: " << i << "\n"; };
void index(N) override { os << "access_empty_array\n" ; };
void property(std::string n) override { os << "access_named_member: " << n << "\n" ; };
void start(N) override { os << "start action\n" ; };
void finish(N) override { os << "finish action\n" ; };
void create_array(N) override { os << "create_array\n"; };
};

auto f = str.begin(), l = str.end();
bool r = phrase_parse(f, l,
x3::with<parser::handlers>(default_handlers{std::cout}) //
[parser::array_def | parser::sequence_def],
x3::space);

See it Live On Coliru once again:

start action
access_named_member: a
finish action
start action
access_named_member: b
start action
start action
access_array: 2
start action
access_named_member: foo
start action
access_empty_array
finish action
start action
access_named_member: c
finish action
create_array
true

But Wait #2 - No Actions

The natural way to expose attributes would be to build an AST. See also Boost Spirit: "Semantic actions are evil"?

Without further ado:

namespace AST {
using Id = std::string;
using Index = int;
struct Member {
std::optional<Id> name;
};
struct Indexer {
std::optional<int> index;
};
struct Action {
Member member;
std::vector<Indexer> indexers;
};

using Actions = std::vector<Action>;
using Sequence = std::vector<Actions>;

struct ArrayCtor {
Sequence actions;
};

using Root = boost::variant<ArrayCtor, Actions>;
}

Of course, I'm making some assumptions. The rules can be much simplified:

namespace parser {
template <typename> struct Tag {};
#define AS(T, p) (x3::rule<Tag<AST::T>, AST::T>{#T} = p)

auto id = AS(Id, +x3::alnum);
auto member = AS(Member, x3::lit('.') >> -id);
auto indexer = AS(Indexer,'[' >> -x3::int_ >> ']');

auto action = AS(Action, member >> *indexer);
auto actions = AS(Actions, +action);

auto sequence = AS(Sequence, actions % '|');
auto array = AS(ArrayCtor, '[' >> -sequence >> ']'); // covers empty array
auto root = AS(Root, array | actions);
} // namespace parser

And the parsing function returns the AST:

AST::Root parse(std::string_view str) {
auto f = str.begin(), l = str.end();

AST::Root parsed;
phrase_parse(f, l, x3::expect[parser::root >> x3::eoi], x3::space, parsed);

return parsed;
}

(Note that it now throws x3::expection_failure if the input is invalid or not completely parsed)

int main() {
std::cout << parse("[.a|.b..[2].foo.[]|.c]");
}

Now prints:

[.a|.b./*none*/./*none*/[2].foo./*none*/[/*none*/]|.c]

See it Live On Coliru

//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/home/x3.hpp>
#include <ostream>
#include <optional>

namespace x3 = boost::spirit::x3;

namespace AST {
using Id = std::string;
using Index = int;
struct Member {
std::optional<Id> name;
};
struct Indexer {
std::optional<int> index;
};
struct Action {
Member member;
std::vector<Indexer> indexers;
};

using Actions = std::vector<Action>;
using Sequence = std::vector<Actions>;

struct ArrayCtor {
Sequence actions;
};

using Root = boost::variant<ArrayCtor, Actions>;
}

BOOST_FUSION_ADAPT_STRUCT(AST::Member, name)
BOOST_FUSION_ADAPT_STRUCT(AST::Indexer, index)
BOOST_FUSION_ADAPT_STRUCT(AST::Action, member, indexers)
BOOST_FUSION_ADAPT_STRUCT(AST::ArrayCtor, actions)

namespace parser {
template <typename> struct Tag {};
#define AS(T, p) (x3::rule<Tag<AST::T>, AST::T>{#T} = p)

auto id = AS(Id, +x3::alnum);
auto member = AS(Member, x3::lit('.') >> -id);
auto indexer = AS(Indexer,'[' >> -x3::int_ >> ']');

auto action = AS(Action, member >> *indexer);
auto actions = AS(Actions, +action);

auto sequence = AS(Sequence, actions % '|');
auto array = AS(ArrayCtor, '[' >> -sequence >> ']'); // covers empty array
auto root = AS(Root, array | actions);
} // namespace parser

AST::Root parse(std::string_view str) {
auto f = str.begin(), l = str.end();

AST::Root parsed;
phrase_parse(f, l, x3::expect[parser::root >> x3::eoi], x3::space, parsed);

return parsed;
}


Related Topics



Leave a reply



Submit