|
REAL
Regular Expression Algorithmic Library — constexpr C++20 regex
|
Recursive-descent parser: a pattern string in, an ast out. More...
#include <ast.hpp>
Classes | |
| struct | loose_buf |
A loose-match key (lowercase, no _/-/space; UAX44-LM3-ish) built into a fixed buffer, so no heap or <string> is needed at parse time. A name longer than the buffer simply fails to match. More... | |
| struct | shorthand_spec |
The classification of a \d \D \w \W \s \S shorthand: its ASCII bitmap, its Unicode range table, and whether it is the negated (uppercase) form. More... | |
Public Member Functions | |
| constexpr | parser (std::string_view pattern, flags initial_flags=flags::none) |
| Binds the parser to a pattern and the constructor flags. | |
| constexpr ast | parse () |
| Parses the whole pattern. | |
Private Member Functions | |
| constexpr flags | current_flags () const |
| The flag set in force at the current nesting level (the scope-stack top). | |
| constexpr bool | is_verbose () const |
True when verbose mode (re.X) is in force here — read from the scope stack, so a scoped (?x:...) is honoured without a global flag read. | |
| constexpr bool | is_icase () const |
True when icase (re.I) is in force at the current scope (a scoped (?i:...) honoured). | |
| constexpr bool | is_ascii_mode () const |
True when ascii (re.A) is in force at the current scope (a scoped (?a:...) honoured). | |
| constexpr void | skip_insignificant () |
In verbose mode, consumes insignificant whitespace and # comments. | |
| template<typename Error = regex_error> | |
| constexpr void | fail (const char *message) const |
| Aborts the parse with a real::regex_error at the current offset. | |
| template<typename = void> | |
| constexpr void | fail_unsupported (const char *message) const |
Like fail, but tags the error as unsupported (well-formed but beyond REAL's linear engine — a backreference, \p{…}, a nested lookaround) so a binding can classify it without matching on the message text. Templated like fail so it stays a valid constexpr. | |
| constexpr bool | eof () const |
Returns true if the read offset is at or past the end of the pattern. | |
| constexpr char | peek () const |
| Returns the current character without consuming it (undefined at eof()). | |
| constexpr bool | accept (char ch) |
Consumes the current character if it equals ch. | |
| constexpr std::int32_t | add_node (ast &out, ast_node node) |
Appends node to the pool. | |
| constexpr std::int32_t | add_class_node (ast &out, const char_class &klass, bool negated, const std::vector< code_range > &ranges={}, bool codepoint_predicate=false) |
| Interns a class bitmap and appends a node_kind::klass node. | |
| constexpr bool | text_shorthand () const |
The non-ASCII (>= 0x80) code-point ranges of a Unicode property table (\d/\s/\w), for a text-mode shorthand. In bytes or ASCII mode (flags::ascii == re.A) the shorthand stays ASCII-only, so this returns nothing and the ASCII bitmap alone is used. | |
| constexpr void | merge_property (char_class &klass, std::vector< code_range > &ranges, const char_class &prop_ascii, std::span< const code_range > table, bool negated, bool &property_derived) const |
Merges an in-class shorthand (\w \d \s or a negated \W \D \S) into the class being built: its ASCII bitmap (or the complement, negated) plus, in text mode, its non-ASCII ranges (or their complement). Sets property_derived so the class is emitted as a match-time klass_cp (text mode only). In bytes / ASCII mode it stays a byte class. | |
| constexpr std::vector< code_range > | shorthand_ranges (std::span< const code_range > table) const |
| constexpr std::vector< code_range > | resolve_property (std::string_view name) const |
Resolves a \p{...} property name to its code-point ranges, or fails with a clear error. An optional gc= / sc= (or general_category= / script=) prefix picks the namespace; a bare name tries General_Category then Script. GC ranges come straight from the table; a Script's ranges are collected from the partition. The alias resolvers are the generated, loose-keyed resolve_gc / resolve_script. | |
| constexpr std::vector< code_range > | parse_property_table () |
Rejects bytes mode, consumes the p/P and the {Name} (or single letter), and resolves it to the property's code-point ranges. Shared by the out-of-class atom and the in-class merge. On entry pos_ is on the p/P; on return it is just past the name. | |
| constexpr void | property_ascii_high (const std::vector< code_range > &table, char_class &ascii, std::vector< code_range > &high) const |
Splits a property's ranges into its ASCII bitmap (< 0x80) and its non-ASCII ranges. Unconditional: unlike \w, flags::ascii (re.A) does not restrict a Unicode property, so both parts are always used (bytes mode having already been rejected). | |
| constexpr std::int32_t | parse_unicode_property (ast &out, bool negated) |
Parses \p{Name} / \P{Name} / \pX (outside a class) into a negatable Unicode code-point class (klass_cp), reusing the same match-time mechanism as \w. Negation is the class-node flag, as for \W. pos_ is on the letter after \; negated distinguishes \P from \p. | |
| constexpr void | merge_unicode_property (char_class &klass, std::vector< code_range > &ranges, const std::vector< code_range > &table, bool negated, bool &property_derived) const |
Merges a \p{Name} / \P{Name} property into the character class being built (the in-class form) — the un-gated twin of merge_property — flags::ascii never restricts it, so it always uses the property's own non-ASCII ranges. A negated \P{...} merges the complement (the inverted ASCII bitmap plus the gaps between the non-ASCII ranges), exactly as \W negates in a class; an enclosing [^...] then negates the whole class on top (so [^\P{L}] == [\p{L}]). bytes mode is already rejected by parse_property_table. | |
| constexpr std::int32_t | parse_alternation (ast &out) |
| Parses ‘alternation := sequence (’|' sequence)*`. | |
| constexpr std::int32_t | parse_sequence (ast &out) |
Parses sequence := (atom quantifier?)*, stopping at | or ). | |
| constexpr std::int32_t | parse_atom (ast &out) |
Parses one atom: a literal, ., a class, a group, an anchor or an escape. | |
| constexpr std::int32_t | parse_quantifier (ast &out, std::int32_t atom) |
Wraps atom in a repeat node if a quantifier follows. | |
| constexpr bool | try_parse_braces (std::int32_t &min, std::int32_t &max) |
Tries to parse {n} / {n,} / {,m} / {n,m} starting at {. | |
| constexpr std::int32_t | parse_repeat_count () |
| Reads an optional decimal repeat count. | |
| constexpr void | expect (char ch, const char *message) |
Consumes ch or fails. | |
| constexpr bool | parse_global_flags_prefix (ast &out) |
Consumes a leading (?ims) global-flags group, if present. | |
| constexpr std::int32_t | parse_group (ast &out) |
| Parses a group construct. | |
| constexpr std::int32_t | parse_lookaround (ast &out, look_dir direction, std::size_t open_pos) |
Parses a lookaround after (?= / (?! (ahead) or (?<= / (?<! (behind) — the =/! is not yet consumed. | |
| constexpr std::int32_t | new_group (ast &out, std::size_t open_pos) |
| Allocates the next capture group number. | |
| constexpr void | parse_group_name (ast &out, std::int32_t group) |
| Parses ‘name := [A-Za-z_][A-Za-z0-9_]* ’>'` and records it. | |
| constexpr std::int32_t | parse_byte_escape () |
| Parses a single-byte escape (valid inside and outside classes). | |
| constexpr std::int32_t | parse_digit_escape () |
Parses a \<digit> escape via the shared decode_digit_escape(). | |
| constexpr std::int32_t | hex_digit () |
| Consumes one hexadecimal digit. | |
| constexpr std::int32_t | parse_unicode_codepoint (bool capital) |
Decodes a \uHHHH (4 hex) or \UHHHHHHHH (8 hex) code-point escape (str only). | |
| constexpr std::int32_t | parse_named_codepoint () |
Decodes a \N{U+XXXX} named-code-point escape (1–6 hex digits) — the same code-point path as \u/\U, spelled by its U+ scalar value. re writes \N{NAME} for the name; the Python binding rewrites a name to this U+XXXX form before parsing, so the engine only ever sees the scalar. A C++ caller writes \N{U+XXXX} directly. | |
| constexpr std::int32_t | emit_codepoint_utf8 (ast &out, std::int32_t cp) |
| Emits a code point as its 1–4 UTF-8 bytes — the same byte-level form a literal multi-byte character produces — as a single atom (a byte node, or a concat). | |
| constexpr std::int32_t | emit_literal_codepoint (ast &out, std::int32_t cp) |
Emits a code-point literal (code-point provenance: a raw character or \\u/\\U). | |
| constexpr std::int32_t | parse_escape (ast &out) |
| Parses an escape outside a character class. | |
| constexpr std::int32_t | parse_class_item (char_class &klass, std::vector< code_range > &ranges, bool &property_derived) |
| Parses one member inside a character class. | |
| constexpr std::int32_t | parse_class (ast &out) |
Parses a bracketed character class [...] or [^...]. | |
Static Private Member Functions | |
| static constexpr bool | is_ascii_alnum (char ch) |
Returns true if ch is in [0-9A-Za-z]. | |
| static constexpr shorthand_spec | shorthand_class (char letter) |
| Maps a shorthand letter to its shorthand_spec. The single place the letter -> (set, range table, negation) fact lives; the atom ladder (parse_escape) and the class ladder (parse_class_item) share it, then each consumes the spec its own way (emit a class node vs merge into a class) – the same shared-decode / divergent-use split as decode_digit_escape. | |
| static constexpr loose_buf | loose_key (std::string_view s) |
| static constexpr flags | flag_for_letter (char letter) |
| Maps a flag letter to its flags value. | |
| static constexpr bool | is_flag_letter (char letter) |
Returns true if letter is a flag letter (imsax). | |
| static constexpr flags | without (flags value, flags bit) |
value with bit cleared. | |
| static constexpr bool | is_name_start (char ch) |
Returns true if ch may start a group name. | |
Private Attributes | |
| std::string_view | pattern_ |
| The pattern being parsed. | |
| std::size_t | pos_ {} |
| Current read offset into pattern_. | |
| std::int32_t | depth_ {} |
| Current group nesting (see max_nesting_depth). | |
| std::vector< flags > | flag_scopes_ |
Stack of the flag set in force per nesting level; the top is current. Replaces a global verbose_ read so a scoped (?x:...) is honoured (see current_flags). | |
| bool | in_lookaround_ {} |
| True while parsing a lookaround sub-pattern (rejects nesting). | |
| bool | bytes_ {} |
In flags::bytes mode, rejects code-point escapes (\u/\U). | |
| bool | ecma_ {} |
ECMAScript grammar: \A \Z \< \> are identity-escape literals, not anchors. | |
|
inlineexplicitconstexpr |
|
inlineconstexprprivate |
|
inlineconstexprprivate |
Interns a class bitmap and appends a node_kind::klass node.
| [in,out] | out | The AST being built. |
| [in] | klass | The class bitmap as written (before negation). |
| [in] | negated | Whether the class was written negated. |
| [in] | ranges | Non-ASCII code-point ranges of the class (code-point mode; empty otherwise). |
| [in] | codepoint_predicate | Emit as a match-time klass_cp (a text-mode Unicode shorthand), not the byte-NFA. |
|
inlineconstexprprivate |
|
inlineconstexprprivate |
|
inlineconstexprprivate |
Emits a code-point literal (code-point provenance: a raw character or \\u/\\U).
Under icase, a CASED literal is promoted to a foldable singleton class so the compiler folds it to its whole case orbit (k↦{k, K, Kelvin}, é↦{é, É}). An ASCII letter folds in any mode; a non-ASCII code point folds only in text mode (a bytes class carries no ranges). A non-cased literal, or no icase, keeps the zero-overhead byte / UTF-8 path. \\xHH has byte provenance and never routes here, so it is never folded — the deliberate provenance split.
|
inlineconstexprprivate |
|
inlineconstexprprivate |
Consumes ch or fails.
| [in] | ch | The required character. |
| [in] | message | Error message if ch is not present. |
| real::regex_error | when the next character is not ch. |
|
inlineconstexprprivate |
Aborts the parse with a real::regex_error at the current offset.
A template so the always-throwing body stays legal inside a constexpr function (the ill-formed, no-diagnostic-required rule does not apply to templates); during constant evaluation the throw fails compilation with message in the diagnostic trace.
| Error | The exception type to throw (defaults to regex_error). |
| [in] | message | The cause, shown in the error and the constexpr trace. |
|
inlineconstexprprivate |
|
inlinestaticconstexprprivate |
Maps a flag letter to its flags value.
| [in] | letter | One of 'i', 'm', 's', 'a'. |
|
inlineconstexprprivate |
Consumes one hexadecimal digit.
[0, 15]. | real::regex_error | if the next character is not a hex digit. |
|
inlinestaticconstexprprivate |
|
inlineconstexprprivate |
|
inlinestaticconstexprprivate |
|
inlineconstexprprivate |
|
inlinestaticconstexprprivate |
|
inlineconstexprprivate |
|
inlinestaticconstexprprivate |
|
inlineconstexprprivate |
Merges an in-class shorthand (\w \d \s or a negated \W \D \S) into the class being built: its ASCII bitmap (or the complement, negated) plus, in text mode, its non-ASCII ranges (or their complement). Sets property_derived so the class is emitted as a match-time klass_cp (text mode only). In bytes / ASCII mode it stays a byte class.
|
inlineconstexprprivate |
Merges a \p{Name} / \P{Name} property into the character class being built (the in-class form) — the un-gated twin of merge_property — flags::ascii never restricts it, so it always uses the property's own non-ASCII ranges. A negated \P{...} merges the complement (the inverted ASCII bitmap plus the gaps between the non-ASCII ranges), exactly as \W negates in a class; an enclosing [^...] then negates the whole class on top (so [^\P{L}] == [\p{L}]). bytes mode is already rejected by parse_property_table.
|
inlineconstexprprivate |
Allocates the next capture group number.
| [in,out] | out | The AST being built. |
| [in] | open_pos | Offset of the group's ( (for error reporting). |
| real::regex_error | beyond max_group_count. |
|
inlineconstexpr |
Parses the whole pattern.
| real::regex_error | on any unsupported or malformed syntax. |
|
inlineconstexprprivate |
|
inlineconstexprprivate |
|
inlineconstexprprivate |
Parses a single-byte escape (valid inside and outside classes).
Handles \n \t \r \f \v \a \0, \xHH and escaped ASCII punctuation.
\d \w \s, etc.). | real::regex_error | on a malformed \x escape. |
|
inlineconstexprprivate |
Parses a bracketed character class [...] or [^...].
Supports ranges, escapes and the embedded set escapes; a ] right after [ or [^ is a literal, and a trailing - is a literal dash.
| [in,out] | out | The AST being built. |
| real::regex_error | on an unterminated class or a bad range. |
|
inlineconstexprprivate |
Parses one member inside a character class.
| [in,out] | klass | The class being built; a set member (\d etc.) is merged directly into it. |
| [in,out] | ranges | The class's non-ASCII code-point ranges; a Unicode shorthand (\d \w \s, or a negated one) appends its ranges here in text mode. |
| [in,out] | property_derived | Set when a Unicode shorthand contributed, so the whole class is emitted as a match-time klass_cp (text mode only). |
klass. | real::regex_error | on a non-ASCII member or an unsupported escape. |
|
inlineconstexprprivate |
Parses a \<digit> escape via the shared decode_digit_escape().
Octal escapes (\0, \012, a three-octal-digit run) become one byte (value & 0xff, mirroring \xHH). A decimal group number is a back-reference, which REAL does not support (a deliberate, documented limitation).
| real::regex_error | on an over-long octal escape or a back-reference. |
|
inlineconstexprprivate |
Parses an escape outside a character class.
Handles the class escapes \d \D \w \W \s \S, the anchors \A \Z \b \B, and single-byte escapes.
| [in,out] | out | The AST being built. |
| real::regex_error | on a dangling or unsupported escape. |
|
inlineconstexprprivate |
Consumes a leading (?ims) global-flags group, if present.
Like Python (3.11+), global flags are only legal at the very start of the pattern; later occurrences are rejected in parse_group.
| [in,out] | out | Receives the flags into ast::inline_flags. |
true if a flags group was consumed (position advanced), else false (position restored, for parse_group to handle).
|
inlineconstexprprivate |
Parses a group construct.
Grammar:
Unsupported extensions (lookaround, backreferences, atomic groups, scoped inline flags) fail with a message naming the feature. Nesting beyond max_nesting_depth is rejected.
| [in,out] | out | The AST being built. |
| real::regex_error | on an unterminated or unsupported group. |
< A (?flags:...) group pushed a scope to pop after the body.
|
inlineconstexprprivate |
Parses ‘name := [A-Za-z_][A-Za-z0-9_]* ’>'` and records it.
| [in,out] | out | The AST; the name is appended to ast::names. |
| [in] | group | The capture number this name refers to. |
| real::regex_error | on a bad character or a duplicate name. |
|
inlineconstexprprivate |
Parses a lookaround after (?= / (?! (ahead) or (?<= / (?<! (behind) — the =/! is not yet consumed.
Builds a node_kind::lookaround node. The sub-pattern is a full alternation; its capture groups advance the global group counter (so outer group numbers stay consistent) but are compiled capture-free (V1 limitation, documented). Nesting a lookaround inside a lookaround is rejected. Boundedness and the byte L_max are enforced later by the compiler.
| [in,out] | out | The AST being built. |
| [in] | direction | Ahead or behind. |
| [in] | open_pos | Offset of the group's ( (for error reporting). |
|
inlineconstexprprivate |
Decodes a \N{U+XXXX} named-code-point escape (1–6 hex digits) — the same code-point path as \u/\U, spelled by its U+ scalar value. re writes \N{NAME} for the name; the Python binding rewrites a name to this U+XXXX form before parsing, so the engine only ever sees the scalar. A C++ caller writes \N{U+XXXX} directly.
Rejected with clear messages: byte mode (no code-point meaning ≡ re's bad escape \N), a missing or malformed {U+…}, a surrogate, or a value beyond U+10FFFF. The backslash and N are already consumed.
[0, 0x10FFFF] (never a surrogate).
|
inlineconstexprprivate |
|
inlineconstexprprivate |
Wraps atom in a repeat node if a quantifier follows.
Grammar: ‘quantifier := (’*' | '+' | '?' | '{n}' | '{n,}' | '{,m}' | '{n,m}') '?'?. An invalid{...}is not a quantifier at all and stays literal text, exactly like Python (e.g.a{,a{2,3x, a{,}` all match literally). A bare anchor cannot be repeated.
| [in,out] | out | The AST being built. |
| [in] | atom | Index of the atom the quantifier would apply to. |
atom unchanged if no quantifier.
|
inlineconstexprprivate |
Reads an optional decimal repeat count.
| real::regex_error | if the count exceeds max_repeat_count (counted repetitions are compiled by unrolling, so they are capped). |
|
inlineconstexprprivate |
|
inlineconstexprprivate |
Decodes a \uHHHH (4 hex) or \UHHHHHHHH (8 hex) code-point escape (str only).
Rejected with clear messages: byte mode (no code-point meaning), a surrogate (U+D800–U+DFFF), beyond U+10FFFF, or incomplete hex. The backslash and u/U are already consumed; this reads the hex digits.
| [in] | capital | True for \U (8 digits), false for \u (4 digits). |
[0, 0x10FFFF] (never a surrogate).
|
inlineconstexprprivate |
|
inlineconstexprprivate |
|
inlineconstexprprivate |
Splits a property's ranges into its ASCII bitmap (< 0x80) and its non-ASCII ranges. Unconditional: unlike \w, flags::ascii (re.A) does not restrict a Unicode property, so both parts are always used (bytes mode having already been rejected).
|
inlineconstexprprivate |
Resolves a \p{...} property name to its code-point ranges, or fails with a clear error. An optional gc= / sc= (or general_category= / script=) prefix picks the namespace; a bare name tries General_Category then Script. GC ranges come straight from the table; a Script's ranges are collected from the partition. The alias resolvers are the generated, loose-keyed resolve_gc / resolve_script.
|
inlinestaticconstexprprivate |
Maps a shorthand letter to its shorthand_spec. The single place the letter -> (set, range table, negation) fact lives; the atom ladder (parse_escape) and the class ladder (parse_class_item) share it, then each consumes the spec its own way (emit a class node vs merge into a class) – the same shared-decode / divergent-use split as decode_digit_escape.
|
inlineconstexprprivate |
|
inlineconstexprprivate |
In verbose mode, consumes insignificant whitespace and # comments.
No-op unless is_verbose. Called only between tokens outside character classes; escaped whitespace (\) is read as a literal by the escape parser, never reaching here.
|
inlineconstexprprivate |
The non-ASCII (>= 0x80) code-point ranges of a Unicode property table (\d/\s/\w), for a text-mode shorthand. In bytes or ASCII mode (flags::ascii == re.A) the shorthand stays ASCII-only, so this returns nothing and the ASCII bitmap alone is used.
Whether a shorthand (\d \w \s) should be a text-mode Unicode code-point predicate: true in the default text mode, false in bytes mode or under flags::ascii (re.A).
|
inlineconstexprprivate |
Tries to parse {n} / {n,} / {,m} / {n,m} starting at {.
| [out] | min | Lower bound on success. |
| [out] | max | Upper bound on success (-1 for unbounded). |
true on a valid quantifier (position advanced); false if the braces are not a quantifier (position restored — literal text). | real::regex_error | when the bounds are impossible (min > max). |
|
private |
In flags::bytes mode, rejects code-point escapes (\u/\U).
|
private |
Current group nesting (see max_nesting_depth).
|
private |
|
private |
Stack of the flag set in force per nesting level; the top is current. Replaces a global verbose_ read so a scoped (?x:...) is honoured (see current_flags).
|
private |
|
private |
|
private |