Header lexy/dsl/terminator.hpp

Rule DSL lexy::dsl::terminator

lexy/dsl/terminator.hpp
namespace lexy::dsl
{
    struct terminator-dsl // note: not a rule itself
    {
        constexpr _branch-rule auto terminator() const;

        constexpr terminator-dsl limit(auto ... limit);
        constexpr rule auto      recovery_rule() const;

        //=== rules ===//
        constexpr rule        auto operator()(rule auto rule) const;
        constexpr rule        auto try_(rule auto rule) const;

        constexpr rule auto opt(rule auto rule) const;

        constexpr rule auto list(rule auto item) const;
        constexpr rule auto list(rule auto item, separator auto sep) const;

        constexpr rule auto opt_list(rule auto item) const;
        constexpr rule auto opt_list(rule auto item, separator auto sep) const;
    };

    constexpr terminator-dsl terminator(branch-rule auto branch);
}

terminator is not a rule, but a DSL for specifying rules that all parse something followed by a terminator.

Many rules require a branch rule as argument, like lexy::dsl::list. However, there isn’t always an easy way to check for a branch condition and sometimes the rule in question is always terminated by a given token (e.g. a semicolon). Then you can use terminator: it specifies a branch rule as the terminator and provides ways of building rules where any branch condition is just "the terminator hasn’t been matched yet". As such, you don’t need to provide a branch condition anymore.

Note
See lexy::dsl::brackets if you want to parse something that has not only a terminator but some prefix as well.

Branch rule .terminator()

lexy/dsl/terminator.hpp
constexpr branch-rule auto terminator() const;

.terminator() returns the rule that was passed to the top-level lexy::dsl::terminator().

.limit()

lexy/dsl/terminator.hpp
constexpr terminator-dsl limit(auto ... limit);

Provide a limit for error recovery.

terminator can also do error recovery after an error by discarding input until the terminator is reached. Similar to lexy::dsl::find or lexy::dsl::recover one can provide a limit, which is a literal rule or lexy::dsl::literal_set. If the limit is reached before the terminator, error recovery fails.

Example 1. Recover while parsing a list of statements
struct statement
{
    static constexpr auto rule = [] {
        // A statement is terminated by a semicolon.
        // Error recovery fails when we've reached the } of the scope.
        auto terminator = dsl::terminator(dsl::semicolon).limit(dsl::lit_c<'}'>);
        return terminator.opt(LEXY_LIT("foo()") | LEXY_LIT("bar()"));
    }();
};

struct production
{
    static constexpr auto whitespace = dsl::ascii::space;

    // Just a list of statements surrounded by {}.
    static constexpr auto rule = dsl::curly_bracketed.list(dsl::p<statement>);
};

.recovery_rule()

lexy/dsl/terminator.hpp
constexpr rule auto recovery_rule() const;

.recovery_rule() returns the rule that is used for the simple error recovery.

It is equivalent to lexy::dsl::recover(terminator()).limit(tokens…​), where tokens are all the tokens passed to every .limit() call. This simply discards input until it can match terminator(). Recovery fails when it reaches EOF or one of the limits, if any have been specified.

Rule .operator()

lexy/dsl/terminator.hpp
constexpr rule auto operator()(rule auto rule) const
{
    return rule + terminator();
}
constexpr branch-rule auto operator()(branch-rule auto rule) const
{
    return rule >> terminator();
}

.operator() returns a rule that parses rule, then parses the terminator.

It behaves entirely equivalent to rule + terminator().

Example 2. Parse a statement
struct production
{
    static constexpr auto rule = [] {
        auto terminator = dsl::terminator(dsl::semicolon);
        return terminator(LEXY_LIT("statement"));
    }();
};

Rule .try_()

lexy/dsl/terminator.hpp
constexpr rule auto try_(rule auto rule) const;

.try_() returns a rule that parses rule, then parses the terminator, but recovers on failure.

Parsing

Parses rule and terminator() in sequence.

Errors

All errors raised by rule and terminator(). It can recover from a failed rule by parsing recovery_rule(). It does not recover from a failed terminator().

Values

All values produced by rule followed by all values produced by terminator().

Example 3. Parse a statement; recover on failure
struct production
{
    static constexpr auto rule = [] {
        auto terminator = dsl::terminator(dsl::semicolon);
        return terminator.try_(LEXY_LIT("statement"));
    }();
};

Rule .opt()

lexy/dsl/terminator.hpp
constexpr rule auto opt(rule auto rule) const;

.opt() returns a rule that parses rule if it is there, then parses the terminator.

Parsing

Tries to parse terminator() and succeeds if that is the case. Otherwise, parses rule and terminator() in sequence.

Errors

All errors raised by (branch) parsing of terminator() and parsing of rule. It can recover from a failed rule by parsing recovery_rule(). It does not recover from a failed terminator().

Values
  • An object of type lexy::nullopt followed by all values produced by terminator() in the first case.

  • All values produced by rule followed by all values produced by terminator() in the second case.

Example 4. Parse a (null) statement
struct production
{
    static constexpr auto rule = [] {
        auto terminator = dsl::terminator(dsl::semicolon);
        return terminator.opt(LEXY_LIT("statement"));
    }();
};
Note
.opt(rule) consumes the same input as lexy::dsl::opt( lexy::dsl::peek_not(terminator()) >> rule ) + terminator(), but more efficiently.

Rule .list()

lexy/dsl/terminator.hpp
constexpr rule auto list(rule auto item) const;
constexpr rule auto list(rule auto item, separator auto sep) const;

.list() returns a rule that parses a non-empty list of item, optionally separated by sep, followed by the terminator.

Parsing

It first parses item once, recovers if necessary. Then it enters the main loop of parsing the rest of the list.

  1. It first tries to parse terminator(). If that succeeds, finishes parsing. Otherwise, it continues with step 2.

  2. If no separator was specified, immediately continues with step 4. Otherwise, tries to parse sep. On success, it continues with step 3. If the separator was missing, immediately recovers by going to step 4. Otherwise, recovers as described below.

  3. Tries to parse terminator() again. On success, handles a trailing separator by raising an error if necessary. It then immediately recovers and succeeds.

  4. Parses item. On success, repeats everything by going back to step 1. Otherwise, recovers as described below.

Errors
  • All errors raised by branch parsing of terminator(). The rule then fails if terminator() has failed and never tries to recover.

  • lexy::unexpected_trailing_separator: if a trailing separator was parsed but is not allowed; at the position of the trailing separator. It then recovers without consuming additional input.

  • All errors raised by branch parsing of sep and parsing item. It then recovers by discarding input until it either matches sep, if sep was specified, or until it reaches item, if no sep was specified. The latter is only possible if item is a branch rule. If sep/item was matched, continues in the appropriate step from the parsing algorithm. If recovery reaches terminator(), parses it and finishes. If recovery reaches the end of the input, or a limit, if one was specified, recovery fails.

Values

It creates a sink of the current context. All items produced by item and sep are forwarded to it; there are separate calls for every iteration and for item and sep. The value of the finished sink is then produced followed by all values of terminator().

Example 5. Parse a list of things terminated by a period
struct production
{
    static constexpr auto rule = [] {
        auto item = LEXY_LIT("abc") | LEXY_LIT("123");

        auto terminator = dsl::terminator(dsl::period);
        return terminator.list(item, dsl::sep(dsl::comma));
    }();
};
Note
.list(rule, sep) consumes the same input as lexy::dsl::list( lexy::dsl::peek_not(terminator()) >> rule, sep ) + terminator(), but more efficiently.

Rule .opt_list()

lexy/dsl/terminator.hpp
constexpr rule auto opt_list(rule auto item) const;
constexpr rule auto opt_list(rule auto item, separator auto sep) const;

.opt_list() returns a rule that parses a (possibly empty) list of item, optionally separated by sep, followed by the terminator.

Parsing

Tries to parse terminator() and succeeds if that is the case. Otherwise, it parses the corresponding .list() rule.

Errors

All errors raised by branch parsing of terminator() or parsing of .list(). It never recovers from the terminator, and recovers from .list() as described there.

Values

The first argument is:

  • a lexy::nullopt object in the first case,

  • The result of the .list() rule in the second case. It is then followed by all values produced by terminator().

Note
This is different from term.opt(term.list(r)) as that would parse the terminator twice: once by .list() and once by .opt(). Apart from that, it behaves identically.

See also