Search code examples
c++boostboost-spiritboost-spirit-qiboost-phoenix

Boost spirit changing variable value in semantic action


I want to change a local variable value in semantic action, like following:

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>

namespace qi = boost::spirit::qi;
namespace spirit = boost::spirit;
namespace ascii = boost::spirit::ascii;
using boost::phoenix::ref;
using boost::phoenix::bind;

void dummy(const std::vector<char>& v, int& var)
{
    var = 7;
}

template <typename Iterator>
struct x_grammar : public qi::grammar<Iterator, std::string(), ascii::space_type>
{
public:
    x_grammar() : x_grammar::base_type(start_rule, "x_grammar")
    {
        using namespace qi;
        int local_var = 0;
        start_rule = (+(char_ - ";"))[bind(dummy, _1, ref(local_var))];
        //repeat(ref(local_var))[some_rule];
    }
private:
    qi::rule<Iterator, std::string(), ascii::space_type> start_rule;
};

int main()
{
    typedef std::string::const_iterator iter;
    std::string storage("string;aaa");
    iter it_begin(storage.begin());
    iter it_end(storage.end());
    std::string read_data;
    using boost::spirit::ascii::space;
    x_grammar<iter> g;
    try {
        bool r = qi::phrase_parse(it_begin, it_end, g, space, read_data);
        std::cout << "Pass!\n";
    } catch (const qi::expectation_failure<iter>& x) {
        std::cout << "Error!\n";
    }
}

I am getting some annoying compile errors using GCC 4.6.1 with boost 1.55.


Solution

  • I can't help but note that if compiler errors annoy you, then perhaps you should write valid code :/


    Instructive Hat On...

    While that's of course a flippant remark, it's also somewhat enlightening.

    I've told you twice now that the whole idea of using constructor local variables in your grammar is fundamentally broken:

    What you want is

    • inherited attributes
    • qi::locals
    • maayyyyybe, maaaayyybe grammar member variables; with the caveat that they make your rules non-re-entrant.

    The important thing here to really get inside your head is

    Boost Spirit generates parser from expression templates. Expression templates are 90% static information (type only), and get "compiled" (.compile()) into "invokable" (.parse()) form.

    Most importantly, while you can write control flow in your semantic actions, none of this actually executed at the definition site. It's "compiled" into a lazy actor that can later be invoked.

    The generated parse will conditionally invoke the lazy actor when the corresponding parse expression matches


    Constructive Hat On...

    It looks like you just want to transform attributes using a function.

    Here's what you can do:

    1. transform as part of the semantic action, placing the result into the regular attribute (maintaining 'functional' semantics for parser composition):

      qi::rule<Iterator, exposed(), Skipper> myrule;
      myrule = int_ [ _val = phx::bind(custom_xform, _1) ];
      

      Where custom_xform is any old-school calleable (including polymorphic ones):

      exposed custom_xform(int i) { return make_new_exposed(i); } 
      // or
      struct custom_xfrom_t {
      
        template <typename> struct result { typedef exposed type; };
      
        template <typename Int>
          exposed operator()(Int i) const {
              return make_new_exposed(i);
          }
      };
      static const custom_xform_t custom_xform;
      
    2. You can add some syntactic sugar [1]

      qi::rule<Iterator, exposed(), Skipper> myrule;
      myrule = int_ [ _val = custom_xform(_1) ];
      

      This requires custom_xform is defined as a lazy actor:

      phx::function<custom_xform_t> custom_xform; // `custom_xform_t` again the (polymorphic) functor
      

      You may note this wouldn't work for a regular function. You could wrap it in a calleable object, or use the BOOST_PHOENIX_ADAPT_FUNCTION macro to do just that for you

    3. If you have some more involved transformations that you want to apply more often, consider using the Spirit Customization Points:

      These work most smoothly if you choose specific types for your attributes (e.g. Ast::Multiplicity or Ast::VelocityRanking, instead of int or double


    [1] using BOOST_SPIRIT_USE_PHOENIX_V3