1

After reading Custom error on rule level? #657 I thought I'll brave, adapt and combine it with sehe's approach at How do I get which() to work correctly in boost spirit x3 expectation_failure?. Unfortunately, I didn't get it to work - somewhere I've got lost, see coliru.

Even with x3::expect[] the expectation_failure what() member returns its own name (expected is 'double', 'int' or even 'char x' in case of use of mandatory):

'a .e-z' error handler id 'rule_a': expect boost::spirit::x3::expectation_failure

Also, the idea was to 'bind' the helper with_error_handler (also known as as) to the mandatory context/struct

template <typename RuleID, typename AttributeT>
struct mandatory_type {
    template <typename Expr>
    auto with_error_handler(Expr&& expr, char const* name = typeid(decltype(expr)).name()) const {
        using tag = my_error_handler<RuleID>;
        return x3::rule<tag, AttributeT>{ name } = x3::as_parser(std::forward<Expr>(expr));
    }
    template <typename Expr>
    auto operator()(Expr&& expr, char const* name = typeid(decltype(expr)).name()) const {
        return x3::expect[ with_error_handler<RuleID, AttributeT>(expr, name) ];
    }
};

which doesn't compile. I hope the intent can be clear seen from source:

#include <boost/spirit/home/x3.hpp>
#include <boost/core/demangle.hpp>
#include <string_view>
#include <iostream>

namespace x3 = boost::spirit::x3;

template <typename RuleID>
struct my_error_handler
{
    template <typename Iterator, typename Exception, typename Context>
    static x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& e, Context const&)
    {
        char const* id_name = typeid(RuleID).name();
        std::cerr << "error handler id '" << boost::core::demangle(id_name) << "': "
                  << "expect " << e.what() << "\n";
        return x3::error_handler_result::accept;
    }
};

template <typename RuleID, typename AttributeT>
struct mandatory_type {
    template <typename Expr>
    auto with_error_handler(Expr&& expr, char const* name = typeid(decltype(expr)).name()) const {
        using tag = my_error_handler<RuleID>;
        return x3::rule<tag, AttributeT>{ name } = x3::as_parser(std::forward<Expr>(expr));
    }
    template <typename Expr>
    auto operator()(Expr&& expr, char const* name = typeid(decltype(expr)).name()) const {
        return x3::expect[ with_error_handler<RuleID, AttributeT>(expr, name) ];
    }
};
template <typename RuleID, typename T> static const mandatory_type<RuleID, T> mandatory = {};

struct rule_a : my_error_handler<rule_a> {};
struct rule_b : my_error_handler<rule_b> {};
struct rule_grammar : my_error_handler<rule_grammar> {};

auto const a = x3::rule<rule_a>{ "double" } = x3::expect[x3::double_];
auto const b = x3::rule<rule_b>{ "int" } = x3::expect[x3::int_];
auto  const grammar = x3::rule<rule_grammar>{ "grammar" } =
      'a' >> a 
    | 'b' >> b 
    | x3::expect['x'] 
    //| mandatory<rule_grammar, char>('x', "char x")
    ;

int main() {
    for (std::string_view test: { "a 1.23", "b 123", "a .e-z", "b .0", "x", "d" }) {
        std::cerr << "'" << test << "' ";
        if (!phrase_parse(begin(test), end(test), grammar, x3::space)) {
            std::cerr << "parse error\n";
        }
        else {
            std::cout << "ok\n";
        }
    }
}

Olx
  • 163
  • 8
  • `whith_error_handler` might be a rule factory similar to `as<>` but I would *never* name it `as<>` because then the name would not remotely describe the purpose. Names should match intent leading to expressive code. – sehe Jul 02 '22 at 20:29

1 Answers1

1

Even with x3::expect[] the expectation_failure what() member returns its own name (expected is 'double', 'int' or even 'char x' in case of use of mandatory):

The exception type is x3::expectation_failure<It> and it has several members (.what(), .where() and .which()). .what() is there for compatibility with std::exception and traditionally returns the name of the exception type, as you see.

Using a more specific errorhandler:

template<typename RuleID>
struct my_error_handler
{
    static std::string
    id()
    {
        return boost::core::demangle(typeid(RuleID).name());
    }

    template<typename Iterator, typename Context>
    static x3::error_handler_result
    on_error(
        Iterator&,
        Iterator const&,
        x3::expectation_failure<Iterator> const& e,
        Context const&)
    {
        std::cerr << "error handler id " << std::quoted(id(), '\'')
                  << ": expect " << e.which() << " at "
                  << std::quoted(std::string(e.where())) << "\n";
        return x3::error_handler_result::accept;
    }
};

Would print e.g.

'd 1.23' ok
'i 123' ok
'd .e-z' error handler id 'rule_a': expect N5boost6spirit2x311real_parserIdNS1_13real_policiesIdEEEE at ".e-z"
ok
'i .0' error handler id 'rule_b': expect N5boost6spirit2x310int_parserIiLj10ELj1ELin1EEE at ".0"
ok
'x' ok
'd' error handler id 'rule_a': expect N5boost6spirit2x311real_parserIdNS1_13real_policiesIdEEEE at ""
ok

That's exactly the symptom from the linked questions, so you'd move the expectation points around to benefit from the named rules:

auto const d = x3::rule<rule_a>{"double"} = x3::double_;
auto const i = x3::rule<rule_b>{"int"} = x3::int_;
auto const grammar = x3::rule<rule_grammar>{"grammar"} = //
    'd' >> x3::expect[d]                                 //
    | 'i' >> x3::expect[i]                               //
    | x3::expect['x']
    //| mandatory<rule_grammar, char>('x', "char x")
    ;

Printing:

'd 1.23' ok
'i 123' ok
'd .e-z' error handler id 'rule_grammar': expect double at ".e-z"
ok
'i .0' error handler id 'rule_grammar': expect int at ".0"
ok
'x' ok
'd' error handler id 'rule_grammar': expect double at ""
ok

CAVEAT:

I was lazy and assuming null-terminated input. You may want to be smarted about printing the error context.


which doesn't compile. I hope the intent can be clear seen from source:

You don't say what the error message is. I assume it's because you use decltype(argument) inside the parameter list. Solve it by using the known deduced type:

Live On Coliru

#include <boost/core/demangle.hpp>
#include <boost/spirit/home/x3.hpp>
#include <iomanip>
#include <iostream>
#include <string_view>

namespace x3 = boost::spirit::x3;

template<typename RuleID>
struct my_error_handler
{
    static std::string
    id()
    {
        return boost::core::demangle(typeid(RuleID).name());
    }

    template<typename Iterator, typename Context>
    static x3::error_handler_result
    on_error(
        Iterator&,
        Iterator const&,
        x3::expectation_failure<Iterator> const& e,
        Context const&)
    {
        std::cerr << "error handler id " << std::quoted(id(), '\'')
                  << ": expect " << e.which() << " at "
                  << std::quoted(std::string(e.where())) << "\n";
        return x3::error_handler_result::accept;
    }
};

template<typename RuleID, typename AttributeT>
struct mandatory_type
{
    template<typename Expr>
    auto
    with_error_handler(
        Expr&& expr, char const* name = typeid(Expr).name()) const
    {
        using tag = my_error_handler<RuleID>;
        return x3::rule<tag, AttributeT>{ name } = x3::as_parser(std::forward<Expr>(expr));
    }
    template<typename Expr>
    auto
    operator()(Expr&& expr, char const* name = typeid(Expr).name()) const
    {
        return x3::expect[ with_error_handler<RuleID, AttributeT>(expr, name) ];
    }
};
template <typename RuleID, typename T> static const mandatory_type<RuleID, T> mandatory = {};

struct rule_a : my_error_handler<rule_a> {};
struct rule_b : my_error_handler<rule_b> {};
struct rule_grammar : my_error_handler<rule_grammar> {};

auto const d = x3::rule<rule_a>{"double"} = x3::double_;
auto const i = x3::rule<rule_b>{"int"} = x3::int_;
auto const grammar = x3::rule<rule_grammar>{"grammar"} = //
    'd' >> x3::expect[d]                                 //
    | 'i' >> x3::expect[i]                               //
    | x3::expect['x']
    //| mandatory<rule_grammar, char>('x', "char x")
    ;

int main() {
    for(std::string_view test : {"d 1.23", "i 123", "d .e-z", "i .0", "x", "d"})
    {
        std::cerr << "'" << test << "' ";
        if(! phrase_parse(begin(test), end(test), grammar, x3::space))
        {
            std::cerr << "parse error\n";
        } else
        {
            std::cout << "ok\n";
        }
    }
}

Prints

'd 1.23' ok
'i 123' ok
'd .e-z' error handler id 'rule_grammar': expect double at ".e-z"
ok
'i .0' error handler id 'rule_grammar': expect int at ".0"
ok
'x' ok
'd' error handler id 'rule_grammar': expect double at ""
ok
sehe
  • 374,641
  • 47
  • 450
  • 633
  • thank you very much for the detailed explanation. By the way, I could find the only advantage of copy&paste - you don't get such 'w' errors (I wrote it without template). Regarding the missing error message: I have https://coliru.stacked-crooked.com/a/b61f0131bd35409f the message below. Same source, written as lambda https://coliru.stacked-crooked.com/a/1052247403268771 but gives wrong error scopes - the messages do not match the rule. Apparently I have a wrong idea of how expectation_failures are thrown and caught; or made some other mistake not immediately obvious to me. – Olx Jul 04 '22 at 16:26