c++boost-spiritboost-spirit-qiboost-phoenix

avoid construct template in boost spirit semantic action


This code works. This question is about making it (look) better. I have seen known the article about utrees but I'm not not sure that is the best way.

Let me show you the "ugly" version of the code, which uses construct<>

newcall =(nocaselit(L"new") > tyname)
            [_val = construct<common_node>(type_cmd_new,key_typename, construct<std::wstring>(_1))];

With the rules declared as:

qi::rule<Iterator, common_node(), Skipper> newcall
qi::rule<Iterator, std::wstring()> tyname;

The target common AST node is:

struct common_node {
    template <typename X>
    common_node(node_type t, node_key k1, const X & m1)

The first parameter is the node type, the second some kind of member key and the last the payload given as template argument (later stored in a variant).

Can we avoid the construct template?


Solution

  • This is the classic case where I always link to Boost Spirit: "Semantic actions are evil"?: avoid semantic actions.

    In this case I don't know what your AST really looks like (what is node_key, where does key_typename come from etc.) so I can't really show you much.

    Usually I'd adapt the node types and declare rules for the concrete node types. If that doesn't work, I prefer phoenix::function<> wrappers:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    namespace qi = boost::spirit::qi;
    
    struct SomeComplicatedType {
        enum Type { None, NewCall };
        struct Key{};
        SomeComplicatedType(Type = {}, Key = {}, std::string n = "") : name(std::move(n)) { }
    
        std::string name;
    };
    
    static SomeComplicatedType::Key const s_default_key;
    
    template <typename It>
    struct Grammar : qi::grammar<It, SomeComplicatedType()>
    {
        Grammar() : Grammar::base_type(start) {
            using namespace qi;
            start  = skip(space) [new_];
            tyname = raw[(alpha|'_') >> +(alnum|'_')];
    
            new_   = no_case["new"] > tyname [_val = make_new(_1)];
    
            BOOST_SPIRIT_DEBUG_NODES((start)(new_)(tyname))
        }
      private:
        qi::rule<It, SomeComplicatedType()> start;
        qi::rule<It, SomeComplicatedType(), qi::space_type> new_;
        qi::rule<It, std::string()> tyname;
    
        struct make_complicated_t {
            SomeComplicatedType::Type _type;
    
            SomeComplicatedType operator()(std::string const& s) const {
                return SomeComplicatedType{_type, s_default_key, s};
            }
        };
        boost::phoenix::function<make_complicated_t> make_new { make_complicated_t{SomeComplicatedType::NewCall } };
    };
    
    int main() {
        std::string const input = "new Sandwich";
    
        SomeComplicatedType result;
        if (parse(input.begin(), input.end(), Grammar<std::string::const_iterator>{}, result))
            std::cout << "Parsed: " << result.name << "\n";
    
    }
    

    Prints

    Parsed: Sandwich