From d59e0528b27804c075d37ffe8a4178dcb2632307 Mon Sep 17 00:00:00 2001 From: Zach Laine Date: Mon, 15 Jan 2024 02:26:31 -0600 Subject: [PATCH] Consolidate documentation on eror handlers into the Error Handling and Debugging section, and add an example of how to write your own error handler. Fixes #43. --- doc/parser.qbk | 9 +- doc/tutorial.qbk | 186 ++++++++++++++++++++------------- example/CMakeLists.txt | 1 + example/user_error_handler.cpp | 96 +++++++++++++++++ 4 files changed, 218 insertions(+), 74 deletions(-) create mode 100644 example/user_error_handler.cpp diff --git a/doc/parser.qbk b/doc/parser.qbk index d51a8c9b..de35b699 100644 --- a/doc/parser.qbk +++ b/doc/parser.qbk @@ -36,6 +36,7 @@ [import ../example/callback_json.cpp] [import ../example/parsing_into_a_struct.cpp] [import ../example/struct_rule.cpp] +[import ../example/user_error_handler.cpp] [import ../include/boost/parser/concepts.hpp] [import ../include/boost/parser/error_handling_fwd.hpp] @@ -103,6 +104,7 @@ [def _report_error_ [funcref boost::parser::_report_error `_report_error()`]] [def _report_warning_ [funcref boost::parser::_report_warning `_report_warning()`]] [def _val_np_ [funcref boost::parser::_val `_val`]] +[def _where_np_ [funcref boost::parser::_where `_where`]] [def _locals_np_ [funcref boost::parser::_locals `_locals`]] [def _params_np_ [funcref boost::parser::_params `_params`]] @@ -190,7 +192,8 @@ [def _ATTR_ ['[^ATTR]]`()`] [def _ATTR_np_ ['[^ATTR]]] -[def _p_api_ [link boost_parser__proposed_.tutorial.the__parse____api the `parse()` API]] +[def _p_api_ [link boost_parser__proposed_.tutorial.the__parse____api The `parse()` API]] +[def _parse_ctx_ [link boost_parser__proposed_.tutorial.the_parse_context The Parse Context]] [def _rule_parsers_ [link boost_parser__proposed_.tutorial.rule_parsers Rule Parsers]] [def _parsing_structs_ [link boost_parser__proposed_.tutorial.parsing__struct_s Parsing `struct`s]] [def _expect_pts_ [link boost_parser__proposed_.tutorial.backtracking.html#boost_parser__proposed_.tutorial.backtracking.expectation_points Expectation points]] @@ -204,6 +207,10 @@ [def _ex_cb_json_ [link boost_parser__proposed_.extended_examples.parsing_json_with_callbacks Parsing JSON With Callbacks]] [def _rationale_ [link boost_parser__proposed_.rationale Rationale]] [def _n_is_weird_ [link boost_parser__proposed_.rationale._classname_alt__boost__parser__none___code__phrase_role__identifier__none__phrase___code___classname__is_weird `none` is weird]] + +[def _err_fwd_hpp_ [headerref boost/parser/error_handling_fwd.hpp error_handling_fwd.hpp]] +[def _err_hpp_ [headerref boost/parser/error_handling.hpp error_handling.hpp]] + [def _kl_ [@https://en.wikipedia.org/wiki/Kleene_star Kleene star]] [def _comb_ [@https://en.wikipedia.org/wiki/Parser_combinator parser combinator]] [def _udl_ [@https://en.cppreference.com/w/cpp/language/user_literal UDL]] diff --git a/doc/tutorial.qbk b/doc/tutorial.qbk index 54eb3952..39af87fc 100644 --- a/doc/tutorial.qbk +++ b/doc/tutorial.qbk @@ -419,73 +419,8 @@ located, without producing any other attributes. [heading _error_handler_] _error_handler_ returns a reference to the error handler associated with the -parser passed to _p_. Any error handler must have the following member -functions: - -[error_handler_api_1] - -[error_handler_api_2] - -If you call the second one, the one without the iterator parameter, it will -call the first with `_where(context).begin()` as the iterator parameter. The -one without the iterator is the one you will use most often. The one with the -explicit iterator parameter can be useful in situations where you have -messages that are related to each other, associated with multiple locations. -For instance, if you are parsing XML, you may want to report that a close-tag -does not match its associated open-tag by showing the line where the open-tag -was found. That may of course not be located anywhere near -`_where(ctx).begin()`. (A description of _globals_ is below.) - - [](auto & ctx) { - // Assume we have a std::vector of open tags, and another - // std::vector of iterators to where the open tags were parsed, in our - // globals. - if (_attr(ctx) != _globals(ctx).open_tags.back()) { - std::string open_tag_msg = - "Previous open-tag \"" + _globals(ctx).open_tags.back() + "\" here:"; - _error_handler(ctx).diagnose( - boost::parser::diagnostic_kind::error, - open_tag_msg, - ctx, - _globals(ctx).open_tags_position.back()); - std::string close_tag_msg = - "does not match close-tag \"" + _attr(ctx) + "\" here:"; - _error_handler(ctx).diagnose( - boost::parser::diagnostic_kind::error, - close_tag_msg, - ctx); - - // Explicitly fail the parse. Diagnostics to not affect parse success. - _pass(ctx) = false; - } - } - -[heading _report_error_ and _report_warning_] - -There are also some convenience functions that make the above code a little -less verbose, _report_error_ and _report_warning_: - - [](auto & ctx) { - // Assume we have a std::vector of open tags, and another - // std::vector of iterators to where the open tags were parsed, in our - // globals. - if (_attr(ctx) != _globals(ctx).open_tags.back()) { - std::string open_tag_msg = - "Previous open-tag \"" + _globals(ctx).open_tags.back() + "\" here:"; - _report_error(ctx, open_tag_msg, _globals(ctx).open_tag_positions.back()); - std::string close_tag_msg = - "does not match close-tag \"" + _attr(ctx) + "\" here:"; - _report_error(ctx, close_tag_msg); - - // Explicitly fail the parse. Diagnostics to not affect parse success. - _pass(ctx) = false; - } - } - -You should use these less verbose functions almost all the time. The only -time you would want to use _error_handler_ is when you are using a custom -error handler, and you want access to some part of it's interface besides -`diagnose()`. +parser passed to _p_. See _eh_debugging_ for more information on what you can +do with the error handler. [heading Accessors for data that are only sometimes available] @@ -3114,8 +3049,7 @@ Clang and/or GCC diagnostics. Most of _Parser_'s error handlers format their diagnostics this way, though you are not bound by that. You can make an error handler type that does -whatever you want, as long as it meets the error handler interface. See -`error_handler` in _concepts_ for details. +whatever you want, as long as it meets the error handler interface. The _Parser_ error handlers are: @@ -3138,10 +3072,116 @@ The _Parser_ error handlers are: * _rethrow_eh_: Does nothing but re-throw any exception that it is asked to handle. Its `diagnose()` member functions are no-ops. -[tip If you want to provide your own error handler, but still want to use the -same formatting as _Parser_ error handlers, you can use the functions -`write_formatted_message()` and -`write_formatted_expectation_failure_error_message()` to do that for you.] +You can set the error handler to any of these, or one of your own, using +_w_eh_ (see _p_api_). If you do not set one, _default_eh_ will be used. + +You can get access to the error handler within any semantic action by calling +`_error_handler(ctx)` (see _parse_ctx_). Any error handler must have the +following member functions: + +[error_handler_api_1] + +[error_handler_api_2] + +If you call the second one, the one without the iterator parameter, it will +call the first with `_where_np_(context).begin()` as the iterator parameter. The +one without the iterator is the one you will use most often. The one with the +explicit iterator parameter can be useful in situations where you have +messages that are related to each other, associated with multiple locations. +For instance, if you are parsing XML, you may want to report that a close-tag +does not match its associated open-tag by showing the line where the open-tag +was found. That may of course not be located anywhere near +`_where(ctx).begin()`. (A description of _globals_ is below.) + + [](auto & ctx) { + // Assume we have a std::vector of open tags, and another + // std::vector of iterators to where the open tags were parsed, in our + // globals. + if (_attr(ctx) != _globals(ctx).open_tags.back()) { + std::string open_tag_msg = + "Previous open-tag \"" + _globals(ctx).open_tags.back() + "\" here:"; + _error_handler(ctx).diagnose( + boost::parser::diagnostic_kind::error, + open_tag_msg, + ctx, + _globals(ctx).open_tags_position.back()); + std::string close_tag_msg = + "does not match close-tag \"" + _attr(ctx) + "\" here:"; + _error_handler(ctx).diagnose( + boost::parser::diagnostic_kind::error, + close_tag_msg, + ctx); + + // Explicitly fail the parse. Diagnostics to not affect parse success. + _pass(ctx) = false; + } + } + +[heading _report_error_ and _report_warning_] + +There are also some convenience functions that make the above code a little +less verbose, _report_error_ and _report_warning_: + + [](auto & ctx) { + // Assume we have a std::vector of open tags, and another + // std::vector of iterators to where the open tags were parsed, in our + // globals. + if (_attr(ctx) != _globals(ctx).open_tags.back()) { + std::string open_tag_msg = + "Previous open-tag \"" + _globals(ctx).open_tags.back() + "\" here:"; + _report_error(ctx, open_tag_msg, _globals(ctx).open_tag_positions.back()); + std::string close_tag_msg = + "does not match close-tag \"" + _attr(ctx) + "\" here:"; + _report_error(ctx, close_tag_msg); + + // Explicitly fail the parse. Diagnostics to not affect parse success. + _pass(ctx) = false; + } + } + +You should use these less verbose functions almost all the time. The only +time you would want to use _error_handler_ directly is when you are using a +custom error handler, and you want access to some part of its interface +besides `diagnose()`. + +Though there is support for reporting warnings using the functions above, none +of the error handlers supplied by _Parser_ will ever report a warning. +Warnings are strictly for user code. + +For more information on the rest of the error handling and diagnostic API, see +the header reference pages for _err_fwd_hpp_ and _err_hpp_. + +[heading Creating your own error handler] + +Creating your own error handler is pretty easy; you just need to implement +three member functions. Say you want an error handler that writes diagnostics +to a file. Here's how you might do that. + +[logging_error_handler] + +That's it. You just need to do the important work of the error handler in its +call operator, and then implement the two overloads of `diagnose()` that it +must provide for use inside semantic actions. The default implementation of +these is even available as the free function `write_formatted_message()`, so +you can just call that, as you see above. Here's how you might use it. + +[using_logging_error_handler] + +We just define a `logging_error_handler`, and pass it by reference to _w_eh_, +which decorates the top-level parser with the error handler. We *could not* +have written `bp::with_error_handler(parser, +logging_error_handler("parse.log"))`, because _w_eh_ does not accept rvalues. +This is becuse the error handler eventually goes into the parse context. The +parse context only stores pointers and iterators, keeping it cheap to copy. + +If we run the example and give it the input `"1,"`, this shows up in the log +file: + +[pre +parse.log:1:2: error: Expected int_ here (end of input): +1, + ^ +] [heading Fixing ill-formed code] diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 38dad0d2..9e426b70 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -26,6 +26,7 @@ add_sample(rule_intro) add_sample(struct_rule) add_sample(parsing_into_a_struct) add_sample(roman_numerals) +add_sample(user_error_handler) if (Boost_FOUND) if (NOT BUILD_WITHOUT_HANA) diff --git a/example/user_error_handler.cpp b/example/user_error_handler.cpp new file mode 100644 index 00000000..d8647400 --- /dev/null +++ b/example/user_error_handler.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2020 T. Zachary Laine +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +#include + +#include +#include +#include + + +namespace bp = boost::parser; + +//[ logging_error_handler +struct logging_error_handler +{ + logging_error_handler() {} + logging_error_handler(std::string_view filename) : + filename_(filename), ofs_(filename_) + { + if (!ofs_) + throw std::runtime_error("Could not open file."); + } + + // This is the function called by Boost.Parser after a parser fails the + // parse at an expectation point and throws a parse_error. It is expected + // to create a diagnostic message, and put it where it needs to go. In + // this case, we're writing it to a log file. This function returns a + // bp::error_handler_result, which is an enum with two enumerators -- fail + // and rethrow. Returning fail fails the top-level parse; returning + // rethrow just re-throws the parse_error exception that got us here in + // the first place. + template + bp::error_handler_result + operator()(Iter first, Sentinel last, bp::parse_error const & e) const + { + bp::write_formatted_expectation_failure_error_message( + ofs_, filename_, first, last, e); + return bp::error_handler_result::fail; + } + + // This function is for users to call within a semantic action to produce + // a diagnostic. + template + void diagnose( + bp::diagnostic_kind kind, + std::string_view message, + Context const & context, + Iter it) const + { + bp::write_formatted_message( + ofs_, + filename_, + bp::_begin(context), + it, + bp::_end(context), + message); + } + + // This is just like the other overload of diagnose(), except that it + // determines the Iter parameter for the other overload by calling + // _where(ctx). + template + void diagnose( + bp::diagnostic_kind kind, + std::string_view message, + Context const & context) const + { + diagnose(kind, message, context, bp::_where(context).begin()); + } + + std::string filename_; + mutable std::ofstream ofs_; +}; +//] + +//[ using_logging_error_handler +int main() +{ + std::cout << "Enter a list of integers, separated by commas. "; + std::string input; + std::getline(std::cin, input); + + constexpr auto parser = bp::int_ >> *(',' > bp::int_); + logging_error_handler error_handler("parse.log"); + auto const result = bp::parse(input, bp::with_error_handler(parser, error_handler)); + + if (result) { + std::cout << "It looks like you entered:\n"; + for (int x : *result) { + std::cout << x << "\n"; + } + } +} +//]