Control and Normal
May 30, 2026 ยท View on GitHub
A control is a class that adheres to an informal control interface and is in control of important behind-the-scenes details of a parsing run.
Contents
Introduction
The control has functions that are called on many occasions during a parsing run. These control functionality can be customized for multiple reasons, most prominently to
- obtain some debug or trace information from a parsing run, or
- customize and/or extend some behavior of a parsing run.
Examples of customized behavior are how exceptions are thrown and which type they have, and how to obtain a rule-dependent error message for an exception.
The normal PEGTL behavior is implemented in the normal control. Additional control adapters are documented in the control reference.
Control Interface
A control is a class template that takes the current Rule as template parameter and implements some static functions.
template< typename Rule >
struct foo_control
{
template< typename ParseInput,
typename... States >
static void start( const ParseInput&, States&&... );
template< typename ParseInput,
typename... States >
static void success( const ParseInput&, States&&... );
template< typename ParseInput,
typename... States >
static void failure( const ParseInput&, States&&... );
template< apply_mode A,
rewind_mode M,
template< typename... > class Action,
template< typename... > class Control,
typename ParseInput,
typename... States >
static auto guard( [[maybe_unused]] ParseInput& in, States&&... /*unused*/ );
template< typename ParseInput,
typename... States >
[[noreturn]] static void raise( const ParseInput&, States&&... );
template< typename Ambient,
typename ParseInput,
typename... States >
[[noreturn]] static void raise_nested( const Ambient&, const ParseInput&&, States&&... );
template< typename ParseInput,
typename... States >
static void unwind( const ParseInput&, States&&... );
template< template< typename... > class Action,
typename Iterator,
typename ParseInput,
typename... States >
static auto apply( const Iterator& begin, const ParseInput& in, States&&... st )
-> decltype( ... );
template< template< typename... > class Action,
typename ParseInput,
typename... States >
static auto apply0( const ParseInput&, States&&... st )
-> decltype( ... );
template< apply_mode A,
rewind_mode M,
template< typename... > class Action,
template< typename... > class Control,
typename ParseInput,
typename... States >
[[nodisard]] static bool match( ParseInput& in, States&&... st );
};
The exact set of control functions is not set in stone, for example the start(), success() and failure() functions are usually only called from tao::pegtl::match() and could be removed if that function is replaced.
However we consider it best practice to go with the interface established by tao::pegtl::normal and tao::pegtl::match().
It is no accident that all additional controls included with the PEGTL are adapters that add some functionality to an existing control which, directly or indirectly, always ends up using normal.
start
The function C< R >::start() is called before matching rule R with current control C.
This function does not need to do anything for a normal parsing run and can be empty.
success
The function C< R >::success() is called after matching rule R with current control C succeeded and after any action attached to R was applied and the action has a return type void or also succeeded.
This function does not need to do anything for a normal parsing run and can be empty.
failure
The function C< R >::failure() is called after matching rule R with current control C locally failed or after R succeeded but the action attached to R was applied, has a return type bool and (locally) failed.
This function does not need to do anything for a normal parsing run and can be empty.
guard
The function C< R >::guard() is called when the implementation of R::match() requires a rewind guard with current control C to ensure it does not consume input on local failure.
raise
The function C< T >::raise() is called to generate a global error during parsing for type T and current control C.
Under normal circumstances, for example the global error being due to a must rule, the type T will be the rule that (globally) failed.
This function must not return.
This function will only be called when exception support is enabled.
raise_nested
The function C< T >::raise_nested() is called to propagate a global error during nested parsing for type T and current control C.
Under normal circumstances this will only be called by parse_nested where T will be the top-level rule from the nested parsing run.
This function must not return.
This function will only be called when exception support is enabled.
unwind
The function C< R >::unwind() is called during stack unwinding when matching rule R produced a global failure.
The type of exception modeling the global error is not relevant.
Unlike all other control functions the unwind() function is optional.
- Its presence is auto-detected at compile-time and it is called only when present, or:
- Its presence is ignored when compiling without exception support.
This function does not need to do anything for a normal parsing run and can be empty, though, to allow for a micro-optimization, it should be omitted when not needed.
This function will only be called when exception support is enabled.
apply
The function C< R >::apply() is called after matching rule R with current control C succeeded and A< R > for current action A has an apply() function that can be called with an action_input and the current states.
The trailing return type of this function must be set up to match the return type of A< R >::apply() when that exists, and to not be defined otherwise.
This disable C< R >::apply() via SFINAE when it is not needed due to no action being attached to R.
apply0
The function C< R >::apply0() is called after matching rule R with current control C succeeded and A< R > for current action A has an apply0() function that can be called with the current states.
The trailing return type of this function must be set up to match the return type of A< R >::apply0() when that exists, and to not be defined otherwise.
This disable C< R >::apply0() via SFINAE when it is not needed due to no action being attached to R.
match
The function C< R >::match() is called to attempt matching rule R.
It is the entry-point to matching R and is directly called by parse and combinators like seq and sor.
This function is therefore crucial to parsing and has to be implemented by every control.
A minimal implementation would be to forward the call to tao::pegtl::match() from include/tao/pegtl/match.hpp.
Normal Control
The normal control is the default control of the PEGTL.
It implements all required control functions in a way that provides for "normal" parsing behavior.
It also adds two features, the enable variable and the possibility to delegate matching to an action.
The optional unwind() function is not implemented.
template< typename Rule >
struct normal
{
static constexpr bool enable; // See also Meta-Data-and-Visit.md
template< typename ParseInput,
typename... States >
static void start( const ParseInput&, States&&... ) noexcept {}
template< typename ParseInput,
typename... States >
static void success( const ParseInput&, States&&... ) noexcept {}
template< typename ParseInput,
typename... States >
static void failure( const ParseInput&, States&&... ) noexcept {}
template< apply_mode A,
rewind_mode M,
template< typename... > class Action,
template< typename... > class Control,
typename ParseInput,
typename... States >
static auto guard( [[maybe_unused]] ParseInput& in, States&&... /*unused*/ ) noexcept;
template< typename ParseInput,
typename... States >
[[noreturn]] static void raise( const ParseInput&, States&&... );
template< typename Ambient,
typename ParseInput,
typename... States >
[[noreturn]] static void raise_nested( const Ambient&, const ParseInput&&, States&&... );
template< template< typename... > class Action,
typename Iterator,
typename ParseInput,
typename... States >
static auto apply( const Iterator& begin, const ParseInput& in, States&&... st ) noecept( auto )
-> decltype( ... );
template< template< typename... > class Action,
typename ParseInput,
typename... States >
static auto apply0( const ParseInput&, States&&... st ) noexcept( auto )
-> decltype( ... );
template< apply_mode A,
rewind_mode M,
template< typename... > class Action,
template< typename... > class Control,
typename ParseInput,
typename... States >
[[nodisard]] static bool match( ParseInput& in, States&&... st );
};
enable
When normal< R >::enable is false for a rule R then no control functions - except match - will be called while matching R.
[NOTE!] This does not disable control functions for sub-rules of
R, only forRitself!
In (nearly?) all cases user-defined controls can use the default from the normal control (where enable is true for all user-visible rules) and otherwise ignore the subject (including the following explanation as to why this exists).
By default enable is true for all user-visible rules, i.e. for all rule classes except those implemented in namespace tao::pegtl::internal.
For these internal rule classes enable is set to false.
To understand which problem this solves consider the fact that some PEGTL combinators are implemented using other rules and combinators.
For example minus does not have its own implementation, it is built from rematch](Rule-Reference.md#rematch-r-s-), [not_at](Rule-Reference.md#not_at-r-) and [eof`.
template< typename M, typename S >
using minus = rematch< M, not_at< S, eof > >;
If control functions were to be called for not_at< S, eof > and eof it would expose the implementation of minus.
This is a problem because it makes all changes to minus a user-visible breaking change, and, more importantly, it can lead to undesired action application:
If the sub-rules not_at< S, eof > and eof were to appear elsewhere in the grammar, and have actions attached, these actions would also be applied to the sub-rules of minus, even though the user never explicitly wrote them down.
The description of the following functions assumes that control functions are enabled since otherwise they would not be called.
start
Empty function.
success
Empty function.
failure
Empty function.
guard
The guard function returns either a rewind guard or an empty dummy object that does nothing, depending on the current rewind_mode.
raise
The function normal< R >::raise() thorws an exception of type parse_error< ParseInput::error_position_t > where ParseInput is the type of the current input.
The message of the exception is either R::error_message if it exists, or "parse error matching" followed by the demangled name of R.
The position in the exception is in.current_position() where in is the current input of type ParseInput.
This function statically asserts that exceptions are enabled.
raise_nested
This function statically asserts that exceptions are enabled.
apply
The function normal< R >::apply() calls A< R >::apply() with an action_input and all state objects as arguments assuming that A is the current action and actions are enabled.
apply0
The function normal< R >::apply0() calls A< R >::apply0() with all state objects assuming that A is the current action and actions are enabled.
match
The normal match function checks whether, for current action A and current rule R, the class A< R > has a static match function that can be called.
If such a function exists it is called instead of the default which is tao::pegtl::match().
Changing Control
The control class template is usually supplied to parse() or parse_nested() at the beginning of a parsing run.
The control can also be changed during a parsing run.
Via Rules
The control combinator parses a sequence of rules just like seq but changes the control its first template parameter.
Similarly the following two lines both start parsing my_grammar with my_action as action and my_control as control.
Like any other rule, control<> can be used anywhere within a grammar.
tao::pegtl::parse< my_grammar, my_action, my_control >( ... );
tao::pegtl::parse< tao::pegtl::control< my_control, my_grammar >, my_action >( ... );
Via Actions
The change_control action can be used to change the control in a non-intrusive fashion.
For example if the custom action my_action is specialized for my_rule as follows then parsing my_grammar will switch to using my_control when starting to match my_rule.
template<>
struct my_action< my_rule >
: tao::pegtl::change_control< my_control > {};
tao::pegtl::parse< my_grammar, my_action >( ... );
Control Traces
The following traces give insight into when which function is called when during an attempt to match rule R.
To keep them readable most template parameters and function arguments have been omitted. Please consult the appropriate header files if all details need to be known.
Rule Success
Parse R success, default Action and Control.
| Function | Event |
|---|---|
tao::pegtl::parse< R >() | Enter |
tao::pegtl::normal< R >::match() | Enter |
tao::pegtl::match< R >() | Enter |
tao::pegtl::normal< R >::guard() | Full call |
tao::pegtl::normal< R >::start() | Full call |
R::match() | Full call returns true |
tao::pegtl::normal< R >::success() | Full call |
tao::pegtl::match< R >() | Return true |
tao::pegtl::normal< R >::match() | Return true |
tao::pegtl::parse< R >() | Return true |
Full Internal Trace
| Function | Event |
|---|---|
tao::pegtl::parse< R >() | Enter |
tao::pegtl::normal< R >::match() | Enter |
tao::pegtl::match< R >() | Enter |
tao::pegtl::normal< R >::guard() | Full call returns rewind guard or dummy |
tao::petl::internal::rewind_guard::rewind_guard() | Remember position if not dummy |
tao::pegtl::normal< R >::start() | Full call |
tao::pegtl::internal::match_control_unwind< R >() | Enter and set up unwind guard if needed |
tao::pegtl::internal::match_no_control< R >() | Enter and detect simple or complex match |
R::match() | Full call returns true |
tao::pegtl::internal::match_no_control< R >() | Return true |
tao::pegtl::internal::match_control_unwind< R >() | Return true |
tao::pegtl::normal< R >::success() | Full call |
tao::petl::internal::rewind_guard::~rewind_guard() | Do nothing or dummy does nothing |
tao::pegtl::match< R >() | Return true |
tao::pegtl::normal< R >::match() | Return true |
tao::pegtl::parse< R >() | Return true |
Whether a rewind guard or a dummy is created depends on the current rewind_mode.
Rule Local Failure
Parse R local failure, default Action and Control.
| Function | Event |
|---|---|
tao::pegtl::parse< R >() | Enter |
tao::pegtl::normal< R >::match() | Enter |
tao::pegtl::match< R >() | Enter |
tao::pegtl::normal< R >::guard() | Full call |
tao::pegtl::normal< R >::start() | Full call |
R::match() | Full call returns false |
tao::pegtl::normal< R >::failure() | Full call |
tao::pegtl::match< R >() | Return false |
tao::pegtl::normal< R >::match() | Return false |
tao::pegtl::parse< R >() | Return false |
Full Internal Trace
| Function | Event |
|---|---|
tao::pegtl::parse< R >() | Enter |
tao::pegtl::normal< R >::match() | Enter |
tao::pegtl::match< R >() | Enter |
tao::pegtl::normal< R >::guard() | Full call returns rewind guard or dummy |
tao::petl::internal::rewind_guard::rewind_guard() | Remember position if not dummy |
tao::pegtl::normal< R >::start() | Full call |
tao::pegtl::internal::match_control_unwind< R >() | Enter and set up unwind guard if needed |
tao::pegtl::internal::match_no_control< R >() | Enter and detect simple or complex match |
R::match() | Full call returns false |
tao::pegtl::internal::match_no_control< R >() | Return false |
tao::pegtl::internal::match_control_unwind< R >() | Return false |
tao::pegtl::normal< R >::failure() | Full call |
tao::petl::internal::rewind_guard::~rewind_guard() | Rewind input if not dummy |
tao::pegtl::match< R >() | Return false |
tao::pegtl::normal< R >::match() | Return false |
tao::pegtl::parse< R >() | Return false |
Whether a rewind guard or a dummy is created depends on the current rewind_mode.
Action Apply
Parse R success, Action A has void apply() for R, default Control.
| Function | Event |
|---|---|
tao::pegtl::parse< R, A >() | Enter |
tao::pegtl::normal< R >::match() | Enter |
tao::pegtl::match< R >() | Enter |
tao::pegtl::normal< R >::guard() | Full call |
tao::pegtl::normal< R >::start() | Full call |
R::match() | Full call returns true |
tao::pegtl::normal< R >::apply() | Enter |
A< R >::apply() | Full call |
tao::pegtl::normal< R >::apply() | Return |
tao::pegtl::normal< R >::success() | Full call |
tao::pegtl::match< R >() | Return true |
tao::pegtl::normal< R >::match() | Return true |
tao::pegtl::parse< R >() | Return true |
Full Internal Trace
| Function | Event |
|---|---|
tao::pegtl::parse< R, A >() | Enter |
tao::pegtl::normal< R >::match() | Enter |
tao::pegtl::match< R >() | Enter |
tao::pegtl::normal< R >::guard() | Full call returns rewind guard |
tao::petl::internal::rewind_guard::rewind_guard() | Remember position |
tao::pegtl::normal< R >::start() | Full call |
tao::pegtl::internal::match_control_unwind< R >() | Enter and set up unwind guard if needed |
tao::pegtl::internal::match_no_control< R >() | Enter and detect simple or complex match |
R::match() | Full call returns true |
tao::pegtl::internal::match_no_control< R >() | Return true |
tao::pegtl::internal::match_control_unwind< R >() | Return true |
tao::pegtl::normal< R >::apply() | Enter |
A< R >::apply() | Full call |
tao::pegtl::normal< R >::apply() | Return |
tao::pegtl::normal< R >::success() | Full call |
tao::petl::internal::rewind_guard::~rewind_guard() | Do nothing or dummy does nothing |
tao::pegtl::match< R >() | Return true |
tao::pegtl::normal< R >::match() | Return true |
tao::pegtl::parse< R >() | Return true |
A rewind guard is created independent of the current rewind_mode because the input position at the beginning of the match is needed for the Action invocation.
This page is part of the PEGTL and its documentation.
Copyright (c) 2014-2026 Dr. Colin Hirsch and Daniel Frey
Distributed under the Boost Software License, Version 1.0
See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt