Search code examples
c++compiler-errorsboost-spirit

Detecting the parameter types in a Spirit semantic action


General case: I can't figure out why my Spirit grammar/semantics actions aren't compiling.

Sometimes, the compiler will complain about assignment or type incompatibilities and I have no clue what's wrong. The problem occurs in two main areas:

  • predicting the type of synthesized attributes for a rule/expression
    • consequently, predicting what types of attributes can be legally defined as the exposed attribute for the rule (relying in builting conversions, fusion adaptors or Spirit customization points)
  • matching the argument types for my semantic action so that
    • the compiler will be able to compile the function invocation
    • the invocation will not invoke unnecessary implicit conversions in the process

The compiler error is not exactly tractable, and either the documentation is wrong, or I misunderstood it.

Is there a way to find out exactly what Spirit passes into my semantic action, anyway?

Example code:

struct mybase             { int a,b; };
struct myderived : mybase { int c,d; };

BOOST_FUSION_ADAPT_STRUCT(mybase,    (int,a)(int,b));
BOOST_FUSION_ADAPT_STRUCT(myderived, (int,a)(int,b)(int,c)(int,d));

auto base_expr = int_ >> int_; // avoids assigning to struct attribute

rule<decltype(f), mybase()   , space_type> base_       = int_ >> int_;
rule<decltype(f), myderived(), space_type> derived_    = base_ >> int_ >> int_;

myderived data;
bool ok = phrase_parse(f,l,derived_,space,data);

This code won't compile, with a huge amount of impenetrable errors.

(loosely adapted from a posting on the spirit-general list)


Solution

  • For clarity - the error here is that base_ >> int_ >> int_ was used as the expression for a rule that creates a myderived, and since base_ is fixed to type mybase, we'd have to create a myderrived from a mybase and two ints, but there's nothing to tell Spirit how to do that.

    You can get boost to print out the type of the value that boost creates from parsing base_ >> int_ >> int_ by defining a functor that will take any parameters, and tell you what they are (the following code is adapted from some code sehe put on SO chat):

    struct what_is_the_attr
    {
        template <typename> struct result { typedef bool type; };
    
        template <typename T>
        static void print_the_type()
        {
            std::cout << "    ";
            std::cout << typeid(T).name();
            if(std::is_const<typename std::remove_reference<T>::type>::value)
                std::cout << " const";
            if(std::is_rvalue_reference<T>::value)
                std::cout << " &&";
            else if(std::is_lvalue_reference<T>::value)
                std::cout << " &";
        }
    
        template <typename Th, typename Th2, typename... Tt>
        static void print_the_type()
        {
            print_the_type<Th>();
            std::cout << ",\n";
            print_the_type<Th2, Tt...>();
        }
    
        template <typename... Ts>
        void operator()(Ts&&...) const
        {
            std::cout << "what_is_the_attr(\n";
            print_the_type<Ts...>();
            std::cout << ")" << std::endl;
        }
    };
    

    Then to use it, use the above actor in a semantic action on initializer for your faulty rule:

    std::string input = "1 2 3 4";
    auto f(std::begin(input)), l(std::end(input));
    
    rule<decltype(f), mybase()   , space_type> base_    = int_ >> int_;
    rule<decltype(f), myderived(), space_type> derived_ = (base_ >> int_ >> int_)[what_is_the_attr()];
    
    myderived data;
    bool ok = phrase_parse(f,l,derived_,space,data);
    

    Note, you cannot use automatic attribute propagation with %= (unless you remove the exposed attribute type from the rule's declared type).

    Running this should then yield an encoded type, which can be decoded with c++filt -t: Live On Coliru

    $ g++ 9404189.cpp -std=c++0x
    $ ./a.out |c++filt -t
    what_is_the_attr(
        boost::fusion::vector3<mybase, int, int> &,
        boost::spirit::context<boost::fusion::cons<boost::spirit::unused_type&, boost::fusion::nil>, boost::fusion::vector0<void> > &,
        bool &)
    

    The first line, boost::fusion::vector3<mybase, int, int>, least tells you that boost is trying to create your return type from 3 objects of types mybase, int and int.