c++boost-spirit

In boost-spirit is there any way to create grammars by function call in order to reduce code?


The following code crashes. I suspect that the grammar cannot be copied.

#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_no_case.hpp>
#include <boost/phoenix/bind/bind_function.hpp>
#include <boost/phoenix/bind/bind_function_object.hpp>
#include <boost/phoenix/operator.hpp>


namespace test
{
namespace qi = boost::spirit::qi;
template<typename IT>
auto make_keyword_parser(const char *const _p, const bool _b)
{   return (qi::eps(_b) >> qi::lit(_p)) | (qi::eps(!_b) >> qi::no_case[qi::lit(_p)]);
}
template<typename IT>
struct booleanParser:qi::grammar<IT, bool(), qi::space_type>
{   qi::rule<IT, bool(), qi::space_type> m_sStart;
    booleanParser(const bool _b)
        :booleanParser::base_type(m_sStart, "booleanParser")
    {   m_sStart = make_keyword_parser<IT>("true", _b)[qi::_val = true]
            | make_keyword_parser<IT>("false", _b)[qi::_val = false];
    }
};
}
int main()
{
    namespace qi = boost::spirit::qi;
    test::booleanParser<const char*> sGrammar(false);
    bool b;
    static constexpr char ac[] = "True";
    auto p = ac;
    if (!boost::spirit::qi::phrase_parse(p, ac + sizeof ac - 1, sGrammar, qi::space_type(), b) || p != ac + sizeof ac - 1)
        std::cerr << "error" << std::endl;
    else
        std::cerr << "b=" << b << std::endl;
}

The code works fine, if the body of the make_keyword_parser() function consists only of return qi::lit(_p);.

It looks like your post is mostly code; please add some more details. It looks like your post is mostly code; please add some more details. It looks like your post is mostly code; please add some more details.


Solution

  • Without reading your code, I've treated multiple very similar questions in the past.

    The TL;DR is "Here be dragons" as it is very easy to accidentally invoke Undefined Behaviour due the fact that Phoenix expressions are not meant to be named, so liberally contain references to temporaries.

    Practically all these posts will spend some time avoiding the UB traps, so caveat emptor.

    Now let me read the question code and see whether I can add something specific to that.

    Review

    Indeed your code also suffered the UB. You can avoid it by requesting a deep-copy of your phoenix-expression when you return it:

    return qi::copy((qi::eps(_b) >> _p) | (qi::eps(!_b) >> qi::no_case[_p]));
    

    qi::copy is just a convenience shorthand for boost::proto::deep_copy.

    Live On Coliru

    #include <boost/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <iostream>
    using namespace std::string_view_literals;
    namespace qi = boost::spirit::qi;
    
    namespace test {
        template <typename IT> auto make_keyword_parser(char const* const _p, bool const _b) {
            return qi::copy((qi::eps(_b) >> _p) | (qi::eps(!_b) >> qi::no_case[_p]));
        }
    
        template <typename It> struct booleanParser : qi::grammar<It, bool()> {
            booleanParser(bool const _b) : booleanParser::base_type(m_sStart, "booleanParser") {
                m_sStart = make_keyword_parser<It>("true", _b)[qi::_val = true] |
                    make_keyword_parser<It>("false", _b)[qi::_val = false];
            }
    
          private:
            qi::rule<It, bool()> m_sStart;
        };
    } // namespace test
    
    int main() {
        for (bool opt : {true, false}) {
            std::cout << " --- Opt: " << std::boolalpha << opt << std::endl;
            test::booleanParser<char const*> sGrammar(opt);
    
            static constexpr auto txt = "True"sv;
    
            if (bool b; parse(begin(txt), end(txt), sGrammar >> qi::eoi, b))
                std::cout << "b=" << b << std::endl;
            else
                std::cout << "error" << std::endl;
        }
    }
    

    Prints

     --- Opt: true
    error
     --- Opt: false
    b=true
    

    I'd be remiss if I didn't make a few remarks:

    I think that's about it for now.