I need to parse a sequence of elements where there is a first number telling how many elements must be parsed next.
As a simplification of what I need: [3 10 20 30] should be parsed as showed next:
- Number of elements: 3
- Vector of elements: {10, 20, 30}
Being used to Qi and its "repeat directive + phoenix" syntax I tried the same for X3, failing in compiling. I have being looking in web for the same problem, and i find next link in stack overflow: Boost Spirit X3 cannot compile repeat directive with variable factor
I am shocked of how something so elegantly solved in Qi can be so ugly and cumberson in X3 (personal opinion, please nobody got offended). Of course I get the reasons about phoonix abandoned due to c++14 replacing it.
But I wonder is there is any further improvement in X3 about this subject due to this post is from 2015. I have been looking but nothing found. Any advise?
NOTE- No code included due that is same case/code that the posted link.
Thanks.
Usually it means that there was no PR for that feature (or it was but has some issues). The repeat
also has design problems. For example you can parse {10 20 30}
with it, but not {10, 20, 30}
(requires a kind of list
parser).
I cannot agree with that Qi has an elegant way of doing it because you have to use a rule with local variable or pass a reference to an external value. The natural way seems to be repeat(len_parser)[item_parser]
, but it has additional design issues with skippers (or skippers has design issues that limits complex directives flexibility).
Fortunately the Spirit X3 is much simpler in writing own parser combinators.
#include <boost/spirit/home/x3.hpp>
namespace x3e {
namespace x3 = boost::spirit::x3;
template <typename LenParser, typename Subject>
struct vlrepeat_directive : x3::unary_parser<Subject, vlrepeat_directive<LenParser, Subject>>
{
using base_type = x3::unary_parser<Subject, vlrepeat_directive<LenParser, Subject>>;
static bool const handles_container = true;
vlrepeat_directive(LenParser const& lp_, Subject const& subject)
: base_type(subject), lp(lp_) {}
template<typename Iterator, typename Context, typename RContext, typename Attribute>
bool parse(Iterator& first, Iterator const& last
, Context const& context, RContext& rcontext, Attribute& attr) const
{
static_assert(x3::traits::has_attribute<LenParser, Context>::value, "must syntesize an attribute");
Iterator iter = first;
typename x3::traits::attribute_of<LenParser, Context>::type len;
if (!lp.parse(iter, last, context, rcontext, len))
return false;
for (; len; --len) {
if (!x3::detail::parse_into_container(
this->subject, iter, last, context, rcontext, attr))
return false;
}
first = iter;
return true;
}
LenParser lp;
};
template <typename LenParser>
struct vlrepeat_gen
{
template <typename Subject>
vlrepeat_directive<LenParser, typename x3::extension::as_parser<Subject>::value_type>
operator[](Subject const& p) const
{
return { lp, x3::as_parser(p) };
}
LenParser lp;
};
template <typename Parser>
vlrepeat_gen<Parser> vlrepeat(Parser const& p)
{
static_assert(x3::traits::is_parser<Parser>::value, "have to be a parser");
return { p };
}
}
template <typename LenParser, typename Subject, typename Context>
struct boost::spirit::x3::traits::attribute_of<x3e::vlrepeat_directive<LenParser, Subject>, Context>
: build_container<typename attribute_of<Subject, Context>::type> {};
And use it:
#include <iostream>
#include <vector>
int main()
{
namespace x3 = boost::spirit::x3;
auto s = "5: 1 2 3 4 5", e = s + std::strlen(s);
std::vector<int> v;
if (phrase_parse(s, e, x3e::vlrepeat(x3::uint_ >> ':')[x3::int_], x3::space, v)) {
std::cout << "Result:\n";
for (auto x : v)
std::cout << x << '\n';
}
else
std::cout << "Failed!\n";
}
Output:
Result:
1
2
3
4
5
https://wandbox.org/permlink/K572K0BMEqA8lMJm
(it has a call to detail::parse_into_container
which is not a public API)