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

How to rewrite qi parsers with inherited attributes in x3?


If the inherited attributes are used in semantic actions, we can use x3::with directive.

What if we want to use the attributes as part of the parser? For example a simple parser matches 1 or more alphabet characters except the character is from a parameter char set.

qi::rule<std::string::const_iterator, qi::unused_type(char const*)> rule =
    +(qi::alpha - qi::char_(qi::_r1));

Or the parameter char set could be used as a lazy parser.

qi::rule<std::string::const_iterator, qi::unused_type(char const*)> rule =
    +(qi::alpha - qi::lazy(qi::_r1));

x3::with directive put this local value in the context. I'm not sure if we could use this context outside a semantic action and eventually generate a parser.


Solution

  • Simply let go of the old habit of rule-ifying everything.

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    
    namespace x3 = boost::spirit::x3;
    
    template <typename... Args>
    auto negate(Args&&... p) {
        return +(x3::char_ - x3::char_(std::forward<Args>(p)...));
    };
    
    int main() {
        std::string input("all the king's men and all the king's horses"), parsed;
        if (parse(input.begin(), input.end(), negate("horse"), parsed))
            std::cout << "'" << input << "' -> '" << parsed << "'\n";
    }
    

    Live On Coliru, prints:

    'all the king's men and all the king's horses' -> 'all t'

    Second flavour:

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    
    namespace x3 = boost::spirit::x3;
    
    template <typename Sub>
    auto negate(Sub p) {
        return +(x3::char_ - x3::as_parser(p));
    };
    
    int main() {
        std::string input("all the king's men and all the king's horses"), parsed;
        if (parse(input.begin(), input.end(), negate("horse"), parsed))
            std::cout << "'" << input << "' -> '" << parsed << "'\n";
    }
    

    Live On Coliru, prints:

    'all the king's men and all the king's horses' -> 'all the king's men and all the king's '

    More complicated stuff

    You can also aggregate sub parsers in a custom parser:

    If you need resursive rules with passing around, I'd suggest x3::with<> (although I'm not sure the contexts build re-entrant state for the with<>, you need to test the precise semantics unless you can find documentation for it)