Skip to content

Commit

Permalink
Document exception changes.
Browse files Browse the repository at this point in the history
  • Loading branch information
ColinH committed Sep 21, 2023
1 parent 437d67c commit 4da3d14
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 55 deletions.
10 changes: 9 additions & 1 deletion doc/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,21 @@
* Added new convenience rule `star_partial`.
* Added new convenience rule `strict`.
* Added new convenience rule `star_strict`.
* Added rule `try_catch_any_return_false`.
* Renamed rule `try_catch` to `try_catch_return_false`.
* Added rule `try_catch_std_return_false`.
* Renamed rule `try_catch_type` to `try_catch_type_return_false`.
* Added rule `try_catch_any_raise_nested`.
* Added rule `try_catch_raise_nested`.
* Added rule `try_catch_std_raise_nested`.
* Added rule `try_catch_type_raise_nested`.
* Moved depth counter to adapter class in contrib.
* Changed default top-level `rewind_mode` to `dontcare`.
* Replaced `rewind_mode` values `dontcare` and `active` with new value `optional`.
* Removed support for `boost::filesystem` and `std::experimental::filesystem`.
* Removed support for building an amalgamated header.
* Removed support for Visual Studio 2017.
* Removed support for GCC 7.
* Removed support for GCC 7 and GCC 8.

## 3.2.7

Expand Down
18 changes: 11 additions & 7 deletions doc/Control-and-Debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ struct normal

template< typename ParseInput,
typename... States >
static void raise( const ParseInput& in, States&&... );
static void raise( const ParseInput&, States&&... );

template< template< typename... > class Action,
template< typename Ambient,
typename... States >
static void raise_nested( const Ambient&, States&&... );

template< template< typename... > class Action,
typename Iterator,
typename ParseInput,
typename... States >
Expand Down Expand Up @@ -78,7 +82,8 @@ It is not included in the default control template `normal`, as the existence of
This might potentially have an impact on the binary size and therefore, one should only add an `unwind()` method if necessary.
Several other control classes utilize the `unwind()` method to track the execution even in the presence of global errors.

The static member function `raise()` is used to create a global error, and any replacement should again throw an exception, or abort the application.
The static member function `raise()` is used to create a global error, and any replacement **must** again throw an exception (or abort the application).
The static member function `raise_nested()` is used to create a global error in cases where a previous error was caught and is to be included in the newly thrown exception as nested exception.

The static member functions `apply()` and `apply0()` can customise how actions with, and without, receiving the matched input are called, respectively.
Note that these functions should only exist or be visible when an appropriate `apply()` or `apply0` exists in the action class template.
Expand Down Expand Up @@ -118,12 +123,11 @@ The included `<tao/pegtl/contrib/trace.hpp>` gives a practical example that show

## Exception Throwing

The control-hook, the `raise()` static member function, **must** throw an exception.
The control-hooks for exceptions, the`raise()` and `raise_nested()` static member function, **must** both throw an exception.
For most parts of the PEGTL the exception class is irrelevant and any user-defined data type can be thrown by a user-defined control hook.

The `try_catch` rule only catches exceptions of type `tao::pegtl::parse_error`!

When custom exception types are used then `try_catch_type` must be used with the custom exception class that they are supposed to catch as first template argument.
The [`try_catch_raise_nested`](Rule-Reference.md#try_catch_raise_nested-r-) and [`try_catch_return_false`](Rule-Reference.md#try_catch_return_false-r-) rules only catches exceptions of type `tao::pegtl::parse_error_base` (or derived)!
When other exception types need to be caught then other members of the `try_catch_*` family of rules need to be used.

## Advanced Control

Expand Down
61 changes: 34 additions & 27 deletions doc/Errors-and-Exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ A parsing run, a call to one of the `parse()` functions as explained in [Inputs
* A return value of `false` is called a *local failure* (even when propagated to the top).
* An exception indicating a *global failure* is thrown.

The PEGTL parsing rules throw exceptions of type `tao::pegtl::parse_error`, some of the inputs throw additional exceptions like `std::system_error` or `std::filesystem_error`.
Other exception classes can be used freely from actions and custom parsing rules.
The PEGTL parsing rules throw exceptions of type `tao::pegtl::parse_error`, some of the inputs can throw other exceptions like `std::system_error` or `std::filesystem_error`.
And other exception classes can be used freely from actions and custom parsing rules.

## Contents

Expand All @@ -23,47 +23,48 @@ Other exception classes can be used freely from actions and custom parsing rules

By default, global failure means that an exception of type `tao::pegtl::parse_error` is thrown.

Note that starting with PEGTL version 4.0.0 `parse_error` is no longer a monolithic class derived from `std::runtime_error`.
To allow for both different types of position information and a single type that can be used to catch all parse errors there is a base class with the non position-type dependent parts as well as the actually thrown class that is templated over the position type.

Synposis:

```c++
namespace tao::pegtl
{
class parse_error
class parse_error_base
: public std::runtime_error
{
parse_error( const char* msg, position p );

parse_error( const std::string& msg, position p )
: parse_error( msg.c_str(), std::move( p ) )
{}
public:
[[nodiscard]] std::string_view message() const noexcept;
[[nodiscard]] std::string_view position_string() const noexcept;
};

template< typename ParseInput >
parse_error( const char* msg, const ParseInput& in )
: parse_error( msg, in.position() )
{}
template< typename Position >
class parse_error_template
: public parse_error_base
{
public:
using position_t = Position;

template< typename ParseInput >
parse_error( const std::string& msg, const ParseInput& in )
: parse_error( msg, in.position() )
{}
template< typename Object >
parse_error_template( const std::string& msg, const Object& obj );

const char* what() const noexcept override;
[[nodiscard]] const position_t& position_object() const noexcept;
};

std::string_view message() const noexcept;
const std::vector< position >& positions() const noexcept;
template< typename Object >
parse_error_template( const std::string&, const Object& ) -> parse_error_template< std::decay_t< decltype( internal::extract_position( std::declval< Object >() ) ) > >;

void add_position( position&& p );
};
using parse_error = parse_error_template< position >;
}
```
The `what()` message will contain all positions as well as the original `msg`.
This allows retrieval of all information if the exception is handled as a `std::runtime_error` in a generic way.
The `message()` function returns the original `msg`, while `position_string()` and `position_object()` provide a string representation of, or the actual position object, respectively.
The `message()` function will return the original `msg`, while `positions()` allows access to the stored positions.
This is useful to decompose the exception and provide more helpful errors to the user.
The `Object` passed to the constructor can be either a PEGTL input class, in which case the current position will be extracted and stored in the exception, or it can be an actual position object that will be used "as is".
The supplied user-defined deduction guide will make sure that the exception object uses the correct type as `position_t` in both of these cases.
The constructors can be used by custom rules to signal global failure, while `add_position()` is often used when you are parsing nested data, so you can append the position in the original file which includes the nested file.
The string returned by the `what()` function inherited from `std::runtime_error` is a concatenation of the position string and the message supplied to the constructor.
## Local to Global Failure
Expand Down Expand Up @@ -95,9 +96,15 @@ See [Custom Exception Messages](#custom-exception-messages) for more information
## Global to Local Failure
To convert global failure to local failure, the grammar rules [`try_catch`](Rule-Reference.md#try_catch-r-) and [`try_catch_type`](Rule-Reference.md#try_catch_type-e-r-) can be used.
To convert global failure to local failure, the grammar rule [`try_catch_return_false`](Rule-Reference.md#try_catch_return_false-r-), or one of its variants that give more control over which kinds of exceptions are caught, can be used.
Since these rules are not very commonplace they are ignored in this document, in other words we assume that global failure always propagages to the top.
## Global to Nested Failure
To add more information to an in-flight exception in the form of another exception the grammar rule [`try_catch_raise_nested`](Rule-Reference.md#try_catch_rause_nested-r-), or one of its variants that give more control over which kinds of exceptions are caught, can be used.
They throw a new exception that contains the previous one as nested exception.
Many applications will not use nested exceptions, and those that do will usually only generate them in the case of [nested parsing](Inputs-and-Parsing.md#nested-parsing).
## Examples for Must Rules
One basic use case of the `must<>` rule is as top-level grammar rule.
Expand Down
2 changes: 1 addition & 1 deletion doc/Getting-Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ Typically, the following pattern helps to print the exceptions in a human friend
catch( const pegtl::parse_error& e ) {

// This catch block needs access to the input
const auto p = e.positions().front();
const auto& p = e.positions_object();
std::cerr << e.what() << '\n'
<< in.line_at( p ) << '\n'
<< std::setw( p.column ) << '^' << std::endl;
Expand Down
28 changes: 9 additions & 19 deletions doc/Inputs-and-Parsing.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ All classes and functions on this page are in namespace `tao::pegtl`.

Some input classes allow a choice of tracking mode, or whether the `byte`, `line` and `column` counters are continuously updated during a parsing run with `tracking_mode::eager`, or only calculated on-demand in `position()` by scanning the complete input again with `tracking_mode::lazy`.

Lazy tracking is recommended when the position is used very infrequently, for example only in the case of throwing a `parse_error`.
Lazy tracking is recommended when the position is used very infrequently, for example at most once when a parsing run ends with an exception of type `parse_error`.

Eager tracking is recommended when the position is used frequently and/or in non-exceptional cases, for example when annotating every AST node with the line number.

Expand Down Expand Up @@ -309,36 +309,26 @@ bool parse( ParseInput& in,
Nested parsing refers to an (inner) parsing run that is performed "in the middle of" another (outer) parsing run, for example when one file "includes" another file.
The difference to the regular `tao::pegtl::parse()` function is that `tao::pegtl::parse_nested()` takes care of adding to the `std::vector` of `tao::pegtl::position` objects in the exception class `tao::pegtl::parse_error`.
This allows generating error messages of the form "error in file F1 line L1 included from file F2 line L2...".
The difference to the regular `tao::pegtl::parse()` function is that when a global failure occurs within `tao::pegtl::parse_nested()` then a new exception is thrown via `Control< Rule >::raise_nested()`.
The new exception contains the previous one as nested exception. The functions in the header `tao/pegtl/contrib/nested_exceptions.hpp` can be used to work with these nested exceptions.
The inner-most exception that was thrown first will be the "most nested" exception, i.e. the final one in the linked list of nested exceptions.
Compared to `parse()`, calling `parse_nested()` requires either the input from the outer parsing run or the position as additional first argument.
Everything else remains the same.
The position information contained in the nested exceptions allows for error messages like "error in file F1 line L1 included from file F2 line L2 etc."
Calling `parse_nested()` requires the input from the outer parsing run, or the position whithin the outer parsing run, as additional first argument ("additional" as compared to `parse()`).
```c++
template< typename Rule,
template< typename... > class Action = nothing,
template< typename... > class Control = normal,
apply_mode A = apply_mode::action,
rewind_mode M = rewind_mode::dontcare,
typename OuterInput,
typename OuterInput, // Can also be class position.
typename ParseInput,
typename... States >
bool parse_nested( const OuterInput& oi,
ParseInput& in,
States&&... st );
template< typename Rule,
template< typename... > class Action = nothing,
template< typename... > class Control = normal,
apply_mode A = apply_mode::action,
rewind_mode M = rewind_mode::required,
typename OuterInput,
typename ParseInput,
typename... States >
bool parse_nested( position op,
ParseInput& in,
States&&... st );
```

## Incremental Input
Expand Down Expand Up @@ -540,7 +530,7 @@ try {
// call parse on the input 'in' here...
}
catch( const parse_error& e ) {
const auto p = e.positions().front();
const auto& p = e.position_object();
std::cerr << e.what() << std::endl
<< in.line_at( p ) << '\n'
<< std::setw( p.column ) << '^' << std::endl;
Expand Down
3 changes: 3 additions & 0 deletions doc/Migration-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

## Version 4.0.0

* The `try_catch` and `try_catch_type` rules were renamed to `try_catch_return_false` and `try_catch_type_return_false`, respectively.
* The `rewind_mode` values `active` and `dontcare` have been replaced by the single new value `optional` wherefore code using the old values needs to be updated.
* Check whether the `rewind_mode` of the top-level `parse()` function, which is now `optional` by default, needs to be set to the previous value of `required` for your parsing runs.
* The PEGTL generated `parse_error` exceptions now contain a single `position` object (previously a `std::vector< position >`). Nested exceptions are now used to convey multiple positions during nested parsing. The header `tao/pegtl/contrib/nested_exceptions.hpp` contains some functions to work with nested exceptions.
* The counter used to limit the nesting depth of certain rules at runtime is no longer part of all input classes. When required it needs to be added back to the input by including `tao/pegtl/contrib/input_with_depth.hpp` and using objects of type `tao::pegtl::input_with_depth< Input >` where previously inputs of type `Input` were used.

## Version 3.0.0

Expand Down

0 comments on commit 4da3d14

Please sign in to comment.