Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 521
haykart Avatar asked Jan 11 '23 13:01

haykart


2 Answers

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:

  • Boost spirit semantic action not invoked
  • Boost spirit using local variables

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:

    • Customization of Spirit's Attribute Handling, specifically:

      • Transform an Attribute to a Different Type
      • Store a Parsed Attribute Value

    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

like image 98
sehe Avatar answered Jan 24 '23 23:01

sehe


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.

like image 38
Tanner Sansbury Avatar answered Jan 25 '23 00:01

Tanner Sansbury