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

How do I get which() to work correctly in boost spirit x3 expectation_failure?


Calling which() in expectation_failure returns a strange std::string.
How can I fix it?

#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/utility/error_reporting.hpp>
#include <iostream>

namespace x3 = boost::spirit::x3;

struct my_error_handler {
  template <typename Iterator, typename Context>
  auto on_error(Iterator&,
                Iterator const&,
                const x3::expectation_failure<Iterator>& x,
                const Context&)
  {
    std::cerr << x.which() << '\n';
    return x3::error_handler_result::fail;
  }
};

struct test_class : my_error_handler {
};

const x3::rule<test_class, int> test{"test"};

const auto test_def = x3::expect[x3::int_];

BOOST_SPIRIT_DEFINE(test)

auto parse(std::string&& input)
{
  auto       first = std::cbegin(input);
  const auto last  = std::cend(input);

  x3::error_handler<decltype(first)> error_handler{first, last, std::cerr};

  const auto parser
    = x3::with<x3::error_handler_tag>(std::ref(error_handler))[test];

  x3::phrase_parse(std::cbegin(input), std::cend(input), parser, x3::space);
}

int main()
{
  parse("a123");
}

Live on wandbox

The execution result is shown below.

N5boost6spirit2x310int_parserIiLj10ELj1ELin1EEE


Solution

  • That string is the mangled type name of the parser. That's the default name if you don't supply one:

        std::cerr << boost::core::demangle(x.which().c_str()) << '\n';
    

    Now it prints Live

    boost::spirit::x3::int_parser<int, 10u, 1u, -1>
    

    If you don't want the default, supply one. You can for rules, e.g. "test":

    Simplified Demo

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    
    namespace x3 = boost::spirit::x3;
    
    namespace Parser {
        x3::rule<struct test_class,   int> const test{"test"};
        x3::rule<struct parser_class, int> const parser{"parser"};
    
        auto const test_def   = x3::int_;
        auto const parser_def = x3::skip(x3::space)[x3::expect[test]];
        BOOST_SPIRIT_DEFINE(test, parser)
    
        struct my_error_handler {
            template <typename It, typename Ctx>
            auto on_error(It, It, x3::expectation_failure<It> const& x, Ctx const&) const
            {
                std::cerr << x.which() << '\n';
                return x3::error_handler_result::fail;
            }
        };
    
        struct test_class   : my_error_handler {};
        struct parser_class : my_error_handler {};
    } // namespace Parser
    
    auto parse(std::string const input)
    {
        x3::parse(begin(input), cend(input), Parser::parser);
    }
    
    int main() { parse("a123"); }
    

    Prints "test"

    Even Simpler?

    Those rules don't actually require DEFINE macros. Instead, perhaps use some helpers:

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <iostream>
    
    namespace x3 = boost::spirit::x3;
    
    namespace Parser {
        struct my_error_handler {
            template <typename It, typename Ctx>
            auto on_error(It, It, x3::expectation_failure<It> const& x, Ctx const&) const
            {
                std::cerr << "Expected: " << x.which() << '\n';
                return x3::error_handler_result::fail;
            }
        };
    
        template <typename T = x3::unused_type>
        auto as(auto p, char const* name = typeid(decltype(p)).name())
        {
            struct tag : my_error_handler { };
            return x3::rule<tag, T>{name} = p;
        }
    
        template <typename T>
        auto mandatory(auto p, char const* name = typeid(decltype(p)).name())
        {
            return x3::expect[as<T>(p, name)];
        }
    
        auto const test   = mandatory<unsigned>(x3::uint_, "non-negative number");
        auto const parser = as(x3::skip(x3::space)[test]);
    
    } // namespace Parser
    
    auto parse(std::string const input)
    {
        x3::parse(begin(input), cend(input), Parser::parser);
    }
    
    int main() { parse("a123"); }
    

    Prints

    Expected: non-negative number
    

    I'm sure you can vary on this theme. If you don't really emphasize attribute type coercion, perhaps you could name the helper with_errors etc.