Repository navigation
This release fixes six bugs found in a review of the parser's structure and simplifies the code around them. Most of the fixes change what some grammar matches or loads, so please read the first section before upgrading.
⚠️ Changes to be aware of
Numeric escapes in grammar text
An escape in a literal or a class takes only as many digits as the Char rule of the PEG grammar allows, and a digit after it is a character of its own. The grammar text was read by that rule, but the escape was resolved by reading every digit after it.
\xtakes one or two hex digits:'\x412'isA2, where it used to be U+0412.- An octal escape takes up to three digits, and three only when the first is
0to3:'\1014'isA4and'\477'is'7.8and9are not octal digits:'\78'is the bell character followed by8, where it used to be@. \utakes six digits when they start with0or10, and four or five otherwise:'\u00004142'isA42, and'ჿFF0'is U+10FFFF followed by0. A\uescape past U+10FFFF used to make the literal empty, so'ሴ56'matched the empty string. It is now U+12345 followed by6.
A grammar blob holds literals and classes with their escapes resolved, so a blob written by v1.21.0 from such a grammar keeps the old meaning. Write it again with serialize_grammar().
Cut before a label
'x'^l is a choice of its own ('x' / %recover(l)), and it cleared a cut made before it. With S <- 'a' ↑ 'x'^l 'y' / 'a' 'x' 'z', axz was accepted by the second alternative. It is now rejected, as the cut says.
no_whitespace on a macro
{ no_whitespace } on a macro was ignored unless the macro was left-recursive. With P(X) <- X X { no_whitespace } and a %whitespace rule, P('a') matched a a. It now matches aa only.
Left recursion after a prefix that can match empty
A rule is left-recursive when what comes before the recursive call can match empty. The check missed two such prefixes: a repetition that must repeat something that can match empty, such as ('x'?){2}, and a choice whose last alternative cannot match empty while another can, such as ('x'? / 'w'). So A <- ('x'?){2} A 'y' / 'z' and A <- ('x'? / 'w') A 'y' / 'z' rejected zy and accepted xzy. Both now match what A <- 'x'? A 'y' / 'z' matches: zy is accepted and xzy is rejected. A left-recursive rule grows as far as it can, so the inner A takes zy and leaves no y for the outer one.
Grammar blobs
load_blob() and GrammarBlob::deserialize() check the whole blob. A string that runs past the end, operators nested more than 2048 levels deep, bytes after the last rule, an undefined start rule and a reference to an undefined rule are errors. A blob with its last byte cut off used to load. When load_blob() returns false, the parser keeps the grammar and the start rule it had.
Blobs written by v1.21.0 still load: the format did not change.
API and ABI
Definitioncan be moved but no longer copied. A copy shared the rule with the original and left the original pointing at the copy, which dangled once the copy was destroyed.peg::parseris still copyable.ActionandPredicatecan be copied, moved and assigned in a chain (f = g = ...). Copying one used to fail to compile.- New:
peg::kFail, the length a failed match returns, for user-defined rules (usr) andleavehandlers.peg::success(len)tests for it. parse_hex_numberandparse_octal_numbertake the maximum number of digits to read.- The vtables of the analysis visitors (
DetectLeftRecursion,TokenCheckerand others) changed, and functions ofContextandParserGeneratorwere added or removed, which changes the ABI.
🐛 Fixes
resolve_escape_sequencekeeps a trailing backslash instead of reading past the end of the string.- The load error for a range such as
[b-a]is spelledcharacter range is out of order....
🔧 Internals
- The operator types are listed once, and the
Ope::Visitordefaults, the tracing names andaccept()are generated from that list. - The blob writer is a visitor that does not compile when an operator type has no case, where a missing case would have dropped the operator from blobs silently. The blobs of the repository's grammars are byte-identical to those of v1.21.0.
- A test checks that the two analyses that decide whether a rule can match empty agree.