Docs / Developer / the regex engine
the regex engine
How bunzai actually RUNS a regex set. Source of truth: src/core/regex/ (pure, no side effects).
The entity it operates on is entities/regex.md; per-platform field/phase
truth lives in src/core/regex/capabilities.ts + platform-fields.ts (the position-picker law:
per-platform facts live in the matrix, never a UI literal).
Safety posture (absolute): a rule is DATA. Execution is a budgeted String.replace - nevereval, never new Function, never a payload. Rule output that looks like HTML renders only throughSealedHtmlPreview. Risu <cbs> flag tokens are stored verbatim and stripped before compiling.
The pipeline (apply.ts)
applyRules(text, rules, options) runs one deterministic pass in sortOrder:
- Every rule is compiled with the
dflag appended so match/group SPANS come back from the engine
itself (TraceMatch { whole, groups }) - the bench's capture chips and diffs are engine truth,
not re-derivation. - Per-rule time budget plus an optional whole-set budget (
setBudgetMs); a rule that blows either
is SKIPPED with a plainskipReason(timeout/set-budget/phase/off/condition),
never a crash. condition: { ruleId, matched }chains a rule on whether an EARLIER rule applied-and-matched
(one pass, no forward references; unknown or forward refs read as not-matched).overlay: truerules match WITHOUT mutating - their spans come back on a separateoverlayschannel for display-layer painting.firstMatchOnly: truereplaces only the first hit even under thegflag.- Patterns are screened after find-side macro substitution. Text construction, match enumeration,
capture counts and retained traces have explicit limits incore/regex/limits.ts.
A clock checked between steps cannot interrupt one native match. Studio execution adds a terminable
worker; direct core callers do not gain that deadline. See regex safety
for the limits and evidence, including what is not proven in a packaged host.
Replacement text goes through replace-ops.ts: $1-style groups, {{match}} sugar, JavaScript's
own $& / $` / $', and the \u \l \U \L \E case transforms, with exact escape-handling
parity between the runtime and the travel lint.
The JavaScript tokens are the asymmetric ones and the lint treats them as such. This engine runs
them; no other surveyed engine does, because none hands the template to String.replace. Each
expands the string itself and understands only numbered and named groups, so $& arrives as two
literal characters. The damage is not the stray characters: a rule written to re-emit its match
CONSUMES it instead, and every later rule keyed on that same text silently stops firing. A rule
using them therefore works while it is being edited and breaks the moment it travels, which is whyjs-dollar-token reports on every lens except this one.
The AST layer (ast/)
A clean-room ECMAScript u-mode regex parser (fuzz-checked against the host RegExp as oracle),
with spans on every node. Everything explanatory is built on it:
explainAst- the plain-words reading shown under the Find box (exhaustive over node kinds,
honest about ASCII\w/\b).examplesFor- generated example matches, each VERIFIED by executing the real regex before
display (engine-truth rule: never show a match the engine did not confirm).analyzeRedos- structural backtracking screening with culprit spans. Execution refuses suspicious
and dangerous reports, and unsupported parser syntax or flags fail closed. Storedvflags remain
intact for conversion, but execution is unsupported until the analysis grammar implements them.dialect.ts- tolerant flag-token parsing (gu<cbs>) for imports.
Travel honesty (travel-lint.ts)
All five platforms execute JS RegExp, so cross-platform divergence is a LINT, not a parser fork.travelLint(rule, profile) reports, with spans: replace/flag extensions the destination engine will
not run ({{match}}, JavaScript's $& family, case transforms, <cbs> tokens), engine-only rule
fields (condition / overlay / first-match-only), and host-age risks (look-behind, v flag). The editor renders these dashed-amber
under the active Write-for lens; nothing is blocked or mangled.
The builder (builder.ts + word-boundary.ts / word-family.ts)
The plain-words layer the Guided and Plain-words modes stand on: word/phrase lists in, pattern +
explanation + execution-verified examples out, with unicode-aware boundaries for space-less scripts
(the whole-word toggle drops out honestly where \b would lie) and the structure atoms (numbers,
anything-between, line anchors). Round-trips through explainPattern, which carries the full AST
reading for any parseable pattern.