Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
732 views
in Technique[技术] by (71.8m points)

c++ - Combining rules at runtime and returning rules

I am trying to write some complex parser made on top of Spirit-X3, so I need to know some things:

? How to combine rules at runtime. (with Nabialek's trick)

? Is it ok to return rules like this:

x3::rule<char> SomeFunction(std::string &str)
{
    x3::rule<char> foo;
    auto bar = baz;
    BOOST_SPIRIT_DEFINE(foo, bar);
    return foo;
}

PS: SomeFunction won't have a fixed return, so I can't use just a x3::sequence

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Yes, x3 makes it a lot easier to compose rules.

Mainly because parser expressions don't have the tendency to keep references to temporaries when assigned to variables, like they used to in the age of Qi1.

Limitation: declaring parsers with external linkage is a lot more ... complicated in X3, requiring the dance with the macros that you show BOOST_SPIRIT_{DECLARE,DEFINE}.

Your Sample

That won't fly because the macro is meant to be used at namespace scope. The good news is you may not need it, because there is no need to declare the rule separately from the definition unless you are dealing with recursively required rules.

As an aside x3::rule<char> is likely a mistake. char is a tag type in that declaration, and that's not a good tag type. If you wanted an attribute type instead, that needs to be the second template argument.

auto SomeFunction(std::string &str)
{
    return x3::rule<struct _tag, std::string> {"dynamic"}
        = '[' >> x3::lit(str) >> ']';
}

In fact I very frequently make little factories at my declaration site:

template <typename Attr>
auto compose = [](auto p1, auto p2) {
     return rule<struct _, Attr> {"compose"}
         = nocase [ 
               lexeme [ "property:" << as_parser(p1) ]
               >> '='
               lexeme [ "value:" << as_parser(p2) ]
           ];                  
};

That's a bit contrived but should give you ideas. Use it like compose<int>("number", x3::int_) or compose<std::string>("name", +x3::graph)

Some Inspirational Examples

  • Understanding the List Operator (%) in Boost.Spirit showing an ad-hoc as<>[] facility:

    namespace {
        template <typename T>
        struct as_type {
            template <typename Expr>
                auto operator[](Expr&& expr) const {
                    return x3::rule<struct _, T>{"as"} = x3::as_parser(std::forward<Expr>(expr));
                }
        };
    
        template <typename T> static const as_type<T> as = {};
    }
    
  • Avoid throwing expectation_failure when expectation parser fails which dynamically composes a symbols lookup:

    x3::symbols<char> const keyword = []{
        x3::symbols<char> kw;
        kw += "for","begin","end","function","while","break","switch";
        return kw;
    }();
    
  • Dynamically switching symbol tables in x3 which is a very complete example with many parser factories:

    // (case insensitive) keyword handling
    static auto kw        = [](auto p) { return x3::lexeme[p >> !(x3::graph - x3::char_("/=,()"))]; };
    static auto ikw       = [](auto p) { return x3::no_case [kw(p)]; };
    static auto qualifier = [](auto p) { return x3::lexeme['/' >> ikw(p)]; };
    

    And even shows how to override as_spirit_parser for your own types:

    // Options and CiOptions
    namespace util {
        template <typename Tag>
        auto as_spirit_parser(Options<Tag> const& o, bool to_lower = false) {
            x3::symbols<typename Options<Tag>::type> p;
            int n = 0;
            for (std::string el : o._options) {
                if (to_lower) boost::to_lower(el);
                p.add(el, n++);
            }
            return kw(p);
        }
    
        template <typename Tag>
        auto as_spirit_parser(IcOptions<Tag> const& o) {
            return x3::no_case [ as_spirit_parser(o, true) ];
        }
    }
    

    And pretty elegant way to write member-wise propagation helpers with auto-generated semantic actions:

        auto set = [](auto member, auto p) {
            auto propagate = [member](auto& ctx) {
                traits::move_to(_attr(ctx), _val(ctx).*(member));
            };
            return as_parser(p)[propagate];
        };
    
        using T = ast::ShowSymbolsCommand;;
        return qualifier("all")  >> set(&T::all, attr(true))
             | qualifier("full") >> set(&T::full, attr(true))
             | qualifier("out")  >> set(&T::out, '=' >> Filespec)
             | qualifier("type") >> set(&T::types, '=' >> SymbolTypes)
             | set(&T::wildcard, Wildcard);
    

I strongly suggest you per-use these examples to get a sense of just how powerful X3 composing is. Only when you /really/ require it would I consider recreating something like qi::lazy in X3


1 or in fact anything Proto-based, like Phoenix too


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...