Search code examples
c++boostboost-spiritboost-spirit-qi

boost spirit assign_to_* customization points


I'm trying to understand how boost spirit assign_to_* customization points work.

Here is an exemple I am using:

I have this parser in a rule in a grammar:

int_ >> lit(':') >> char_

And I want the result to be put in this struct:

struct IntAndChar{ int i; char c; };

(This is just an exemple to use the customization point so I won't use the BOOST_FUSION_ADAPT_STRUCT or semantic actions.)

I thought I could just define a specialization of assign_to_attribute_from_value but I only get the int this way and the second element is dropped.

Can someone give me a hint to understand how it works?


Solution

  • You don't want to assign to the attribute¹. Instead you wish to transform boost::fusion::vector2<int, char> into IntAndChar.

    Therefore, let's start off telling spirit our type is not container-like:

    template<>
    struct is_container<IntAndChar, void> : mpl::false_ { };
    

    Next, tell it how it can transform e between raw and cooked forms of our attributes:

    template<>
    struct transform_attribute<IntAndChar, fusion::vector2<int, char>, qi::domain, void> {
        using Transformed = fusion::vector2<int, char>; 
        using Exposed     = IntAndChar;
        using type        = Transformed;
    
        static Transformed pre(Exposed&) { return Transformed(); }
    
        static void post(Exposed& val, Transformed const& attr) {
            val.i = fusion::at_c<0>(attr);
            val.c = fusion::at_c<1>(attr);
        }
    
        static void fail(Exposed&) {}
    };
    

    That's it! There is one catch though. It won't work unless you trigger a transformation. The docs say:

    It is invoked by Qi rule, semantic action and attr_cast, [...]

    1. Using qi::rule (not very helpful)

    So here's a solution using rule:

    Live On Coliru

    int main() {
        using It = std::string::const_iterator;
    
        qi::rule<It, boost::fusion::vector2<int, char>(), qi::space_type> rule = qi::int_ >> ':' >> qi::char_;
        //qi::rule<It, IntAndChar(), qi::space_type> rule = qi::attr_cast(qi::int_ >> ':' >> qi::char_);
    
        for (std::string const input : { "123:a", "-4 :   \r\nq" }) {
            It f = input.begin(), l = input.end();
            IntAndChar data;
    
            bool ok = qi::phrase_parse(f, l, rule, qi::space, data);
    
            if (ok)     std::cout << "Parse success: " << data.i << ", " << data.c << "\n";
            else        std::cout << "Parse failure ('" << input << "')\n";
            if (f != l) std::cout << "Remaining unparsed input: '" << std::string(f, l) << "'\n";
        }
    }
    

    Prints:

    Parse success: 123, a
    Parse success: -4, q
    

    Of course this approach requires you to spell out boost::fusion::vector2<int, char> which is tedious and error-prone.

    2. Using qi::attr_cast

    You can use qi::attr_cast to trigger the transform:

    qi::rule<It, IntAndChar(), qi::space_type> rule = qi::attr_cast<IntAndChar, boost::fusion::vector2<int, char> >(qi::int_ >> ':' >> qi::char_);
    // using deduction:
    qi::rule<It, IntAndChar(), qi::space_type> rule = qi::attr_cast<IntAndChar>(qi::int_ >> ':' >> qi::char_);
    // using even more deduction:
    qi::rule<It, IntAndChar(), qi::space_type> rule = qi::attr_cast(qi::int_ >> ':' >> qi::char_);
    

    CAVEAT That should work. However, due to very subtle behaviour (bugs?) you need to deep-copy the Proto expression tree there, in order for it to work without Undefined Behaviour:

    qi::rule<It, IntAndChar(), qi::space_type> rule = qi::attr_cast(qi::copy(qi::int_ >> ':' >> qi::char_));
    

    Bringing it all together, we can even do without the qi::rule:

    Live On Coliru

    int main() {
        using It = std::string::const_iterator;
    
        for (std::string const input : { "123:a", "-4 :   \r\nq" }) {
            It f = input.begin(), l = input.end();
            IntAndChar data;
    
            bool ok = qi::phrase_parse(f, l, qi::attr_cast(qi::copy(qi::int_ >> ':' >> qi::char_)), qi::space, data);
    
            if (ok)     std::cout << "Parse success: " << data.i << ", " << data.c << "\n";
            else        std::cout << "Parse failure ('" << input << "')\n";
            if (f != l) std::cout << "Remaining unparsed input: '" << std::string(f, l) << "'\n";
    
        }
    }
    

    Prints

    Parse success: 123, a
    Parse success: -4, q
    

    ¹ (unless you want to treat IntAndChar as a container, which is a different story)