Module regex/parser

regex/parser
Stability: unstable, and deliberately not public. Its output type (`RegexNode`) is internal and its input grammar is still growing — every construct added (inline `(?i)` flag groups, `\Q...\E`, atomic groups, named-class shorthands) changes both `NodeKind` and `RegexError`. `RegexError` is the one piece that escapes, and its own `## Stability` section explains why an exhaustively-matched error enum cannot be frozen while the grammar moves. — stable modules only change additively; this one may still change.

Regex pattern parser — parses a regex pattern string into an AST of RegexNode objects. Uses an iterative stack-based approach.

Internal to std/regex. Nothing here is public API: import std/regex and use Regex. This module's shape changes freely — the one piece of it users see is RegexError, which std/regex re-exports.

Why iterative

Nesting in a pattern is unbounded (((((a))))), and a recursive-descent parser turns that into unbounded native stack. parse keeps an explicit ArrayList(ParseFrame) instead: ( pushes the enclosing frame and starts a fresh one, ) pops and folds the finished group into its parent's current sequence. So pattern depth costs heap, not stack, and a deeply nested pattern from untrusted input cannot overflow.

Alternation is handled by the same frames rather than by precedence: | closes the current sequence into the frame's alternatives list and starts a new one, and the frame is folded into right-nested binary RegexNode.alternation nodes when it closes. That is why a|b|c becomes alternation(a, alternation(b, c)) and not a three-child node.

Positions are BYTE offsets into the pattern throughout — _pos indexes _bytes — and every RegexError reports one. Bytes outside ASCII are decoded as runes only where the grammar cares (literals, class members); the structural scan is byte-wise, which is safe because every metacharacter is ASCII and UTF-8 is self-synchronising.

Stability

unstable, and deliberately not public. Its output type (RegexNode) is internal and its input grammar is still growing — every construct added (inline (?i) flag groups, \Q...\E, atomic groups, named-class shorthands) changes both NodeKind and RegexError. RegexError is the one piece that escapes, and its own ## Stability section explains why an exhaustively-matched error enum cannot be frozen while the grammar moves.

Types

RegexParser object
RegexParser

A parser over one pattern: the byte cursor plus the capture-numbering state that outlives the parse.

SINGLE USE — parse consumes the cursor to the end and the group counters are cumulative, so a second parse on the same instance would return an empty pattern with the previous run's group table. Build one per pattern, call parse, then read group_count() / group_names() off it.

Fields

NameTypeDescription
_sourceString
_bytesArrayList(u8)
_posusize
_group_countusize
_group_namesArrayList(GroupNameEntry)
impl(RegexParser, ...)
new : (RegexParser) fn(pattern : String) -> RegexParser

A parser positioned at the start of pattern, with no groups seen yet.

Parameters

NameTypeNotes
patternString

Returns: RegexParser

_peek : (RegexParser) fn(self : RegexParser) -> Option(u8)

Parameters

NameTypeNotes
selfRegexParser

Returns: Option(u8)

_advance : (RegexParser) fn(self : RegexParser) -> Option(u8)

Parameters

NameTypeNotes
selfRegexParser

Returns: Option(u8)

_read_codepoint : (RegexParser) fn(self : RegexParser, first : u8) -> u32

Parameters

NameTypeNotes
selfRegexParser
firstu8

Returns: u32

_at_end : (RegexParser) fn(self : RegexParser) -> bool

Parameters

NameTypeNotes
selfRegexParser

Returns: bool

group_count : (RegexParser) fn(self : RegexParser) -> usize

How many capturing groups the pattern declared, not counting group 0.

Only meaningful AFTER parse — it counts groups as they are opened, so reading it early reports a partial count. compile uses it to size the VM's capture array.

Parameters

NameTypeNotes
selfRegexParser

Returns: usize

group_names : (RegexParser) fn(self : RegexParser) -> ArrayList(GroupNameEntry)

The (?<name>...) declarations, in the order they were opened. Valid after parse, for the same reason as group_count.

Returns the parser's own list, not a copy — it travels into the compiled program and out to RegexMatch.named_group, and nobody mutates it after the parse.

Parameters

NameTypeNotes
selfRegexParser

Returns: ArrayList(GroupNameEntry)

_lookup_group_name : (RegexParser) fn(self : RegexParser, name : String) -> Option(usize)

Parameters

NameTypeNotes
selfRegexParser
nameString

Returns: Option(usize)

_escape_char_codepoint : (RegexParser) fn(self : RegexParser, ch : u8) -> u32

Parameters

NameTypeNotes
selfRegexParser
chu8

Returns: u32

_parse_number : (RegexParser) fn(self : RegexParser) -> Option(usize)

Parameters

NameTypeNotes
selfRegexParser

Returns: Option(usize)

_parse_greedy_modifier : (RegexParser) fn(self : RegexParser) -> bool

Parameters

NameTypeNotes
selfRegexParser

Returns: bool

_make_digit_ranges : (RegexParser) fn(self : RegexParser) -> ArrayList(CharRange)

Parameters

NameTypeNotes
selfRegexParser

Returns: ArrayList(CharRange)

_make_word_ranges : (RegexParser) fn(self : RegexParser) -> ArrayList(CharRange)

Parameters

NameTypeNotes
selfRegexParser

Returns: ArrayList(CharRange)

_make_space_ranges : (RegexParser) fn(self : RegexParser) -> ArrayList(CharRange)

Parameters

NameTypeNotes
selfRegexParser

Returns: ArrayList(CharRange)

_parse_hex_byte : (RegexParser) fn(self : RegexParser) -> Option(u32)

Parameters

NameTypeNotes
selfRegexParser

Returns: Option(u32)

_parse_class_unwind : (RegexParser) fn(self : RegexParser) -> Result(ArrayList(CharRange), RegexError)

Parameters

NameTypeNotes
selfRegexParser

Returns: Result(ArrayList(CharRange), RegexError)

_try_parse_char_range : (RegexParser) fn(self : RegexParser, ranges : ArrayList(CharRange), low : u32) -> unit

Parameters

NameTypeNotes
selfRegexParser
rangesArrayList(CharRange)
lowu32

Returns: unit

_parse_char_class_content : (RegexParser) fn(self : RegexParser, ranges : ArrayList(CharRange)) -> Result(unit, RegexError)

Parameters

NameTypeNotes
selfRegexParser
rangesArrayList(CharRange)

Returns: Result(unit, RegexError)

_parse_char_class : (RegexParser) fn(self : RegexParser) -> Result(RegexNode, RegexError)

Parameters

NameTypeNotes
selfRegexParser

Returns: Result(RegexNode, RegexError)

_parse_unicode_property : (RegexParser) fn(self : RegexParser, negated : bool) -> Result(RegexNode, RegexError)

Parameters

NameTypeNotes
selfRegexParser
negatedbool

Returns: Result(RegexNode, RegexError)

_parse_unwind : (RegexParser) fn(self : RegexParser) -> Result(RegexNode, RegexError)

Parameters

NameTypeNotes
selfRegexParser

Returns: Result(RegexNode, RegexError)

_parse_counted_quantifier : (RegexParser) fn(self : RegexParser, atom : RegexNode) -> Result(RegexNode, RegexError)

Parameters

NameTypeNotes
selfRegexParser
atomRegexNode

Returns: Result(RegexNode, RegexError)

_parse_atom : (RegexParser) fn(self : RegexParser) -> Result(RegexNode, RegexError)

Parameters

NameTypeNotes
selfRegexParser

Returns: Result(RegexNode, RegexError)

_maybe_quantify : (RegexParser) fn(self : RegexParser, a : RegexNode) -> Result(RegexNode, RegexError)

Parameters

NameTypeNotes
selfRegexParser
aRegexNode

Returns: Result(RegexNode, RegexError)

_parse_quantified : (RegexParser) fn(self : RegexParser) -> Result(RegexNode, RegexError)

Parameters

NameTypeNotes
selfRegexParser

Returns: Result(RegexNode, RegexError)

impl(RegexParser, ...)
_make_sequence : (RegexParser) fn(self : RegexParser, nodes : ArrayList(<struct:struct_decl_1053484_file____home_runner_work_Yo_Yo_std_regex_node_yo>)) -> RegexNode

Parameters

NameTypeNotes
selfRegexParser
nodesArrayList(<struct:struct_decl_1053484_file____home_runner_work_Yo_Yo_std_regex_node_yo>)

Returns: RegexNode

_finalize_frame : (RegexParser) fn(self : RegexParser, alts : ArrayList(ArrayList(<struct:struct_decl_1053484_file____home_runner_work_Yo_Yo_std_regex_node_yo>)), seq : ArrayList(<struct:struct_decl_1053484_file____home_runner_work_Yo_Yo_std_regex_node_yo>)) -> RegexNode

Parameters

NameTypeNotes
selfRegexParser
altsArrayList(ArrayList(<struct:struct_decl_1053484_file____home_runner_work_Yo_Yo_std_regex_node_yo>))
seqArrayList(<struct:struct_decl_1053484_file____home_runner_work_Yo_Yo_std_regex_node_yo>)

Returns: RegexNode

parse : (RegexParser) fn(self : RegexParser) -> Result(RegexNode, RegexError)

Parse the whole pattern into an AST, or report the first fault.

One left-to-right pass with the explicit frame stack described in the module header — no backtracking and no second pass, so the FIRST error wins and later faults in the same pattern are never seen. Errors carry the byte offset the parser had reached, which is not always where the construct began (a ( opened at 0 and never closed reports the end of the pattern).

Backreferences are validated here rather than at compile time: \1 past group_count() is InvalidBackreference and \k<name> with no such name is UnknownGroupName, so the compiler and VM can assume every Backreference node names a real group.

An empty pattern is legal and parses to an empty Sequence, which matches the empty string at every position.

Parameters

NameTypeNotes
selfRegexParser

Returns: Result(RegexNode, RegexError)