自定义预期失败的完整错误消息(boost :: spirit :: x3) [英] Customizing the full error message for expectation failures (boost::spirit::x3)

查看:76
本文介绍了自定义预期失败的完整错误消息(boost :: spirit :: x3)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

boost :: spirit :: x3 错误处理实用程序允许用户选择在发生期望失败时向用户显示的内容.但是,消息的行号部分似乎并非如此,这正是我要修改的内容.因此,与其代替在第1行中打印出等.我想在其位置打印具有相同行号信息的其他消息.有谁知道我该怎么做,或者一开始是否可以修改?

The boost::spirit::x3 error handling utilities allow for the user to choose what is shown to the user when an expectation failure occurs. This, however, does not seem to be the case for the line number portion of the message, which is exactly what I'd like to modify. So instead of it printing out In line 1: etc. I would like to print some other message in it's place with the same line number info. Anyone know how I could do that, or if it is even modifiable in the first place?

以下是直接来自 https://www.boost.org/doc/libs/1_68_0/libs/spirit/doc/x3/html/spirit_x3/tutorials/error_handling.html :

struct error_handler
{
    template <typename Iterator, typename Exception, typename Context>
    x3::error_handler_result on_error(
        Iterator& first, Iterator const& last
      , Exception const& x, Context const& context)
    {
        auto& error_handler = x3::get<x3::error_handler_tag>(context).get();
        std::string message = "Error! Expecting: " + x.which() + " here:";
        error_handler(x.where(), message);
        return x3::error_handler_result::fail;
    }
};

除了打印错误消息的 on_error 函数外,它还会打印"In line x:",其中x是行号.我真的不能拥有它,丝毫不适合我的项目.

In addition to the on_error function printing out the message, it prints "In line x: ", where x is the line number. I really can't have that, it does not fit in with my project in the slightest.

推荐答案

哇.首先,我不知道有关该示例和 x3 :: error_handler<> 的所有详细信息.

Wow. First of all, I did not know all details about that example and x3::error_handler<>.

有关如何根据基本原理在X3中提供错误处理/诊断消息的详细信息,请参阅以下演练:

For a good break-down of how to provide error handling/diagnostic messages in X3 from basic principles, see this walk-through: Spirit X3, Is this error handling approach useful?

传统上(如Qi),我们将使用迭代器适配器进行位置跟踪:

Traditionally (as in Qi) we would do the position tracking using an iterator adaptor:

乍一看,看来 position_cache 可以单独使用(请参见例如

At first glance it looks like the position_cache can be used separately (see eg. Boost Spirit x3 not compiling).

但是,事实证明-令人遗憾的是- x3 :: annotate_on_success 将注释任务与错误处理进行了合并,方法是假设位置缓存将始终存在错误中处理程序.这一次意味着:

However, it turns out that - sadly - x3::annotate_on_success conflated the annotation task with error-handling, by assuming that position cache will always live inside the error handler. This at once means:

  • the error handler is more complicated than strictly required
  • this compounds with the fact that x3::error_handler<> is not well-suited for inheritance (due to private members and tricky to unambiguously overload operator() while keeping some overloads)
  • x3::annotate_on_success is simply not available to you unless you at least have a no-op error-handler like (Live On Coliru)

 template <typename It> struct dummy_handler_for_annotate_on_success {
    x3::position_cache<std::vector<It> > pos_cache;
    dummy_handler_for_annotate_on_success(It f, It l) : pos_cache(f,l) {}

    template <typename T> void tag(T& ast, It first, It last) {
        return pos_cache.annotate(ast, first, last);
    }
};

并将其显示在 x3 :: error_handler_tag 下的上下文中,以使 annotate_on_success 正常工作.

and have that present in the context under the x3::error_handler_tag for annotate_on_success to work.

肯定的是,确实的好处是不需要两次单独的上下文注入,例如:

On the positive, this does have the benefit of not requiring two separate context injections, like:

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

因此,这是我提供自定义错误处理程序实现的观点.我从内置版本¹中对其进行了简化.

So, here's my take on providing a custom error-handler implementation. I simplified it a bit from the built-in version¹.

一个简化也是一种优化,它基于迭代器类型为双向的假设.如果没有,我认为您最好使用上面链接的 spirit :: line_pos_iterator<> .

One simplification is also an optimization, resting on the assumption that the iterator type is bidirectional. If not, I think you'd be better off using spirit::line_pos_iterator<> as linked above.

template <typename It> class diatnostics_handler {
    x3::position_cache<std::vector<It> > _pos_cache;
    std::ostream& _os;

  public:
    diatnostics_handler(It f, It l, std::ostream& os) : _pos_cache(f, l), _os(os) {}

    void operator()(x3::position_tagged const& ast, std::string const& error_message) const {
        auto where = _pos_cache.position_of(ast);
        operator()(where.begin(), where.end(), error_message);
    }

    void operator()(It err_first, std::string const& error_message) const {
        operator()(err_first, boost::none, error_message);
    }

    void operator()(It err_first, boost::optional<It> err_last, std::string const& error_message) const {
        auto first = _pos_cache.first(),
             last  = _pos_cache.last();

        while (err_first != last && std::isspace(*err_first))
            ++err_first;

        _os << "L:"<< line_number(err_first) << " "
            << error_message << std::endl;

        It cursor = get_line_start(first, err_first);
        print_line(cursor, last);

        auto score = [&](It& it, char fill) -> auto& {
            auto f = _os.fill();
            auto n = std::distance(cursor, it);
            cursor = it;
            return _os << std::setfill(fill) << std::setw(n) << "" << std::setfill(f);
        };
        if (err_last.has_value()) {
            score(err_first, ' ');
            score(*err_last, '~') << " <<-- Here" << std::endl;
        } else {
            score(err_first, '_') << "^_" << std::endl;
        }
    }

    template <typename AST> void tag(AST& ast, It first, It last) {
        return _pos_cache.annotate(ast, first, last);
    }

    auto const& get_position_cache() const { return _pos_cache; }

  private:
    static constexpr std::array crlf { '\r', '\n' };

    auto get_line_start(It first, It pos) const {
        return std::find_first_of( // assumed bidir iterators
                std::make_reverse_iterator(pos), std::make_reverse_iterator(first),
                crlf.begin(), crlf.end()
            ).base();
    }

    auto line_number(It i) const {
        return 1 + std::count(_pos_cache.first(), i, '\n');
    }

    void print_line(It f, It l) const {
        std::basic_string s(f, std::find_first_of(f, l, crlf.begin(), crlf.end()));
        _os << boost::locale::conv::utf_to_utf<char>(s) << std::endl;
    }
};

然后您可以像 在Coliru上直播

Which you can then demo like Live On Coliru

custom::diatnostics_handler<It> diags(iter, end, std::clog);

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

std::vector<ast::employee> ast;
if (phrase_parse(iter, end, parser >> x3::eoi, x3::space, ast)) {
    std::cout << "Parsing succeeded\n";

    for (auto const& emp : ast) {
        std::cout << "got: " << emp << std::endl;

        diags(emp.who.last_name, "note: that's a nice last name");
        diags(emp.who, "warning: the whole person could be nice?");
    }
} ...

哪些印刷品:

With custom diagnostics only:
Parsing succeeded
got: (23 (Amanda Stefanski) 1000.99)
L:1 note: that's a nice last name
{ 23, "Amanda", "Stefanski", 1000.99 },
                ~~~~~~~~~~~ <<-- Here
L:1 warning: the whole person could be nice?
{ 23, "Amanda", "Stefanski", 1000.99 },
      ~~~~~~~~~~~~~~~~~~~~~ <<-- Here
got: (35 (Angie Chilcote) 2000.99)
L:2 note: that's a nice last name
        { 35, "Angie", "Chilcote", 2000.99 }
                       ~~~~~~~~~~ <<-- Here
L:2 warning: the whole person could be nice?
        { 35, "Angie", "Chilcote", 2000.99 }
              ~~~~~~~~~~~~~~~~~~~ <<-- Here


 ----- Now with parse error:
L:3 error: expecting: person
 'Amanda', "Stefanski", 1000.99 },
_^_
Parsing failed

简化

通过破坏 annotate_on_success x3 :: error_handler_tag 上下文之间的错误耦合,您可以将其精简很多:

Simplifying Down

By breaking the false coupling between annotate_on_success and x3::error_handler_tag context, you could slim it down, a lot:

template <typename It> struct diagnostics_handler {
    It _first, _last;
    std::ostream& _os;

    void operator()(It err_first, std::string const& error_message) const {
        size_t line_no = 1;
        auto bol = _first;
        for (auto it = bol; it != err_first; ++it)
            if (*it == '\n') {
                bol = it+1;
                line_no += 1;
            }

        _os << "L:" << line_no
            << ":" << std::distance(bol, err_first)
            << " " << error_message << "\n";
    }
};

查看 在Coliru上直播

See it Live On Coliru

#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/position_tagged.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

#include <iostream>
#include <iomanip>
#include <string>
namespace x3 = boost::spirit::x3;

namespace ast {
    struct name : std::string, x3::position_tagged {
        using std::string::string;
        using std::string::operator=;
    };
    struct person   : x3::position_tagged { ast::name first_name, last_name; };
    struct employee : x3::position_tagged { int age; person who; double salary; };
    using boost::fusion::operator<<;
}

BOOST_FUSION_ADAPT_STRUCT(ast::person, first_name, last_name)
BOOST_FUSION_ADAPT_STRUCT(ast::employee, age, who, salary)

namespace custom {
    struct diagnostics_handler_tag;

    template <typename It> struct diagnostics_handler {
        It _first, _last;
        std::ostream& _os;

        void operator()(It err_first, std::string const& error_message) const {
            size_t line_no = 1;
            auto bol = _first;
            for (auto it = bol; it != err_first; ++it)
                if (*it == '\n') {
                    bol = it+1;
                    line_no += 1;
                }

            _os << "L:"<< line_no
                << ":" << std::distance(bol, err_first)
                << " " << error_message << "\n";
        }
    };

} // namespace custom

namespace parser {
    namespace x3 = boost::spirit::x3;
    namespace ascii = boost::spirit::x3::ascii;

    struct error_handler {
        template <typename It, typename E, typename Ctx>
        x3::error_handler_result on_error(It&, It const&, E const& x, Ctx const& ctx) {
            auto& handler = x3::get<custom::diagnostics_handler_tag>(ctx);
            handler(x.where(), "error: expecting: " + x.which());
            return x3::error_handler_result::fail;
        }
    };

    struct annotate_position {
        template <typename T, typename Iterator, typename Context>
        inline void on_success(const Iterator &first, const Iterator &last, T &ast, const Context &context)
        {
            auto &position_cache = x3::get<annotate_position>(context).get();
            position_cache.annotate(ast, first, last);
        }
    };

    struct quoted_string_class : annotate_position {};
    struct person_class : annotate_position {};
    struct employee_class : error_handler, annotate_position {};

    x3::rule<quoted_string_class, ast::name>     const name = "name";
    x3::rule<person_class,        ast::person>   const person        = "person";
    x3::rule<employee_class,      ast::employee> const employee      = "employee";

    auto const name_def
        = x3::lexeme['"' >> +(x3::char_ - '"') >> '"']
        ;
    auto const person_def
        = name > ',' > name
        ;

    auto const employee_def
        = '{' > x3::int_ > ',' > person > ',' > x3::double_ > '}'
        ;

    BOOST_SPIRIT_DEFINE(name, person, employee)

    auto const employees = employee >> *(',' >> employee);
}

void parse(std::string const& input) {
    using It = std::string::const_iterator;

    It iter = input.begin(), end = input.end();
    x3::position_cache<std::vector<It> > pos_cache(iter, end);
    custom::diagnostics_handler<It> diags { iter, end, std::clog };

    auto const parser =
        x3::with<parser::annotate_position>(std::ref(pos_cache)) [
            x3::with<custom::diagnostics_handler_tag>(diags) [
                 parser::employees
            ]
        ];

    std::vector<ast::employee> ast;
    if (phrase_parse(iter, end, parser >> x3::eoi, x3::space, ast)) {
        std::cout << "Parsing succeeded\n";

        for (auto const& emp : ast) {
            std::cout << "got: " << emp << std::endl;

            diags(pos_cache.position_of(emp.who.last_name).begin(), "note: that's a nice last name");
            diags(pos_cache.position_of(emp.who).begin(), "warning: the whole person could be nice?");
        }
    } else {
        std::cout << "Parsing failed\n";
        ast.clear();
    }
}

static std::string const
    good_input = R"({ 23, "Amanda", "Stefanski", 1000.99 },
        { 35, "Angie", "Chilcote", 2000.99 }
    )", 
    bad_input = R"(
        { 23,
 'Amanda', "Stefanski", 1000.99 },
    )";

int main() {
    std::cout << "With custom diagnostics only:" << std::endl;
    parse(good_input);

    std::cout << "\n\n ----- Now with parse error:" << std::endl;
    parse(bad_input);
}

打印:

With custom diagnostics only:
Parsing succeeded
got: (23 (Amanda Stefanski) 1000.99)
L:1:16 note: that's a nice last name
L:1:6 warning: the whole person could be nice?
got: (35 (Angie Chilcote) 2000.99)
L:2:23 note: that's a nice last name
L:2:14 warning: the whole person could be nice?


 ----- Now with parse error:
L:2:13 error: expecting: person
Parsing failed


¹还修复了一个错误,该错误导致使用 x3 :: error_handler<> 实现的诊断在第一行(?)上错误显示


¹ also fixed a bug that causes diagnostics to display wrongly on the first line(?) with x3::error_handler<> implementation

这篇关于自定义预期失败的完整错误消息(boost :: spirit :: x3)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆