# Architecture

Regex Tools is a static browser application with framework-neutral regex
boundaries.

```text
React workbench
  ├─ Pattern SyntaxSupervisor → syntax.worker
  │    ├─ Regexpp ECMAScript provider → normalized AST
  │    ├─ PCRE2 lexical provider → partial normalized structure
  │    ├─ Python lexical provider → partial normalized structure
  │    ├─ Java lexical provider → partial normalized structure
  │    └─ PHP/Perl/Ruby/C++/Go/Rust/.NET/Scala lexical providers → partial structures
  ├─ Replacement SyntaxSupervisor → syntax.worker → typed replacement tokens
  ├─ EngineSupervisor
  │    ├─ ecmascript.worker → native RegExp → match DTOs
  │    ├─ pcre2.worker → PCRE2 10.47 WASM ABI → match DTOs
  │    ├─ php.worker → PHP 8.5.8 preg / PCRE2 10.44 → match DTOs
  │    ├─ perl.worker → legacy WebPerl 0.09-beta / Perl 5.28.1 → match DTOs
  │    ├─ python.worker → Pyodide 314.0.3 → CPython 3.14.2 re → match DTOs
  │    ├─ ruby.worker → ruby.wasm → CRuby 4.0.0 Regexp → match DTOs
  │    ├─ java.worker → TeaVM 0.15.0 java.util.regex module → match DTOs
  │    ├─ cpp.worker → Emscripten 6.0.4 libc++ std::wregex → match DTOs
  │    ├─ go.worker → Go 1.26.5 standard-library regexp → match DTOs
  │    ├─ rust.worker → Rust regex crate 1.13.1 → match DTOs
  │    ├─ dotnet.worker → .NET 10.0.10 System.Text.RegularExpressions → match DTOs
  │    └─ scala.worker → explicit compatibility adapter → TeaVM Java module
  ├─ Pcre2TraceSupervisor → pcre2-trace.worker
  │    └─ separate ABI-v3 PCRE2_AUTO_CALLOUT operation → bounded trace DTOs
  ├─ AnalysisPanel
  │    ├─ normalized-AST ECMAScript static heuristics
  │    └─ AnalysisSupervisor → analysis.worker → native RegExp benchmark/growth probes
  ├─ ComparisonOrchestrator
  │    ├─ two exact syntax snapshots + independently killable engine workers
  │    └─ normalized UTF-16 match/capture alignment + explicit incomplete states
  ├─ PCRE2 C17 generator → validated snapshot → exact UTF-8 byte-array source
  ├─ SubjectMinimizer
  │    ├─ fixed syntax snapshots + exact real-engine failure or comparison oracle
  │    └─ deterministic Unicode-scalar reducer → bounded local-minimal result
  ├─ CaseGenerationOrchestrator
  │    ├─ generation.worker → deterministic bounded ECMAScript AST candidates
  │    └─ EngineSupervisor → selected actual-engine label verification
  ├─ ECMAScript literal/control formatter
  │    ├─ exact regexpp literal-token ranges → idempotent candidate preview
  │    └─ PatternFormatValidator → paired syntax + actual-engine workers and exact tests
  ├─ Corpus EngineSupervisor → sequential bounded document jobs → summaries/outputs
  ├─ Test SyntaxSupervisor + EngineSupervisor → isolated cancellable test runs
  └─ project validator / serializer / IndexedDB persistence
```

Provider AST objects never cross the worker boundary. The syntax worker emits
application-owned nodes, tokens, diagnostics, capture definitions and explicit
provenance. React never imports or traverses regexpp's cyclic AST.

Every engine request carries a protocol version, monotonically increasing
request ID and worker generation. A supervisor allows one active request,
rejects stale responses, terminates on timeout/crash/cancel, and lazily creates
a new generation. Timeout is a distinct error state. A separate idempotent
load operation verifies a worker's runtime identity before execution; its
flavour-specific startup deadline is not charged to the 250 ms live or selected
manual execution deadline. A killed generation must load and verify again.
The supervisor has a two-worker retention target; before creating another
profile worker it evicts the least recently used idle generation. Active work
is never evicted and may temporarily exceed that target.

The Python worker imports only the self-hosted Pyodide loader and pack, then
evaluates the application-owned bounded bridge in CPython. The bridge returns
code-point ranges, which are retained as native values and converted only at
valid boundaries to editor UTF-16. The Java worker imports a verified TeaVM
ES2015 module. That module executes TeaVM's own `java.util.regex.Pattern` and
`Matcher` class-library implementation and returns native UTF-16 ranges; it is
not OpenJDK.

PHP, Perl, Ruby, C++, Go, Rust and .NET each use a fixed bounded bridge to the
named self-hosted runtime. User values cross as structured/JSON data, never as
generated source. Scala intentionally reuses the TeaVM Java module and rewrites
only request/result profile metadata; it contains no Scala runtime. The legacy
Perl topology is unusual: the supervised module worker owns the classic worker
required by WebPerl, so supervisor termination still discards the complete
worker tree.

Ruby's production bridge avoids ruby.wasm's generic JavaScript object
conversion: the worker writes one bounded length-prefixed request into its
private WASI memory filesystem, calls the fixed bridge with no user arguments,
reads one bounded inert metadata JSON string, and obtains bounded UTF-8
replacement output from a separate private file. That path avoids JSON output
amplification and passes the same strict-CSP browser gate as the other
profiles.

PCRE2 tracing is not an `EngineSupervisor` match or replacement operation. Its
own worker compiles with `PCRE2_AUTO_CALLOUT`, copies only complete fixed
records and bounded mark bytes, and stops natively at either trace cap.
Reported callout fields retain byte positions; movement classifications are a
separate derived UI layer. Normal execution, corpus, tests and benchmarks never
run through the trace operation.

Static analysis traverses the normalized ECMAScript tree on the UI side under
node/finding caps. Timing and generated-input growth probes use the independent
analysis worker. Timeout, cancellation or crash discards that worker; no trace
mode is benchmarked.

Comparison is a separate orchestration layer, not an `EngineSupervisor`
operation. It runs explicit ECMAScript and PCRE2 syntax/execution snapshots,
retains each engine identity and native range model, and aligns only normalized
editor ranges. Timeout, cancellation, compile rejection and truncation remain
distinct non-comparable outcomes. Shared patterns are sent unchanged;
per-flavour ports are user-owned variants.

The PCRE2 C generator is application-owned and never implements browser
execution. It validates one exact PCRE2 snapshot, emits user strings as
independent UTF-8 byte arrays, and states the host-level mappings that the
PCRE2 API cannot express directly. Its advertised C17 target is protected by
deterministic golden identities and an exact PCRE2 10.47 compile/execute gate.

Subject minimization is a main-thread bounded reducer whose expensive predicate
checks stay in independently terminable ECMAScript or PCRE2 workers. Fixed
pattern syntax is parsed once for capture metadata. Every other profile is
blocked at this feature boundary rather than routed to a different engine. The
baseline and every accepted candidate retain the exact target and engine
identity.
Timeout, crash, cancellation, worker failure and incomplete output are separate
outcomes. Deterministic ddmin deletion is followed by a fixed-point local sweep
over Unicode-scalar deletion and lower-rank canonical replacement; only
completion of that sweep permits a transform-local minimality claim.

Generated-case synthesis and verification are separate trust boundaries. The
dedicated generation worker receives only a validated, complete ECMAScript
normalized tree plus explicit settings and seed. The main-thread orchestrator
then sends each bounded candidate to `EngineSupervisor`; only the selected real
adapter can establish a `should-match` or `should-not-match` label. Worker
timeout, crash, cancellation, compile rejection, wrong outcome and ordinary
failure stay distinct. Settings/results are ephemeral, while explicitly added
unit tests retain field-validated generator provenance.

Formatting is not an engine operation and never rewrites from heuristic text
alone. The ECMAScript formatter consumes an exact accepted regexpp snapshot and
changes only parser-reported literal/control token ranges. Its validator
reparses source and candidate separately, preserves capture shape, runs both in
separate actual-engine workers against the current replacement snapshot, then
replays every enabled test tied to the exact active pattern/version/flags/options.
Any difference, incomplete output, one-sided timeout, identity change or
inconclusive worker outcome blocks apply. The preview and observations are
ephemeral, and user confirmation is required even after all bounded gates pass.

Pattern and replacement syntax use independent supervisors. A pattern snapshot
is bound to its exact pattern, ordered flags and revision; execution cannot use
acceptance or capture metadata from an earlier input. Replacement output is
built while the native engine produces the same bounded authoritative match
sequence that is returned to the UI.

Per-match replacement ranges and token contributions are deterministic mappings
over those actual matches, the complete replacement-token model and the bounded
output. They execute no replacement callback or user code and have separate
presentation and token-evaluation limits.

Corpus documents are ephemeral React session state, not project state. A
dedicated engine supervisor processes one document at a time and is terminated
on cancellation, timeout or workspace exit. Batch orchestration retains only
per-document summaries and, for apply runs, bounded outputs. It enforces
document-count, per-document byte, aggregate input byte, aggregate match,
aggregate output and batch wall-time limits. Exact-output export is unavailable
if any document is partial, failed, cancelled or not run.

Whole-document mode sends each document to the engine once. Independent-line
mode segments CRLF, CR, LF, U+2028 and U+2029 without discarding them, executes
each logical line as its own subject, and reattaches the exact original
separator during apply. This makes anchor and cross-line behavior deliberately
different rather than silently rewriting the pattern. Match DTOs are reduced to
bounded per-group participation counts and samples before the next document.

Native ranges are retained in the engine's declared unit. ECMAScript, Java,
Scala compatibility and .NET use UTF-16 code units. Standalone PCRE2, PHP, Go
and Rust expose UTF-8 byte ranges. CPython, CRuby, libc++ `std::wregex` and
legacy Perl expose Unicode code-point positions. Browser editor ranges are
half-open UTF-16 code-unit ranges. Reusable converters cover UTF-8 byte and
Unicode code-point offsets, including invalid-boundary rejection and
lone-surrogate detection.

The release boundary is `dist/` plus checked-in legal/source documents. The
Portal consumes the resulting immutable ZIP and never imports React source.
