Search code examples
c++boost-spiritboost-spirit-qiboost-phoenixsemantic-actions

In boost spirit how can one set the force-rule-to-not-match bool reference parameter from a semantic action routine creating an attribute?


Currently I have something like this:

qi::_val = boost::phoenix::bind(
        [](const std::string&, const boost::optional<std::string>&)
        {       return std::string();
        },
        qi::_1,
        qi::_2
)

I would like to optionally cause this rule not to match. The documentation regarding semantic functions says nothing about how the attribute of the created object is returned.

I tried adding two additional parameters to the lambda function:

const boost::fusion::unused_type&, bool&

but this does not work.


Solution

  • The signatures documented here:

    void f(Attrib const&);
    void f(Attrib const&, Context&);
    void f(Attrib const&, Context&, bool&);
    

    Note the precision about function objects. I think the difference is only due to legacy C++03 compatibility

    are low-level, shall we say "raw" semantic-action functions. They're only interesting if you're a library developer wanting to learn all the nitty gritty details about how the Context is statically composed, and dynamically accessed.

    The context contains skippers, local attributes, inherited attributes, synthesized attributes, the bound attribute reference and the parse-result flag.

    Instead of dealing with these signatures, people use Phoenix expressions to create concrete semantic actions. Qi defines placeholder actors to correspond to the context elements:

    • _1, _2... , _N
      Nth attribute of p

    • _val
      The enclosing rule's synthesized attribute.

    • _r1, _r2... , _rN
      The enclosing rule's Nth inherited attribute.

    • _a, _b... , _j
      The enclosing rule's local variables (_a refers to the first).

    • _pass
      Assign false to _pass to force a parser failure.

    You already chose Phoenix (using phoenix::bind). So I'd recommend staying with phoenix-style actions. The simplest way to get your example to work:

    qi::_val = boost::phoenix::bind(
            [](const std::string&, const boost::optional<std::string>&, bool& pass)
            {    pass = rand()%2;   
                 return std::string();
            },
            qi::_1,
            qi::_2,
            qi::_pass
    )
    

    Bonus/Modernize

    But note that you can also employ more highlevel Phoenix facilities.

    I personally prefer Phoenix adapting callables. E.g. let's write a more functional semantic action:

    struct magic_f {
        std::string operator()(std::string const& s1, boost::optional<std::string> s2, bool& pass) const {
            if (s2) {
                std::reverse(s2->begin(), s2->end());
            } else {
                s2 = s1;
            }
    
            pass = (s1 == s2);
            return std::move(s2.value());
        }
    };
    

    The validation logic it implements is: if the second string is present it must match the first one in reverse.

    Now, you can use phoenix::function to create a corresponding actor:

    boost::phoenix::function<magic_f> magic;
    

    This means you can write the action simply:

    word  = +qi::graph;
    pair  = (word >> -word)[ _val = magic(_1, _2, _pass) ];
    

    See this Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/phoenix.hpp>
    #include <iomanip>
    namespace qi = boost::spirit::qi;
    
    using Word = std::string;
    
    template <typename It> struct Demo : qi::grammar<It, Word()> {
        Demo() : Demo::base_type(start) {
            using namespace qi::labels; // for the phoenix expression placeholders
    
            word  = +qi::graph;
            pair  = (word >> -word)[ _val = magic(_1, _2, _pass) ];
    
            start = qi::skip(qi::space)[ pair ];
        }
    
      private:
        struct magic_f {
            Word operator()(Word const& s1, boost::optional<Word> s2, bool& pass) const {
                if (s2)
                    std::reverse(s2->begin(), s2->end());
                else
                    s2 = s1;
    
                pass = (s1 == s2);
                return std::move(s2.value());
            }
        };
        boost::phoenix::function<magic_f> magic;
    
        qi::rule<It, Word()> start, word;
        qi::rule<It, Word(), qi::space_type> pair;
    };
    
    int main() {
        static Demo<Word::const_iterator> const p;
    
        for (std::string const s : {"", "foo", "foo foo", "foo bar", "foo oof", "bar bra", "bar rab"})
            if (Word word; parse(begin(s), end(s), p, word))
                std::cout << std::setw(12) << quoted(s) << " -> MATCH " << quoted(word) << std::endl;
            else
                std::cout << std::setw(12) << quoted(s) << " -> FAIL" << std::endl;
    }
    

    Output:

              "" -> FAIL
           "foo" -> MATCH "foo"
       "foo foo" -> FAIL
       "foo bar" -> FAIL
       "foo oof" -> MATCH "foo"
       "bar bra" -> FAIL
       "bar rab" -> MATCH "bar"
    

    Observations:

    Note that this way you are in no way required to take bool& pass as a parameter. Instead, you could return the bool:

    struct magic_f {
        bool operator()(Word& s1, boost::optional<Word> s2, Word& out) const {
            if (s2) {
                std::reverse(s2->begin(), s2->end());
                if (s1!=s2)
                    return false;
            }
    
            out = std::move(s1);
            return true;
        }
    };
    

    And write the action like:

    pair  = (word >> -word)[ _pass = magic(_1, _2, _val) ];
    

    With the exact same results.

    Often you can leverage existing actors (e.g. for standard library functions and operators) and write without a custom function at all, e.g. to match a word only if followed by its length:

    pair %= word >> qi::omit[qi::int_[_pass = size(_val) == _1]];
    

    See it Live On Coliru

    C++17 sweetness

    In C++17 you can use lambdas conveniently due to CTAD. That way you can avoid defining the magic_f type as before:

    boost::phoenix::function magic = [](auto& s1, auto& s2, bool& pass) {
        if (s2)
            std::reverse(s2->begin(), s2->end());
        else
            s2 = s1;
    
        pass = (s1 == s2);
        return std::move(s2.value());
    };
    
    word  = +qi::graph;
    pair  = (word >> -word)[ _val = magic(_1, _2, _pass) ];
    

    See it Live On Coliru