Search code examples
c++referenceboost-spiritboost-spirit-qiboost-phoenix

boost spirit qi on_error pass error_handler struct by reference


I have yet another blocker issue with Spirit Qi.

I have implemented error handling in a functor struct called error_handler. This is passed to the grammar constructor by reference (see the MiniC example for Qi).

I then have on_error<fail>s defined in the grammar's constructor:

typedef boost::phoenix::function<error_handler<> > error_handler_function;
on_error<fail>(gr_instruction,
        error_handler_function(err_handler)(L"Error: Expecting ", _4, _3));
        // more on_error<fail>s...

However, my error_handler has private members. It seems every time on_error is invoked, the err_handler object is copied, hence once the functor leaves, the local variables changed are destroyed.

I tried passing the handler by reference:

typedef boost::phoenix::function<error_handler<>& > error_handler_function; // <--- Note the ampersand!

on_error<fail>(gr_instruction,
        error_handler_function(err_handler)(L"Error: Expecting ", _4, _3));
        // more on_error<fail>s...

However, the problem remains: on_error() works on copies of err_handler, not a single instance!!

I have also tried variations of boost::phoenix::ref(err_handler) with nothing but compile errors.

Surely, there must be an easy solution to passing the handler via reference?

I would appreciate any input. Thank you for your help.


Solution

  • Yes, phx::bind and phx::function<> will take their wrapper calleables by value by default. However.

    Let's say[1], you have an error handler like this... minimalist example:

    template <typename=void> struct my_error_handler {
        my_error_handler() = default;
        my_error_handler(my_error_handler const&) = delete;
    
        template<typename...> struct result { typedef void type; };
        template<typename... T> void operator()(T&&...) const { 
            std::cerr << "my_error_handler invoked " << proof++ << "\n";
        }
        mutable int proof = 0;
    };
    

    (As you can see I made it explicitely non-copyable to ensure the compiler wouldn't silently generate the code behind my back.)

    Now, I'm not sure whether this is a combination you accidentally missed, but:

    on_error<fail>(function,       phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
    on_error<fail>(start,          phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
    on_error<fail>(gr_instruction, phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
    

    works nicely, as does

    auto ll = phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3);
    on_error<fail>(function,       ll);
    on_error<fail>(start,          ll);
    on_error<fail>(gr_instruction, ll);
    

    Note that because bind takes a reference, we need to ensure lifetime of err_handler matches (or exceeds) that of the parser, so I made err_handler a member of the parser class.

    When I pass it input to fail, my program will be able to print proof of the invocations of my_error_handler:

    std::cout << "The 'proof' in the err_handler instance is: " << p.err_handler.proof << "\n";
    

    As always, a completely integrated sample program:

    #define BOOST_SPIRIT_USE_PHOENIX_V3
    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi  = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    namespace asmast
    {
        typedef std::string label;
    }
    
    template <typename=void> struct my_error_handler {
        my_error_handler() = default;
        my_error_handler(my_error_handler const&) = delete;
    
        template<typename...> struct result { typedef void type; };
        template<typename... T> void operator()(T&&...) const { 
            std::cerr << "my_error_handler invoked " << proof++ << "\n";
        }
        mutable int proof = 0;
    };
    
    template <typename It, typename Skipper = qi::blank_type>
        struct parser : qi::grammar<It, Skipper>
    {
        parser() : 
            parser::base_type(start)
        {
            using namespace qi;
    
            start = lexeme["Func" >> !(alnum | '_')] > function;
            function = gr_identifier
                        >> "{"
                        >> -(
                                  gr_instruction
                                | gr_label
                              //| gr_vardecl
                              //| gr_paramdecl
                            ) % eol
                        > "}";
    
            gr_instruction_names.add("Mov", unused);
            gr_instruction_names.add("Push", unused);
            gr_instruction_names.add("Exit", unused);
    
            gr_instruction = lexeme [ gr_instruction_names >> !(alnum|"_") ] > gr_operands;
            gr_operands = -(gr_operand % ',');
    
            gr_identifier = lexeme [ alpha >> *(alnum | '_') ];
            gr_operand    = gr_identifier | gr_string;
            gr_string     = lexeme [ '"' >> *("\"\"" | ~char_("\"")) >> '"' ];
    
            gr_newline = +( char_('\r')
                           |char_('\n')
                          );
    
            gr_label = gr_identifier >> ':' > gr_newline;
    
    #if 1
            on_error<fail>(function,       phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
            on_error<fail>(start,          phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
            on_error<fail>(gr_instruction, phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3));
    #else
            auto ll = phx::bind(phx::ref(err_handler), L"Error: Expecting ", _4, _3);
            on_error<fail>(function,       ll);
            on_error<fail>(start,          ll);
            on_error<fail>(gr_instruction, ll);
    #endif
            // more on_error<fail>s...
    
            BOOST_SPIRIT_DEBUG_NODES((start)(function)(gr_instruction)(gr_operands)(gr_identifier)(gr_operand)(gr_string));
        }
    
        my_error_handler<> err_handler;
      private:
        qi::symbols<char, qi::unused_type> gr_instruction_names;
        qi::rule<It, Skipper> start, function, gr_instruction, gr_operands, gr_operand, gr_string;
        qi::rule<It, qi::unused_type()> gr_newline;
        qi::rule<It, asmast::label(), Skipper> gr_label, gr_identifier;
    };
    
    int main()
    {
        typedef boost::spirit::istream_iterator It;
        std::cin.unsetf(std::ios::skipws);
        It f(std::cin), l;
    
        parser<It, qi::blank_type> p;
    
        try
        {
            bool ok = qi::phrase_parse(f,l,p,qi::blank);
            if (ok)   std::cout << "parse success\n";
            else      std::cerr << "parse failed: '" << std::string(f,l) << "'\n";
    
            if (f!=l) std::cerr << "trailing unparsed: '" << std::string(f,l) << "'\n";
    
            std::cout << "The 'proof' in the err_handler instance is: " << p.err_handler.proof << "\n";
            return ok;
        } catch(const qi::expectation_failure<It>& e)
        {
            std::string frag(e.first, e.last);
            std::cerr << e.what() << "'" << frag << "'\n";
        }
    
        return false;
    }
    

    When fed the input

    Func Ident{
        Mov name, "hello" 
        Push 5
        Exit
    }
    

    It prints (as the first/last lines)

    my_error_handler invoked 0
    my_error_handler invoked 1
    ...
    The 'proof' in the err_handler instance is: 2
    

    [1] It's not the first time I'm having to imagine relevant code