|
REAL
Regular Expression Algorithmic Library — constexpr C++20 regex
|
Compiles an ast into a dynamic_program (NFA bytecode). More...
#include <compiler.hpp>
Public Member Functions | |
| constexpr | compiler (const ast &tree, flags compile_flags) |
| Binds the compiler to a parsed pattern and its flags. | |
| constexpr dynamic_program | compile () |
| Emits the full NFA program for the bound AST. | |
Static Public Member Functions | |
| static constexpr bool | inner_literal_starts_at (const dynamic_program &prog, std::size_t pc) |
True when the FULL inner literal starts at pc as consecutive byte ops. | |
Private Member Functions | |
| constexpr void | emit_any_codepoint_class (dynamic_program &prog, const char_class &ascii) const |
| Emits "one codepoint matching \p ascii, or any non-ASCII codepoint". | |
| constexpr void | emit_byte_sequences (dynamic_program &prog, const std::vector< std::vector< char_class > > &branches) const |
Emits an alternation of byte-range sequences (branches) as split/jump. Each branch is a chain of klass steps; the leftmost matching branch wins. The shared backbone of emit_class_codepoints (used for both \p{...}-style specific code-point ranges and, via emit_any_codepoint_class, the single [U+0080, U+10FFFF] "any non-ASCII" range ./negated-ASCII-only classes need). | |
| constexpr void | emit_class_codepoints (dynamic_program &prog, const char_class &ascii, const std::vector< code_range > &ranges) const |
Emits a code-point class: the ASCII bitmap (one byte, if any) OR the canonical UTF-8 byte sequences of each code-point range. emit_any_codepoint_class is a thin wrapper over this for the specific [U+0080, U+10FFFF] "any non-ASCII" range. | |
| constexpr class_def | effective_class (const ast_node &node) const |
The class a node_kind::klass node effectively accepts, after negation, icase folding and the bytes/code-point split. This is the ONE source of truth consumed by both emit_node and l_max_bytes, so what is emitted and its measured width can never disagree. Positive: as written. Negated: the ASCII complement plus, in code-point mode, the code-point complement over [U+0080, U+10FFFF] minus surrogates. | |
| constexpr class_def | finish_class (const ast_node &node, class_def folded) const |
| Applies negation (and its mode-dependent complement) to an already-folded class. | |
| constexpr void | emit_node (dynamic_program &prog, std::int32_t index, bool capture_free=false) const |
Emits the bytecode for the AST node at index (recursively). | |
| constexpr assert_kind | assert_kind_for (anchor_kind anchor, flags node_flags) const |
| Maps an AST anchor_kind to the runtime assert_kind. | |
| constexpr bool | fuse_single_atom_alternation (const ast_node &node, class_def &out) const |
| The class an alternation of single ATOMS is, when it is one. | |
| constexpr void | emit_effective_class (dynamic_program &prog, const class_def &eff) const |
| Emits an already-materialised class the one way this compiler emits classes. | |
| constexpr void | emit_alternation (dynamic_program &prog, const ast_node &node, bool capture_free) const |
| Emits an alternation: branches chained with leftmost-preferred splits. | |
| constexpr void | emit_unbounded_body (dynamic_program &prog, std::int32_t child, bool capture_free) const |
| Emits an UNBOUNDED quantifier's body, promoting a bare literal byte to a one-member byte class so the shape routes can see it. | |
| constexpr void | emit_repeat (dynamic_program &prog, const ast_node &node, bool capture_free) const |
| Emits a quantifier (Thompson construction). | |
| constexpr void | emit_lookaround (dynamic_program &prog, const ast_node &node, bool capture_free) const |
Emits a bounded lookaround: an assert_lookaround whose sub-program is a capture-free region the main flow jumps over. | |
| constexpr std::int32_t | l_max_bytes (std::int32_t index) const |
Upper bound, in bytes, on what the sub-AST at index can consume; -1 if unbounded (a *, + or {n,} repeat) or if it nests a lookaround. | |
| constexpr bool | is_single_atom (std::int32_t index) const |
Is index a bare, unwrapped single atom (a literal byte, a character class, or .)? | |
| constexpr bool | is_tier1_body (std::int32_t index) const |
Tier 1 eligibility: is index a bare single atom, or an ordinary (non-atomic) capturing group wrapping exactly one (X*+, (a)*+, (?>X*), …)? | |
| constexpr std::int32_t | tier1_atom (std::int32_t index) const |
The bare atom Tier 1 should test: index itself, or its single captured child when index is a capturing-group wrapper. is_tier1_body must hold. | |
| constexpr std::int32_t | tier1_capture_group (std::int32_t index) const |
| The capture group number Tier 1 should wrap the loop in, or -1 for none. is_tier1_body must hold. | |
| constexpr bool | is_deterministic (std::int32_t index) const |
Would compiling the sub-AST at index ever emit a split opcode? | |
| constexpr std::int32_t | emit_tier1_atom_test (dynamic_program &prog, std::int32_t atom, std::int32_t capture_start_slot) const |
Emits a Tier 1 atom-test instruction (byte_loop_possessive/klass_loop_ possessive/klass_cp_loop_possessive), with a placeholder secondary_target (the on-no-match exit — patch before use) and primary_target set to capture_start_slot (-1 for uncaptured, else the capture group's start slot; the end slot is always start+1). | |
| constexpr void | emit_tier1_loop (dynamic_program &prog, std::int32_t atom, std::int32_t min, std::int32_t max, std::int32_t capture_group, bool capture_free) const |
| Emits a Tier 1 possessive loop over a single atom, optionally wrapped in one capturing group. | |
| constexpr void | emit_possessive_repeat (dynamic_program &prog, std::int32_t body, std::int32_t min, std::int32_t max, bool capture_free) const |
Dispatches a possessive quantifier body to Tier 1 or a clean rejection — shared by emit_repeat (X*+/X++/X?+/X{n,m}+, (a)*+-style single-captured-atom bodies) and emit_atomic_group's (?>X*)-style desugaring. | |
| constexpr void | emit_atomic_group (dynamic_program &prog, const ast_node &node, bool capture_free) const |
Emits an atomic group (?>...). | |
Static Private Member Functions | |
| static constexpr std::int32_t | here (const dynamic_program &prog) |
| Returns the index of the next instruction. | |
| static constexpr void | emit (dynamic_program &prog, instr instruction) |
| Appends one instruction, enforcing the program-size cap. | |
| static constexpr std::int32_t | emit_split (dynamic_program &prog) |
Emits a split with placeholder targets. | |
| static constexpr std::int32_t | emit_jump (dynamic_program &prog) |
Emits a jump with a placeholder target. | |
| static constexpr void | patch_primary (dynamic_program &prog, std::int32_t pc, std::int32_t target) |
Sets the primary branch target of the instruction at pc. | |
| static constexpr void | patch_secondary (dynamic_program &prog, std::int32_t pc, std::int32_t target) |
Sets the secondary branch target of the split at pc. | |
| static constexpr std::uint16_t | intern_class (dynamic_program &prog, const char_class &klass) |
Interns klass into prog.classes (deduplicating), returning its index. | |
| static constexpr void | emit_klass (dynamic_program &prog, const char_class &klass) |
Emits a klass instruction, interning klass through intern_class. | |
| static constexpr std::uint16_t | intern_cp_class (dynamic_program &prog, const class_def &cd) |
Interns cd into prog.cp_classes/prog.cp_ranges (deduplicating), returning its index. | |
| static constexpr void | emit_klass_cp (dynamic_program &prog, const class_def &cd) |
Emits a match-time code-point predicate for a Unicode shorthand (\w \d \s and their negations) in text mode: a klass_cp over the interned code-point class, followed by a three-instruction continuation chain (klass utf8_cont ×3). At match time klass_cp decodes one code point and, on membership, enters the chain at a computed skip so the remaining continuation bytes are walked one per step — see pike.hpp. The class is the already-effective set (the fold and any external negation were materialised by effective_class), so membership is a plain positive test. | |
| static constexpr bool | try_emit_fixed_width_class (dynamic_program &prog, const class_def &eff, bool probe_only=false) |
| Emits a small non-ASCII class as FIXED-WIDTH bytes, when every member encodes to the same length and they differ in exactly one byte position. Returns false otherwise. | |
Private Attributes | |
| std::array< std::int32_t, fold_cache_ways > | fold_key_ {-1, -1, -1, -1} |
| Cache tag per way: the (class index, fold mode, negated) key held there, or -1 for empty. | |
| std::array< class_def, fold_cache_ways > | fold_val_ {} |
| Cached FINISHED class per way – folded, coalesced and negated. Default-constructed, so an unused way holds an empty class_def and costs no allocation. | |
| const ast & | tree_ |
| The AST being compiled. | |
| flags | flags_ {flags::none} |
| Effective compilation flags. | |
Static Private Attributes | |
| static constexpr std::size_t | fold_cache_ways {4} |
| Ways in the case-fold cache effective_class keeps. Four is enough for a repeat to hit its own way every time; a pattern alternating more distinct folded classes than this simply misses, which is what every pattern did before. | |
| static constexpr std::size_t | fixed_width_class_max_members {8} |
| Most members a try_emit_fixed_width_class candidate may have. | |
Compiles an ast into a dynamic_program (NFA bytecode).
|
inlineconstexpr |
Binds the compiler to a parsed pattern and its flags.
| [in] | tree | The AST to compile (borrowed, must outlive the compiler). |
| [in] | compile_flags | The effective compilation flags. |
|
inlineconstexprprivate |
Maps an AST anchor_kind to the runtime assert_kind.
^ and $ depend on the multiline flag; everything else maps one-to-one.
| [in] | anchor | The AST anchor kind. |
| [in] | node_flags | The flag set in force at this anchor's scope; its multiline selects the line-relative vs absolute form of ^/$ (a scoped (?m:...)). |
|
inlineconstexpr |
Emits the full NFA program for the bound AST.
| real::regex_error | if the program exceeds max_program_size. |
|
inlineconstexprprivate |
The class a node_kind::klass node effectively accepts, after negation, icase folding and the bytes/code-point split. This is the ONE source of truth consumed by both emit_node and l_max_bytes, so what is emitted and its measured width can never disagree. Positive: as written. Negated: the ASCII complement plus, in code-point mode, the code-point complement over [U+0080, U+10FFFF] minus surrogates.
| [in] | node | The node_kind::klass node. |
|
inlinestaticconstexprprivate |
Appends one instruction, enforcing the program-size cap.
The check lives inside emit so it fires during a large unroll loop, before the vector grows to the full bad size — this is the central defense (max_program_size) against the DoS where tiny nested bounded quantifiers expand to hundreds of millions of instructions. It is constexpr-friendly: exceeding the cap fails compilation for a static_regex, or throws at run time.
| [in,out] | prog | The program being built. |
| [in] | instruction | The instruction to append. |
| real::regex_error | when max_program_size would be exceeded. |
|
inlineconstexprprivate |
Emits an alternation: branches chained with leftmost-preferred splits.
Every branch but the last jumps to a shared exit, patched once at the end.
| [in,out] | prog | The program being built. |
| [in] | node | The node_kind::alternation node. |
| [in] | capture_free | Propagated to each branch (see emit_node). |
|
inlineconstexprprivate |
Emits "one codepoint matching \p ascii, or any non-ASCII codepoint".
The non-ASCII branches go through emit_class_codepoints for the code-point range [U+0080, U+10FFFF] — the SAME canonical-splitting algorithm (utf8_range_sequences, RE2-style) already used for \p{...}, so the emitted branches narrow each lead byte's first continuation byte to the sub-range that excludes overlong and surrogate encodings (0xE0, 0xED, 0xF0, 0xF4 each get their own branch; every other lead byte keeps the generic [0x80,0xBF] continuation, same as before). This replaces a hand-written 4-branch/16- instruction block with flat lead-byte classes (charclass.hpp's utf8_lead2/3/4_set / utf8_cont_set) that DIDN'T narrow the first continuation byte — a since-fixed correctness gap (overlong/surrogate byte sequences read as one valid codepoint).
| [in,out] | prog | The program being built. |
| [in] | ascii | The accepted ASCII bytes (the non-ASCII branches are always included). |
|
inlineconstexprprivate |
Emits an atomic group (?>...).
Three shapes, in order:
repeat over a Tier-1-eligible body (a bare atom, or one wrapped in exactly one capturing group) — (?>X*), (?>(a)+), … — "upgraded" to possessive regardless of that inner repeat's own flag, detected BEFORE any bounded-width check, so (?>[^"]*)/(?>\d+) compile (a measured detection-order requirement). This is a REPEATED construct, so it is subject to the same Tier-1-only restriction (and the same lookaround rejection) as emit_possessive_repeat.(?>ab), (?>), any fixed/compound-but-split-free body: nothing to give back regardless of how compound the body is (it never loops), so this is unconditionally safe compiled inline via ordinary emit_node — zero new opcodes touched, safe even inside a lookaround.(?>ab|a)): an outer give-back could still backtrack INTO the alternation's own choice via its split even with no repeat wrapping it, so inline compilation would be a silent correctness bug, not just a missed optimization — the clean rejection emit_possessive_repeat documents.| [in,out] | prog | The program being built. |
| [in] | node | The node_kind::group node (possessive == true). |
| [in] | capture_free | Propagated to the body. |
|
inlineconstexprprivate |
Emits an alternation of byte-range sequences (branches) as split/jump. Each branch is a chain of klass steps; the leftmost matching branch wins. The shared backbone of emit_class_codepoints (used for both \p{...}-style specific code-point ranges and, via emit_any_codepoint_class, the single [U+0080, U+10FFFF] "any non-ASCII" range ./negated-ASCII-only classes need).
| [in,out] | prog | The program being built. |
| [in] | branches | One byte-range chain per branch, tried in order. |
|
inlineconstexprprivate |
Emits a code-point class: the ASCII bitmap (one byte, if any) OR the canonical UTF-8 byte sequences of each code-point range. emit_any_codepoint_class is a thin wrapper over this for the specific [U+0080, U+10FFFF] "any non-ASCII" range.
| [in,out] | prog | The program being built. |
| [in] | ascii | The class's ASCII bitmap; skipped when empty. |
| [in] | ranges | Its non-ASCII code-point ranges. |
|
inlineconstexprprivate |
Emits an already-materialised class the one way this compiler emits classes.
Extracted so the alternation fusion cannot diverge from a class written by hand: (?:é|à|è) and [éàè] are the same language and must become the same program. Emitting the fused set directly as a code-point class instead measured FASTER on the bare form, precisely because it took a different route – a licence for one spelling to compile differently from the other, not a reason, and refused for that.
That observation was then chased on its own terms and REFUTED, so it is not retried: the two supported ISAs DISAGREE about which emission a STANDALONE non-ASCII class wants – one prefers the code-point class, the other the fixed-width form, on the same patterns and by comparable margins. No single choice wins on both, which removes the argument for choosing at all.
Where a literal PRECEDES the class they agree emphatically, and that is the case the fixed-width form was introduced for and the one it keeps.
| [in,out] | prog | The program being built. |
| [in] | eff | The effective class (ASCII bitmap + non-ASCII ranges). |
|
inlinestaticconstexprprivate |
Emits a jump with a placeholder target.
| [in,out] | prog | The program being built. |
|
inlinestaticconstexprprivate |
Emits a klass instruction, interning klass through intern_class.
| [in,out] | prog | The program being built. |
| [in] | klass | The class bitmap to match. |
| real::regex_error | if more than 65536 distinct classes are needed. |
|
inlinestaticconstexprprivate |
Emits a match-time code-point predicate for a Unicode shorthand (\w \d \s and their negations) in text mode: a klass_cp over the interned code-point class, followed by a three-instruction continuation chain (klass utf8_cont ×3). At match time klass_cp decodes one code point and, on membership, enters the chain at a computed skip so the remaining continuation bytes are walked one per step — see pike.hpp. The class is the already-effective set (the fold and any external negation were materialised by effective_class), so membership is a plain positive test.
| [in,out] | prog | The program being built. |
| [in] | cd | The effective code-point class (ASCII bitmap + non-ASCII ranges). |
|
inlineconstexprprivate |
Emits a bounded lookaround: an assert_lookaround whose sub-program is a capture-free region the main flow jumps over.
Layout: assert_lookaround sub_id; jump AFTER; [sub-program] match; AFTER: …. The main VM only steps the assert_lookaround (epsilon) and the skip-jump; the sub region is entered solely by the sub-VM at code_offset. The sub-pattern must be bounded (L_max in bytes ≤ max_lookaround_length) — the linear-time guarantee.
| [in,out] | prog | The program being built. |
| [in] | node | The node_kind::lookaround node. |
| [in] | capture_free | True only when already inside a lookaround (rejected). |
| real::regex_error | on an unbounded or over-long sub-pattern, or nesting. |
|
inlineconstexprprivate |
Emits the bytecode for the AST node at index (recursively).
| [in,out] | prog | The program being built. |
| [in] | index | Index of the node in ast::nodes. |
| [in] | capture_free | When true, capturing groups emit no save ops — used inside a lookaround sub-program, whose captures do not participate in the overall match. |
|
inlineconstexprprivate |
Dispatches a possessive quantifier body to Tier 1 or a clean rejection — shared by emit_repeat (X*+/X++/X?+/X{n,m}+, (a)*+-style single-captured-atom bodies) and emit_atomic_group's (?>X*)-style desugaring.
A general "Tier 1.5" for arbitrary compound deterministic bodies ((?:ab)*+, (?:X++)*+) is OUT OF SCOPE against a verified VM-architecture wall, not an assumed one: basic_thread_list (pike.hpp) stores one uniform position per round for its whole thread list — no per-thread position. A possessive loop's "give up, exit" transition for a compound body would need to be offered ONLY once the body's own internal attempt has DEFINITIVELY failed, at whatever round that happens to be — but a Pike-VM thread that fails simply dies silently; it cannot redirect to an external exit target from wherever inside the body it died, unless every leaf-emitting instruction in the compiler (byte/ klass/klass_cp/save/assert_position/assert_lookaround) carries that redirect — real new infrastructure, not "new opcodes only." A bare atom (or one wrapped in exactly one capturing group) sidesteps this entirely because it fails ONLY within its own single dispatch — the opcode IS its own fail-redirect, with nothing to propagate. Deferred to a future design (bounded compound bodies may fit the same priority-kill sub-VM approach lookaround already uses; unbounded compound bodies will likely stay rejected, the same boundary as lookbehind's own unbounded rejection).
| [in,out] | prog | The program being built. |
| [in] | body | Index of the quantified body. |
| [in] | min | Minimum repetition count. |
| [in] | max | Maximum repetition count (-1 = unbounded). |
| [in] | capture_free | Whether captures are suppressed here (inside a lookaround). |
| real::regex_error | when body is not Tier 1 eligible, or capture_free is true (a possessive/atomic construct inside a lookaround) — the lookaround sub-VM's own dispatch (pike.hpp's lookahead_matches/sub_fullmatch_window/ sub_add_thread) hard-assumes only byte/klass/klass_cp ever appear in a sub-region; klass_cp_loop_possessive there would silently read the WRONG class table (classes instead of cp_classes, since in.arg16 means something different in each) — a latent corruption, not just a missed optimization, so this is a hard compile-time reject rather than an attempt to thread the new opcodes through three separate hand-written dispatchers as well. |
|
inlineconstexprprivate |
Emits a quantifier (Thompson construction).
Greedy prefers split.primary_target (enter the body); lazy swaps the branches. Counted forms unroll: min mandatory copies, then either a loop (max == -1) or optional copies sharing one exit.
| [in,out] | prog | The program being built. |
| [in] | node | The node_kind::repeat node. |
| [in] | capture_free | Propagated to the body copies (see emit_node). |
|
inlinestaticconstexprprivate |
Emits a split with placeholder targets.
| [in,out] | prog | The program being built. |
|
inlineconstexprprivate |
Emits a Tier 1 atom-test instruction (byte_loop_possessive/klass_loop_ possessive/klass_cp_loop_possessive), with a placeholder secondary_target (the on-no-match exit — patch before use) and primary_target set to capture_start_slot (-1 for uncaptured, else the capture group's start slot; the end slot is always start+1).
On a match, the opcode itself (pike.hpp's step()) writes BOTH capture slots directly, using the position before the test (start) and after it (end) — rather than a separate save emitted BEFORE the test, which would have to fire speculatively before knowing the test succeeds. A possessive loop always attempts one more repetition after every success, so a plain leading save would overwrite a PRIOR successful iteration's start the moment the NEXT (ultimately failing) attempt began — corrupting the capture with a torn [new-but-failed-start, old-end) pair. Writing both slots atomically with the consume, only on confirmed success, avoids that. klass_cp_loop_possessive still emits the ordinary 3-slot UTF-8 continuation chain right after itself (identical layout to emit_klass_cp) — the membership decision is already fully made at the first byte; the chain is the architecture's mandatory one-byte-per-round validation of the remaining bytes either way, and the capture write happens once, at the first byte's dispatch.
| [in,out] | prog | The program being built. |
| [in] | atom | Index of the single-atom AST node (byte/klass/any). |
| [in] | capture_start_slot | The capture group's start slot, or -1 for none. |
secondary_target needs patching).
|
inlineconstexprprivate |
Emits a Tier 1 possessive loop over a single atom, optionally wrapped in one capturing group.
Mandatory copies (up to min) are ordinary, unconditional emission — identical to how a bare atom, or a capturing group wrapping one, already compiles (via emit_node): failure there needs no exit path, since min is required and the thread simply dies, exactly like any plain consuming instruction. The optional tail is either a genuine self-loop (max == -1: jump back to the tail's own start on a match) or a chain of unrolled optional copies (bounded: natural pc+1 fallthrough chains them, no jump needed) — each copy is one emit_tier1_atom_test, whose secondary_target (on no match) is collected and patched to the construct's shared exit once everything is emitted, matching the pattern emit_alternation already uses for its own forward jump targets.
| [in,out] | prog | The program being built. |
| [in] | atom | Index of the single-atom body (byte, klass, or any). |
| [in] | min | Minimum repetition count. |
| [in] | max | Maximum repetition count (-1 = unbounded). |
| [in] | capture_group | Capture group number to wrap the loop in, or -1 for none. |
| [in] | capture_free | Whether captures are suppressed here (inside a lookaround). |
|
inlineconstexprprivate |
Emits an UNBOUNDED quantifier's body, promoting a bare literal byte to a one-member byte class so the shape routes can see it.
A bare a+ compiles its body to a byte op, and the class-loop recognizer matches on klass only — so without this promotion a quantifier over a single character reaches no fast route at all, while the semantically identical [a]+ does, and the two spellings differ by more than an order of magnitude. A byte IS a one-member class; nothing but the opcode is in the way.
Only for max == -1 (+, *, {n,}), which is exactly what the class loop serves. A bounded form like a{3} keeps its bytes: those copies are a fixed literal run, and the literal routes that read them would not recognise three classes.
| [in,out] | prog | The program being built. |
| [in] | child | The quantifier's body node. |
| [in] | capture_free | Propagated to emit_node. |
|
inlineconstexprprivate |
Applies negation (and its mode-dependent complement) to an already-folded class.
Split out of effective_class so the fold can be memoized while this stays per node: negation is a property of the node, the fold a property of (class, scope).
| [in] | node | The class node being emitted. |
| [in] | folded | Its class after any case fold. |
|
inlineconstexprprivate |
The class an alternation of single ATOMS is, when it is one.
(?:a|b|c) is [abc] written the long way, and (?:é|à|è) is [éàè] — the split chain matches no shape recognizer while the class does. EXACT rather than approximate: every branch consumes exactly one atom and none captures, so leftmost-first preference among them has no observable effect, the span being the same whichever branch a backtracker would have picked.
Asked from BOTH emission sites — emit_alternation and emit_unbounded_body — because the right class emission differs between them (the quantifier body wants a code-point class, the bare form wants whatever a hand-written class gets), while the QUESTION is the same one. Two places restating it is the drift this file has paid for before.
| [in] | node | The alternation node. |
| [out] | out | The fused class, valid only when this returns true. |
true if every branch is one atom and there are at least two of them.
|
inlinestaticconstexprprivate |
Returns the index of the next instruction.
| [in] | prog | The program. |
|
inlinestaticconstexpr |
True when the FULL inner literal starts at pc as consecutive byte ops.
Both hint anchors below used to ask only whether code[pc] equalled inner_literal[0], and a byte equal to the literal's FIRST byte is not the literal. (?:a){2}ax has the literal ax and two a bytes in front of it, so the prefix's own a was taken for the literal's start: the derived hint then described the wrong distance and the route walked back to the wrong place.
The result was a SILENT FALSE NEGATIVE wherever the route actually runs — static_regex at any subject size, and a dynamic regex past the size floor. It is invisible on a short dynamic subject, because the small-haystack guard abandons to the core VM before the bad hint is used, which is why a probe on short subjects reports the pattern as fine.
| [in] | prog | The program being built. |
| [in] | pc | Index of the candidate first byte op. |
|
inlinestaticconstexprprivate |
Interns klass into prog.classes (deduplicating), returning its index.
Factored out of emit_klass so Tier 1's klass_loop_possessive can share the exact same interning without emitting the ordinary single-consume opcode. Identical bitmaps share one slot, so the UTF-8 continuation class is stored once however often it is emitted.
| [in,out] | prog | The program being built. |
| [in] | klass | The class bitmap to intern. |
prog.classes. | real::regex_error | if more than 65536 distinct classes are needed. |
|
inlinestaticconstexprprivate |
Interns cd into prog.cp_classes/prog.cp_ranges (deduplicating), returning its index.
Factored out of emit_klass_cp so Tier 1's klass_cp_loop_possessive can share the exact same interning for ANY effective class (predicate or not, negated or not, . included) without emitting the ordinary opcode or its continuation chain.
| [in,out] | prog | The program being built. |
| [in] | cd | The effective code-point class (ASCII bitmap + non-ASCII ranges). |
prog.cp_classes.
|
inlineconstexprprivate |
Would compiling the sub-AST at index ever emit a split opcode?
Used ONLY by emit_atomic_group's no-outer-repeat shape: a ONE-SHOT atomic group ((?>ab), (?>), any fixed/deterministic body with no repetition at all) has NOTHING to give back regardless of how compound its body is, so compiling it inline via ordinary emit_node is unconditionally safe — zero new opcodes touched, zero VM risk. This is NOT the same question as Tier 1.5's (a REPEATED compound body, which emit_possessive_repeat's own note explains is genuinely unsafe in this VM regardless of determinism) — a one-shot atomic group never loops, so there is no exit-thread/silent- death concern to sidestep in the first place.
Mirrors the ACTUAL compiled shape of each node kind, not an approximation:
alternation: always emits split — a genuine choice an outer give-back could still backtrack into, so false here correctly routes to a real rejection, not silent miscompilation.repeat, non-possessive: emits split UNLESS it is an exact bounded count (min == max, max != -1) — emit_repeat's own "optional copies" loop runs zero times then.group: an atomic group (possessive == true) is always opaque-deterministic from the outer view — it either compiles deterministically or the compiler rejects it outright, so it never leaks a split. An ordinary group is transparent.lookaround: zero-width from the outer view; any split inside its own sub-pattern is isolated in a separate, bounded sub-VM region, never part of the outer flow.| [in] | index | Index of the sub-AST node. |
true if compiling index introduces no split reachable from the outer flow.
|
inlineconstexprprivate |
Is index a bare, unwrapped single atom (a literal byte, a character class, or .)?
| [in] | index | Index of the sub-AST node. |
true if index is byte, klass, or any.
|
inlineconstexprprivate |
Tier 1 eligibility: is index a bare single atom, or an ordinary (non-atomic) capturing group wrapping exactly one (X*+, (a)*+, (?>X*), …)?
The dominant real-world shape — the loop carries its own failure locally, within ONE opcode dispatch (see emit_possessive_repeat's note on why this is what stays VM-integration-safe when a general compound body does not).
| [in] | index | Index of the sub-AST node. |
true if index is Tier 1 eligible.
|
inlineconstexprprivate |
Upper bound, in bytes, on what the sub-AST at index can consume; -1 if unbounded (a *, + or {n,} repeat) or if it nests a lookaround.
Codepoint-consuming shapes (., a negated class outside bytes mode) count as one codepoint = up to 4 bytes (A1); a literal byte or an ASCII class is one byte.
| [in] | index | Index of the sub-AST node. |
|
inlinestaticconstexprprivate |
Sets the primary branch target of the instruction at pc.
| [in,out] | prog | The program being built. |
| [in] | pc | Index of the split/jump to patch. |
| [in] | target | Instruction index to branch to. |
|
inlinestaticconstexprprivate |
Sets the secondary branch target of the split at pc.
| [in,out] | prog | The program being built. |
| [in] | pc | Index of the split to patch. |
| [in] | target | Instruction index to branch to. |
|
inlineconstexprprivate |
The bare atom Tier 1 should test: index itself, or its single captured child when index is a capturing-group wrapper. is_tier1_body must hold.
| [in] | index | The loop body's node index. |
|
inlineconstexprprivate |
The capture group number Tier 1 should wrap the loop in, or -1 for none. is_tier1_body must hold.
| [in] | index | The loop body's node index. |
|
inlinestaticconstexprprivate |
Emits a small non-ASCII class as FIXED-WIDTH bytes, when every member encodes to the same length and they differ in exactly one byte position. Returns false otherwise.
This is the shape an icase fold makes of an accented Latin letter: é/É are C3 A9 and C3 89 — two bytes, one shared lead, one differing continuation — so the class is really byte C3 followed by a two-member BYTE class, which is fixed width. Sent through emit_klass_cp instead it becomes variable width, and that is what stops the prefilter's fixed-offset walk and the literal routes behind it — the difference between a routed scan and the general VM on accented prose.
DELIBERATELY NARROW, because the neighbouring wide shape is already known to be a trap: an icase ASCII class like (?i)[a-z] gains the long s and the Kelvin sign, whose encodings are 2 and 3 bytes, so expressing it byte-wise needs an ALTERNATION — which is slower than the klass_cp it would replace (see the emission site's note), and slower again than the class form here. Requiring one common length AND a single varying position is exactly what excludes every alternation-shaped case: no branch is ever emitted, only a run of byte with one klass among them.
| [in,out] | prog | The program being built. |
| [in] | eff | The effective class (ASCII bitmap + non-ASCII ranges). |
| [in] | probe_only | When true, answers whether the shape matches and emits nothing. emit_unbounded_body needs the ANSWER without the emission – under an unbounded quantifier this form is the wrong one, and asking here rather than restating the condition keeps one source of truth for the shape. |
true if the class was emitted here (or matches, when probing); false if the caller must emit it otherwise.
|
mutableprivate |
Cache tag per way: the (class index, fold mode, negated) key held there, or -1 for empty.
`mutable` because the emit path reaches \ref effective_class through const member functions, and WRITTEN ONLY outside constant evaluation. MSVC's constant evaluator has rejected this header before over an indeterminate subobject, and a `static_regex` gains nothing from the cache anyway: its budget problem is the fold's step count, not its repetition.