Search code examples
c++boostboost-spirit

Boost spirit, returned value from a semantic action interferes with the rule attribute


The following program is an artificial example (reduced from a larger grammar on which I'm working) to exhibit a strange behaviour.

The output of the program run as is is "hello" and is incorrect.

If I remove the (useless in this example) semantic action from the quoted_string rule the output is the expected "foo=hello".

#define BOOST_RESULT_OF_USE_DECLTYPE
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <vector>
#include <string>
#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include "utils.hpp"

namespace t {
    using std::vector;
    using std::string;
    namespace qi = boost::spirit::qi;
    namespace phx = boost::phoenix;

    template <typename Iterator, typename Skipper=qi::space_type>
    struct G1 : qi::grammar<Iterator, string(), Skipper> {

        template <typename T>
        using rule = qi::rule<Iterator, T, Skipper>;

        qi::rule<Iterator, string(), qi::locals<char>> quoted_string;
        rule<string()> start;

        G1() : G1::base_type(start, "G1") {
            {
                using qi::_1;
                using qi::_a;

                using attr_signature = vector<char>;
                auto handler = [](attr_signature const& elements) -> string {
                    string output;
                    for(auto& e : elements) {
                        output += e;
                    }
                    return output;
                };
                quoted_string = (qi::omit[qi::char_("'\"")[_a = _1]]
                    >> +(qi::char_ - qi::char_(_a))
                    >> qi::lit(_a))[qi::_val = phx::bind(handler, _1)];
            }
            start = qi::string("foo") >> -(qi::string("=") >> quoted_string);
        }
    };

    string parse(string const input) {
        G1<string::const_iterator> g;
        string result;
        phrase_parse(begin(input), end(input), g, qi::standard::space, result);
        return result;
    }
};

int main() {
    using namespace std;
    auto r = t::parse("foo='hello'");
    cout << r << endl;
}

I can definitely find a workaround, but I'd figure out what am I missing


Solution

  • Like @cv_and_he explained, you're overwriting the attribute with the result of handler(_1). Since attributes are passed by reference, you lose the original value.

    Automatic attribute propagation rules know how to concatenate "string" container values, so why don't you just use the default implementation?

        quoted_string %= qi::omit[qi::char_("'\"")[_a = _1]]
            >> +(qi::char_ - qi::char_(_a))
            >> qi::lit(_a);
    

    (Note the %=; this enables automatic propagation even in the presence of semantic actions).

    Alternatively, you can push-back from inside the SA:

     >> +(qi::char_ - qi::char_(_a)) [ phx::push_back(qi::_val, _1) ]
    

    And, if you really need some processing done in handler, make it take the string by reference:

    auto handler = [](attr_signature const& elements, std::string& attr) {
        for(auto& e : elements) {
            attr += e;
        }
    };
    quoted_string = (qi::omit[qi::char_("'\"")[_a = _1]]
        >> +(qi::char_ - qi::char_(_a))
        >> qi::lit(_a)) [ phx::bind(handler, _1, qi::_val) ];
    

    All these approaches work.

    For really heavy duty things, I have in the past used a custom string type with boost::spirit::traits customization points to do the transformations: