I have been working on a better understanding of compile time code especially where spirit::x3 is concerned. I have a class my_uuid
and it is working fine compiled with x3::rule. But as Iterator
is declared outside of the scope, it is only good for std::string::iterator
. So I thought I'd just create a component written like x3::int_parser
. And as far as I can see, it has exactly the same signature. I've tried putting it in the x3
namespace and including right after the `..\x3\numeric\int.hpp' header thinking there is some good reason, but it evades me.
If I set a break point in my uuid_parser and the int_parser I can see that the int_parser is left alone, called right from the parser sequence. But my parser is wrapped as if it were a container. If I uncomment the parser at line 40, the compiler starts looking for the likes of insert
. So, that is even more confusing, why doesn't it compile with the need of insert
or move
if I leave it as the last component in the sequence?
I seem to be missing something very fundamental here. My question is not how to fix this but what am I missing. Thanks.
#include<iostream>
#include <vector>
#include <boost/spirit/home/x3.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
namespace x3 = boost::spirit::x3;
struct uuid_parser : x3::parser<uuid_parser> {
typedef boost::uuids::uuid attribute_type;
static bool const has_attribute = true;
static bool const handles_container = false;
template <typename Iterator, typename Context, typename Attribute>
bool parse(Iterator& first, Iterator const& last, Context const& context, x3::unused_type, Attribute& uuid) const {
boost::iterator_range<Iterator> rng;
auto ret = x3::parse(first, last, x3::raw[*x3::char_("0-9a-fA-F-")], rng);
try {
uuid = boost::uuids::string_generator()(rng.begin(), rng.end());
}
catch (std::exception& e) {
boost::ignore_unused(e);
return false;
}
return true;
}
};
const uuid_parser uuid_ = {};
int main() {
std::string uuid_str(R"( id(78461bab-6d7c-48bc-a79a-b716d1ab97cb
id(9350cf32-7fe5-40d2-8a0d-c7f7562d7a15
id(bad-9350cf32-7fe5-40d2-8a0d-c7f7562d7a15
)");
auto first(uuid_str.begin());
std::vector<boost::uuids::uuid> attr;
x3::phrase_parse(first, uuid_str.end(), *(x3::lit("id(") >> uuid_), x3::space, attr);
//x3::phrase_parse(first, uuid_str.end(), *(x3::lit("uid(") >> uuid_ >> ')'), x3::space, attr);
for (auto& item : attr)
std::cout << item << std::endl;
std::string int_str(" x(1) x(2) x(3)"); auto ibegin(int_str.begin());
std::vector<int> int_vect;
x3::phrase_parse(ibegin, int_str.end(), *(x3::lit("x(") >> x3::int_ >> ')'), x3::space, int_vect);
for (auto& item : int_vect)
std::cout << item << std::endl;
return 0;
}
The confusion is that uuids::uuid
looks like a container. You can observe this by adding:
template <> struct x3::traits::detail::is_container_impl<boost::uuids::uuid> {
using type = std::false_type;
};
#include <iostream>
#include <vector>
#include <boost/spirit/home/x3.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
namespace x3 = boost::spirit::x3;
struct uuid_parser : x3::parser<uuid_parser> {
using attribute_type = boost::uuids::uuid;
static bool const has_attribute = true;
static bool const handles_container = false;
template <typename Iterator, typename Context, typename Attribute>
bool parse(Iterator& first, Iterator const& last, Context const& context,
x3::unused_type, Attribute& uuid) const
{
try {
boost::iterator_range<Iterator> rng;
auto ret = x3::parse(first, last, x3::raw[*(x3::xdigit | '-')], rng);
if (ret)
uuid = boost::uuids::string_generator()(rng.begin(), rng.end());
return true;
} catch (std::exception const&) {
return false;
}
}
};
template <> struct x3::traits::detail::is_container_impl<boost::uuids::uuid> {
using type = std::false_type;
};
int main() {
const uuid_parser uuid_ = {};
std::string uuid_str(R"( uid(78461bab-6d7c-48bc-a79a-b716d1ab97cb)
uid(9350cf32-7fe5-40d2-8a0d-c7f7562d7a15)
uid(bad-9350cf32-7fe5-40d2-8a0d-c7f7562d7a15)
)");
std::vector<boost::uuids::uuid> vec;
x3::phrase_parse(uuid_str.begin(), uuid_str.end(), *("uid(" >> uuid_ >> ")"), x3::space, vec);
for (auto const& item : vec)
std::cout << item << std::endl;
}
Prints
78461bab-6d7c-48bc-a79a-b716d1ab97cb
9350cf32-7fe5-40d2-8a0d-c7f7562d7a15
auto u = x3::rule<struct _u, boost::uuids::uuid>{"u"} = uuid_;
auto r = x3::rule<struct _r, boost::uuids::uuid>{"r"} = "uid(" >> u >> ")";
x3::phrase_parse(uuid_str.begin(), uuid_str.end(), *r, x3::space, vec);
Prints
<r>
<try> uid(78461bab-6d7</try>
<u>
<try>78461bab-6d7c-48bc-a</try>
<success>)\n uid(9350cf</success>
<attributes>78461bab-6d7c-48bc-a79a-b716d1ab97cb</attributes>
</u>
<success>\n uid(9350cf3</success>
<attributes>78461bab-6d7c-48bc-a79a-b716d1ab97cb</attributes>
</r>
<r>
<try>\n uid(9350cf3</try>
<u>
<try>9350cf32-7fe5-40d2-8</try>
<success>)\n uid(bad-93</success>
<attributes>9350cf32-7fe5-40d2-8a0d-c7f7562d7a15</attributes>
</u>
<success>\n uid(bad-935</success>
<attributes>9350cf32-7fe5-40d2-8a0d-c7f7562d7a15</attributes>
</r>
<r>
<try>\n uid(bad-935</try>
<u>
<try>bad-9350cf32-7fe5-40</try>
<fail/>
</u>
<fail/>
</r>
78461bab-6d7c-48bc-a79a-b716d1ab97cb
9350cf32-7fe5-40d2-8a0d-c7f7562d7a15
In fact, as you can see you don't need to use BOOST_SPIRIT_DEFINE
, separating instantiation from declaration at all:
#include <iostream>
#include <vector>
#include <boost/spirit/home/x3.hpp>
#include <boost/lexical_cast/try_lexical_convert.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
namespace x3 = boost::spirit::x3;
template <> struct x3::traits::detail::is_container_impl<boost::uuids::uuid> {
using type = std::false_type;
};
namespace Parser {
template <typename T> static inline auto as(auto p) {
return x3::rule<struct _, T>{"as"} = p;
}
static inline auto const to_uuid = [](auto& ctx) {
auto r = _attr(ctx);
_pass(ctx) = boost::conversion::try_lexical_convert<boost::uuids::uuid>(
std::string_view{r.begin(), r.end()}, _val(ctx));
};
static inline auto const uuid_ =
as<boost::uuids::uuid>(x3::raw[*(x3::xdigit | '-')][to_uuid]);
} // namespace Parser
int main() {
std::string uuid_str(R"( uid(78461bab-6d7c-48bc-a79a-b716d1ab97cb)
uid(9350cf32-7fe5-40d2-8a0d-c7f7562d7a15)
uid(bad-9350cf32-7fe5-40d2-8a0d-c7f7562d7a15)
)");
std::vector<boost::uuids::uuid> vec;
x3::phrase_parse(uuid_str.begin(), uuid_str.end(), *("uid(" >> Parser::uuid_ >> ")"), x3::space, vec);
for (auto const& item : vec)
std::cout << item << std::endl;
}
Prints
78461bab-6d7c-48bc-a79a-b716d1ab97cb
9350cf32-7fe5-40d2-8a0d-c7f7562d7a15
You said "Iterator" was hardcoded. In fact, it is. But realize that's used only for explicit template specialization. Nothing prevents you from specializing for several iterator types!
See e.g. You could workaround by manufacturing a correct context type and instantiate that (as well)