Search code examples
c++boostboost-spirit-x3

Why does my component compile as a container but spirit::x3::int_parser does not?


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

Solution

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

    Live On Coliru

    #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
    

    Add Some Debuggability

    Live On Coliru

    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
    

    Simplify

    In fact, as you can see you don't need to use BOOST_SPIRIT_DEFINE, separating instantiation from declaration at all:

    Live On Coliru

    #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
    

    Also, Besides...

    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)