REAL
Regular Expression Algorithmic Library — constexpr C++20 regex
Loading...
Searching...
No Matches
real::detail::compiler Class Reference

Compiles an ast into a dynamic_program (NFA bytecode). More...

#include <compiler.hpp>

Collaboration diagram for real::detail::compiler:
[legend]

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_waysfold_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_waysfold_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 asttree_
 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.
 

Detailed Description

Compiles an ast into a dynamic_program (NFA bytecode).

Constructor & Destructor Documentation

◆ compiler()

constexpr real::detail::compiler::compiler ( const ast tree,
flags  compile_flags 
)
inlineconstexpr

Binds the compiler to a parsed pattern and its flags.

Parameters
[in]treeThe AST to compile (borrowed, must outlive the compiler).
[in]compile_flagsThe effective compilation flags.

Member Function Documentation

◆ assert_kind_for()

constexpr assert_kind real::detail::compiler::assert_kind_for ( anchor_kind  anchor,
flags  node_flags 
) const
inlineconstexprprivate

Maps an AST anchor_kind to the runtime assert_kind.

^ and $ depend on the multiline flag; everything else maps one-to-one.

Parameters
[in]anchorThe AST anchor kind.
[in]node_flagsThe flag set in force at this anchor's scope; its multiline selects the line-relative vs absolute form of ^/$ (a scoped (?m:...)).
Returns
The assertion the engine should evaluate.

◆ compile()

constexpr dynamic_program real::detail::compiler::compile ( )
inlineconstexpr

Emits the full NFA program for the bound AST.

Returns
The compiled dynamic_program (code, classes, names, hints).
Exceptions
real::regex_errorif the program exceeds max_program_size.

◆ effective_class()

constexpr class_def real::detail::compiler::effective_class ( const ast_node node) const
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.

Parameters
[in]nodeThe node_kind::klass node.
Returns
The set it accepts, after negation, folding and the bytes/code-point split.

◆ emit()

static constexpr void real::detail::compiler::emit ( dynamic_program prog,
instr  instruction 
)
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.

Parameters
[in,out]progThe program being built.
[in]instructionThe instruction to append.
Exceptions
real::regex_errorwhen max_program_size would be exceeded.

◆ emit_alternation()

constexpr void real::detail::compiler::emit_alternation ( dynamic_program prog,
const ast_node node,
bool  capture_free 
) const
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.

Parameters
[in,out]progThe program being built.
[in]nodeThe node_kind::alternation node.
[in]capture_freePropagated to each branch (see emit_node).

◆ emit_any_codepoint_class()

constexpr void real::detail::compiler::emit_any_codepoint_class ( dynamic_program prog,
const char_class ascii 
) const
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).

Parameters
[in,out]progThe program being built.
[in]asciiThe accepted ASCII bytes (the non-ASCII branches are always included).

◆ emit_atomic_group()

constexpr void real::detail::compiler::emit_atomic_group ( dynamic_program prog,
const ast_node node,
bool  capture_free 
) const
inlineconstexprprivate

Emits an atomic group (?>...).

Three shapes, in order:

  1. The child is itself an ordinary 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.
  2. No outer repeat at all, and the body is deterministic (is_deterministic) — (?>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.
  3. Otherwise (a genuine choice with no outer repeat, e.g. (?>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.
Parameters
[in,out]progThe program being built.
[in]nodeThe node_kind::group node (possessive == true).
[in]capture_freePropagated to the body.

◆ emit_byte_sequences()

constexpr void real::detail::compiler::emit_byte_sequences ( dynamic_program prog,
const std::vector< std::vector< char_class > > &  branches 
) const
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).

Parameters
[in,out]progThe program being built.
[in]branchesOne byte-range chain per branch, tried in order.

◆ emit_class_codepoints()

constexpr void real::detail::compiler::emit_class_codepoints ( dynamic_program prog,
const char_class ascii,
const std::vector< code_range > &  ranges 
) const
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.

Parameters
[in,out]progThe program being built.
[in]asciiThe class's ASCII bitmap; skipped when empty.
[in]rangesIts non-ASCII code-point ranges.

◆ emit_effective_class()

constexpr void real::detail::compiler::emit_effective_class ( dynamic_program prog,
const class_def eff 
) const
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.

Parameters
[in,out]progThe program being built.
[in]effThe effective class (ASCII bitmap + non-ASCII ranges).

◆ emit_jump()

static constexpr std::int32_t real::detail::compiler::emit_jump ( dynamic_program prog)
inlinestaticconstexprprivate

Emits a jump with a placeholder target.

Parameters
[in,out]progThe program being built.
Returns
Its instruction index, for the caller to patch.

◆ emit_klass()

static constexpr void real::detail::compiler::emit_klass ( dynamic_program prog,
const char_class klass 
)
inlinestaticconstexprprivate

Emits a klass instruction, interning klass through intern_class.

Parameters
[in,out]progThe program being built.
[in]klassThe class bitmap to match.
Exceptions
real::regex_errorif more than 65536 distinct classes are needed.

◆ emit_klass_cp()

static constexpr void real::detail::compiler::emit_klass_cp ( dynamic_program prog,
const class_def cd 
)
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.

Parameters
[in,out]progThe program being built.
[in]cdThe effective code-point class (ASCII bitmap + non-ASCII ranges).

◆ emit_lookaround()

constexpr void real::detail::compiler::emit_lookaround ( dynamic_program prog,
const ast_node node,
bool  capture_free 
) const
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.

Parameters
[in,out]progThe program being built.
[in]nodeThe node_kind::lookaround node.
[in]capture_freeTrue only when already inside a lookaround (rejected).
Exceptions
real::regex_erroron an unbounded or over-long sub-pattern, or nesting.

◆ emit_node()

constexpr void real::detail::compiler::emit_node ( dynamic_program prog,
std::int32_t  index,
bool  capture_free = false 
) const
inlineconstexprprivate

Emits the bytecode for the AST node at index (recursively).

Parameters
[in,out]progThe program being built.
[in]indexIndex of the node in ast::nodes.
[in]capture_freeWhen true, capturing groups emit no save ops — used inside a lookaround sub-program, whose captures do not participate in the overall match.

◆ emit_possessive_repeat()

constexpr void real::detail::compiler::emit_possessive_repeat ( dynamic_program prog,
std::int32_t  body,
std::int32_t  min,
std::int32_t  max,
bool  capture_free 
) const
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).

Parameters
[in,out]progThe program being built.
[in]bodyIndex of the quantified body.
[in]minMinimum repetition count.
[in]maxMaximum repetition count (-1 = unbounded).
[in]capture_freeWhether captures are suppressed here (inside a lookaround).
Exceptions
real::regex_errorwhen 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.

◆ emit_repeat()

constexpr void real::detail::compiler::emit_repeat ( dynamic_program prog,
const ast_node node,
bool  capture_free 
) const
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.

Parameters
[in,out]progThe program being built.
[in]nodeThe node_kind::repeat node.
[in]capture_freePropagated to the body copies (see emit_node).

◆ emit_split()

static constexpr std::int32_t real::detail::compiler::emit_split ( dynamic_program prog)
inlinestaticconstexprprivate

Emits a split with placeholder targets.

Parameters
[in,out]progThe program being built.
Returns
Its instruction index, for the caller to patch.

◆ emit_tier1_atom_test()

constexpr std::int32_t real::detail::compiler::emit_tier1_atom_test ( dynamic_program prog,
std::int32_t  atom,
std::int32_t  capture_start_slot 
) const
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.

Parameters
[in,out]progThe program being built.
[in]atomIndex of the single-atom AST node (byte/klass/any).
[in]capture_start_slotThe capture group's start slot, or -1 for none.
Returns
The emitted TEST instruction's index (its secondary_target needs patching).

◆ emit_tier1_loop()

constexpr void real::detail::compiler::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
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.

Parameters
[in,out]progThe program being built.
[in]atomIndex of the single-atom body (byte, klass, or any).
[in]minMinimum repetition count.
[in]maxMaximum repetition count (-1 = unbounded).
[in]capture_groupCapture group number to wrap the loop in, or -1 for none.
[in]capture_freeWhether captures are suppressed here (inside a lookaround).

◆ emit_unbounded_body()

constexpr void real::detail::compiler::emit_unbounded_body ( dynamic_program prog,
std::int32_t  child,
bool  capture_free 
) const
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.

Parameters
[in,out]progThe program being built.
[in]childThe quantifier's body node.
[in]capture_freePropagated to emit_node.

◆ finish_class()

constexpr class_def real::detail::compiler::finish_class ( const ast_node node,
class_def  folded 
) const
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).

Parameters
[in]nodeThe class node being emitted.
[in]foldedIts class after any case fold.
Returns
The class as the node means it.

◆ fuse_single_atom_alternation()

constexpr bool real::detail::compiler::fuse_single_atom_alternation ( const ast_node node,
class_def out 
) const
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.

Parameters
[in]nodeThe alternation node.
[out]outThe fused class, valid only when this returns true.
Returns
true if every branch is one atom and there are at least two of them.

◆ here()

static constexpr std::int32_t real::detail::compiler::here ( const dynamic_program prog)
inlinestaticconstexprprivate

Returns the index of the next instruction.

Parameters
[in]progThe program.
Returns
The index of the next instruction.

◆ inner_literal_starts_at()

static constexpr bool real::detail::compiler::inner_literal_starts_at ( const dynamic_program prog,
std::size_t  pc 
)
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.

Parameters
[in]progThe program being built.
[in]pcIndex of the candidate first byte op.
Returns
Whether the whole literal, all pattern_hints::inner_literal_len bytes of it, is here.

◆ intern_class()

static constexpr std::uint16_t real::detail::compiler::intern_class ( dynamic_program prog,
const char_class klass 
)
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.

Parameters
[in,out]progThe program being built.
[in]klassThe class bitmap to intern.
Returns
Its index in prog.classes.
Exceptions
real::regex_errorif more than 65536 distinct classes are needed.

◆ intern_cp_class()

static constexpr std::uint16_t real::detail::compiler::intern_cp_class ( dynamic_program prog,
const class_def cd 
)
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.

Parameters
[in,out]progThe program being built.
[in]cdThe effective code-point class (ASCII bitmap + non-ASCII ranges).
Returns
Its index in prog.cp_classes.

◆ is_deterministic()

constexpr bool real::detail::compiler::is_deterministic ( std::int32_t  index) const
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.
Parameters
[in]indexIndex of the sub-AST node.
Returns
true if compiling index introduces no split reachable from the outer flow.

◆ is_single_atom()

constexpr bool real::detail::compiler::is_single_atom ( std::int32_t  index) const
inlineconstexprprivate

Is index a bare, unwrapped single atom (a literal byte, a character class, or .)?

Parameters
[in]indexIndex of the sub-AST node.
Returns
true if index is byte, klass, or any.

◆ is_tier1_body()

constexpr bool real::detail::compiler::is_tier1_body ( std::int32_t  index) const
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).

Parameters
[in]indexIndex of the sub-AST node.
Returns
true if index is Tier 1 eligible.

◆ l_max_bytes()

constexpr std::int32_t real::detail::compiler::l_max_bytes ( std::int32_t  index) const
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.

Parameters
[in]indexIndex of the sub-AST node.
Returns
The byte upper bound, or -1 when not statically bounded.

◆ patch_primary()

static constexpr void real::detail::compiler::patch_primary ( dynamic_program prog,
std::int32_t  pc,
std::int32_t  target 
)
inlinestaticconstexprprivate

Sets the primary branch target of the instruction at pc.

Parameters
[in,out]progThe program being built.
[in]pcIndex of the split/jump to patch.
[in]targetInstruction index to branch to.

◆ patch_secondary()

static constexpr void real::detail::compiler::patch_secondary ( dynamic_program prog,
std::int32_t  pc,
std::int32_t  target 
)
inlinestaticconstexprprivate

Sets the secondary branch target of the split at pc.

Parameters
[in,out]progThe program being built.
[in]pcIndex of the split to patch.
[in]targetInstruction index to branch to.

◆ tier1_atom()

constexpr std::int32_t real::detail::compiler::tier1_atom ( std::int32_t  index) const
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.

Parameters
[in]indexThe loop body's node index.
Returns
The bare atom's node index.

◆ tier1_capture_group()

constexpr std::int32_t real::detail::compiler::tier1_capture_group ( std::int32_t  index) const
inlineconstexprprivate

The capture group number Tier 1 should wrap the loop in, or -1 for none. is_tier1_body must hold.

Parameters
[in]indexThe loop body's node index.
Returns
The enveloping group's number, or -1 when the body is not wrapped in one.

◆ try_emit_fixed_width_class()

static constexpr bool real::detail::compiler::try_emit_fixed_width_class ( dynamic_program prog,
const class_def eff,
bool  probe_only = false 
)
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.

Parameters
[in,out]progThe program being built.
[in]effThe effective class (ASCII bitmap + non-ASCII ranges).
[in]probe_onlyWhen 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.
Returns
true if the class was emitted here (or matches, when probing); false if the caller must emit it otherwise.

Member Data Documentation

◆ fold_key_

std::array<std::int32_t, fold_cache_ways> real::detail::compiler::fold_key_ {-1, -1, -1, -1}
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. 

The documentation for this class was generated from the following file: