haykart
haykart

Reputation: 957

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.

Upvotes: 4

Views: 1502

Answers (2)

sehe
sehe

Reputation: 394054

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

Upvotes: 7

Tanner Sansbury
Tanner Sansbury

Reputation: 51971

The code compiles with C++03. However, when using GCC 4.6's C++11 support, the code fails to compile. Here are the relevant excerpts from the error:

/usr/local/include/boost/spirit/home/support/action_dispatch.hpp: In static
    member function 'static void boost::spirit::traits::action_dispatch<
    Component>::caller(F&&, A&& ...) [with F =
    const std::_Bind<with Boost.Phoenix actors>]'

...

main.cpp:25:9: instantiated from 'x_grammar<Iterator>::x_grammar() [...]

/usr/local/include/boost/spirit/home/support/action_dispatch.hpp:142:13: error:
    no matching function for call to 'boost::spirit::traits::
    action_dispatch<...>::do_call(const std::_Bind<with Boost.Phoenix actors>)'

Despite the using boost::phoenix::bind directive, the unqualified call to bind() is resolving to std::bind() rather than boost::phoenix::bind(), but the arguments are resolving to Boost.Phoenix actors. The Boost.Spirit documentation specifically warns about not mixing placeholders from different libraries:

You have to make sure not to mix placeholders with a library they don't belong to and not to use different libraries while writing a semantic action.

Hence, the compilation problem can be resolved by being explicit when defining the semantic action. Use either:

std::bind(dummy, std::placeholders::_1, std::ref(local_var))

or:

boost::phoenix::bind(dummy, _1, ref(local_var))

While that resolves the compiler error, it is worth noting that the ref(local_var) object will maintain a dangling reference, as its lifetime extends beyond that of local_var. Here is a working example where local_var's lifetime is extend to beyond the scope of the constructor by making it static.

Upvotes: 2

Related Questions