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

Remove a spirit qi symbol in semantic action


In other threads I've read how to add a symbol to the symbol table in a semantic action, but I don't know how to remove it.

The idea behind my question is, that I want to allow to rename keywords in the parsed text. So, several keywords with values are given, but the user can reassign them:

reassign(keyword)(mykeyword)

I have a rule with semantic action

using namespace boost::spirit::qi;
...
qi::symbols<char, TYPE> keywords;
...
key_replace = ( lit("reassign") >> lit("(") >> keywords >> lit(")") >> 
                                   lit("(") >> lexeme [ raw [ ( alpha >> *( alnum | '_' ) ) ] ] >> lit (")") ) 
              [ boost::phoenix::bind(keywords.remove, _1) ]; // TODO: replace not remove

The problem is, that I don't get a reference to the symbol itself, but to the stored value. So calling remove doesn't work.

How can I get a reference to the parsed symbol during parsing?

Is there a simpler way to exchange a symbol while preserving the value during parsing?


Solution

  • The naive 'phoenix' way would be to

     rule_assign = key >> value
            [ phx::bind(keywords.add, _1, _2) ];
     rule_remove = key
            [ phx::bind(keywords.remove, _1) ];
    
     // and voila: (BROKEN)
     rule_replace = key >> value
            [ phx::bind(keywords.remove, _1),
              phx::bind(keywords.add, _1, _2)
            ];
    

    The latter doesn't work, I believe due to the fact that the first bind returns an object that overloads operator, itself, and it gets preferred over phoenix's operator,.

    I suggest you work around this by writing a little helper:

    struct assign_symbol_f
    {
        assign_symbol_f(Symbols& sym) : sym(sym) {}
    
        typedef bool result_type;
    
        template<typename Key, typename Value>
        bool operator()(Key const& key, Value const& value) const
        {
            bool replaced = (nullptr != sym.find(key));
    
            sym.remove(key); 
            sym.add(key, value);
    
            return replaced;
        }
    
    private:
        Symbols& sym;
    };
    

    This transparently assigns or reassigns an item in a symbols tree. Use it as follows:

    rule_replace_or_add = key >> value
            [ phx::bind(assign_symbol_f(keywords), qi::_1, qi::_2) ];
    

    Now, you could split things and be more specific:

    assign_symbol_f assign_sym(keywords);
    
    rule_assign = key >> value
            [ qi::_pass = !phx::bind(assign_sym, _1, _2) ];
    
    rule_replace = key >> value
            [ qi::_pass =  phx::bind(assign_sym, _1, _2) ];
    

    Bonus

    As a bonus you can have a little bit of syntactic sugar by creating a lazy actor for your functor:

    phx::function<assign_symbol_f> assign_sym;
    
    // use it like
    rule_assign = key >> value
            [ qi::_pass = assign_sym(_1, _2) ];
    
    rule_replace = key >> value
            [ qi::_pass = assign_sym(_1, _2) ];
    

    Look ma! No more phx::bind.

    Full demo

    Complete with a rudimentary test suite :)

    #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 phx = boost::phoenix;
    
    typedef qi::symbols<char, int> Symbols;
    
    struct assign_symbol_f
    {
        assign_symbol_f(Symbols& sym) : sym(sym) {}
    
        typedef bool result_type;
    
        template<typename Key, typename Value>
        bool operator()(Key const& key, Value const& value) const
        {
            bool replaced = (nullptr != sym.find(key));
    
            sym.remove(key); 
            sym.add(key, value);
    
            return replaced;
        }
    
    private:
        Symbols& sym;
    };
    
    template <typename Iter> struct parser : qi::grammar<Iter, qi::space_type> 
    {
        parser(Symbols &dict) 
            : parser::base_type(start), 
              assign_sym(dict)
        {
            using namespace qi;
            identifier = +graph;
    
            add_rule = lit("+") >> (identifier >> int_)
                [ assign_sym(_1, _2) ]
                ;
    
            del_rule = lit("-") >> identifier
                [ phx::bind(dict.remove, _1) ]
                ;
    
            start = (del_rule | add_rule) % ";";
        }
    
      private:
        phx::function<assign_symbol_f> assign_sym;
    
        qi::rule<Iter, qi::space_type> start, del_rule, add_rule;
        qi::rule<Iter, std::string()> identifier; // no skipper
    };
    
    bool execute(std::string const& test, Symbols& dict)
    {
        auto f = test.begin(), l = test.end();
    
        parser<std::string::const_iterator> prsr(dict);
        return 
            qi::phrase_parse(f, l, prsr, qi::space)
            && (f == l);
    }
    
    int main() {
        Symbols dict;
    
        assert(execute("+foo 3; +bar 4; -foo", dict));
    
        assert(!dict.find("foo"));
        assert( dict.find("bar") && (4 == dict.at("bar")));
        assert(!dict.find("zap"));
    
        assert(execute("+zap -42; +bar 5; +foo 33", dict));
    
        assert( dict.find("zap") && (-42 == dict.at("zap")));
        assert( dict.find("bar") && (5   == dict.at("bar"))); // replaced
        assert( dict.find("foo") && (33  == dict.at("foo")));
    }