1 Commits

Author SHA1 Message Date
7079cde15f feat: add Python and Java regex engines 2026-07-27 02:02:21 +02:00
95 changed files with 8866 additions and 251 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ node_modules/
dist/ dist/
release/ release/
.engine-build/ .engine-build/
engines/java/target/
coverage/ coverage/
playwright-report/ playwright-report/
test-results/ test-results/

View File

@@ -1 +1,3 @@
public/engines/pcre2/pcre2.mjs public/engines/pcre2/pcre2.mjs
public/engines/java/
public/engines/python/

View File

@@ -2,6 +2,31 @@
## Unreleased ## Unreleased
## 0.3.0 — 2026-07-27
- Add a self-hosted Pyodide 314.0.3 worker containing CPython 3.14.2 `re`, with
runtime identity verification, bounded compilation/matching/replacement and
exact code-point-to-editor-UTF-16 range normalization.
- Add TeaVM 0.15.0's `java.util.regex` class-library implementation as a
verified self-hosted ES module, with bounded `Pattern`/`Matcher` execution,
native replacement and exact UTF-16 ranges.
- Identify the Java engine as TeaVM class-library code rather than OpenJDK and
report its documented `UNICODE_CHARACTER_CLASS`, single-digit replacement
and unavailable named-replacement compatibility boundaries.
- Add partial application-owned Python and Java lexical syntax and replacement
providers while keeping the selected runtime compiler authoritative.
- Separate heavyweight engine loading from the per-request execution deadline,
retain worker recovery after timeout/crash/cancellation and keep every
runtime local to the release.
- Add flavour-specific quick reference and capability content for all four
executable engines.
- Keep comparison, generated cases, code generation, minimization, analysis
and formatting behind their explicitly verified ECMAScript/PCRE2 boundaries;
Python and Java are never routed through another flavour as a fallback.
- Extend deterministic engine-pack provenance, checksums, licence notices,
release verification and nested-path delivery checks to the Python and Java
runtime assets.
## 0.2.0 — 2026-07-27 ## 0.2.0 — 2026-07-27
- Open Quick reference and Capabilities as populated, accessible in-viewport - Open Quick reference and Capabilities as populated, accessible in-viewport

103
README.md
View File

@@ -8,16 +8,23 @@ Patterns, replacement templates, test text and project files stay in the
browser. There is no account, backend, upload, remote execution, telemetry, browser. There is no account, backend, upload, remote execution, telemetry,
runtime CDN, remote font, or cloud-save service. runtime CDN, remote font, or cloud-save service.
## Current release — 0.2.0 ## Current release — 0.3.0
The v0.2.0 release contains two real production engine slices: ECMAScript and The v0.3.0 release contains four executable engine slices:
PCRE2 10.47.
- the current browser's native ECMAScript `RegExp`;
- official PCRE2 10.47 as a self-hosted 8-bit WebAssembly pack;
- CPython 3.14.2 `re` in the self-hosted Pyodide 314.0.3 runtime; and
- TeaVM 0.15.0's `java.util.regex` class-library implementation as a
self-hosted ES2015 module.
The Java flavour is TeaVM class-library code compiled to JavaScript. It is not
OpenJDK and is not advertised as an OpenJDK compatibility oracle.
- Syntax acceptance: `@eslint-community/regexpp` 4.12.2, ECMAScript 2025 - Syntax acceptance: `@eslint-community/regexpp` 4.12.2, ECMAScript 2025
grammar; application-owned structural explanations are partial and grammar; application-owned structural explanations are partial and
feature-matrix tested. feature-matrix tested.
- Execution engine: the current browser's native ECMAScript `RegExp`. - ECMAScript native and editor offsets are UTF-16 code units.
- Native and editor offsets: UTF-16 code units.
- PCRE2 execution and substitution: pinned official PCRE2 10.47 compiled to a - PCRE2 execution and substitution: pinned official PCRE2 10.47 compiled to a
self-hosted 8-bit WebAssembly pack with UTF/UCP always enabled. self-hosted 8-bit WebAssembly pack with UTF/UCP always enabled.
- PCRE2 native UTF-8 byte ranges are retained and normalized to exact editor - PCRE2 native UTF-8 byte ranges are retained and normalized to exact editor
@@ -26,10 +33,22 @@ PCRE2 10.47.
- PCRE2 automatic callouts are copied through a separate ABI operation and - PCRE2 automatic callouts are copied through a separate ABI operation and
displayed from a dedicated killable trace worker under 50,000-event and displayed from a dedicated killable trace worker under 50,000-event and
10 MiB serialized-data hard caps. 10 MiB serialized-data hard caps.
- Python compilation, matching and replacement use the bundled CPython `re`
module. Native code-point ranges are retained and normalized to exact editor
UTF-16 ranges.
- Java compilation, matching and replacement use TeaVM's
`java.util.regex.Pattern` and `Matcher`. Native ranges are UTF-16 code units.
- TeaVM 0.15.0 replacement supports its actual single-digit `$n` subset.
`$12` is therefore `$1` followed by literal `2`; Java SE `${name}`
replacement is reported as unavailable rather than presented as OpenJDK
behavior.
- Python and Java use deliberately partial application-owned lexical syntax
providers; their actual runtime compiler remains authoritative.
- Parsing and execution run in separate workers. - Parsing and execution run in separate workers.
- Actual execution can be terminated by killing its worker. - Actual execution can be terminated by killing its worker.
- The worker is recreated after timeout, crash or cancellation. - The worker is recreated after timeout, crash or cancellation.
- Capture ranges use the engine's `d` indices result. - ECMAScript capture ranges use the engine's `d` indices result; the other
flavours return their native range records through bounded bridges.
- Named, numbered, optional, empty and repeated-final captures are represented. - Named, numbered, optional, empty and repeated-final captures are represented.
- Match, replacement, typed list/extraction, corpus/apply and unit-test modes - Match, replacement, typed list/extraction, corpus/apply and unit-test modes
are available. are available.
@@ -73,7 +92,8 @@ as a complete record of every internal engine action.
## Workbench modes ## Workbench modes
- **Match** — live highlighting, extraction tree and bounded capture table. - **Match** — live highlighting, extraction tree and bounded capture table.
- **Replace** — engine-native bounded substitution for ECMAScript and PCRE2; - **Replace** — engine-native bounded substitution for all four executable
flavours;
output completeness is reported separately from bounded per-match output completeness is reported separately from bounded per-match
original/result, changed-range and token-contribution previews that original/result, changed-range and token-contribution previews that
synchronize the replacement, pattern, subject and output editors. synchronize the replacement, pattern, subject and output editors.
@@ -121,10 +141,12 @@ Pattern and flags are stored separately; delimiters are presentation only.
ECMAScript preserves native `g` and `y`; PCRE2 offers the application-level ECMAScript preserves native `g` and `y`; PCRE2 offers the application-level
`g` iteration flag plus `i`, `m`, `s`, `x`, `U` and `J`. PCRE2 UTF/UCP mode is `g` iteration flag plus `i`, `m`, `s`, `x`, `U` and `J`. PCRE2 UTF/UCP mode is
mandatory because browser strings are Unicode and partial-byte ranges cannot be mandatory because browser strings are Unicode and partial-byte ranges cannot be
represented safely in the editor. The optional **Scan all** action adds only an represented safely in the editor. Python exposes application-level `g` plus
internal iteration flag to that request and does not change saved flags. CPython `re` flags `a`, `i`, `m`, `s` and `x`. Java exposes application-level
ECMAScript may add internal `d` to obtain exact indices without changing match `g` plus TeaVM `Pattern` flags `i`, `d`, `m`, `s`, `u`, `x` and the documented
semantics. `U` compatibility request. The optional **Scan all** action adds only an
internal iteration request and does not change saved flags. ECMAScript may add
internal `d` to obtain exact indices without changing match semantics.
Zero-length global iteration advances using the selected Unicode semantics and Zero-length global iteration advances using the selected Unicode semantics and
cannot loop forever. cannot loop forever.
@@ -247,6 +269,10 @@ npm run engines:verify
npm run engines:pcre2:build -- --source-dir /path/to/pcre2 --emcc /path/to/emcc npm run engines:pcre2:build -- --source-dir /path/to/pcre2 --emcc /path/to/emcc
npm run engines:pcre2:verify npm run engines:pcre2:verify
npm run engines:pcre2:install npm run engines:pcre2:install
node scripts/python-engine-pack.mjs node_modules/pyodide .engine-build/python
node scripts/install-python-engine.mjs .engine-build/python
node scripts/java-engine-pack.mjs
node scripts/install-java-engine.mjs
npm run build npm run build
npm run toolbox:check npm run toolbox:check
npm run check npm run check
@@ -266,18 +292,30 @@ The deterministic release ZIP can be consumed by Toolbox Portal using an exact
artifact URL, manifest ID, version and SHA-256. Portal builds consume the artifact URL, manifest ID, version and SHA-256. Portal builds consume the
artifact; they do not build Regex Tools source. artifact; they do not build Regex Tools source.
The PCRE2 worker loads self-hosted JavaScript and WebAssembly only. Deployment The PCRE2 and Python workers load only self-hosted JavaScript, WebAssembly and
must serve `.wasm` as `application/wasm` and allow WebAssembly compilation in Python-standard-library assets; Java loads its self-hosted ES module.
`script-src`; see [`docs/PORTAL_REQUIREMENTS.md`](docs/PORTAL_REQUIREMENTS.md). Deployment must serve `.wasm` as `application/wasm` and allow WebAssembly
compilation in `script-src`; see
[`docs/PORTAL_REQUIREMENTS.md`](docs/PORTAL_REQUIREMENTS.md).
## Flavour roadmap ## Flavour support and boundaries
PCRE2 10.47 is the second implemented flavour. Its application-owned ABI PCRE2's application-owned ABI provides bounded compilation, matching, native
provides bounded compilation, matching, native substitution and separate substitution and separate automatic-callout tracing, copied caller-owned
automatic-callout tracing, copied caller-owned records, deterministic records, deterministic allocation cleanup and explicit result completeness.
allocation cleanup and explicit result completeness. The exact The exact source/toolchain build is reproducible offline and the verified pack
source/toolchain build is reproducible offline and the verified pack is bundled is bundled with its metadata, checksums and licence.
with its metadata, checksums and licence.
The Python slice executes actual CPython 3.14.2 `re` in Pyodide 314.0.3; it does
not translate a Python pattern into ECMAScript. Pyodide startup is a separate
worker-load operation and is excluded from the normal execution deadline.
The Java slice executes TeaVM 0.15.0's own `java.util.regex` class library. It
does not translate a Java pattern into ECMAScript, but it is also not OpenJDK:
in particular, TeaVM 0.15.0 does not implement
`Pattern.UNICODE_CHARACTER_CLASS`. The UI reports the `U` flag as a
compatibility request and preserves TeaVM's predefined-class and word-boundary
behaviour.
Generated-case version 1 is ECMAScript-only. It traverses the complete Generated-case version 1 is ECMAScript-only. It traverses the complete
application-owned ECMAScript AST under node, depth, attempt, byte, repetition application-owned ECMAScript AST under node, depth, attempt, byte, repetition
@@ -286,17 +324,17 @@ browser `RegExp` adapter. PCRE2 generation remains unavailable until its syntax
provider can expose a complete normalized tree. provider can expose a complete normalized tree.
Pattern formatting is likewise ECMAScript-only. It consumes the complete Pattern formatting is likewise ECMAScript-only. It consumes the complete
regexpp token model and does not reinterpret the partial PCRE2 structure. regexpp token model and does not reinterpret the partial PCRE2, Python or Java
PCRE2 formatting remains unavailable until a complete grammar-backed structures. Those flavours remain unavailable until complete grammar-backed
implementation passes equivalent idempotence, reparse, engine and test gates. implementations pass equivalent idempotence, reparse, engine and test gates.
The first advertised generated-code target is the PCRE2 10.47 8-bit C API. The first advertised generated-code target is the PCRE2 10.47 8-bit C API.
Its deterministic match and replacement fixtures compile and execute against Its deterministic match and replacement fixtures compile and execute against
the exact official release. See the exact official release. See
[`docs/COMPARISON_AND_CODEGEN.md`](docs/COMPARISON_AND_CODEGEN.md). [`docs/COMPARISON_AND_CODEGEN.md`](docs/COMPARISON_AND_CODEGEN.md).
Python, Go, Rust, .NET, Java and legacy PCRE follow only through their actual Go, Rust, .NET and legacy PCRE follow only through their actual named runtimes.
named runtimes. No flavour is emulated through JavaScript and relabelled. No flavour is emulated through JavaScript and relabelled.
## Licensing ## Licensing
@@ -308,7 +346,7 @@ notices; see [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) and
No regex101 application source, branding, generated explanation prose, No regex101 application source, branding, generated explanation prose,
reference text or visual design is copied. RegexLib and RGXP.RU fixtures are not reference text or visual design is copied. RegexLib and RGXP.RU fixtures are not
redistributed because a suitable fixture licence was not established. No redistributed because a suitable fixture licence was not established. No
RegexHub fixture is shipped in v0.2.0. RegexHub fixture is shipped in v0.3.0.
## Known limitations ## Known limitations
@@ -332,6 +370,14 @@ RegexHub fixture is shipped in v0.2.0.
recognizes common capture forms and PCRE2-only constructs for reference, but recognizes common capture forms and PCRE2-only constructs for reference, but
the actual PCRE2 compiler is authoritative. Branch-reset capture numbering is the actual PCRE2 compiler is authoritative. Branch-reset capture numbering is
omitted from the explanation rather than guessed. omitted from the explanation rather than guessed.
- Python and Java structural explanations are deliberately partial lexical
models. CPython `re` and TeaVM `Pattern` respectively remain authoritative
for compilation, group count and matching.
- Python reports native Unicode code-point offsets and converts them at valid
boundaries to editor UTF-16 ranges. Java and ECMAScript report native UTF-16
offsets; PCRE2 reports native UTF-8 bytes. Native units remain visible.
- TeaVM `java.util.regex` is not OpenJDK. Its `U` compatibility request does
not add OpenJDK's `UNICODE_CHARACTER_CLASS` semantics.
- Capture-table rendering is paged at 200 rows while aggregate collection is - Capture-table rendering is paged at 200 rows while aggregate collection is
bounded separately. bounded separately.
- Explanation/extraction trees and editor decorations show bounded 2,000-node - Explanation/extraction trees and editor decorations show bounded 2,000-node
@@ -366,6 +412,9 @@ RegexHub fixture is shipped in v0.2.0.
- Pattern formatting is intentionally narrow and ECMAScript-only. Its bounded - Pattern formatting is intentionally narrow and ECMAScript-only. Its bounded
source/candidate and unit-test validation is evidence for the checked source/candidate and unit-test validation is evidence for the checked
snapshots, not a proof of equivalence over all subjects. snapshots, not a proof of equivalence over all subjects.
- Cross-flavour comparison, PCRE2 C generation and subject minimization remain
scoped to their explicitly supported ECMAScript/PCRE2 paths; selecting
Python or Java does not silently substitute one of those engines.
Architecture, security, performance, provenance and flavour details live in Architecture, security, performance, provenance and flavour details live in
[`docs/`](docs/). [`docs/`](docs/).

View File

@@ -1,10 +1,10 @@
# Source identity # Source identity
- Project: Regex Tools - Project: Regex Tools
- Release version: `0.2.0` - Release version: `0.3.0`
- Release tag: `v0.2.0` - Release tag: `v0.3.0`
- Repository: <https://git.add-ideas.de/zemion/regex-tools> - Repository: <https://git.add-ideas.de/zemion/regex-tools>
- Previous release tag: `v0.1.0` - Previous release tag: `v0.2.0`
- Licence: GPL-3.0-or-later - Licence: GPL-3.0-or-later
- Copyright: © 2026 Albrecht Degering - Copyright: © 2026 Albrecht Degering
@@ -20,14 +20,26 @@ runtime or build-time CDN fetch. Complete preferred-form source is the tagged
repository. The release archive contains compiled static assets plus legal, repository. The release archive contains compiled static assets plus legal,
source-identity and attribution documents. source-identity and attribution documents.
The explicit offline PCRE2 build is documented in `docs/ENGINE_BUILDS.md`. The engine-pack inputs and rebuild gates are documented in
PCRE2 source is not vendored: the gate accepts only the pinned, clean official `docs/ENGINE_BUILDS.md`.
10.47 checkout and Emscripten 6.0.4 compiler. Generated staging is ignored; the
verified runtime files, deterministic metadata, checksums and exact licence are
installed under `public/engines/pcre2/` and included in the application source
and build.
The v0.2.0 release includes PCRE2. Python, Go, Rust, .NET, Java and legacy PCRE - PCRE2 source is not vendored: the gate accepts only the pinned, clean
remain unimplemented. The v0.1.0 release remains available under its historical official 10.47 checkout and Emscripten 6.0.4 compiler.
tag and artifact coordinates. A future public artifact must bump package, - The Python pack is copied from the exact pinned Pyodide 314.0.3 npm package
manifest, tag and source identity together; it must not reuse `v0.2.0`. and contains its CPython 3.14.2 runtime, Python standard library, loader,
WebAssembly module, metadata, checksums and licence material.
- The Java bridge source is under `engines/java/`. Its bundled module is
compiled against TeaVM 0.15.0's `java.util.regex` class library and is
identified as TeaVM rather than OpenJDK.
Generated staging is ignored. Only verified runtime packs with deterministic
metadata, closed file sets, checksums and exact licence/notice material are
installed under `public/engines/` and included in the application source and
build.
The v0.3.0 release includes native browser ECMAScript, PCRE2 10.47, CPython
3.14.2 `re` through Pyodide 314.0.3 and TeaVM 0.15.0 `java.util.regex`. Go,
Rust, .NET and legacy PCRE remain unimplemented. Earlier releases remain
available under their historical tags and artifact coordinates. A future
public artifact must bump package, manifest, tag and source identity together;
it must not reuse `v0.3.0`.

View File

@@ -15,6 +15,9 @@ Exact dependency resolution is recorded in `package-lock.json`.
| `fflate` | 0.8.3 | MIT | Runtime and build dependency | Local corpus-output ZIP and deterministic release ZIP; <https://github.com/101arrowz/fflate> | | `fflate` | 0.8.3 | MIT | Runtime and build dependency | Local corpus-output ZIP and deterministic release ZIP; <https://github.com/101arrowz/fflate> |
| Emscripten generated runtime | 6.0.4 | MIT | Generated WebAssembly glue | PCRE2 module loader/runtime; <https://github.com/emscripten-core/emscripten> | | Emscripten generated runtime | 6.0.4 | MIT | Generated WebAssembly glue | PCRE2 module loader/runtime; <https://github.com/emscripten-core/emscripten> |
| PCRE2 | 10.47 | BSD-3-Clause WITH PCRE2-exception | WebAssembly runtime | Pinned official 8-bit engine; <https://github.com/PCRE2Project/pcre2> | | PCRE2 | 10.47 | BSD-3-Clause WITH PCRE2-exception | WebAssembly runtime | Pinned official 8-bit engine; <https://github.com/PCRE2Project/pcre2> |
| Pyodide | 314.0.3 | MPL-2.0 | Python runtime pack | Self-hosted CPython browser runtime; <https://github.com/pyodide/pyodide> |
| CPython | 3.14.2 | PSF-2.0 | Python runtime/stdlib | `re` execution and bundled standard library; <https://github.com/python/cpython> |
| TeaVM | 0.15.0 | Apache-2.0 | Generated Java engine module | `java.util.regex` class-library implementation; <https://github.com/konsoletyper/teavm> |
| Vite | 8.1.5 | MIT | Generated helpers | Production build; <https://github.com/vitejs/vite> | | Vite | 8.1.5 | MIT | Generated helpers | Production build; <https://github.com/vitejs/vite> |
| Rolldown | 1.1.5 | MIT | Generated helpers | Production bundling; <https://github.com/rolldown/rolldown> | | Rolldown | 1.1.5 | MIT | Generated helpers | Production bundling; <https://github.com/rolldown/rolldown> |
@@ -23,9 +26,13 @@ development-only and are not part of the static runtime bundle.
`recheck` 4.5.0 and `regexp-ast-analysis` 0.7.1 were inspected but deliberately `recheck` 4.5.0 and `regexp-ast-analysis` 0.7.1 were inspected but deliberately
not installed or shipped. PCRE2 source is not vendored; its generated not installed or shipped. PCRE2 source is not vendored; its generated
WebAssembly pack is shipped with exact metadata, checksums and licence. WebAssembly pack is shipped with exact metadata, checksums and licence. Pyodide
is pinned as an npm build input and its reviewed self-hosted runtime files are
copied into the Python pack. The TeaVM module contains its compiled class
library and is explicitly identified as TeaVM, not OpenJDK.
Corresponding licence texts and copyright notices are in `LICENSES/`. The Licence texts and copyright notices for the ordinary bundled dependencies are
release package additionally carries the exact Vite and Rolldown legal files in `LICENSES/`. Each engine pack carries its exact upstream licence/notice
under `LICENSES/build/`, including Vite's bundled-dependency notices for code files alongside the runtime. The release package additionally carries the exact
emitted by the production build. Vite and Rolldown legal files under `LICENSES/build/`, including Vite's
bundled-dependency notices for code emitted by the production build.

View File

@@ -3,7 +3,7 @@
Regex Tools provides one deliberately scoped analysis implementation: Regex Tools provides one deliberately scoped analysis implementation:
ECMAScript 2025 patterns parsed into the application-owned normalized AST and ECMAScript 2025 patterns parsed into the application-owned normalized AST and
executed by the browser's native `RegExp` runtime. The analyser does not apply executed by the browser's native `RegExp` runtime. The analyser does not apply
ECMAScript findings to PCRE2 or any other flavour. ECMAScript findings to PCRE2, Python, Java or any other flavour.
Analysis is advisory. The UI uses “potential risk”, “possibly” and “observed Analysis is advisory. The UI uses “potential risk”, “possibly” and “observed
under selected limits”. It never turns an empty finding list into “safe”, under selected limits”. It never turns an empty finding list into “safe”,

View File

@@ -7,11 +7,15 @@ boundaries.
React workbench React workbench
├─ Pattern SyntaxSupervisor → syntax.worker ├─ Pattern SyntaxSupervisor → syntax.worker
│ ├─ Regexpp ECMAScript provider → normalized AST │ ├─ Regexpp ECMAScript provider → normalized AST
─ PCRE2 lexical provider → partial normalized structure ─ PCRE2 lexical provider → partial normalized structure
│ ├─ Python lexical provider → partial normalized structure
│ └─ Java lexical provider → partial normalized structure
├─ Replacement SyntaxSupervisor → syntax.worker → typed replacement tokens ├─ Replacement SyntaxSupervisor → syntax.worker → typed replacement tokens
├─ EngineSupervisor ├─ EngineSupervisor
│ ├─ ecmascript.worker → native RegExp → match DTOs │ ├─ ecmascript.worker → native RegExp → match DTOs
─ pcre2.worker → PCRE2 10.47 WASM ABI → match DTOs ─ pcre2.worker → PCRE2 10.47 WASM ABI → match DTOs
│ ├─ python.worker → Pyodide 314.0.3 → CPython 3.14.2 re → match DTOs
│ └─ java.worker → TeaVM 0.15.0 java.util.regex module → match DTOs
├─ Pcre2TraceSupervisor → pcre2-trace.worker ├─ Pcre2TraceSupervisor → pcre2-trace.worker
│ └─ separate ABI-v3 PCRE2_AUTO_CALLOUT operation → bounded trace DTOs │ └─ separate ABI-v3 PCRE2_AUTO_CALLOUT operation → bounded trace DTOs
├─ AnalysisPanel ├─ AnalysisPanel
@@ -42,7 +46,18 @@ provenance. React never imports or traverses regexpp's cyclic AST.
Every engine request carries a protocol version, monotonically increasing Every engine request carries a protocol version, monotonically increasing
request ID and worker generation. A supervisor allows one active request, request ID and worker generation. A supervisor allows one active request,
rejects stale responses, terminates on timeout/crash/cancel, and lazily creates rejects stale responses, terminates on timeout/crash/cancel, and lazily creates
a new generation. Timeout is a distinct error state. 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 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.
PCRE2 tracing is not an `EngineSupervisor` match or replacement operation. Its PCRE2 tracing is not an `EngineSupervisor` match or replacement operation. Its
own worker compiles with `PCRE2_AUTO_CALLOUT`, copies only complete fixed own worker compiles with `PCRE2_AUTO_CALLOUT`, copies only complete fixed
@@ -70,13 +85,14 @@ PCRE2 API cannot express directly. Its advertised C17 target is protected by
deterministic golden identities and an exact PCRE2 10.47 compile/execute gate. deterministic golden identities and an exact PCRE2 10.47 compile/execute gate.
Subject minimization is a main-thread bounded reducer whose expensive predicate Subject minimization is a main-thread bounded reducer whose expensive predicate
checks stay in independently terminable engine workers. Fixed pattern syntax checks stay in independently terminable ECMAScript or PCRE2 workers. Fixed
is parsed once for capture metadata. The baseline and every accepted candidate pattern syntax is parsed once for capture metadata. Python and Java are blocked
retain the exact target and engine identity. Timeout, crash, cancellation, at this feature boundary rather than routed to a different engine. The baseline
worker failure and incomplete output are separate outcomes. Deterministic and every accepted candidate retain the exact target and engine identity.
ddmin deletion is followed by a fixed-point local sweep over Unicode-scalar Timeout, crash, cancellation, worker failure and incomplete output are separate
deletion and lower-rank canonical replacement; only completion of that sweep outcomes. Deterministic ddmin deletion is followed by a fixed-point local sweep
permits a transform-local minimality claim. 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 Generated-case synthesis and verification are separate trust boundaries. The
dedicated generation worker receives only a validated, complete ECMAScript dedicated generation worker receives only a validated, complete ECMAScript
@@ -123,12 +139,13 @@ separator during apply. This makes anchor and cross-line behavior deliberately
different rather than silently rewriting the pattern. Match DTOs are reduced to different rather than silently rewriting the pattern. Match DTOs are reduced to
bounded per-group participation counts and samples before the next document. bounded per-group participation counts and samples before the next document.
Native ranges are retained in the engine's declared unit. PCRE2 always uses Native ranges are retained in the engine's declared unit. ECMAScript and Java
UTF/UCP and exposes UTF-8 byte ranges; its ABI copies scalar records and names use UTF-16 code units. PCRE2 always uses UTF/UCP and exposes UTF-8 byte ranges;
out of WebAssembly before returning and retains no code pointer. Browser editor ranges its ABI copies scalar records and names out of WebAssembly before returning and
are half-open UTF-16 code-unit ranges. Reusable converters cover UTF-8 byte and retains no code pointer. CPython `re` exposes Unicode code-point indices.
Unicode code-point offsets, including invalid-boundary rejection and Browser editor ranges are half-open UTF-16 code-unit ranges. Reusable
lone-surrogate detection. 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 The release boundary is `dist/` plus checked-in legal/source documents. The
Portal consumes the resulting immutable ZIP and never imports React source. Portal consumes the resulting immutable ZIP and never imports React source.

View File

@@ -6,6 +6,10 @@ The comparison panel sends two explicit requests: one to the native browser
ECMAScript worker and one to the bundled PCRE2 10.47 worker. It does not ECMAScript worker and one to the bundled PCRE2 10.47 worker. It does not
translate patterns, flags, options or replacement templates. translate patterns, flags, options or replacement templates.
This is an explicit two-engine feature, not a generic active-flavour
comparison. Selecting Python or Java for the main workbench does not insert it
into either side or route it through ECMAScript/PCRE2.
Two pattern models are available: Two pattern models are available:
- **Shared pattern** sends the same string unchanged to both engines. - **Shared pattern** sends the same string unchanged to both engines.
@@ -52,8 +56,10 @@ result. The panel renders at most 250 differences and 100 alignments at once.
## PCRE2 C17 generator ## PCRE2 C17 generator
The first reviewed code-generation target is the PCRE2 10.47 8-bit C API. The first reviewed code-generation target is the PCRE2 10.47 8-bit C API.
Other languages remain unavailable until their generators have equivalent Python, Java and other targets remain unavailable until their generators have
escaping, semantic and toolchain gates. equivalent escaping, semantic and named-runtime/toolchain gates. The executable
Python and Java browser flavours do not by themselves imply a code-generation
target.
The generator consumes a validated PCRE2 snapshot and: The generator consumes a validated PCRE2 snapshot and:

View File

@@ -1,9 +1,9 @@
# Engine builds # Engine builds
The production application uses the browser's native `RegExp` for ECMAScript The production application uses the browser's native `RegExp` for ECMAScript
and bundles a verified PCRE2 10.47 WebAssembly pack. Normal application builds and bundles verified PCRE2, Python and Java runtime packs. Normal application
never download or compile an engine: the reviewed files under builds never download or compile an engine: reviewed files under
`public/engines/pcre2/` are copied into `dist/` and verified there. `public/engines/` are copied into `dist/` and verified there.
## Rebuilding PCRE2 ## Rebuilding PCRE2
@@ -52,7 +52,77 @@ The immutable signed-tag object is pinned. OpenPGP trust validation remains a
release-operator step because the offline builder does not install or trust a release-operator step because the offline builder does not install or trust a
key. key.
## Runtime boundary ## Installing the Python pack
The Python pack is derived from the exact `pyodide@314.0.3` npm package locked
with integrity:
```text
sha512-sK40My6m8tmBUYtYH9au9rXUeh9x0wfahtHdOlGmJxZDsKBGKtP6KznyFB2+u/klbQTdDionR0uaVd176zVQzQ==
```
That Pyodide release is tag/commit
`ac57031be7564f864d061cb37c5c152e59f83ad4`. Direct executable identity
verification reports CPython 3.14.2; the shipped `pyodide-lock.json`
independently reports `info.python` as 3.14.0, and the verifier records rather
than conflates those two upstream identities. The pack copies only the reviewed
loader, WebAssembly runtime, Python standard library and lock metadata required
for a local CPython `re` worker. It adds deterministic engine metadata, a
closed-file-set checksum manifest and exact Pyodide/CPython licence material.
The install operation reads from the already installed pinned npm package. It
does not contact a CDN or package index. It verifies source package identity,
file hashes, runtime metadata and licence inputs before atomically replacing
only `public/engines/python/`. The distribution verifier checks the same closed
set under `dist/`, validates WebAssembly magic and confirms that all runtime
references remain relative and self-hosted.
```sh
node scripts/python-engine-pack.mjs \
node_modules/pyodide \
.engine-build/python
node scripts/install-python-engine.mjs .engine-build/python
```
The installed closed set is:
- `pyodide.mjs`, `pyodide.asm.mjs` and `pyodide.asm.wasm`;
- `python_stdlib.zip` and `pyodide-lock.json`;
- `engine-metadata.json` and `SHA256SUMS`; and
- `LICENSE.pyodide.txt` and `LICENSE.cpython.txt`.
## Building the Java pack
The Java bridge under `engines/java/` wraps TeaVM 0.15.0
`java.util.regex.Pattern` and `Matcher`. TeaVM tag/commit
`ee91b03e616c4b45401cd11fb0cd7eb0daf6649b` is pinned through the Maven build.
The build produces a minified ES2015 module without source maps, source files,
remote imports or an OpenJDK runtime.
The pack gate verifies the Maven coordinates, bridge source identity, TeaVM
engine identity and self-test, generated module, deterministic metadata,
checksums, Apache-2.0 licence and TeaVM NOTICE. Only the verified pack is
installed under `public/engines/java/`.
```sh
node scripts/java-engine-pack.mjs
node scripts/install-java-engine.mjs
```
The pack command performs two clean offline Maven builds and requires identical
output. The installed closed set is `java-regex.mjs`,
`engine-metadata.json`, `SHA256SUMS`, `LICENSE.txt` and `NOTICE.txt`.
TeaVM supplies its own Apache Harmony-derived class-library implementation.
The resulting engine is therefore identified as
`TeaVM java.util.regex 0.15.0`, not OpenJDK. TeaVM 0.15.0 does not implement
OpenJDK's `Pattern.UNICODE_CHARACTER_CLASS`; the bridge treats the application
`U` flag as a compatibility request, implies Unicode case folding and reports
that predefined-character classes and word boundaries retain TeaVM behaviour.
## Runtime boundaries
### PCRE2
ABI version 3 has no retained native handles. Each call compiles, obtains ABI version 3 has no retained native handles. Each call compiles, obtains
capture names, matches/substitutes or traces and releases its code, match data capture names, matches/substitutes or traces and releases its code, match data
@@ -72,3 +142,24 @@ Normal execution lives only in `pcre2.worker`. Instrumented
never call it. Each supervisor terminates its worker on timeout, crash, never call it. Each supervisor terminates its worker on timeout, crash,
cancellation or supersession and lazily creates a clean generation for the cancellation or supersession and lazily creates a clean generation for the
next request. next request.
### Python
The Python worker loads the self-hosted Pyodide pack and verifies Pyodide,
CPython and `re` identities before accepting work. The application bridge is
evaluated inside that interpreter and exposes bounded compile, match and native
replacement operations. It reports CPython's Unicode code-point positions;
the TypeScript adapter retains them and converts only valid boundaries to
half-open editor UTF-16 ranges. Runtime load has a separate startup deadline.
Execution timeout, cancellation or crash terminates the worker and its complete
Python/WebAssembly heap.
### Java
The Java worker imports the self-hosted TeaVM ES module and checks the bridge
ABI, declared engine identity and compiled self-test before accepting work.
Every request compiles a `Pattern`, obtains bounded `Matcher` results or native
replacement output, and returns native half-open UTF-16 ranges. No Java virtual
machine, OpenJDK classes, remote service or ECMAScript regex fallback is
involved. Runtime load has a separate startup deadline; termination discards
the complete worker module state.

View File

@@ -5,6 +5,10 @@ results, but no V8, SpiderMonkey or JavaScriptCore execution trace. Regex Tools
does not relabel structural explanations, static findings or timing does not relabel structural explanations, static findings or timing
observations as an ECMAScript engine trace. observations as an ECMAScript engine trace.
The bundled CPython `re` and TeaVM `java.util.regex` bridges likewise expose
compilation, bounded match/capture and replacement results, not internal engine
steps. Regex Tools does not label their partial lexical syntax trees as traces.
PCRE2 10.47 has a separate actual automatic-callout vertical. ABI version 3 PCRE2 10.47 has a separate actual automatic-callout vertical. ABI version 3
compiles the requested pattern with `PCRE2_AUTO_CALLOUT`, registers an compiles the requested pattern with `PCRE2_AUTO_CALLOUT`, registers an
application callback, and copies only: application callback, and copies only:

View File

@@ -20,8 +20,16 @@ common capture forms. It does not infer branch-reset numbering or claim full
nesting; authoritative compilation, capture counts, names and result ranges nesting; authoritative compilation, capture counts, names and result ranges
come from PCRE2 itself. come from PCRE2 itself.
The Python and Java providers likewise emit deliberately partial lexical trees.
They recognize their documented capture and replacement forms for navigation
and display, but do not claim full grammar acceptance. CPython `re` and TeaVM
`Pattern` remain authoritative for compilation. Java capture names displayed
with native numbered spans come from the bounded lexical metadata; they are not
invented as a TeaVM-reported name table.
The explanation tree is structural. It is never called an actual execution The explanation tree is structural. It is never called an actual execution
trace. Selecting a node selects and scrolls its exact editor range; selecting trace. Selecting a node selects and scrolls its exact editor range; selecting
pattern text chooses the smallest enclosing node. PCRE2s separate trace viewer pattern text chooses the smallest enclosing node. PCRE2s separate trace viewer
contains actual bounded automatic callouts; its movement labels remain a contains actual bounded automatic callouts; its movement labels remain a
distinct derived layer and are never copied into the structural explanation. distinct derived layer and are never copied into the structural explanation.
Python and Java expose no actual execution trace.

View File

@@ -18,10 +18,14 @@ budget. Consumers must inspect the preview status rather than treating a
preview as complete. In particular, list export is disabled if its template preview as complete. In particular, list export is disabled if its template
would consume an incomplete value. would consume an incomplete value.
Repeated ECMAScript captures expose only the final retained capture. The UI Repeated ECMAScript, PCRE2, Python and Java captures expose only the final
labels this limitation and does not invent history. Later .NET support may add retained capture. The UI labels this limitation and does not invent history.
actual engine capture-history nodes without degrading them to this model. Later .NET support may add actual engine capture-history nodes without
degrading them to this model.
The syntactic hierarchy remains useful even if actual spans overlap or fall The syntactic hierarchy remains useful even if actual spans overlap or fall
outside a parent's geometry; native and normalized ranges are preserved rather outside a parent's geometry; native and normalized ranges are preserved rather
than “fixed”. than “fixed”. ECMAScript and Java native offsets are UTF-16 code units, PCRE2
native offsets are UTF-8 bytes and Python native offsets are Unicode code
points. All editor ranges are normalized to UTF-16 without discarding the
native unit.

View File

@@ -1,15 +1,15 @@
# Flavour support # Flavour support
| Flavour | Syntax | Execution | Replacement | Captures | Trace | Analysis | Generated cases | Native offsets | | Flavour | Syntax | Execution | Replacement | Captures | Trace | Analysis | Generated cases | Native offsets |
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------ | ---------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------- | ------------------- | | ---------------------- | ------------------------------------------------------------ | --------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------- | -------------- |
| ECMAScript | regexpp 4.12.2; explanations partial/feature-matrix tested | Current browser `RegExp`; conformance tested | Bounded ECMAScript `GetSubstitution` | Named/numbered; final repeated capture; no history | Unavailable | Experimental static heuristics + bounded native probes | Bounded normalized-AST sampling; actual-engine verified | UTF-16 | | ECMAScript | regexpp 4.12.2; explanations partial/feature-matrix tested | Current browser `RegExp`; conformance tested | Bounded ECMAScript `GetSubstitution` | Named/numbered; final repeated capture; no history | Unavailable | Experimental static heuristics + bounded native probes | Bounded normalized-AST sampling; actual-engine verified | UTF-16 |
| PCRE2 10.47 | Application lexical provider partial; compiler authoritative | Pinned 8-bit WASM; bounded/killable/conformance tested | Native bounded `pcre2_substitute()` loop | Native named/numbered records; no capture history | Bounded reported automatic callouts; derived movement labels | Unavailable; ECMAScript heuristics are never applied | Unavailable; structural syntax provider is partial | UTF-8 bytes | | PCRE2 10.47 | Application lexical provider partial; compiler authoritative | Pinned 8-bit WASM; bounded/killable/conformance tested | Native bounded `pcre2_substitute()` loop | Native named/numbered records; no capture history | Bounded reported automatic callouts; derived movement labels | Unavailable; ECMAScript heuristics are never applied | Unavailable; structural syntax provider is partial | UTF-8 bytes |
| Python `re` | Application lexical provider partial; compiler authoritative | CPython 3.14.2 in self-hosted Pyodide 314.0.3 | Native bounded `re` expansion | Native named/numbered spans; no capture history | Unavailable | Unavailable; ECMAScript heuristics are never applied | Unavailable; structural syntax provider is partial | Code points |
| Java `java.util.regex` | Application lexical provider partial; compiler authoritative | TeaVM 0.15.0 class library; bounded/killable; not OpenJDK | TeaVM single-digit `$n`; no `${name}` | Native numbered ranges; lexical names; no history | Unavailable | Unavailable; ECMAScript heuristics are never applied | Unavailable; structural syntax provider is partial | UTF-16 |
| PCRE | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | | PCRE | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable |
| Python `re` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned code points | | Go `regexp` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned bytes |
| Go `regexp` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned UTF-8 bytes | | Rust `regex` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned bytes |
| Rust `regex` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned UTF-8 bytes |
| .NET | Unavailable | Unavailable | Unavailable | Planned capture history | Unavailable | Unavailable | Unavailable | Planned UTF-16 | | .NET | Unavailable | Unavailable | Unavailable | Planned capture history | Unavailable | Unavailable | Unavailable | Planned UTF-16 |
| Java | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned UTF-16 |
ECMAScript tests cover its complete exposed flag matrix, internal indices, ECMAScript tests cover its complete exposed flag matrix, internal indices,
zero-length iteration, captures, lookbehind, backreferences, Unicode properties zero-length iteration, captures, lookbehind, backreferences, Unicode properties
@@ -22,6 +22,19 @@ and result/output truncation. Trace tests cover reported automatic callouts,
bounded mark copying, native stop at the complete-event cap, compile ranges, bounded mark copying, native stop at the complete-event cap, compile ranges,
worker loading and browser rendering. worker loading and browser rendering.
Python tests cover application-level `g`, CPython flags `a`, `i`, `m`, `s` and
`x`, Python-only named-group/replacement syntax, native `re` substitution,
astral code-point/UTF-16 normalization, zero-length iteration, compile errors,
result limits, runtime identity and worker recovery.
Java tests cover application-level `g`, TeaVM `Pattern` flags, Java named-group
syntax, TeaVM's native single-digit `$n` `Matcher` replacement, explicit
rejection of unsupported `${name}`, astral UTF-16 ranges, zero-length
iteration, compile errors, result limits, module identity and worker recovery.
The runtime and UI explicitly report that TeaVM 0.15.0 is not OpenJDK and does
not implement OpenJDK `Pattern.UNICODE_CHARACTER_CLASS`; `U` is a compatibility
request rather than an exact implementation of that flag.
ECMAScript static findings and generated-input timing observations are ECMAScript static findings and generated-input timing observations are
advisory. They describe only the selected ECMAScript 2025 pattern, browser advisory. They describe only the selected ECMAScript 2025 pattern, browser
runtime and bounded inputs; they are not proofs of safety or general runtime and bounded inputs; they are not proofs of safety or general
@@ -30,13 +43,14 @@ complexity.
ECMAScript generated-case version 1 uses only a complete accepted normalized ECMAScript generated-case version 1 uses only a complete accepted normalized
AST, an explicit deterministic seed and bounded sampling strategies. Every AST, an explicit deterministic seed and bounded sampling strategies. Every
retained positive or negative label is confirmed by the actual selected retained positive or negative label is confirmed by the actual selected
browser engine. PCRE2 generation is unavailable because its current lexical browser engine. PCRE2, Python and Java generation are unavailable because
provider is intentionally partial; ECMAScript behavior is never relabelled as their current lexical providers are intentionally partial; ECMAScript behavior
PCRE2. is never relabelled as another flavour.
The PCRE2 explanation provider deliberately does not invent full structural The PCRE2, Python and Java explanation providers deliberately do not invent
coverage. In particular, branch-reset numbering is omitted from the syntax tree full structural coverage. PCRE2 branch-reset numbering is omitted from the
while actual capture numbers and names still come from PCRE2 results. syntax tree, while actual capture numbers, names and ranges still come from the
selected runtime result.
ECMAScript and PCRE2 support exact-request comparison for syntax acceptance, ECMAScript and PCRE2 support exact-request comparison for syntax acceptance,
engine compilation, matches, captures and engine-native replacement. Results engine compilation, matches, captures and engine-native replacement. Results
@@ -47,8 +61,14 @@ equivalent results.
Pattern formatting is available only for an exact accepted ECMAScript regexpp Pattern formatting is available only for an exact accepted ECMAScript regexpp
snapshot. It canonicalizes literal slashes and raw control/line-separator snapshot. It canonicalizes literal slashes and raw control/line-separator
representations, then requires paired actual-engine and applicable exact-test representations, then requires paired actual-engine and applicable exact-test
validation before apply. PCRE2 formatting is unavailable because the current validation before apply. PCRE2, Python and Java formatting is unavailable
provider is deliberately partial; its syntax is never treated as ECMAScript. because those providers are deliberately partial; their syntax is never
treated as ECMAScript.
Subject minimization accepts only the verified ECMAScript and PCRE2 paths.
Python and Java selections are disabled rather than being minimized through a
different flavour. Semantic comparison is likewise an explicit
ECMAScript/PCRE2 operation; it is not a generic four-way equivalence checker.
PCRE2 is the only current code-generation target. The reviewed C17 generator is PCRE2 is the only current code-generation target. The reviewed C17 generator is
pinned to the PCRE2 10.47 8-bit API; other language generators remain pinned to the PCRE2 10.47 8-bit API; other language generators remain

View File

@@ -23,8 +23,9 @@ pretty-printer. The transform is idempotent.
Formatting requires the exact current, accepted, non-recovered syntax snapshot Formatting requires the exact current, accepted, non-recovered syntax snapshot
from the bundled `@eslint-community/regexpp` provider. Transformations are from the bundled `@eslint-community/regexpp` provider. Transformations are
derived from the provider's literal-token ranges rather than from a heuristic derived from the provider's literal-token ranges rather than from a heuristic
text scan. A stale parse, a malformed pattern, a partial provider, PCRE2 or any text scan. A stale parse, a malformed pattern, a partial provider, PCRE2,
other flavour is reported as unavailable and is never silently rewritten. Python, Java or any other flavour is reported as unavailable and is never
silently rewritten.
## Mandatory validation before apply ## Mandatory validation before apply

View File

@@ -25,10 +25,11 @@ The coverage report says `covered`, `partial`, `unsupported` or
sampling strategy was requested. It does not mean every string in the regular sampling strategy was requested. It does not mean every string in the regular
language was enumerated. language was enumerated.
PCRE2 generation is unavailable in version 1. Its actual engine is complete for PCRE2, Python and Java generation is unavailable in version 1. Their actual
the advertised execution operations, but its current structural syntax engines are complete for the advertised execution operations, but their
provider is intentionally partial. Regex Tools does not feed PCRE2 syntax to current structural syntax providers are intentionally partial. Regex Tools
the ECMAScript generator or relabel ECMAScript behavior. does not feed their syntax to the ECMAScript generator or relabel ECMAScript
behaviour.
## Unsupported and partial constructs ## Unsupported and partial constructs
@@ -75,7 +76,9 @@ removes the generation provenance.
Candidate synthesis runs in a dedicated killable worker. The verifier then Candidate synthesis runs in a dedicated killable worker. The verifier then
sends each exact candidate, pattern, flavour version, flags, engine options and sends each exact candidate, pattern, flavour version, flags, engine options and
scan mode through `EngineSupervisor`, which selects the registered real engine scan mode through `EngineSupervisor`, which selects the registered real engine
worker. worker. Version 1's eligibility gate accepts only the exact current ECMAScript
snapshot; the generic supervisor boundary does not make partial-provider
flavours eligible.
- An intended positive is labelled `should-match` only after the engine returns - An intended positive is labelled `should-match` only after the engine returns
at least one match. at least one match.

View File

@@ -10,6 +10,10 @@ selected real ECMAScript or PCRE2 worker. It can preserve:
- the same one-sided comparison timeout while the other engine completes with - the same one-sided comparison timeout while the other engine completes with
authoritative, untruncated output. authoritative, untruncated output.
Python and Java are not supported minimization targets in v0.3.0. The workspace
disables reduction for those active flavours; it does not substitute
ECMAScript, PCRE2 or a lexical provider as an execution oracle.
The fixed pattern, ordered flags, options, replacement and engine versions form The fixed pattern, ordered flags, options, replacement and engine versions form
the target identity. Syntax is parsed once to obtain capture metadata. Every the target identity. Syntax is parsed once to obtain capture metadata. Every
accepted subject candidate is then verified through the selected execution accepted subject candidate is then verified through the selected execution

View File

@@ -9,11 +9,17 @@ community
It uses only reviewed open-source dependencies and builds without private It uses only reviewed open-source dependencies and builds without private
registry credentials. ECMAScript uses regexpp 4.12.2. PCRE2 uses an registry credentials. ECMAScript uses regexpp 4.12.2. PCRE2 uses an
application-owned lexical provider for partial explanations and the actual application-owned lexical provider for partial explanations and the actual
bundled PCRE2 compiler for authoritative acceptance. bundled PCRE2 compiler for authoritative acceptance. Python and Java use
application-owned lexical providers for partial explanations and
flavour-native replacement tokens; bundled CPython `re` and TeaVM
`java.util.regex` respectively remain authoritative for compilation.
The Java provider follows the bundled TeaVM 0.15.0 replacement subset:
single-digit `$n` is executable, `$12` is `$1` followed by literal `2`, and
Java SE `${name}` references are surfaced as a compatibility limitation.
The product deliberately does not implement or reference an `@r101/parser` The product deliberately does not implement or reference an `@r101/parser`
profile. No commercial package alias, import, tarball, credential, licence key profile. No commercial package alias, import, tarball, credential, licence key
or private CI path exists. Additional flavour syntax will be implemented or private CI path exists. Additional flavour syntax is implemented
incrementally as open-source providers behind the same application-owned incrementally as open-source providers behind the same application-owned
interface. Partial coverage is exposed as metadata and never presented as a interface. Partial coverage is exposed as metadata and never presented as a
complete parser. complete parser.

View File

@@ -10,12 +10,22 @@ browser, engine version, pattern and subject. The UI therefore reports observed
elapsed time and exact runtime identity but makes no universal throughput or elapsed time and exact runtime identity but makes no universal throughput or
safety claim. safety claim.
PCRE2 cold execution includes loading and instantiating the self-hosted PCRE2 cold worker startup includes loading and instantiating the self-hosted
WebAssembly module inside its worker; warm requests reuse that instance. WebAssembly module; warm requests reuse that instance.
Every request still compiles and frees its pattern so no unbounded compiled-code Every request still compiles and frees its pattern so no unbounded compiled-code
cache can accumulate. Native match-step, depth and heap limits complement the cache can accumulate. Native match-step, depth and heap limits complement the
supervisor wall clock, and cancellation discards the complete worker heap. supervisor wall clock, and cancellation discards the complete worker heap.
Python has a materially larger cold start because its worker loads the
self-hosted Pyodide JavaScript/WebAssembly runtime and Python standard library
before verifying CPython and `re`. Java imports and self-tests its bundled
TeaVM ES module. Engine loading is an explicit operation with a
flavour-specific startup deadline; it is not charged to the 250 ms live or
selected manual execution timeout. Warm requests reuse the verified worker.
Killing a worker for timeout, crash or cancellation discards its complete
runtime, so the next request pays the cold-load cost and verifies identity
again.
Automatic-callout tracing is deliberately excluded from every benchmark. It Automatic-callout tracing is deliberately excluded from every benchmark. It
compiles an independently instrumented PCRE2 pattern in a separate worker and compiles an independently instrumented PCRE2 pattern in a separate worker and
can materially change runtime work. The trace path retains at most 50,000 can materially change runtime work. The trace path retains at most 50,000
@@ -44,12 +54,12 @@ pages of 200. Individual result-value previews are capped at 64 Ki UTF-16 units
and share an 8 Mi-unit aggregate engine-result budget. The extraction tree and share an 8 Mi-unit aggregate engine-result budget. The extraction tree
shows 100 UTF-16 units per value and capture-table cells show 512, with explicit shows 100 UTF-16 units per value and capture-table cells show 512, with explicit
range lengths and separate engine-prefix labels; copy and eligible JSON export range lengths and separate engine-prefix labels; copy and eligible JSON export
retain the bounded engine value. List export is disabled when a source value is incomplete, so a bounded retain the bounded engine value. List export is disabled when a source value is
preview cannot be mistaken for the full value. List generation additionally incomplete, so a bounded preview cannot be mistaken for the full value. List
stops at 250,000 template-token evaluations, a 64 Ki UTF-16 row preview, or an generation additionally stops at 250,000 template-token evaluations, a 64 Ki
8 Mi-unit aggregate materialization bound. The UI shows 500 list rows while an UTF-16 row preview, or an 8 Mi-unit aggregate materialization bound. The UI
eligible export still contains every generated row; any incomplete result shows 500 list rows while an eligible export still contains every generated
disables export. row; any incomplete result disables export.
Replacement output has a separate 64 MiB bound. Match/capture-limit Replacement output has a separate 64 MiB bound. Match/capture-limit
incompleteness and output-byte truncation are tracked separately; either makes incompleteness and output-byte truncation are tracked separately; either makes
the aggregate replacement result incomplete. the aggregate replacement result incomplete.

View File

@@ -9,17 +9,23 @@ Required delivery behavior:
revalidation/no-cache resources. revalidation/no-cache resources.
- Hashed Vite assets may use immutable long-term caching. - Hashed Vite assets may use immutable long-term caching.
- CSP must allow `worker-src 'self' blob:`. - CSP must allow `worker-src 'self' blob:`.
- `engines/pcre2/pcre2.wasm` must use `application/wasm`. - `engines/pcre2/pcre2.wasm` and
`engines/python/pyodide.asm.wasm` must use `application/wasm`.
- Engine `.mjs` files must use a JavaScript MIME type;
`engines/python/python_stdlib.zip`, JSON, licence, notice and checksum files
must be delivered without content transformation.
- `script-src` must permit self-hosted WebAssembly compilation, normally with - `script-src` must permit self-hosted WebAssembly compilation, normally with
`'wasm-unsafe-eval'`; broad `'unsafe-eval'` is neither needed nor recommended. `'wasm-unsafe-eval'`; broad `'unsafe-eval'` is neither needed nor recommended.
- `engines/pcre2/pcre2.mjs`, metadata, checksums, licence and WASM should use - `connect-src 'self'` must allow the Pyodide worker to fetch its own
revalidation or a release-scoped immutable cache. They are not same-origin WebAssembly, standard-library and lock files.
- Every file below `engines/pcre2/`, `engines/python/` and `engines/java/`
should use revalidation or a release-scoped immutable cache. These are not
content-hashed filenames and must never drift under one release identity. content-hashed filenames and must never drift under one release identity.
The Portal must consume the immutable release ZIP, verify its SHA-256, verify The Portal must consume the immutable release ZIP, verify its SHA-256, verify
manifest ID `de.add-ideas.regex-tools` and version `0.2.0`, and mount it at a manifest ID `de.add-ideas.regex-tools` and version `0.3.0`, and mount it at a
relative target such as `apps/regex/`. relative target such as `apps/regex/`.
The package, manifest, tag, artifact URL and checksum must all use the v0.2.0 The package, manifest, tag, artifact URL and checksum must all use the v0.3.0
identity. The historical v0.1.0 artifact coordinates remain immutable and must identity. Historical artifact coordinates remain immutable and must not be
not be overwritten or reused. overwritten or reused.

View File

@@ -1,14 +1,17 @@
# Reference implementations and sources # Reference implementations and sources
Inspection date: 2026-07-24. Inspection date: 2026-07-27.
| Reference | Exact revision/version | Licence | Use and copying status | | Reference | Exact revision/version | Licence | Use and copying status |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------- | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Regex Tools | Unborn repository at initial inspection; release identity `v0.1.0` | GPL-3.0-or-later | Application source written for this project. | | Regex Tools | Release identity `v0.3.0` | GPL-3.0-or-later | Application source written for this project. |
| Toolbox SDK | `53c40a61ba1581246f65773fcbb1c1cfd31ac98e` / 0.2.2 | Apache-2.0 | Contract, shell and release conventions; package APIs used, no source adapted. | | Toolbox SDK | `53c40a61ba1581246f65773fcbb1c1cfd31ac98e` / 0.2.2 | Apache-2.0 | Contract, shell and release conventions; package APIs used, no source adapted. |
| Toolbox Portal | `a9c31c8986c40a0097966318e925083302e91e13` / 0.5.0 | AGPL-3.0-only | Assembly/lock conventions inspected; no source copied into the app. | | Toolbox Portal | `a9c31c8986c40a0097966318e925083302e91e13` / 0.5.0 | AGPL-3.0-only | Assembly/lock conventions inspected; no source copied into the app. |
| regexpp | 4.12.2; npm integrity `sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==` | MIT | Pinned ECMAScript syntax provider. Provider AST is normalized in a worker. | | regexpp | 4.12.2; npm integrity `sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==` | MIT | Pinned ECMAScript syntax provider. Provider AST is normalized in a worker. |
| PCRE2 | signed tag 10.47; commit `f454e231fe5006dd7ff8f4693fd2b8eb94333429` | BSD-3-Clause WITH PCRE2-exception | Pinned offline build; generated verified WebAssembly pack is shipped, source is not vendored. | | PCRE2 | signed tag 10.47; commit `f454e231fe5006dd7ff8f4693fd2b8eb94333429` | BSD-3-Clause WITH PCRE2-exception | Pinned offline build; generated verified WebAssembly pack is shipped, source is not vendored. |
| Pyodide | tag 314.0.3; commit `ac57031be7564f864d061cb37c5c152e59f83ad4`; pinned npm integrity | MPL-2.0 | Reviewed self-hosted runtime pack; no CDN use. |
| CPython | executable runtime identity 3.14.2 in Pyodide 314.0.3 | PSF-2.0 | Actual `re` compiler, matcher and replacement runtime; Python source is not vendored. |
| TeaVM | tag/commit `ee91b03e616c4b45401cd11fb0cd7eb0daf6649b` / 0.15.0 | Apache-2.0 | `java.util.regex` class library compiled into the verified module; not OpenJDK. |
| ECMAScript specification | <https://tc39.es/ecma262/>; living specification inspected 2026-07-24 | ECMA terms | Semantics reference; no prose copied. | | ECMAScript specification | <https://tc39.es/ecma262/>; living specification inspected 2026-07-24 | ECMA terms | Semantics reference; no prose copied. |
| regex101 | <https://regex101.com/> and public parser documentation | Product/commercial terms | Product reference only. No source, branding, explanations, reference prose or visual design copied. | | regex101 | <https://regex101.com/> and public parser documentation | Product/commercial terms | Product reference only. No source, branding, explanations, reference prose or visual design copied. |
| `recheck` | 4.5.0 | MIT | Metadata inspected; not installed or shipped. | | `recheck` | 4.5.0 | MIT | Metadata inspected; not installed or shipped. |
@@ -17,5 +20,5 @@ Inspection date: 2026-07-24.
| RegexLib | Website inspected | Redistribution licence not established | No fixture copied. | | RegexLib | Website inspected | Redistribution licence not established | No fixture copied. |
| RGXP.RU | Website inspected | Redistribution licence not established | No fixture copied. | | RGXP.RU | Website inspected | Redistribution licence not established | No fixture copied. |
All v0.1.0 and v0.2.0 conformance cases are project-authored. See All v0.1.0, v0.2.0 and v0.3.0 conformance cases are project-authored. See
`tests/fixtures/README.md`. `tests/fixtures/README.md`.

View File

@@ -18,9 +18,13 @@ on publication failure, replaces an existing exact version only with
`--force`, and produces: `--force`, and produces:
```text ```text
release/regex-tools-0.2.0.zip release/regex-tools-0.3.0.zip
release/regex-tools-0.2.0.zip.sha256 release/regex-tools-0.3.0.zip.sha256
``` ```
The existing `release/regex-tools-0.1.0.zip` and matching sidecar are historical Before packaging, the engine verifier checks the closed PCRE2, Python and Java
immutable artifacts. Creating v0.2.0 must not replace, rename or remove them. file sets in `dist/`, including checksums, metadata, licences/notices, module
and WebAssembly identities and self-hosted relative asset references.
The existing v0.1.0 and v0.2.0 ZIPs and matching sidecars are historical
immutable artifacts. Creating v0.3.0 must not replace, rename or remove them.

View File

@@ -19,6 +19,18 @@ untrusted.
- PCRE2 runs only in mandatory UTF/UCP mode. Exact UTF-8 byte boundaries are - PCRE2 runs only in mandatory UTF/UCP mode. Exact UTF-8 byte boundaries are
normalized to editor UTF-16 offsets; lone surrogates are rejected instead of normalized to editor UTF-16 offsets; lone surrogates are rejected instead of
being silently replaced by browser encoding. being silently replaced by browser encoding.
- Python starts only after the worker verifies Pyodide, CPython and `re`
identities. Pattern, subject, flags and replacement remain string data passed
to the fixed application bridge; no user-supplied Python source is evaluated.
Native code-point boundaries are checked before conversion to editor UTF-16.
- Java starts only after the worker verifies the TeaVM bridge ABI, engine
identity and compiled self-test. It exposes only fixed bounded
`Pattern`/`Matcher` entry points; no JVM, Java class loading or user-supplied
JavaScript is available. The runtime is TeaVM, not OpenJDK.
- Every bundled engine directory has a closed file set, deterministic
metadata/checksums and licence material verified both before installation and
in `dist/`. Runtime imports and fetches resolve only against the same-origin
release path.
- Replacement templates are strings; executable callbacks are unavailable. - Replacement templates are strings; executable callbacks are unavailable.
- Replacement/list token decorations, rows, chips, diagnostics and list - Replacement/list token decorations, rows, chips, diagnostics and list
evaluation work have separate presentation/operation caps; incomplete list evaluation work have separate presentation/operation caps; incomplete list
@@ -72,10 +84,14 @@ untrusted.
predicate check runs in the selected killable engine worker. Timeout, crash, predicate check runs in the selected killable engine worker. Timeout, crash,
cancellation and worker error remain distinct; truncated results fail cancellation and worker error remain distinct; truncated results fail
closed. Subjects and results are ephemeral and never uploaded or persisted. closed. Subjects and results are ephemeral and never uploaded or persisted.
- No backend, uploads, telemetry, remote corpus URL, CDN, remote font, `eval`, - No backend, uploads, telemetry, remote corpus URL, CDN, remote font,
`Function`, arbitrary command line or unsafe HTML rendering exists. arbitrary command line or unsafe HTML rendering exists. Application code
does not evaluate user-controlled JavaScript or Python. The generic generated
Pyodide loader contains an Emscripten dynamic-link branch that uses
JavaScript `eval`, but the shipped self-contained CPython/stdlib path does not
exercise it and loads under the CSP below without broad `'unsafe-eval'`.
The PCRE2-enabled build needs a CSP equivalent to: The engine-enabled build needs a CSP equivalent to:
```text ```text
default-src 'self'; default-src 'self';
@@ -91,9 +107,11 @@ base-uri 'none';
form-action 'none'; form-action 'none';
``` ```
The Toolbox shell currently requires inline styles. Corpus ZIP generation and The Toolbox shell currently requires inline styles. Corpus ZIP generation,
PCRE2 use self-hosted modules and local workers under the existing PCRE2, Pyodide and TeaVM use self-hosted modules/assets and local workers under
`worker-src` policy. Broad `unsafe-eval` is not required. the existing `worker-src` and `connect-src` policy. Broad `'unsafe-eval'` is not
required; `'wasm-unsafe-eval'` remains required for the two WebAssembly
runtimes.
Report vulnerabilities privately to the repository owner before public issue Report vulnerabilities privately to the repository owner before public issue
details are posted. details are posted.

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,11 @@
This product includes software developed by Alexey Andreev
(http://teavm.org).
This product includes software developed by The Apache Software
Foundation (http://www.apache.org/).
=============================================================================
= NOTICE file corresponding to section 4d of the Apache License Version 2.0 =
=============================================================================
This product includes software developed by
Joda.org (http://www.joda.org/).

25
engines/java/README.md Normal file
View File

@@ -0,0 +1,25 @@
# TeaVM Java regular-expression engine
This project compiles a bounded bridge around TeaVM 0.15.0's
`java.util.regex.Pattern` and `Matcher` class-library implementation to a
minified ES2015 module.
The runtime is intentionally identified as `TeaVM java.util.regex 0.15.0`.
TeaVM supplies its own Apache Harmony-derived Java class-library subset; the
generated engine is not OpenJDK and must not be presented as exact OpenJDK
semantics.
The `U` application flag is a compatibility request. TeaVM 0.15.0 does not
implement OpenJDK's `Pattern.UNICODE_CHARACTER_CLASS`; the bridge implies
Unicode case folding but retains TeaVM's predefined-character-class and word
boundary behaviour. The TypeScript adapter reports this boundary.
Replacement is delegated to TeaVM 0.15.0's native `Matcher` implementation.
That implementation recognizes only a single decimal digit after `$` (`$0`
through `$9`, subject to the compiled group count). It does not implement
OpenJDK's `${name}` named-replacement syntax; the bridge returns a
`replacement-error` when such a replacement is attempted.
Maven builds `target/generated/java-regex.mjs`. The repository pack script
verifies the pinned source identity, bridge source hashes, licence/notice and
generated module before it can be installed under `public/engines/java`.

84
engines/java/pom.xml Normal file
View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.addideas.regextools</groupId>
<artifactId>java-regex-engine</artifactId>
<version>0.1.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<project.build.outputTimestamp>2026-06-06T15:27:10Z</project.build.outputTimestamp>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<teavm.version>0.15.0</teavm.version>
</properties>
<dependencies>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-classlib</artifactId>
<version>${teavm.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-jso</artifactId>
<version>${teavm.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${maven.compiler.release}</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<outputTimestamp>${project.build.outputTimestamp}</outputTimestamp>
</configuration>
</plugin>
<plugin>
<groupId>org.teavm</groupId>
<artifactId>teavm-maven-plugin</artifactId>
<version>${teavm.version}</version>
<executions>
<execution>
<id>java-regex-browser-engine</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<mainClass>de.addideas.regextools.javaengine.RegexBridge</mainClass>
<targetDirectory>${project.build.directory}/generated</targetDirectory>
<targetFileName>java-regex.mjs</targetFileName>
<targetType>JAVASCRIPT</targetType>
<jsModuleType>ES2015</jsModuleType>
<minifying>true</minifying>
<optimizationLevel>ADVANCED</optimizationLevel>
<debugInformationGenerated>false</debugInformationGenerated>
<sourceMapsGenerated>false</sourceMapsGenerated>
<sourceFilesCopied>false</sourceFilesCopied>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,346 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* The generated module links TeaVM 0.15.0 class-library code distributed
* under Apache-2.0. Its runtime identity is TeaVM java.util.regex, not OpenJDK.
*/
package de.addideas.regextools.javaengine;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.teavm.jso.JSExport;
public final class RegexBridge {
private static final int BRIDGE_ABI_VERSION = 1;
private static final String ENGINE_IDENTITY = "TeaVM java.util.regex 0.15.0";
private static final int MAXIMUM_CAPTURE_GROUPS = 1_000;
private static final int MAXIMUM_NATIVE_OUTPUT_UTF16 = 64 * 1024 * 1024;
private static final int FLAG_CASE_INSENSITIVE = 1 << 0;
private static final int FLAG_MULTILINE = 1 << 1;
private static final int FLAG_DOTALL = 1 << 2;
private static final int FLAG_UNICODE_CASE = 1 << 3;
private static final int FLAG_COMMENTS = 1 << 4;
private static final int FLAG_UNICODE_CHARACTER_COMPATIBILITY = 1 << 5;
private static final int FLAG_UNIX_LINES = 1 << 6;
private RegexBridge() {
}
public static void main(String[] arguments) {
// TeaVM requires a main class. All useful entry points are JS exports.
}
@JSExport
public static int bridgeAbiVersion() {
return BRIDGE_ABI_VERSION;
}
@JSExport
public static String engineIdentity() {
return ENGINE_IDENTITY;
}
@JSExport
public static int selfTest() {
try {
Matcher matcher = Pattern.compile("(?<=a)(b)\\1").matcher("abb");
if (!matcher.find() || matcher.start() != 1 || matcher.end() != 3
|| matcher.start(1) != 1 || matcher.end(1) != 2) {
return 1;
}
StringBuffer output = new StringBuffer();
matcher.appendReplacement(output, "<$1>");
matcher.appendTail(output);
return "a<b>".contentEquals(output) ? 0 : 2;
} catch (RuntimeException failure) {
return 3;
}
}
@JSExport
public static String execute(
String pattern,
String subject,
int applicationFlags,
boolean iterate,
int maximumMatches,
int maximumCaptureRows) {
return run(
pattern,
subject,
applicationFlags,
iterate,
maximumMatches,
maximumCaptureRows,
null);
}
@JSExport
public static String replace(
String pattern,
String subject,
String replacement,
int applicationFlags,
boolean iterate,
int maximumMatches,
int maximumCaptureRows) {
return run(
pattern,
subject,
applicationFlags,
iterate,
maximumMatches,
maximumCaptureRows,
replacement);
}
private static String run(
String patternText,
String subject,
int applicationFlags,
boolean iterate,
int maximumMatches,
int maximumCaptureRows,
String replacement) {
if (patternText == null || subject == null) {
return failure("bridge-error", "NullPointerException",
"Pattern and subject must not be null.", -1);
}
if (maximumMatches < 1 || maximumCaptureRows < 1) {
return failure("limit-error", "IllegalArgumentException",
"Result limits must be positive.", -1);
}
Pattern pattern;
try {
pattern = Pattern.compile(patternText, nativeFlags(applicationFlags));
} catch (PatternSyntaxException failure) {
String message = failure.getDescription();
if (message == null || message.length() == 0) {
message = failure.getMessage();
}
return failure("compile-error", "PatternSyntaxException", message, failure.getIndex());
} catch (RuntimeException failure) {
return failure("compile-error", failure.getClass().getSimpleName(),
safeMessage(failure, "TeaVM rejected the pattern."), -1);
}
Matcher matcher = pattern.matcher(subject);
int groupCount = matcher.groupCount();
if (groupCount > MAXIMUM_CAPTURE_GROUPS) {
return failure("limit-error", "CaptureGroupLimit",
"The pattern exceeds the 1,000 capture-group execution limit.", -1);
}
StringBuilder records = new StringBuilder();
StringBuffer output = replacement == null ? null : new StringBuffer();
int matchCount = 0;
int captureRows = 0;
int lastReplacementEnd = 0;
boolean resultsTruncated = false;
boolean outputTruncated = false;
try {
while (matcher.find()) {
int rowsForMatch = Math.max(1, groupCount);
if (matchCount >= maximumMatches
|| captureRows + rowsForMatch > maximumCaptureRows) {
resultsTruncated = true;
break;
}
appendNumber(records, matcher.start());
appendNumber(records, matcher.end());
for (int group = 1; group <= groupCount; group++) {
appendNumber(records, matcher.start(group));
appendNumber(records, matcher.end(group));
}
matchCount++;
captureRows += rowsForMatch;
if (output != null && !outputTruncated) {
long expansionLength = replacementExpansionLength(matcher, replacement);
long projectedLength = (long) output.length()
+ matcher.start() - lastReplacementEnd
+ expansionLength;
if (projectedLength > MAXIMUM_NATIVE_OUTPUT_UTF16) {
appendBounded(
output,
subject,
lastReplacementEnd,
matcher.start(),
MAXIMUM_NATIVE_OUTPUT_UTF16);
outputTruncated = true;
} else {
matcher.appendReplacement(output, replacement);
lastReplacementEnd = matcher.end();
}
}
if (!iterate) {
break;
}
}
if (output != null && !outputTruncated) {
long projectedLength = (long) output.length()
+ subject.length() - lastReplacementEnd;
if (projectedLength > MAXIMUM_NATIVE_OUTPUT_UTF16) {
appendBounded(
output,
subject,
lastReplacementEnd,
subject.length(),
MAXIMUM_NATIVE_OUTPUT_UTF16);
outputTruncated = true;
} else {
matcher.appendTail(output);
}
}
} catch (RuntimeException failure) {
String kind = replacement == null ? "execution-error" : "replacement-error";
String fallback = replacement == null
? "TeaVM failed while matching."
: "TeaVM Matcher rejected the replacement.";
return failure(kind, failure.getClass().getSimpleName(),
safeMessage(failure, fallback), -1);
}
FieldWriter result = new FieldWriter();
result.field("1");
result.field("ok");
result.number(groupCount);
result.bool(resultsTruncated);
result.number(matchCount);
result.raw(records);
if (output != null) {
result.bool(outputTruncated);
result.field(output.toString());
}
return result.toString();
}
private static int nativeFlags(int applicationFlags) {
int flags = 0;
if ((applicationFlags & FLAG_CASE_INSENSITIVE) != 0) {
flags |= Pattern.CASE_INSENSITIVE;
}
if ((applicationFlags & FLAG_MULTILINE) != 0) {
flags |= Pattern.MULTILINE;
}
if ((applicationFlags & FLAG_DOTALL) != 0) {
flags |= Pattern.DOTALL;
}
if ((applicationFlags & FLAG_UNICODE_CASE) != 0
|| (applicationFlags & FLAG_UNICODE_CHARACTER_COMPATIBILITY) != 0) {
flags |= Pattern.UNICODE_CASE;
}
if ((applicationFlags & FLAG_COMMENTS) != 0) {
flags |= Pattern.COMMENTS;
}
if ((applicationFlags & FLAG_UNIX_LINES) != 0) {
flags |= Pattern.UNIX_LINES;
}
return flags;
}
private static long replacementExpansionLength(Matcher matcher, String replacement) {
long length = 0;
for (int index = 0; index < replacement.length(); index++) {
char current = replacement.charAt(index);
if (current == '\\') {
index++;
if (index >= replacement.length()) {
throw new IndexOutOfBoundsException();
}
length++;
} else if (current == '$') {
index++;
if (index >= replacement.length()) {
throw new IllegalArgumentException("");
}
int group = Character.digit(replacement.charAt(index), 10);
if (group < 0) {
throw new IllegalArgumentException("");
}
String value = matcher.group(group);
// TeaVM 0.15.0's native replacement path dereferences this
// value too, including its unmatched-capture behaviour.
length += value.length();
} else {
length++;
}
if (length > MAXIMUM_NATIVE_OUTPUT_UTF16) {
return length;
}
}
return length;
}
private static void appendBounded(
StringBuffer output,
String value,
int start,
int end,
int maximumLength) {
int remaining = maximumLength - output.length();
if (remaining <= 0 || start >= end) {
return;
}
int boundedEnd = Math.min(end, start + remaining);
if (boundedEnd < end
&& boundedEnd > start
&& Character.isHighSurrogate(value.charAt(boundedEnd - 1))
&& Character.isLowSurrogate(value.charAt(boundedEnd))) {
boundedEnd--;
}
output.append(value, start, boundedEnd);
}
private static String failure(String status, String name, String message, int index) {
FieldWriter result = new FieldWriter();
result.field("1");
result.field(status);
result.field(name == null ? "Error" : name);
result.field(message == null ? "" : message);
result.number(index);
return result.toString();
}
private static String safeMessage(RuntimeException failure, String fallback) {
String message = failure.getMessage();
return message == null || message.length() == 0 ? fallback : message;
}
private static void appendNumber(StringBuilder output, int value) {
String encoded = Integer.toString(value);
output.append(encoded.length()).append(':').append(encoded);
}
private static final class FieldWriter {
private final StringBuilder output = new StringBuilder();
void field(String value) {
output.append(value.length()).append(':').append(value);
}
void number(int value) {
field(Integer.toString(value));
}
void bool(boolean value) {
field(value ? "1" : "0");
}
void raw(StringBuilder encodedFields) {
output.append(encodedFields);
}
@Override
public String toString() {
return output.toString();
}
}
}

277
engines/python/bridge.py Normal file
View File

@@ -0,0 +1,277 @@
"""Fixed, bounded CPython ``re`` bridge used by the Python engine worker.
This file is bundled as source by Vite and evaluated once while the trusted
Pyodide runtime starts. User-controlled values are passed as function
arguments; they are never evaluated as Python source.
"""
import json
import re
import sys
_BRIDGE_ABI_VERSION = 1
_MAX_CAPTURE_GROUPS = 1_000
_MAX_NATIVE_EXPANSION_CODE_POINTS = 16 * 1024 * 1024
def _json_result(value):
return json.dumps(value, ensure_ascii=True, separators=(",", ":"))
def _error(phase, code, error, position=None):
message = str(error)
if len(message) > 1_024:
message = message[:1_023] + "\N{HORIZONTAL ELLIPSIS}"
result = {
"abi": _BRIDGE_ABI_VERSION,
"ok": False,
"phase": phase,
"code": code,
"message": message,
}
if isinstance(position, int) and position >= 0:
result["position"] = position
return _json_result(result)
def _flag_bits(flags):
bits = 0
if "a" in flags:
bits |= re.ASCII
if "i" in flags:
bits |= re.IGNORECASE
if "m" in flags:
bits |= re.MULTILINE
if "s" in flags:
bits |= re.DOTALL
if "x" in flags:
bits |= re.VERBOSE
return bits
def _match_record(match, group_count):
captures = []
for group_number in range(1, group_count + 1):
start, end = match.span(group_number)
captures.append(None if start < 0 else [start, end])
start, end = match.span(0)
return {"span": [start, end], "captures": captures}
def _utf8_width(character):
code_point = ord(character)
if code_point <= 0x7F:
return 1
if code_point <= 0x7FF:
return 2
if code_point <= 0xFFFF:
return 3
return 4
def _append_utf8_bounded(chunks, value, byte_count, maximum_bytes):
remaining = maximum_bytes - byte_count
if not value:
return byte_count, True
end = 0
for character in value:
width = _utf8_width(character)
if width > remaining:
break
remaining -= width
byte_count += width
end += 1
if end:
chunks.append(value[:end])
return byte_count, end == len(value)
def _expansion_code_points(parsed_template, match):
total = 0
for part in parsed_template:
if isinstance(part, int):
start, end = match.span(part)
if start >= 0:
total += end - start
else:
total += len(part)
if total > _MAX_NATIVE_EXPANSION_CODE_POINTS:
return total
return total
def _identity():
return _json_result(
{
"abi": _BRIDGE_ABI_VERSION,
"ok": True,
"identity": {
"implementation": sys.implementation.name,
"pythonVersion": ".".join(
str(part) for part in sys.version_info[:3]
),
"reModule": re.__name__,
},
}
)
def _regex_tools_run(
operation,
pattern,
flags,
subject,
maximum_matches,
maximum_capture_rows,
replacement,
maximum_output_bytes,
):
if operation == "identity":
return _identity()
if operation not in ("execute", "replace"):
return _error("bridge", "invalid-operation", "Unsupported bridge operation.")
try:
compiled = re.compile(pattern, _flag_bits(flags))
except re.PatternError as error:
return _error(
"compile",
"compile-error",
error,
getattr(error, "pos", None),
)
except Exception as error:
return _error("compile", "compile-error", error)
group_count = compiled.groups
if group_count > _MAX_CAPTURE_GROUPS:
return _error(
"compile",
"capture-group-limit",
(
f"The pattern declares {group_count} capture groups; "
f"the bridge limit is {_MAX_CAPTURE_GROUPS}."
),
)
group_names = sorted(
([group_number, name] for name, group_number in compiled.groupindex.items()),
key=lambda item: item[0],
)
parsed_template = None
if operation == "replace":
try:
parsed_template = re._parser.parse_template(replacement, compiled)
except (re.PatternError, IndexError) as error:
return _error(
"replacement",
"replacement-error",
error,
getattr(error, "pos", None),
)
except Exception as error:
return _error("replacement", "replacement-error", error)
iterate = "g" in flags
try:
matches = compiled.finditer(subject) if iterate else (
candidate for candidate in (compiled.search(subject),) if candidate
)
records = []
capture_rows = 0
results_truncated = False
output_chunks = []
output_bytes = 0
output_truncated = False
output_stopped = False
next_source_position = 0
for match in matches:
rows_for_match = max(1, group_count)
if (
len(records) >= maximum_matches
or capture_rows + rows_for_match > maximum_capture_rows
):
results_truncated = True
break
records.append(_match_record(match, group_count))
capture_rows += rows_for_match
if operation == "replace" and not output_stopped:
start, end = match.span(0)
output_bytes, complete = _append_utf8_bounded(
output_chunks,
subject[next_source_position:start],
output_bytes,
maximum_output_bytes,
)
if not complete:
output_truncated = True
output_stopped = True
else:
expansion_size = _expansion_code_points(
parsed_template, match
)
if expansion_size > _MAX_NATIVE_EXPANSION_CODE_POINTS:
return _error(
"replacement",
"replacement-expansion-limit",
(
"One native match expansion exceeds the "
f"{_MAX_NATIVE_EXPANSION_CODE_POINTS} "
"code-point safety limit."
),
)
expanded = match.expand(replacement)
output_bytes, complete = _append_utf8_bounded(
output_chunks,
expanded,
output_bytes,
maximum_output_bytes,
)
if not complete:
output_truncated = True
output_stopped = True
next_source_position = end
if operation == "replace" and not output_stopped:
output_bytes, complete = _append_utf8_bounded(
output_chunks,
subject[next_source_position:],
output_bytes,
maximum_output_bytes,
)
if not complete:
output_truncated = True
result = {
"abi": _BRIDGE_ABI_VERSION,
"ok": True,
"groupCount": group_count,
"groupNames": group_names,
"matches": records,
"resultsTruncated": results_truncated,
}
if operation == "replace":
result.update(
{
"output": "".join(output_chunks),
"outputBytes": output_bytes,
"outputTruncated": output_truncated,
}
)
return _json_result(result)
except re.PatternError as error:
phase = "replacement" if operation == "replace" else "match"
code = "replacement-error" if operation == "replace" else "match-error"
return _error(phase, code, error, getattr(error, "pos", None))
except Exception as error:
phase = "replacement" if operation == "replace" else "match"
code = "replacement-error" if operation == "replace" else "match-error"
return _error(phase, code, error)
_regex_tools_run

48
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "regex-tools", "name": "regex-tools",
"version": "0.2.0", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "regex-tools", "name": "regex-tools",
"version": "0.2.0", "version": "0.3.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@add-ideas/toolbox-contract": "0.2.2", "@add-ideas/toolbox-contract": "0.2.2",
@@ -39,6 +39,7 @@
"globals": "17.7.0", "globals": "17.7.0",
"jsdom": "29.1.1", "jsdom": "29.1.1",
"prettier": "3.9.5", "prettier": "3.9.5",
"pyodide": "314.0.3",
"typescript": "6.0.3", "typescript": "6.0.3",
"typescript-eslint": "8.64.0", "typescript-eslint": "8.64.0",
"vite": "8.1.5", "vite": "8.1.5",
@@ -1385,6 +1386,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/emscripten": {
"version": "1.41.5",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
"integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/esrecurse": { "node_modules/@types/esrecurse": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
@@ -3405,6 +3413,20 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/pyodide": {
"version": "314.0.3",
"resolved": "https://registry.npmjs.org/pyodide/-/pyodide-314.0.3.tgz",
"integrity": "sha512-sK40My6m8tmBUYtYH9au9rXUeh9x0wfahtHdOlGmJxZDsKBGKtP6KznyFB2+u/klbQTdDionR0uaVd176zVQzQ==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"@types/emscripten": "^1.41.4",
"ws": "^8.5.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.2.7", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
@@ -4101,6 +4123,28 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml-name-validator": { "node_modules/xml-name-validator": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "regex-tools", "name": "regex-tools",
"version": "0.2.0", "version": "0.3.0",
"description": "Develop, explain, test and apply regular expressions locally in the browser.", "description": "Develop, explain, test and apply regular expressions locally in the browser.",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"author": "Albrecht Degering", "author": "Albrecht Degering",
@@ -35,6 +35,12 @@
"engines:pcre2:build": "node scripts/build-engines.mjs --pcre2", "engines:pcre2:build": "node scripts/build-engines.mjs --pcre2",
"engines:pcre2:install": "node scripts/install-pcre2-engine.mjs", "engines:pcre2:install": "node scripts/install-pcre2-engine.mjs",
"engines:pcre2:verify": "node scripts/verify-engine-assets.mjs --pcre2-pack .engine-build/pcre2", "engines:pcre2:verify": "node scripts/verify-engine-assets.mjs --pcre2-pack .engine-build/pcre2",
"engines:python:build": "node scripts/python-engine-pack.mjs",
"engines:python:install": "node scripts/install-python-engine.mjs",
"engines:python:verify": "node scripts/verify-engine-assets.mjs --python-pack public/engines/python",
"engines:java:build": "node scripts/java-engine-pack.mjs",
"engines:java:install": "node scripts/install-java-engine.mjs",
"engines:java:verify": "node scripts/verify-engine-assets.mjs --java-pack public/engines/java",
"engines:verify": "node scripts/verify-engine-assets.mjs", "engines:verify": "node scripts/verify-engine-assets.mjs",
"manifest:generate": "node scripts/generate-toolbox-manifest.mjs", "manifest:generate": "node scripts/generate-toolbox-manifest.mjs",
"manifest:check": "node scripts/generate-toolbox-manifest.mjs --check", "manifest:check": "node scripts/generate-toolbox-manifest.mjs --check",
@@ -75,6 +81,7 @@
"globals": "17.7.0", "globals": "17.7.0",
"jsdom": "29.1.1", "jsdom": "29.1.1",
"prettier": "3.9.5", "prettier": "3.9.5",
"pyodide": "314.0.3",
"typescript": "6.0.3", "typescript": "6.0.3",
"typescript-eslint": "8.64.0", "typescript-eslint": "8.64.0",
"vite": "8.1.5", "vite": "8.1.5",

View File

@@ -1,9 +1,13 @@
# Engine assets # Engine assets
Regex Tools executes ECMAScript through the browser's native `RegExp` engine. Regex Tools executes ECMAScript through the browser's native `RegExp` engine.
It also ships the declared, self-hosted PCRE2 10.47 WebAssembly pack in It also ships three declared, self-hosted runtime packs:
`pcre2/`.
The PCRE2 pack is built from pinned, documented open-source code and - `pcre2/`: official PCRE2 10.47, 8-bit WebAssembly;
toolchain revisions. `engine-metadata.json`, `SHA256SUMS` and `LICENSE.txt` - `python/`: Pyodide 314.0.3 with CPython 3.14.2 `re`; and
travel with the runtime files and are verified by the release gate. - `java/`: TeaVM 0.15.0's `java.util.regex` class library compiled to an
ES2015 module. This module is not OpenJDK.
The packs come from pinned, documented open-source inputs. Runtime files travel
with deterministic metadata, closed-set checksums and the applicable upstream
licence/notice material, all of which are verified by the release gate.

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,11 @@
This product includes software developed by Alexey Andreev
(http://teavm.org).
This product includes software developed by The Apache Software
Foundation (http://www.apache.org/).
=============================================================================
= NOTICE file corresponding to section 4d of the Apache License Version 2.0 =
=============================================================================
This product includes software developed by
Joda.org (http://www.joda.org/).

View File

@@ -0,0 +1,4 @@
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE.txt
71bbe79ff78d0e5e64096acdd418589abfe886b8f827048dc655df438a57e477 NOTICE.txt
719cb99761c8f0964835bf7b954fcf6e0df30ec14d2c06050c07c23a978d6d22 engine-metadata.json
fd7974e7bde911ef0682cdc432d28e93c5db9d56f7268482a62e40a2ad86d62f java-regex.mjs

View File

@@ -0,0 +1,118 @@
{
"schemaVersion": 1,
"engine": "java",
"engineName": "TeaVM java.util.regex",
"engineVersion": "0.15.0",
"semanticIdentity": "TeaVM 0.15.0 classlib java.util.regex; Apache Harmony-derived; not OpenJDK",
"status": "production-compatibility-runtime",
"bridge": {
"abiVersion": 1,
"maximumPatternUtf16": 65536,
"maximumSubjectBytes": 16777216,
"maximumReplacementUtf16": 65536,
"maximumMatches": 10000,
"maximumCaptureRows": 100000,
"maximumCaptureGroups": 1000,
"maximumOutputBytes": 67108864,
"offsetUnit": "utf16",
"exports": [
"bridgeAbiVersion",
"engineIdentity",
"execute",
"replace",
"selfTest"
],
"sourceFiles": [
{
"path": "engines/java/pom.xml",
"sha256": "8f70247241da0e1a1db9eb64a604206a69eb6a573b5634f1faa196fa9bb07b15",
"bytes": 2995
},
{
"path": "engines/java/README.md",
"sha256": "7ce93387533e889221179515b1c36c67fd54f490edd2d7624c4ee5744bf42cf1",
"bytes": 1309
},
{
"path": "engines/java/LICENSE-TeaVM.txt",
"sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
"bytes": 11358
},
{
"path": "engines/java/NOTICE-TeaVM.txt",
"sha256": "71bbe79ff78d0e5e64096acdd418589abfe886b8f827048dc655df438a57e477",
"bytes": 493
},
{
"path": "engines/java/src/main/java/de/addideas/regextools/javaengine/RegexBridge.java",
"sha256": "8512906fa000cc8ef10f5c8d7e01b7037bbfca24882e834d2de5a706bc1e1cd5",
"bytes": 12469
}
]
},
"source": {
"repository": "https://github.com/konsoletyper/teavm.git",
"tag": "0.15.0",
"commitSha1": "ee91b03e616c4b45401cd11fb0cd7eb0daf6649b",
"treeSha1": "1cdc5c332d5809828c8952f96d73f9e7504a0cec",
"license": "Apache-2.0",
"upstreamLicenseSha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
"upstreamNoticeSha256": "6c2b3372a6beee7fbaad482248ac0f5ad2b099d92ff2a528c04261cc9c0636f4"
},
"toolchain": {
"mavenVersion": "3.9.15",
"javaVersion": "25.0.3",
"teaVmArtifacts": [
{
"coordinate": "org.teavm:teavm-classlib:0.15.0",
"sha256": "7d805be4670a892a34c89d9d750a8afd65e2f8201b7b3a10343634ea19d1c410"
},
{
"coordinate": "org.teavm:teavm-jso:0.15.0",
"sha256": "58b337b93924106bf819c45890c7c231a0c740145afe77795f1e0f099f621d71"
},
{
"coordinate": "org.teavm:teavm-maven-plugin:0.15.0",
"sha256": "1133e16e45f1fd3cb93129fd96d142e1f5ef3608891d83b2273442d5446a3879"
}
]
},
"configuration": {
"target": "JavaScript ES2015 module",
"minified": true,
"optimizationLevel": "ADVANCED",
"sourceMaps": false,
"filesystem": false,
"supportedApplicationFlags": [
"g",
"i",
"d",
"m",
"s",
"u",
"x",
"U"
],
"unixLines": "d maps to TeaVM Pattern.UNIX_LINES.",
"unicodeCharacterClassCompatibility": "TeaVM 0.15.0 lacks OpenJDK UNICODE_CHARACTER_CLASS; U implies Unicode case folding only.",
"replacement": "TeaVM 0.15.0 Matcher appendReplacement/appendTail semantics: only single-digit $n references; ${name} is rejected; not OpenJDK Matcher parity."
},
"sourceDateEpoch": 1780759630,
"files": [
{
"path": "LICENSE.txt",
"sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
"bytes": 11358
},
{
"path": "NOTICE.txt",
"sha256": "71bbe79ff78d0e5e64096acdd418589abfe886b8f827048dc655df438a57e477",
"bytes": 493
},
{
"path": "java-regex.mjs",
"sha256": "fd7974e7bde911ef0682cdc432d28e93c5db9d56f7268482a62e40a2ad86d62f",
"bytes": 136962
}
]
}

View File

@@ -0,0 +1,441 @@
"use strict";
let Js=2463534242,OY=()=>{let x=Js;x^=x<<13;x^=x>>>17;x^=x<<5;Js=x;return x;},M=f=>function(){return f(this);},O=f=>function(p1){return f(this,p1);},Bq=f=>function(p1,p2){return f(this,p1,p2);},S=f=>function(p1,p2,p3){return f(this,p1,p2,p3);},Bz=f=>function(p1,p2,p3,p4){return f(this,p1,p2,p3,p4);},JT=f=>(args,callback)=>{if(!args){args=[];}let javaArgs=K(CH(),args.length);for(let i=0;i<args.length;++i){javaArgs.data[i]=Bt(args[i]);}M7(()=>{f.call(null,javaArgs);},callback);},B3=target=>target.$clinit=()=>
{},RB=obj=>{let cls=obj.constructor;let arrayDegree=0;while(cls[Bc]&&cls[Bc].item){++arrayDegree;cls=cls[Bc].item;}let clsName="";if(cls[Bc].primitiveKind!==0){clsName=cls[Bc].name;}else {clsName=cls[Bc]?cls[Bc].name||"a/"+cls.name:"@"+cls.name;}while(arrayDegree-->0){clsName+="[]";}return clsName;},L=superclass=>{if(superclass===0){return function(){};}if(superclass===void 0){superclass=CH();}return function(){superclass.call(this);};},Bc=Symbol("teavm_meta"),B9=cls=>{if(cls[Bc].classObject===null){cls[Bc].classObject
=K1(cls);}return cls[Bc].classObject;},CH=()=>J,Fl=source=>{return Object.assign({name:null,binaryName:null,parent:null,superinterfaces:[],modifiers:0,primitiveKind:0,itemType:null,arrayType:null,enclosingClass:null,declaringClass:null,simpleName:null,clinit:()=>{},constructor:null,enumConstants:()=>null,resolvedEnumConstants:null,reflection:null,classObject:null,assignableCache:null,valueToObject:o=>o,objectToValue:o=>o},source||{});},Dl=(name,binaryName,kind,config)=>{let cls=()=>{};let meta=Fl({name:name,
binaryName:binaryName,modifiers:1|1<<4,primitiveKind:kind});cls[Bc]=meta;if(typeof config==='function'){config(meta);}return cls;},JO=Dl("byte","B",2,meta=>{}),HC=Dl("char","C",4,meta=>{}),Gs=Dl("int","I",5,meta=>{}),N$=Dl("void","V",9),B1=(a,b)=>a===b?0:a<b? -1:1,DF=Math.imul||function(a,b){let ah=a>>>16&0xFFFF;let al=a&0xFFFF;let bh=b>>>16&0xFFFF;let bl=b&0xFFFF;return al*bl+(ah*bl+al*bh<<16>>>0)|0;},EP=(a,b)=>(a>>>0)/(b>>>0)>>>0,RG=(a,b)=>(a>>>0)%(b>>>0)>>>0,Es=(a,b)=>{a>>>=0;b>>>=0;return a<b? -1:a>b?1:
0;},RN=BigInt(0),BM=val=>BigInt.asIntN(64,BigInt(val|0)),JK=(a,b)=>a>b,FW=(a,b)=>a<=b,Cv=(a,b)=>BigInt.asIntN(64,a+b),HU=(a,b)=>BigInt.asIntN(64,a -b),K=(cls,sz)=>{let data=new Array(sz);data.fill(null);return new (CG(cls))(data);},BJ=sz=>new Pn(new Uint16Array(sz)),I0=sz=>new Pf(new Int8Array(sz)),BC=sz=>new Iu(new Int32Array(sz)),HV=data=>{let buffer=new Int32Array(data.length);buffer.set(data);return new Iu(buffer);},CG=cls=>{let result=cls[Bc].arrayType;if(result===null){function JavaArray(data){(CH()).call(this);this.data
=data;}JavaArray.prototype=Object.create((CH()).prototype);JavaArray.prototype.type=cls;JavaArray.prototype.constructor=JavaArray;JavaArray.prototype.toString=function(){let str="[";for(let i=0;i<this.data.length;++i){if(i>0){str+=", ";}str+=this.data[i].toString();}str+="]";return str;};JavaArray.prototype.ix=function(){let dataCopy;if('slice' in this.data){dataCopy=this.data.slice();}else {dataCopy=new this.data.constructor(this.data.length);for(let i=0;i<dataCopy.length;++i){dataCopy[i]=this.data[i];}}return new (CG(this.type))(dataCopy);};let name
="["+cls[Bc].binaryName;JavaArray[Bc]=Fl({name:name,binaryName:name,parent:CH(),itemType:cls});result=JavaArray;cls[Bc].arrayType=JavaArray;}return result;};
function Qn(array){return array.data.length;}
let E4,K$=strings=>{P2();E4=new Array(strings.length);for(let i=0;i<strings.length;++i){E4[i]=Gd(Bt(strings[i]));}},I=index=>E4[index],Hh=(array,offset,count)=>{let result="";let limit=offset+count;for(let i=offset;i<limit;i=i+1024|0){let next=Math.min(limit,i+1024|0);result+=String.fromCharCode.apply(null,array.subarray(i,next));}return result;},Bt=str=>str===null?null:HS(str),D$=str=>str===null?null:str.eh,P2=()=>Ce(),Gd;
{Gd=str=>str;}let Di=(from,to)=>{if(from===to){return true;}let map=from[Bc].assignableCache;if(map===null){map=new Map();from[Bc].assignableCache=map;}let cachedResult=map.get(to);if(typeof cachedResult!=='undefined'){return cachedResult;}if(to[Bc].itemType!==null){let result=from[Bc].itemType!==null&&Di(from[Bc].itemType,to[Bc].itemType);map.set(to,result);return result;}let parent=from[Bc].parent;if(parent!==null&&parent!==from){if(Di(parent,to)){map.set(to,true);return true;}}let superinterfaces=from[Bc].superinterfaces;for
(let i=0;i<superinterfaces.length;i=i+1|0){if(Di(superinterfaces[i],to)){map.set(to,true);return true;}}map.set(to,false);return false;},Q=ex=>{throw LG(ex);},Cw=Symbol("javaException"),LG=ex=>{if(!ex.$jsException){G0(ex);}return ex.$jsException;},G0=ex=>{let javaCause=R7(ex);let jsCause=javaCause!==null?javaCause.$jsException:void 0;let cause=typeof jsCause==="object"?{cause:jsCause}:void 0;let err=new B_("Java exception thrown",cause);if(typeof Error.captureStackTrace==="function"){Error.captureStackTrace(err);}err[Cw]
=ex;ex.$jsException=err;I7(err,ex);},I7=(err,ex)=>{if(typeof Kx==="function"&&err.stack){let stack=Kx(err.stack);let javaStack=K(KY(),stack.length);let elem;let noStack=false;for(let i=0;i<stack.length;++i){let element=stack[i];elem=Pk(Bt(element.className),Bt(element.methodName),Bt(element.fileName),element.lineNumber);if(elem==null){noStack=true;break;}javaStack.data[i]=elem;}if(!noStack){Lv(ex,javaStack);}}},B_;
if(typeof Reflect==='object'){let defaultMessage=Symbol("defaultMessage");B_=function B_(message,cause){let self=Reflect.construct(Error,[void 0,cause],B_);Object.setPrototypeOf(self,B_.prototype);self[defaultMessage]=message;return self;}
;B_.prototype=Object.create(Error.prototype,{constructor:{configurable:true,writable:true,value:B_},message:{get(){try {let javaException=this[Cw];if(typeof javaException==='object'){let javaMessage=NK(javaException);if(typeof javaMessage==="object"){return javaMessage!==null?javaMessage.toString():null;}}return this[defaultMessage];}catch(e){return "Exception occurred trying to extract Java exception message: "+e;}}}});}else {B_=Error;}let N_=e=>e instanceof Error&&typeof e[Cw]==='object'?e[Cw]:null,BS=err=>
{let ex=err[Cw];if(!ex){ex=Oy(Bt("(JavaScript) "+err.toString()));err[Cw]=ex;ex.$jsException=err;I7(err,ex);}return ex;},Oy=message=>Rd(message),NK=t=>RV(t),R7=t=>R8(t),KY=()=>CH(),Pk=(className,methodName,fileName,lineNumber)=>{{return null;}},Lv=(e,stack)=>{},FY=null,LQ=data=>{let i=0;let packages=new Array(data.length);for(let j=0;j<data.length;++j){let prefixIndex=data[i++];let prefix=prefixIndex>=0?packages[prefixIndex]:"";packages[j]=prefix+data[i++]+".";}FY=packages;},Ld=[],Cu=data=>{let packages=FY;let i
=0;while(i<data.length){let cls=data[i++];Ld.push(cls);let m=Fl();cls[Bc]=m;let className=data[i++];m.name=className!==0?className:null;if(m.name!==null){let packageIndex=data[i++];if(packageIndex>=0){m.name=packages[packageIndex]+m.name;}}m.binaryName="L"+m.name+";";let superclass=data[i++];m.parent=superclass!==0?superclass:null;m.superinterfaces=data[i++];if(m.parent){cls.prototype=Object.create(m.parent.prototype);}else {cls.prototype={};}cls.prototype.constructor=cls;m.modifiers=data[i++];m.primitiveKind
=0;let innerClassInfo=data[i++];if(innerClassInfo!==0){let enclosingClass=innerClassInfo[0];m.enclosingClass=enclosingClass!==0?enclosingClass:null;let declaringClass=innerClassInfo[1];m.declaringClass=declaringClass!==0?declaringClass:null;let simpleName=innerClassInfo[2];m.simpleName=simpleName!==0?simpleName:null;}let clinit=data[i++];m.clinit=clinit!==0?()=>{m.clinit=()=>{};clinit();}:()=>{};let virtualMethods=data[i++];if(virtualMethods!==0){for(let j=0;j<virtualMethods.length;j+=2){let name=virtualMethods[j];let func
=virtualMethods[j+1];if(typeof name==='string'){name=[name];}for(let k=0;k<name.length;++k){cls.prototype[name[k]]=func;}}}}},M7=(runner,callback)=>{let result;try {result=runner();}catch(e){result=e;}if(typeof callback!=='undefined'){callback(result);}else if(result instanceof Error){throw result;}};
function J(){this.$id$=0;}
let Co=a=>{return B9(Mw(a));},Mw=a=>{return a.constructor;},Is=a=>{let b,c,d,e,f,g,h,i;b=a;if(!b.$id$)b.$id$=OY();c=a.$id$;BN();if(!c)d=I(0);else{e=(((32-Hp(c)|0)+4|0)-1|0)/4|0;f=BJ(e);g=f.data;e=(e-1|0)*4|0;h=0;while(e>=0){i=h+1|0;g[h]=Fm((c>>>e|0)&15,16);e=e-4|0;h=i;}d=Dk(f);}b=new W;Y(b);T(T(b,I(1)),d);return X(b);},B$=L(0),DX=L(0),EY=L(0);
function Br(){J.call(this);this.fF=0;}
let N9=null,Gh=null,KS=null,Ce=()=>{Ce=B3(Br);MV();},GJ=(a,b)=>{Ce();a.eh=Hh(b.data,0,b.data.length);},Dk=a=>{let b=new Br();GJ(b,a);return b;},ML=(a,b)=>{a.eh=b;},HS=a=>{let b=new Br();ML(b,a);return b;},Jy=(a,b,c,d)=>{let e,f;Ce();e=b.data.length;if(c>=0&&d>=0&&d<=(e-c|0)){a.eh=Hh(b.data,c,d);return;}f=new Bo;Z(f);Q(f);},D3=(a,b,c)=>{let d=new Br();Jy(d,a,b,c);return d;},P=(a,b)=>{let c;if(b>=0&&b<a.eh.length)return a.eh.charCodeAt(b);c=new CJ;Z(c);Q(c);},Dz=a=>{return a.eh.length;},KN=(a,b)=>{let c;if(a.eh.length
!=b.er)return 0;c=0;while(c<a.eh.length){if(a.eh.charCodeAt(c)!=Er(b,c))return 0;c=c+1|0;}return 1;},H6=(a,b,c)=>{let d,e,f;if((c+b.eh.length|0)>a.eh.length)return 0;d=0;while(d<b.eh.length){e=P(b,d);f=c+1|0;if(e!=P(a,c))return 0;d=d+1|0;c=f;}return 1;},II=(a,b)=>{if(a===b)return 1;return H6(a,b,0);},DH=(a,b,c)=>{let d,e,f,g;d=BR(0,c);if(b<65536){e=b&65535;while(true){if(d>=a.eh.length)return (-1);if(a.eh.charCodeAt(d)==e)break;d=d+1|0;}return d;}f=Ft(b);g=EH(b);while(true){if(d>=(a.eh.length-1|0))return (-1);if
(a.eh.charCodeAt(d)==f&&a.eh.charCodeAt((d+1|0))==g)break;d=d+1|0;}return d;},C0=(a,b,c)=>{let d,e,f,g;d=Bw(c,a.eh.length-1|0);if(b<65536){e=b&65535;while(true){if(d<0)return (-1);if(a.eh.charCodeAt(d)==e)break;d=d+(-1)|0;}return d;}f=Ft(b);g=EH(b);while(true){if(d<1)return (-1);if(a.eh.charCodeAt(d)==g){c=d-1|0;if(a.eh.charCodeAt(c)==f)break;}d=d+(-1)|0;}return c;},HW=(a,b)=>{return C0(a,b,a.eh.length-1|0);},FJ=(a,b,c)=>{let d,e,f;d=BR(0,c);e=a.eh.length-b.eh.length|0;a:while(true){if(d>e)return (-1);f=0;while
(true){if(f>=b.eh.length)break a;if(P(a,d+f|0)!=P(b,f))break;f=f+1|0;}d=d+1|0;}return d;},BW=(a,b,c)=>{let d,e,f;d=a.eh.length;e=B1(b,c);if(!e)return Gh;if(!b&&c==d)return a;if(b>=0&&e<=0&&c<=d)return HS(a.eh.substring(b,c));f=new CJ;Z(f);Q(f);},DV=(a,b)=>{return BW(a,b,a.eh.length);},Px=a=>{return a;},D4=a=>{let b,c,d,e;b=BJ(a.eh.length);c=b.data;d=0;e=c.length;while(d<e){c[d]=P(a,d);d=d+1|0;}return b;},Df=b=>{Ce();return b===null?I(2):b.g();},Hg=b=>{let c;Ce();c=new W;Y(c);return X(BY(c,b));},C1=(a,b)=>{let c;if
(a===b)return 1;if(!(b instanceof Br))return 0;c=b;return a.eh!==c.eh?0:1;},MV=()=>{let b;N9=BJ(0);b=new Br;Ce();b.eh="";Gh=b;KS=new Hw;},Fk=L();
function Dw(){Fk.call(this);this.ij=0;}
let Lb=null,Ea=null,BN=()=>{BN=B3(Dw);KR();},JL=(a,b)=>{BN();a.ij=b;},GU=a=>{let b=new Dw();JL(b,a);return b;},C7=b=>{BN();return (G7(OD(20),b,10)).g();},DN=(b,c)=>{BN();if(b!==null)return N5(b,0,b.eh.length,c);b=new BK;Bu(b,I(3));Q(b);},N5=(b,c,d,e)=>{let f,g,h,i,j,k,l,m;BN();if(c==d){b=new BK;Bu(b,I(4));Q(b);}if(e>=2&&e<=36){a:{f=0;b=b;switch(P(b,c)){case 43:g=c+1|0;break a;case 45:f=1;g=c+1|0;break a;default:}g=c;}h=0;i=1+(2147483647/e|0)|0;if(g==d){b=new BK;Z(b);Q(b);}while(g<d){j=g+1|0;k=P(b,g);k=k>=48
&&k<=57?k-48|0:k>=97&&k<=122?(k-97|0)+10|0:k>=65&&k<=90?(k-65|0)+10|0:(-1);if(k<0){l=new BK;b=Df(BW(b,c,d));m=new W;Y(m);T(T(m,I(5)),b);Bu(l,X(m));Q(l);}if(k>=e){l=new BK;b=Df(BW(b,c,d));m=new W;Y(m);T(T(BY(T(m,I(6)),e),I(7)),b);Bu(l,X(m));Q(l);}if(h>i){b=new BK;Bu(b,I(8));Q(b);}h=DF(e,h)+k|0;if(h<0){if(j==d&&h==(-2147483648)&&f)return (-2147483648);l=new BK;b=Df(BW(b,c,d));m=new W;Y(m);T(T(m,I(9)),b);Bu(l,X(m));Q(l);}g=j;}if(f)h= -h|0;return h;}b=new BK;l=new W;Y(l);BY(T(l,I(10)),e);Bu(b,X(l));Q(b);},RS=b=>
{BN();return DN(b,10);},Hp=b=>{let c,d;BN();if(!b)return 32;c=0;d=b>>>16|0;if(d)c=16;else d=b;b=d>>>8|0;if(!b)b=d;else c=c|8;d=b>>>4|0;if(!d)d=b;else c=c|4;b=d>>>2|0;if(!b)b=d;else c=c|2;if(b>>>1|0)c=c|1;return (32-c|0)-1|0;},DO=b=>{let c,d;BN();if(!b)return 32;c=0;d=b<<16;if(d)c=16;else d=b;b=d<<8;if(!b)b=d;else c=c|8;d=b<<4;if(!d)d=b;else c=c|4;b=d<<2;if(!b)b=d;else c=c|2;if(b<<1)c=c|1;return (32-c|0)-1|0;},KR=()=>{Lb=B9(Gs);};
function Cl(){let a=this;J.call(a);a.eD=null;a.er=0;}
let Y=a=>{CX(a,16);},Sk=()=>{let a=new Cl();Y(a);return a;},CX=(a,b)=>{a.eD=BJ(b);},OD=a=>{let b=new Cl();CX(b,a);return b;},B4=(a,b)=>{return a.x(a.er,b);},Eh=(a,b,c)=>{let d,e,f,g;if(b>=0&&b<=a.er){if(c===null)c=I(2);else if(c.eh.length?0:1)return a;a.y(a.er+c.eh.length|0);d=a.er-1|0;while(d>=b){a.eD.data[d+c.eh.length|0]=a.eD.data[d];d=d+(-1)|0;}a.er=a.er+c.eh.length|0;e=0;while(e<c.eh.length){f=a.eD.data;g=b+1|0;f[b]=P(c,e);e=e+1|0;b=g;}return a;}c=new CJ;Z(c);Q(c);},G7=(a,b,c)=>{return OZ(a,a.er,b,c);},OZ
=(a,b,c,d)=>{let e,f,g,h,i,j,k;e=1;if(c<0){e=0;c= -c|0;}a:{if(Es(c,d)<0){if(e)Cy(a,b,b+1|0);else{Cy(a,b,b+2|0);f=a.eD.data;g=b+1|0;f[b]=45;b=g;}a.eD.data[b]=Fm(c,d);}else{h=1;i=1;j=EP((-1),d);b:{while(true){k=DF(h,d);if(Es(k,c)>0){k=h;break b;}i=i+1|0;if(Es(k,j)>0)break;h=k;}}if(!e)i=i+1|0;Cy(a,b,b+i|0);if(e)e=b;else{f=a.eD.data;e=b+1|0;f[b]=45;}while(true){if(!k)break a;f=a.eD.data;b=e+1|0;f[e]=Fm(EP(c,k),d);c=RG(c,k);k=EP(k,d);e=b;}}}return a;},Bh=(a,b)=>{return a.B(a.er,b);},In=(a,b,c)=>{Cy(a,b,b+1|0);a.eD.data[b]
=c;return a;},EQ=(a,b)=>{let c,d,e,f,g;c=a.eD.data.length;if(c>=b)return;d=c>=1073741823?2147483647:BR(b,BR(c*2|0,5));e=a.eD.data;f=BJ(d);g=f.data;b=Bw(d,e.length);c=0;while(c<b){g[c]=e[c];c=c+1|0;}a.eD=f;},X=a=>{return D3(a.eD,0,a.er);},Er=(a,b)=>{let c;if(b>=0&&b<a.er)return a.eD.data[b];c=new Bo;Z(c);Q(c);},E3=(a,b,c,d)=>{return a.D(a.er,b,c,d);},Gr=(a,b,c,d,e)=>{let f,g;if(d<=e&&e<=c.E()&&d>=0){Cy(a,b,(b+e|0)-d|0);while(d<e){f=a.eD.data;g=b+1|0;f[b]=c.h(d);d=d+1|0;b=g;}return a;}c=new Bo;Z(c);Q(c);},Fo=
(a,b)=>{return a.F(b,0,b.E());},HQ=(a,b,c,d)=>{return a.G(a.er,b,c,d);},GA=(a,b,c,d,e)=>{let f,g,h,i;Cy(a,b,b+e|0);f=e+d|0;while(d<f){g=c.data;h=a.eD.data;e=b+1|0;i=d+1|0;h[b]=g[d];b=e;d=i;}return a;},DR=(a,b)=>{return a.H(b,0,b.data.length);},Cy=(a,b,c)=>{let d,e,f,g;d=a.er;e=d-b|0;a.y((d+c|0)-b|0);f=e-1|0;while(f>=0){g=a.eD.data;g[c+f|0]=g[b+f|0];f=f+(-1)|0;}a.er=a.er+(c-b|0)|0;},EL=L(0),W=L(Cl),PB=a=>{Y(a);},L7=()=>{let a=new W();PB(a);return a;},T=(a,b)=>{let c,d;c=a.er;d=a;b=b===null?I(2):b.g();Eh(d,c,
b);return a;},Lf=(a,b)=>{B4(a,b);return a;},BY=(a,b)=>{G7(a,b,10);return a;},F$=(a,b)=>{Bh(a,b);return a;},N6=(a,b,c)=>{let d,e,f,g,h,i;if(b>=0){d=B1(b,c);if(d<=0){e=a.er;if(b<=e){if(d){if(c>e)c=e;f=e-c|0;a.er=e-(c-b|0)|0;e=0;while(e<f){g=a.eD.data;d=b+1|0;h=c+1|0;g[b]=g[c];e=e+1|0;b=d;c=h;}}return a;}}}i=new CJ;Z(i);Q(i);},GB=(a,b)=>{let c,d,e,f;if(b>=0){c=a.er;if(b<c){c=c-1|0;a.er=c;while(b<c){d=a.eD.data;e=b+1|0;d[b]=d[e];b=e;}return a;}}f=new CJ;Z(f);Q(f);},Jb=(a,b,c)=>{let d;d=a;if(b<=c&&b>=0&&c<=d.er)return D3(d.eD,
b,c-b|0);d=new Bo;Z(d);Q(d);},QF=(a,b,c,d,e)=>{GA(a,b,c,d,e);return a;},JU=(a,b,c,d)=>{HQ(a,b,c,d);return a;},OI=(a,b,c,d,e)=>{Gr(a,b,c,d,e);return a;},RF=(a,b,c,d)=>{E3(a,b,c,d);return a;},MC=(a,b)=>{return Er(a,b);},C5=a=>{return a.er;},C9=a=>{return X(a);},Q6=(a,b)=>{EQ(a,b);},Kc=(a,b,c)=>{In(a,b,c);return a;},RA=(a,b,c)=>{Eh(a,b,c);return a;};
function Ff(){let a=this;J.call(a);a.gw=null;a.iq=null;a.ht=0;a.hI=0;}
let Si=a=>{return a;},G5=a=>{G0(a);},RV=a=>{return a.gw;},R8=a=>{let b;b=a.iq;if(b===a)b=null;return b;},Cr=L(Ff),Bi=L(Cr),Z=a=>{G5(a);a.ht=1;a.hI=1;},Sf=()=>{let a=new Bi();Z(a);return a;},Bu=(a,b)=>{G5(a);a.ht=1;a.hI=1;a.gw=b;},Rd=a=>{let b=new Bi();Bu(b,a);return b;},CT=L(),Me=L(CT),KP=(a,b)=>{switch(a.primitiveKind){default:return K(a,b);}},LO=L(CT),GE=L(0),Jl=L(0),FU=L(0);
function GX(){let a=this;J.call(a);a.fP=0;a.eJ=null;a.gE=null;a.gW=null;}
let K1=b=>{let c;c=new GX;c.eJ=b;return c;},I6=a=>{let b,c,d,e,f;b=a.fP;if(!(b&1)){a.fP=b|1;c=a.eJ[Bc].name;d=c===null?null:Bt(c);if(d===null){e=a.eJ[Bc].itemType;if(e!==null){f=I6(B9(e));if(f!==null){if(e[Bc].itemType!==null){c=new W;Y(c);Bh(c,91);T(c,f);d=X(c);}else{c=new W;Y(c);Bh(T(T(c,I(11)),f),59);d=X(c);}}}}a.gE=d;}return a.gE;},Fh=a=>{let b,c,d,e;b=a.fP;if(!(b&2)){a.fP=b|2;c=a.eJ[Bc].simpleName;d=c===null?null:Bt(c);if(d===null){if(a.eJ[Bc].itemType!==null){c=Fh(B9(a.eJ[Bc].itemType));d=new W;Y(d);T(T(d,
c),I(12));d=X(d);}else{c=a.eJ[Bc].enclosingClass;if((c===null?null:B9(c))!==null)d=I(13);else{d=I6(a);e=HW(d,36);if(e==(-1)){b=HW(d,46);if(b!=(-1))d=DV(d,b+1|0);}else{d=DV(d,e+1|0);if(P(d,0)>=48&&P(d,0)<=57)d=I(13);}}}}a.gW=d;}return a.gW;},DI=a=>{return !a.eJ[Bc].primitiveKind?0:1;},Ek=a=>{let b;b=a.eJ[Bc].itemType;return b===null?null:B9(b);},I_=L(),Bx=()=>{Bx=B3(I_);Mx();},O0=b=>{Bx();},H0=(b,c,d,e,f,g,h)=>{let i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,ba,bb,$$je;Bx();if(b!==null&&c!==null){if(f>=1&&g>=1){a:{b:
{try{i=FT(b,NZ(d));break a;}catch($$e){$$je=BS($$e);if($$je instanceof CB){j=$$je;break b;}else if($$je instanceof Bi){j=$$je;}else{throw $$e;}}return CR(I(14),Fh(Co(j)),Jr(j,I(15)),(-1));}k=j.gp;if(!(k!==null&&k.eh.length)){b=I(13);e=j.fv;if(e>=1){l=BJ(e);m=l.data;e=0;f=m.length;if(e>f){b=new Bs;Z(b);Q(b);}while(e<f){g=e+1|0;m[e]=32;e=g;}b=Dk(l);}c=j.gp;h=j.f3;if(h!==null&&h.eh.length){d=j.fv;h=j.f3;n=new W;Y(n);T(T(T(T(BY(n,d),I(16)),h),I(16)),b);b=X(n);}else b=I(13);h=new W;Y(h);T(T(h,c),b);k=X(h);}return CR(I(14),
I(17),k,j.fv);}o=Ha(i,c);p=o.eI.gz-1|0;if(p>1000)return CR(I(18),I(19),I(20),(-1));q=new W;Y(q);if(h===null)r=null;else{r=new DT;Y(r);}s=0;t=0;u=0;v=0;w=0;c:{try{d:{e:{while(G6(o)){x=BR(1,p);if(s>=f)break e;t=t+x|0;if(t>g)break e;Ec(q,C$(o));Ec(q,Dq(o));y=1;while(y<=p){Ec(q,Fq(o,y));Ec(q,Eu(o,y));y=y+1|0;}s=s+1|0;if(r!==null&&!w){z=Li(o,h);if(FW(Cv(HU(Cv(BM(I$(r)),BM(C$(o))),BM(u)),z),BM(67108864))){Gm(o,r,h);u=Dq(o);}else{Gy(r,c,u,C$(o),67108864);w=1;}}if(!e)break d;}break d;}v=1;}if(r!==null&&!w){if(FW(HU(Cv(BM(I$(r)),
BM(Dz(c))),BM(u)),BM(67108864)))GK(o,r);else{Gy(r,c,u,Dz(c),67108864);w=1;}}}catch($$e){$$je=BS($$e);if($$je instanceof Bi){j=$$je;break c;}else{throw $$e;}}ba=FX();B5(ba,I(21));B5(ba,I(22));ED(ba,p);FE(ba,v);ED(ba,s);Fo(ba.fO,q);if(r!==null){FE(ba,w);B5(ba,X(r));}return HX(ba);}n=h!==null?I(23):I(24);bb=h!==null?I(25):I(26);return CR(n,Fh(Co(j)),Jr(j,bb),(-1));}return CR(I(18),I(27),I(28),(-1));}return CR(I(29),I(30),I(31),(-1));},NZ=b=>{let c;Bx();c=0;if(b&1)c=2;if(b&2)c=c|8;if(b&4)c=c|32;if(!(!(b&8)&&!(b
&32)))c=c|64;if(b&16)c=c|4;if(b&64)c=c|1;return c;},Li=(b,c)=>{let d,e,f,g;Bx();d=RN;e=0;while(e<c.eh.length){f=P(c,e);if(f==92){e=e+1|0;if(e>=c.eh.length){b=new Bo;Z(b);Q(b);}d=Cv(d,BM(1));}else if(f!=36)d=Cv(d,BM(1));else{e=e+1|0;if(e>=c.eh.length){b=new Bs;Bu(b,I(13));Q(b);}g=Fc(P(c,e),10);if(g<0){b=new Bs;Bu(b,I(13));Q(b);}d=Cv(d,BM((Ep(b,g)).eh.length));}if(JK(d,BM(67108864)))return d;e=e+1|0;}return d;},Gy=(b,c,d,e,f)=>{let g,h;Bx();g=f-b.er|0;if(g>0&&d<e){h=Bw(e,d+g|0);if(h<e&&h>d&&BA(P(c,h-1|0))&&BI(P(c,
h)))h=h+(-1)|0;E3(b,c,d,h);return;}},CR=(b,c,d,e)=>{let f;Bx();f=FX();B5(f,I(21));B5(f,b);if(c===null)c=I(32);B5(f,c);if(d===null)d=I(13);B5(f,d);ED(f,e);return HX(f);},Jr=(b,c)=>{let d;Bx();d=b.gw;if(d!==null&&d.eh.length)c=d;return c;},Ec=(b,c)=>{let d;Bx();d=C7(c);b=BY(b,d.eh.length);Bh(b,58);B4(b,d);},D=()=>{Bx();return 1;},E=()=>{Bx();return "TeaVM java.util.regex 0.15.0";},F=()=>{let b,c,d,$$je;Bx();a:{try{b=Ha(RJ(I(33)),I(34));if(G6(b)&&C$(b)==1&&Dq(b)==3&&Fq(b,1)==1&&Eu(b,1)==2){c=QH();Gm(b,c,I(35));GK(b,
c);d=!KN(I(36),c)?2:0;break a;}d=1;break a;}catch($$e){$$je=BS($$e);if($$je instanceof Bi){d=3;break a;}else{throw $$e;}}}return d;},G=(b,c,d,e,f,g)=>{Bx();return D$(H0(Bt(b),Bt(c),d,e?1:0,f,g,null));},H=(b,c,d,e,f,g,h)=>{Bx();return D$(H0(Bt(b),Bt(c),e,f?1:0,g,h,Bt(d)));},Mx=()=>{return;},Pw=L(),Q1=L(),RT=L(),E7=L(0),Hw=L(),E9=L(),La=null,Eo=null,Fb=null,EB=null,EW=null,PL=null,Dt=null,Ds=null,Du=null,Dv=null,Bb=()=>{Bb=B3(E9);Mr();},HA=b=>{let c,d;Bb();c=new Br;d=BJ(1);d.data[0]=b;GJ(c,d);return c;},EM=b=>
{Bb();return b>=65536&&b<=1114111?1:0;},BA=b=>{Bb();return (b&64512)!=55296?0:1;},BI=b=>{Bb();return (b&64512)!=56320?0:1;},Dx=(b,c)=>{Bb();return BA(b)&&BI(c)?1:0;},Cm=(b,c)=>{Bb();return ((b&1023)<<10|c&1023)+65536|0;},Ft=b=>{Bb();return (55296|(b-65536|0)>>10&1023)&65535;},EH=b=>{Bb();return (56320|b&1023)&65535;},Cj=b=>{Bb();return CV(b)&65535;},CV=b=>{Bb();if(EB===null){if(Dt===null)Dt=Pq();EB=F6(Hx((Dt.value!==null?Bt(Dt.value):null)));}return Go(EB,b);},Ch=b=>{Bb();return Dh(b)&65535;},Dh=b=>{Bb();if
(Fb===null){if(Ds===null)Ds=KT();Fb=F6(Hx((Ds.value!==null?Bt(Ds.value):null)));}return Go(Fb,b);};
let Go=(b,c)=>{let d,e,f,g,h,i;Bb();d=b.gJ.data;if(c<d.length)return c+d[c]|0;d=b.hS.data;e=0;f=d.length;g=(f/2|0)-1|0;a:{while(true){h=(e+g|0)/2|0;i=B1(d[h*2|0],c);if(!i)break;if(i<=0){e=h+1|0;if(e>g)break a;}else{h=h-1|0;if(h<e)break a;g=h;}}}if(h>=0){h=h*2|0;if(h<f)return c+d[h+1|0]|0;}return 0;},Fc=(b,c)=>{let d,e,f,g,h,i,j,k,l,m;Bb();if(c>=2&&c<=36){if(Eo===null){if(Du===null)Du=Oa();d=(Du.value!==null?Bt(Du.value):null);e=JC(D4(d));f=DY(e);g=BC(f*2|0);h=g.data;i=0;j=0;k=0;l=0;while(l<f){j=j+Ev(e)|0;k=
k+Ev(e)|0;m=i+1|0;h[i]=j;i=m+1|0;h[m]=k;l=l+1|0;}Eo=g;}g=Eo.data;i=0;j=(g.length/2|0)-1|0;a:{while(j>=i){k=(i+j|0)/2|0;l=k*2|0;f=B1(b,g[l]);if(f>0)i=k+1|0;else{if(f>=0){b=g[l+1|0];break a;}j=k-1|0;}}b=(-1);}if(b>=c)b=(-1);}else b=(-1);return b;},Fm=(b,c)=>{Bb();if(c>=2&&c<=36&&b>=0&&b<c)return b<10?(48+b|0)&65535:((97+b|0)-10|0)&65535;return 0;},CI=b=>{let c,d,e;Bb();if(!(b>=0&&b<=1114111?1:0)){c=new Bs;Z(c);Q(c);}if(b<65536){d=BJ(1);d.data[0]=b&65535;return d;}d=BJ(2);e=d.data;e[0]=Ft(b);e[1]=EH(b);return d;},DD
=b=>{Bb();return BB(b);},BB=b=>{let c,d,e,f,g;Bb();if(b>0&&b<=65535?1:0){c=b&65535;if(!BA(c)&&!BI(c)?0:1)return 19;}if(EW===null){if(Dv===null)Dv=Re();EW=RR((Dv.value!==null?Bt(Dv.value):null));}d=EW.data;e=0;c=d.length-1|0;while(e<=c){f=(e+c|0)/2|0;g=d[f];if(b>=g.gF)e=f+1|0;else{c=g.gY;if(b>=c)return g.g7.data[b-c|0];c=f-1|0;}}return 0;},HY=b=>{Bb();return Ho(b);},Ho=b=>{Bb();a:{switch(BB(b)){case 1:case 2:case 3:case 4:case 5:case 9:break;case 6:case 7:case 8:break a;default:break a;}return 1;}return 0;},C4
=b=>{Bb();a:{if(!(b>=0&&b<=8)&&!(b>=14&&b<=27)){if(b<127)break a;if(b>159)break a;}return 1;}return BB(b)!=16?0:1;},JB=b=>{Bb();switch(BB(b)){case 12:case 13:case 14:break;default:return 0;}return 1;},HI=b=>{Bb();switch(b){case 9:case 10:case 11:case 12:case 13:case 28:case 29:case 30:case 31:break;case 160:case 8199:case 8239:return 0;default:return JB(b);}return 1;},Mr=()=>{La=B9(HC);PL=K(E9,128);},Pq=()=>{return {"value":"NY H#F#U 4%F#O #F#/ d%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #a1# #%# #%# #%# %%# #%# #%# #%# #%# #%# #%# #%# %%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #<+#%# #%# #%# \'.3#%# #%# #{1#%# #w1%%# %J\'#k1#o1#%# #w1#!3# #23#*3#%# \'23#:3# #>3#%# #%# #%# #N3#%# #N3# %%# #N3#%# #J3%%# #%# #R3#%# \'%# /)#%# #)#%# #)#%# #%# #%# #%# #%# #%# #%# #%# #%# %%# #%# #%# #%# #%# #%# #%# #%# #%# %)#%# #%# #8)#L%#%# #%# #%# #"
+"%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #a+# #%# #%# #%# #%# #%# #%# #%# #%# #%# /B45#%# #,/#645# %%# #P1#!\'#*\'#%# #%# #%# #%# #%# <-%# #%# \'%# 1&++ %_## #Z#)k%%g%% #F#W hA# 1%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# +]%# %%# #?#%# %a+\'N\'AF#b &#%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# 3%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #^#%# #%# #%# #%# #%# #%# #%# %%# #%# #%# #%# #%# #%# #%# #%"
+"# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# %*%p kB#oq-&# _?gejg#A1 a$#%# -mo%&# {-%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# 3,4/# #%# #%"
+"# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# 3C1 1C1 1C1 1C1 1C1 3C/ 1C1 QC1 1C1 1C1 1C%8\'%G# 7i\')G# 7C%D)\' 7C%u)%?# 7X+%P+%G# L-q*/# \'Pw/#8m/# -6## |bA G%# kC.#U !r*%&# &#%# #,05#qX\'#H.5# %%# #%# #%# #e25#D05#q25#m25# #%# %%# 1865%%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# "
+"#%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# 1%# #%# )%# (a=%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# G%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# y%%# #%# #%# #%# #%# #%# #%# \'%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #%# 5%# #%# #4Fd#%# #%# #%# #%# #%# )%# #<{p# %%# #%# \'%# #%# #%# #%# #%# #%# #%# #%# #%# #%# #P}p#}}p#m}p#D}p#P}p# #@yp#D{p#Lyp#Br#%# #%# #%"
+"# #%# #%# #%# #%# #%# #,%#L}p#LJd#%# #%# #$$r#%# #%# #%# #%# #%# #%# #%# #%# #P6r#}!rI )%# :GL#) i,5F#U TUg#r {%g#r >\'c#p Lnk%F# .\'F#S HB#F#b o@5F#b F#2#W 4Z;%# /%# #%# %%# \'%# M%# #%# #%# #%# \'%# #%# #%# #%# #%# #%# #%# u.#N#f "};},KT=()=>{return {"value":"L[ ,%H#U :#>b# vH#O #H#/:+# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #,5# #\'# #\'# #\'# %\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# %\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# %\'# #\'# #\'#(;#N1# %\'# #\'# %\'# \'\'# +\'# %6)# \'\'#*/#N6r# %_+# %\'# #\'# #\'# %\'# )\'# %\'# \'\'# #\'# %\'# \'\'# #J%# +\'#+# #\'#+# #\'#+# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'#L\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# %\'#+# #\'# \'\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'#"
+" #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# \'\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# 1\'# %665% #\'# )\'# #\'# #\'# #\'# #\'#o25#c25#k25#03#}1# #y1% #m1# #q1#{}p# \'y1#k}p# #$3#!$r#:{p#N}p# #,3#43#N}p#*05#B}p# %43# #B05#<3# %@3# #{!r# ){!r#F.5# %P3# #J}p#P3# \'B{p#P3#$\'#L3%,\'# +T3# 5Jyp#>yp# Z\'_\'# x\'# #\'# \'\'\' #_+\' !#a##]#\' #H#CD##H#3m%#i%% #e%#P%# \'(%#D%#C# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'#i\'#P\'#=#(+# #4)# %\'# %\'# .#H#bP\'A #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# 3\'# #\'# #\'# #\'# #\'# #\'# "
+"#\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# %\'# #\'# #\'# #\'# #\'# #\'# #\'#`# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'% &#,%n mB#ko%x %ko%\' RAC1 >$#yu+#uu+#Pu+#Hu+%Lu+#0u+#io+#>@d# #\'- (+2Fd# \'oX\'# AJJd# N%\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #"
+"\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# +X%# +\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'#A1 1A1 1A1 1A1 1A1 3A# #A# #A# #A% /A1 16\'%g\')B)%V+%s)%N+)A1 1A1 1A1 1A% #E# 5<m-# )E# 9A% =A% \'=# ;E# R/8## ddA )\'# @E0#U Nr,%&# #\'# \'D4"
+"5#845# #\'# #\'# #\'# -\'# %\'# 5\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# 1\'# #\'# )\'- /qq-&# i]=\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# G\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# y%\'# #\'# #\'# #\'# #\'# #\'# #\'# \'\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'#"
+" #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# 5\'# #\'# %\'# #\'# #\'# #\'# #\'# )\'# )\'# #\'#*%# %\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# 7\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# )\'# #\'# %\'# #\'# #\'# #\'# #\'# #\'# #\'# #\'# U\'# REJ#% -Dr# Yiejg# e*5H#U eUi#r {%i#r <\'e#t {nm%:# V%H#^ >B#H#b o@5H#b <#4#P# eV;\'# /\'# #\'# %\'# \'\'# M\'# #\'# #\'# #\'# \'\'# #\'# #\'# #\'# #\'# #\'# #\'# Z0#P#f "};},Oa=()=>{return {"value":"6G*% %%%%%%%%%%%%%%%%%%A%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%=,#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%_H#T#%%%%%%%%%%%%%%%%%%s+G%%%%%%%%%%%%%%%%%%_1G%%%%%%%%%%%%%%%%%%{CG%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%6)G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%*\'G%%%%%%%%%%%%%%%%%%.9G%%%%%%%%%%%%%%%%%%*\'G%%%%%%%%%%%%%%%%%%!i#G"
+"%%%%%%%%%%%%%%%%%%c#G%%%%%%%%%%%%%%%%%%*;G%%%%%%%%%%%%%%%%%%Z+G%%%%%%%%%%%%%%%%%%:/G%%%%%%%%%%%%%%%%%%=G%%%%%%%%%%%%%%%%%%{/G%%%%%%%%%%%%%%%%%%k\'G%%%%%%%%%%%%%%%%%%s+G%%%%%%%%%%%%%%%%%%=G%%%%%%%%%%%%%%%%%%R@dG%%%%%%%%%%%%%%%%%%R[G%%%%%%%%%%%%%%%%%%c#G%%%%%%%%%%%%%%%%%%_1G%%%%%%%%%%%%%%%%%%!#G%%%%%%%%%%%%%%%%%%k\'G%%%%%%%%%%%%%%%%%%cCG%%%%%%%%%%%%%%%%%%o*IG%%%%%%%%%%%%%%%%%%A%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%=,#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%c:#T#%%%%%%%%%%%%%%%%%%w&%G%%%%%"
+"%%%%%%%%%%%%%=G%%%%%%%%%%%%%%%%%%_fG%%%%%%%%%%%%%%%%%%Z+G%%%%%%%%%%%%%%%%%%_%G%%%%%%%%%%%%%%%%%%>-G%%%%%%%%%%%%%%%%%%.9G%%%%%%%%%%%%%%%%%%w=G%%%%%%%%%%%%%%%%%%2+G%%%%%%%%%%%%%%%%%%>AG%%%%%%%%%%%%%%%%%%N)G%%%%%%%%%%%%%%%%%%=G%%%%%%%%%%%%%%%%%%%G%%%%%%%%%%%%%%%%%%B\'G%%%%%%%%%%%%%%%%%%FEG%%%%%%%%%%%%%%%%%%N)G%%%%%%%%%%%%%%%%%%oYG%%%%%%%%%%%%%%%%%%k\'G%%%%%%%%%%%%%%%%%%g5G%%%%%%%%%%%%%%%%%%*\'G%%%%%%%%%%%%%%%%%%F%G%%%%%%%%%%%%%%%%%%Z?G%%%%%%%%%%%%%%%%%%ow?G%%%%%%%%%%%%%%%%%%s4%G%%%%%%%%%%%%%%%%%%k\'G%%%%%%%%%%%%%%"
+"%%%%s+G%%%%%%%%%%%%%%%%%%:OG%%%%%%%%%%%%%%%%%%c#G%%%%%%%%%%%%%%%%%%N&OG%%%%%%%%%%%%%%%%%%VZ%G%%%%%%%%%%%%%%%%%%%G%%%%%%%%%%%%%%%%%%%G%%%%%%%%%%%%%%%%%%%G%%%%%%%%%%%%%%%%%%%G%%%%%%%%%%%%%%%%%%!8%G%%%%%%%%%%%%%%%%%%FEG%%%%%%%%%%%%%%%%%%sKG%%%%%%%%%%%%%%%%%%k5G%%%%%%%%%%%%%%%%%%.lG%%%%%%%%%%%%%%%%%%wN)G%%%%%%%%%%%%%%%%%%"};},Re=()=>{return {"value":"PA-Y$;Y$679:95Y#J+Y#Z$Y#B;697<8<C;6:7:PB-9[%=9<=&>:1=<=:L#<#Y#<,&?L$9B8:B(C9:C)!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!C#!#!#!#!#!#!#!#!C#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#B##!#!C$B##!#B##B$C#B%#B##B$C$B##B##!#!#B##!C#!#B##B$#!#B#C#&!C$F%!$#!$#!$#!#!#!#!#!#!#!#!C#!#!#!#!#!#!#!#!#!C#!$#!#B$#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!C(B##B#C#!#B%#!#!#!#!CgF#C;E3]%E-]/E&](%<%]2b\'Q! !#!#%<!#A#%C$9!A%]#!9B$ ! B##B2 B*CD!C#B$C$!#!#!#!#!#!#!#!#!#!#!#!C&!#:!#B#C#BTCQ!#!#!#!"
+"#!#!#!#!#!#!#!#!#!#!#!#!#!#=G&H#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#B##!#!#!#!#!#!C#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!# BG E#Y\'CJ95E#^#; GN5\'9G#9G#9G$A\'F<A%F%Y#A,Q\'Z$Y#;Y#^#G,91Y$FA%F+G6J+Y%F#\'b&D! 9&G(1=G\'E#G#=G%F#J+F$^#&Y/ 1&\'F?G<A#b&:! G,&A/J+FBG*E#=Y$%A#\'[#F7G%%G*%G$%G&A#Y0 F:G$A#9 F,A&F9<F(Q#A&G*FJ%G91GA)FW\')\'&I$G)I%\'I#&G(F+G#Y#J+9%F0\'I#\'F)A#F#A#F7 F( &A$F%A#\'&I$G%A#I#A#I#\'&A))A%F# F$G#A#J+F#[#L\'=;&9\'& G#) F\'A%F#A#F7 F( F# F"
+"# F#A#\' I$G#A%G#A#G$A$\'A(F% &A(J+G#F$\'9A+G#) F* F$ F7 F( F# F&A#\'&I$G& G#) I#\'A#&A0F#G#A#J+9;A(&G\' \'I# F)A#F#A#F7 F( F# F&A#\'&)\')G%A#I#A#I#\'A&G%)A%F# F$G#A#J+=&L\'A+\'& F\'A$F$ F%A$F# & F#A$F#A$F$A$F-A%I#\'I#A$I$ I$\'A#&A\')A/J+L$^\';=A&\'I$\'F) F$ F8 F1A#\'&G$I% G$ G%A(G# F$ F#A#F#G#A#J+A(9L(=&\'I#9F) F$ F8 F+ F&A#\'&)\'I& \'I# I#G#A(I#A&F$ F#G#A#J+ F#)A-G#I#F* F$ FJG#&I$G% I$ I$\'&=A%F$)L(F$G#A#J+L*=F\' \'I# F3A$F9 F* &A#F(A$\'A%I$G$ \' I)A\'J+A#I#9A-FQ\'F#G(A%;F\'%G)9J+Y#AFF# & F& F9 & F+\'F#G*&A#F& % G( J+A#F%AA&^$Y0=9^$G#^\'J"
+"+L+=\'=\'=\'6767I#F) FEA%G/)G&9G#F&G, GE ^)\'^\' ^#Y&^%Y#AFFLI#G%)G\')G#I#G#&J+Y\'F\'I#G#F%G$&I$F#I(F$G%F.\'I#G#I\'\'&)J+I$\'^#BG !A&!A#CL9%C$b&*& F%A#F( & F%A#FJ F%A#FB F%A#F( & F%A#F0 FZ F%A#FeA#G$Y*L5A$F1^+A\'b!7! A#C\'A#5b&M* =9F2-F;67A$FmY$K$F)A(F3G$)A*F4G#)Y#A*F3G#A-F. F$ G#A-FUG#)G(I)\'I#G,Y$%Y$;&\'A#J+A\'L+A\'Y\'5Y%G$1\'J+A\'FD%FWA\'F&G#FC\'&A&FhA+F@ G$I%G#I$A%I#\'I\'G$A%=A$Y#J+F?A#F&A,FMA%F;A\'J+,A$^CF8G#I#\'A#Y#FV)\')G( \')\'I#G)I\'G+A#\'J+A\'J+A\'Y(%Y\'A#G/(GSA0G%)FP\')G&)\'I&\'I#F) Y#J+Y(^+G*^*Y$G#)F?)G%I#G#)G$F#J+FM\')G#I$\')G$I#A)Y"
+"%FEI)G)I#G#A$Y&J+A$F$J+F?E\'Y#C*!#A&BLA#B$Y)A)G$9G.)G(F%\'F\'\'F#)G#&A&CMEaC.%CCEFGb!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!C*!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!C*B)C\'A#B\'A#C)B)C)B)C\'A#B\'A#C) ! ! ! !C)B)C/A#C)D)C)D)C)D)C& C#B%$<#]$C$ C#B%$]$C%A#C#B% ]$C)B&]$A#C$ C#B%$]# M,Q&U\'Y#>?6_#?6>Y)./Q&-Y*>?Y%X#Y$:67Y,:98Y+-Q& Q+,%A#L\'Z$67%L+Z$67E2[FA,G."
+"H%\'H$G-A0^#!^%!^##B$C#B$#=!^#:B&^\'!=!=!=B%=#B%#F%#^#C#B#Z&!C%=:^##=L1KD!#K%,^#A%Z&^&Z#^%:^#:^#:^(:^@Z#^#:=:^@b:-% ^)6767^5Z#^(67b=2! :^?Z:^IZ\'^jA7^,A6L^^pL7b=X# :^*:^WZ)b=P! :b=Y$ 67676767676767L?^MZ&67Z@6767676767Z1b= % b:$# 6767676767676767676767Za6767ZA67b:#% ^QZ6^#Z\'^HA#b=+# BQCQ!#B$C#!#!#!#B%#!C#!C\'E#B$#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!C#^\'!#!#G$!#A&Y%,Y#CG #A&#A#FYA(%9A/\'F8A*F( F( F( F( F( F( F( F( GAY#>?>?Y$>?9>?Y*5Y#59>?Y#>?67676767Y&%Y"
+"+U#Y%596Y.^#Y$676767675A#Y#67A=^; b=:! A-b=7$ A;^1-Y$=%&+6767676767^#6767676756W#=K*G%I#5E&^#K$%&9^# b&7! A#G#]#E#&5b&;! 9E$&A&FL b&?! ^#L%^+FA^GA*=F1^@ L+^?L)=L0^AL+^HL0b= & b& H!^bb& %b&6)!%b&X2 A$^XA*FIE\'Y#b&-% %Y$F1J+F#A5!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#&\'H$9G+9%!#!#!#!#!#!#!#!#!#!#!#!#!#!#E#G#FhK+G#Y\'A)]8E*]#!#!#!#!#!#!#!C$!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#!#%C)!#!#B##!#!#!#!#%]#!#!#&!#!C$!#!#!#!#!#!#!#!#!#!#B&#B&#!#!#!#!#!#!#!#B%#!#B##!#!#!#!#!#!#!#B#A%!A/E%!#&"
+"E##F(\'F$\'F%\'F8I#G#)^%\'A$L\'^#;=A\'FUY%A)I#FSI1G#A)Y#J+A\'G3F\'Y$&9F#\'J+F=G)Y#F8G,I#A,9F>A$G$)FP\'I#G%I#G#I$Y. %J+A%Y#F&\'%F*J+F& FJG\'I#G#I#G#A*F$\'F)\')A#J+A#Y%F1%F\'^$&)\')FS\'&G$F#G#F&G#&\'&A9F#%Y#F,)G#I#Y#&E#)\'A+F\'A#F\'A#F\'A*F( F( CL<E%C*%]#B#A#b#1! FDI#\'I#\'I#9)\'A#J+A\'b&EO#A-F8A%FRA%b4 A b3 E!b&O& A#b&K! AGC(A-C&A&&\'F+:F. F& & F# F# b&M! ]2^1b&L& 76^1Fb^#FW^)AAF-;^$G1Y(679A\'G19U#X#6767676767676767Y#67Y%X$Y$ Y%5676767Y$:5Z$ 9;Y#A%F& b&(# A#1 Y$;Y$679:95Y#J+Y#Z$Y#B;697<8<C;6:7:67967Y#F+%FNE#F@A$F\'A#F\'A#F\'A#F$A$[#:<=[# "
+"=Z%^#A+Q$^#A#F- F; F4 F# F0A#F/ACb&]! A&Y$A%LNA$^*KVL%^2L#^$ ^.A$=AP^N\'b ## F>A$FRA0\'L<A%FAL%A*F5+F)+A&FGG&A&F? 9FEA%F)9K&AKBICIFpA#J+A\'BEA%CEA%FIA)FUA,9B, B0 B( B# C, C0 C( C#A$FUA-b&X% A*F7A+F)A9E\' EK E/AbF\'A#& FM F#A$&A#F8 9L)F8^#L(F@A)L*AQF4 F#A&L&F7L\'A$9F;A&9F;AGFYA%L#F#L1A#LO&G$ G#A&G%F% F$ F>A#G$A%\'L*A(Y*A(F>L#9F>L$AAF)=F=G#A%L&Y(A*FWA$Y(F7A#L)F4A&L)F3A(Y%A-L(b 1! FkAXBTA.CTA(L\'FEG%A)J+A\'J+F%%&B7A$G&5%C7A)Z#b 1$ L@ FK G#5A#F#A1F$%F# ]#G&9^)F7 G1F>L+&A)F7G,L%Y&A7F3G%Y%AGF6L(A5F8A*)\')FVG0Y(A%L5J+\'F#G#&"
+"A*G$)FNI$G%I#G#Y#1Y%\'A+1A#F:A(J+A\'G$FEG&)G) J+Y%&I#&A)FD\'Y#&A*G#)FQI$G*I#F%Y%G%9)\'J+&9&Y$ L5A,F3 F:I$G$I#\')G#Y\'\'F#\'A`F( & F% F0 F+9A\'FP\'I$G)A&J+A\'G#I# F)A#F#A#F7 F( F# F& G#&I#\'I%A#I#A#I$A#&A\')A&F&I#A#G(A$G&A,F+ &A#& FG &I$G\' )A#) I% I#\')\'&\'&Y# Y#A)G#A>FVI$G)I#G$)\'F%Y&J+Y# 9\'F$A?FQI$G\')\'I%G#)G#F#9&A)J+b G# FPI$G%A#I%G#)G#Y8F%G#ACFQI$G)I#\')G#Y$&A,J+A\'Y.A4FL\')\'I#G\')\'&9A\'J+A\'J5A=F<A#\')\'I#G%)G&A%J+L#Y$=F(b Z# FMI$G*)G#9b E! BACAJ+L*A-F)A#&A#F) F# F9I\' I#A#G#)\'&)&)\'Y$A*J+AhF)A#FHI$G%A#G#I%\'&9&)A<&G+FIG\')&G%Y)\'A)"
+"&G\'I#G$FOG.)G#Y$&Y&A.FkA(Y+&b 6! \')G$)\')b 9! FB9A/J+A\'F* FF)G( G\')\'&Y&A+J+L4A$Y#F?A#G7 )G()G#)G#AkF( F# FGG\'A$\' G# G(&\'A)J+A\'F\' F# FAI& G# I#\')\'&A(J+A\'FJ%F#A%J+b W$ F4G#I#Y#A(G#&)F. FCI#G&A$I#\')\'Y.J+\'b 6! &A0L6^)[%^2A.9b&;/ b G! b+Q! Y&K,b&%$ A-b+X% b *E b&B! Y#A.b&Q1 Q1\'F\'G0A+b&<` A&b&(* b ZK!F?G-I$G$J+b \'< b&Z) A(F@ J+A%Y#Fq J+A\'F?A#G&9A+FQG(Y&^%E%9=A+J+ L( F6A&F4b Q\' E$FIE#Y$J+A\'F9\'F%\'A#J+b 7# BACAL8Y%A&B:A#C:AMFmA%\'&IXA(G%E.AbE#9%\'A,I#E#K$A*b&<T!AEFCb @! b&T! A.b&3/ A/FTb >Y!E% E( E# b&J% A*&A>F$A#&A/F&"
+"A(b&-\' b %E b&L! A&F.A$F*A(F+A#=G#9Q%b =_ b=Q$ J+^$A$b=U\' A\'^8 ^$A)Z$^1Z/A#GOA#G8A*b=U! A^b=W$ A+^HG#^^I#G$^$I\'Q)G)^#G(^?G%b=5# G$=A+I$^)G#^#)^AI#A`L5A-L5A-b=8! A*L:b (# B;C;B;C( C3B;C;! B#A#!A#B#A#B% B)C% # C( C,B;C;B# B%A#B) B( C;B# B% B& !A$B( C;B;C;B;C;B;C;B;C;B;C;B;C> B::C::C\'B::C::C\'B::C::C\'B::C::C\'B::C::C\'!#A#JSb= ) GX^%GS^)\'^/\'^#Y&A0G& G0b 1! Z>b D0 C+&CV!C(!#!C#!C$!C7!#!#!#!C$!#!#!#!#!#!#!#F#A/C(AWETG( G2A#G( G# G&A&E`AB\'b Q! FNA$G(E(A#J+A%&=b & F?\'A2FMG%J+A&;b 1( F<%G%J+b 7$ F?G#&J+A%9b $ F@ F$\'"
+"F#\'F(G#F&\'A)&%b A$ F( F% F# F0 b&&$ A#L*G(AJBCCCG(%A%J+A%Y#b 2- L]=L$;L%AnLN=L0b #$ F% F< F# &A#& F+ F% & &A\'&A%& & & F$ F# &A#& & & & & F# &A#F% F( F% F% & F+ F2A&F$ F& F2AUZ#b /% ^MA%b=E! A-^0A#^0 ^0 ^FA+L.b=C# AX^>A.^MA%^*A(^#A/^\'b ;# b=]$ ]&b=;, A#^2A$^.A$b==$ A%^-A%^=A%^YA)^+A\'^IA)^?A#^-A%^#A/Z*AHb=9& A)^/A#^.A$^i =A$^3 ^.A$^-A&b=4# b==! J+=b &1 b& %b& %b&A<#AAb&@%! b&/;!A#b&RU!A0b&O* b CG b&?) b C8 b&,.!A&b&K%#b %b %b \'O!b& R#b %b %b %b %b %b %b %b %b %b %b %b %b %b "
+"%b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b %b !0 1A?b1A! b # b\'Q$ b %b %b %b 1Y$b3 %b3 %b3 %b3`a$A#b3 %b3 %b3 %b3`a$"};},LX=L(),Rf=L();
function HH(){let a=this;J.call(a);a.ei=null;a.eQ=0;a.gt=null;a.hb=0;a.fa=0;a.e1=0;a.ey=0;a.f2=null;a.fb=null;}
let Ha=(a,b)=>{let c,d,e,f,g,h,i,j,k;c=new FL;c.fI=(-1);c.f9=(-1);c.ir=a;c.gS=a.f2;c.fk=b;c.fI=0;d=b.eh.length;c.f9=d;e=new Jf;f=c.fI;g=a.fa;h=a.e1+1|0;i=a.ey+1|0;j=a.fb;e.fr=(-1);g=g+1|0;e.gz=g;e.eS=BC(g*2|0);e.ic=j;k=BC(i);e.fM=k;DS(k,(-1));if(h>0)e.gv=BC(h);DS(e.eS,(-1));IG(e,b,f,d);c.eI=e;e.e8=1;return c;},Fv=a=>{return a.ei.eE;},FT=(b,c)=>{let d,e,f,g,h,i,j;if(b===null){b=new Do;Bu(b,I(37));Q(b);}if(c&&(c|255)!=255){b=new Bs;Bu(b,I(13));Q(b);}Dc();CD=1;d=new HH;d.gt=K(By,10);d.fa=(-1);d.e1=(-1);d.ey=(-1);e
=new Hs;e.eY=1;e.eE=b;if((c&16)>0){f=new W;Y(f);B4(f,I(38));g=0;while(true){h=FJ(b,I(39),g);if(h<0)break;i=h+2|0;B4(f,BW(b,g,i));B4(f,I(40));g=i;}B4(f,DV(b,g));B4(f,I(39));b=X(f);}e.et=BJ(b.eh.length+2|0);G2(D4(b),0,e.et,0,b.eh.length);j=e.et.data;i=j.length;j[i-1|0]=0;j[i-2|0]=0;e.gK=i;e.fe=c;Cx(e);Cx(e);d.ei=e;d.eQ=c;d.f2=I3(d,(-1),c,null);if(!BQ(d.ei)){b=new CB;d=d.ei;DB(b,I(13),d.eE,d.eL);Q(b);}if(d.hb)d.f2.bP();b=d.fb;if(b===null){DA();d.fb=GL;}else{DA();e=new IF;e.ip=b;d.fb=e;}return d;},I3=(a,b,c,d)=>
{let e,f,g,h,i,j,k,l,m;e=HB();f=a.eQ;g=0;if(c!=f)a.eQ=c;a:{switch(b){case -1073741784:h=new IN;c=a.ey+1|0;a.ey=c;CE(h,c);break a;case -536870872:case -268435416:break;case -134217688:case -67108824:h=new FQ;c=a.ey+1|0;a.ey=c;CE(h,c);break a;case -33554392:h=new Gc;c=a.ey+1|0;a.ey=c;CE(h,c);break a;default:c=a.fa+1|0;a.fa=c;if(d===null){h=new Cz;CE(h,0);g=1;}else{h=RU(c);if(b==(-2130706392)){if(a.fb===null){i=new Hf;H7(i,16,0.75);i.g2=0;i.fN=null;a.fb=i;}i=a.fb;j=a.ei.hr;k=a.fa;BN();if(k>=(-128)&&k<=127){b:{if
(Ea===null){Ea=K(Dw,256);l=0;while(true){m=Ea.data;if(l>=m.length)break b;m[l]=GU(l-128|0);l=l+1|0;}}}d=Ea.data[k+128|0];}else d=GU(k);i.bR(j,d);}}k=a.fa;if(k<=(-1))break a;if(k>=10)break a;a.gt.data[k]=h;break a;}h=new IK;CE(h,(-1));}while(true){if(Cq(a.ei)&&a.ei.ej==(-536870788)){d=Hk(Bp(a,2),Bp(a,64));while(!BQ(a.ei)&&Cq(a.ei)){i=a.ei;k=i.ej;if(k&&k!=(-536870788)&&k!=(-536870871))break;Bv(d,V(i));i=a.ei;if(i.ev!=(-536870788))continue;V(i);}i=Fu(a,d);i.bY(h);}else if(a.ei.ev==(-536870788)){i=Cp(h);V(a.ei);}
else{i=IY(a,h);d=a.ei;if(d.ev==(-536870788))V(d);}if(i!==null)C2(e,i);if(BQ(a.ei))break;if(a.ei.ev==(-536870871))break;}if(a.ei.gb==(-536870788))C2(e,Cp(h));if(a.eQ!=f&&!g){a.eQ=f;d=a.ei;d.fe=f;d.ej=d.ev;d.e5=d.e0;g=d.eL;d.ek=g+1|0;d.fw=g;Cx(d);}switch(b){case -1073741784:break;case -536870872:d=new F4;Cn(d,e,h);return d;case -268435416:d=new Gx;Cn(d,e,h);return d;case -134217688:d=new HK;Cn(d,e,h);return d;case -67108824:d=new Hb;Cn(d,e,h);return d;case -33554392:d=new BZ;Cn(d,e,h);return d;default:switch(e.eC)
{case 0:break;case 1:return Ob(BT(e,0),h);default:return QX(e,h);}return Cp(h);}d=new D7;Cn(d,e,h);return d;},R4=a=>{let b,c,d,e,f,g,h;b=BC(4);c=(-1);d=(-1);if(!BQ(a.ei)&&Cq(a.ei)){e=b.data;c=V(a.ei);e[0]=c;d=c-4352|0;}if(d>=0&&d<19){e=BJ(3);b=e.data;b[0]=c&65535;f=a.ei;g=f.ev;h=g-4449|0;if(h>=0&&h<21){b[1]=g&65535;V(f);f=a.ei;g=f.ev;c=g-4519|0;if(c>=0&&c<28){b[2]=g&65535;V(f);return G$(e,3);}return G$(e,2);}if(!Bp(a,2))return Fx(b[0]);if(Bp(a,64))return IZ(b[0]);return Iz(b[0]);}e=b.data;c=1;while(c<4&&!BQ(a.ei)
&&Cq(a.ei)){h=c+1|0;e[c]=V(a.ei);c=h;}if(c==1){h=e[0];if(!(OH.b7(h)==J3?0:1))return IE(a,e[0]);}if(!Bp(a,2))return Ry(b,c);if(Bp(a,64)){f=new H9;ER(f,b,c);return f;}f=new Hy;ER(f,b,c);return f;},IY=(a,b)=>{let c,d,e,f,g,h,i;if(Cq(a.ei)&&!E1(a.ei)&&DM(a.ei.ej)){if(Bp(a,128)){c=R4(a);if(!BQ(a.ei)){d=a.ei;e=d.ev;if(!(e==(-536870871)&&!(b instanceof Cz))&&e!=(-536870788)&&!Cq(d))c=E8(a,b,c);}}else if(!HP(a.ei)&&!Ji(a.ei)){d=new DT;Y(d);while(!BQ(a.ei)&&Cq(a.ei)&&!HP(a.ei)&&!Ji(a.ei)){if(!(!E1(a.ei)&&!a.ei.ej)&&
!(!E1(a.ei)&&DM(a.ei.ej))){f=a.ei.ej;if(f!=(-536870871)&&(f&(-2147418113))!=(-2147483608)&&f!=(-536870788)&&f!=(-536870876))break;}e=V(a.ei);if(!EM(e))Bh(d,e&65535);else DR(d,CI(e));}if(!Bp(a,2)){c=new IA;BV(c);c.eG=X(d);e=d.er;c.eA=e;c.f6=Hc(e);c.f5=Hc(c.eA);g=0;while(g<(c.eA-1|0)){HR(c.f6,P(c.eG,g),(c.eA-g|0)-1|0);HR(c.f5,P(c.eG,(c.eA-g|0)-1|0),(c.eA-g|0)-1|0);g=g+1|0;}}else c=Bp(a,64)?Q8(d):Sc(d);}else c=E8(a,b,H8(a,b));}else{d=a.ei;if(d.ev!=(-536870871))c=E8(a,b,H8(a,b));else{if(b instanceof Cz)Q(Bl(I(13),
d.eE,d.eL));c=Cp(b);}}a:{if(!BQ(a.ei)){e=a.ei.ev;if(!(e==(-536870871)&&!(b instanceof Cz))&&e!=(-536870788)){h=IY(a,b);if(c instanceof BF&&!(c instanceof Ck)&&!(c instanceof BG)&&!(c instanceof Ci)){i=c;d=i;if(!h.ck(d.eo)){c=new GW;d=d.eo;i=i;Cc(c,d,i.eg,i.fC);c.eo.bY(c);}}if((h.cm()&65535)!=43)c.bY(h);else c.bY(h.eo);break a;}}if(c===null)return null;c.bY(b);}if((c.cm()&65535)!=43)return c;return c.eo;},E8=(a,b,c)=>{let d,e,f,g,h;d=a.ei;e=d.ev;if(c!==null&&!(c instanceof Bn)){switch(e){case -2147483606:V(d);d
=new JF;BP(d,c,b,e);Ct();c.bY(CW);return d;case -2147483605:V(d);d=new FG;BP(d,c,b,(-2147483606));Ct();c.bY(CW);return d;case -2147483585:V(d);d=new Jh;BP(d,c,b,(-536870849));Ct();c.bY(CW);return d;case -2147483525:f=new GG;d=CO(d);g=a.e1+1|0;a.e1=g;Dr(f,d,c,b,(-536870849),g);Ct();c.bY(CW);return f;case -1073741782:case -1073741781:V(d);d=new Iv;BP(d,c,b,e);c.bY(d);return d;case -1073741761:V(d);d=new G8;BP(d,c,b,(-536870849));c.bY(b);return d;case -1073741701:f=new IP;d=CO(d);e=a.e1+1|0;a.e1=e;Dr(f,d,c,b,(-536870849),
e);c.bY(f);return f;case -536870870:case -536870869:V(d);if(c.cm()!=(-2147483602)){d=new BG;BP(d,c,b,e);}else if(Bp(a,32)){d=new Iw;BP(d,c,b,e);}else{d=new H$;f=Fr(a.eQ);BP(d,c,b,e);d.ga=f;}c.bY(d);return d;case -536870849:V(d);d=new CN;BP(d,c,b,(-536870849));c.bY(b);return d;case -536870789:f=new Cf;d=CO(d);e=a.e1+1|0;a.e1=e;Dr(f,d,c,b,(-536870849),e);c.bY(f);return f;default:}return c;}h=null;if(c!==null)h=c;switch(e){case -2147483606:case -2147483605:V(d);d=new JH;Cc(d,h,b,e);h.eg=d;return d;case -2147483585:V(d);c
=new GN;Cc(c,h,b,(-2147483585));return c;case -2147483525:c=new IX;Fp(c,CO(d),h,b,(-2147483525));return c;case -1073741782:case -1073741781:V(d);d=new G4;Cc(d,h,b,e);h.eg=d;return d;case -1073741761:V(d);c=new HG;Cc(c,h,b,(-1073741761));return c;case -1073741701:c=new HL;Fp(c,CO(d),h,b,(-1073741701));return c;case -536870870:case -536870869:V(d);d=PI(h,b,e);h.eg=d;return d;case -536870849:V(d);c=new Ci;Cc(c,h,b,(-536870849));return c;case -536870789:return KA(CO(d),h,b,(-536870789));default:}return c;},H8=(a,
b)=>{let c,d,e,f,g,h,i,j;c=null;d=b instanceof Cz;while(true){a:{e=a.ei;f=e.ev;if((f&(-2147418113))==(-2147483608)){V(e);g=(f&16711680)>>16;f=f&(-16711681);if(f==(-16777176))a.eQ=g;else{if(f!=(-1073741784))g=a.eQ;c=I3(a,f,g,b);e=a.ei;if(e.ev!=(-536870871))Q(Bl(I(13),e.eE,e.eL));V(e);}}else{b:{c:{switch(f){case -2147483599:case -2147483598:case -2147483597:case -2147483596:case -2147483595:case -2147483594:case -2147483593:case -2147483592:case -2147483591:break c;case -2147483583:break;case -2147483582:V(e);c
=GR(0);break a;case -2147483577:V(e);c=new H1;Bm(c);break a;case -2147483558:V(e);c=new EO;h=a.ey+1|0;a.ey=h;Gu(c,h);break a;case -2147483550:V(e);c=GR(1);break a;case -2147483526:V(e);c=new Hi;Bm(c);break a;case -536870876:V(e);a.ey=a.ey+1|0;if(Bp(a,8)){if(Bp(a,1)){c=Oc(a.ey);break a;}c=Lw(a.ey);break a;}if(Bp(a,1)){c=JZ(a.ey);break a;}c=Kv(a.ey);break a;case -536870866:V(e);if(Bp(a,32)){c=Ov();break a;}c=Q0(Fr(a.eQ));break a;case -536870821:V(e);i=0;c=a.ei;if(c.ev==(-536870818)){i=1;V(c);}c=Fu(a,C3(a,i));c.bY(b);e
=a.ei;if(e.ev!=(-536870819))Q(Bl(I(13),e.eE,e.eL));IC(e,1);V(a.ei);break a;case -536870818:V(e);a.ey=a.ey+1|0;if(!Bp(a,8)){c=new EU;Bm(c);break a;}c=new GZ;e=Fr(a.eQ);Bm(c);c.gB=e;break a;case 0:j=e.e0;if(j!==null)c=Fu(a,j);else{if(BQ(e)){c=Cp(b);break a;}c=Fx(f&65535);}V(a.ei);break a;default:break b;}V(e);c=new EU;Bm(c);break a;}h=(f&2147483647)-48|0;if(a.fa<h)Q(Bl(I(13),Ca(e),OF(a.ei)));V(e);a.ey=a.ey+1|0;c=!Bp(a,2)?MI(h,a.ey):Bp(a,64)?Od(h,a.ey):Qq(h,a.ey);a.gt.data[h].gr=1;a.hb=1;break a;}if(f>=0&&!C_(e))
{c=IE(a,f);V(a.ei);}else if(f==(-536870788))c=Cp(b);else{if(f!=(-536870871)){b=new CB;c=!C_(a.ei)?HA(f&65535):a.ei.e0.g();e=a.ei;DB(b,c,e.eE,e.eL);Q(b);}if(d){b=new CB;e=a.ei;DB(b,I(13),e.eE,e.eL);Q(b);}c=Cp(b);}}}if(f!=(-16777176))break;}return c;},C3=(a,b)=>{let c,d,e,f,g,h,i,j,$$je;c=Hk(Bp(a,2),Bp(a,64));Cb(c,b);d=(-1);e=0;f=0;g=1;a:{b:{c:while(true){if(BQ(a.ei))break a;h=a.ei;b=h.ev;f=b==(-536870819)&&!g?0:1;if(!f)break a;d:{switch(b){case -536870874:if(d>=0)Bv(c,d);d=V(a.ei);h=a.ei;if(h.ev!=(-536870874))
{d=38;break d;}if(h.ej==(-536870821)){V(h);e=1;d=(-1);break d;}V(h);if(g){c=C3(a,0);break d;}if(a.ei.ev==(-536870819))break d;Hm(c,C3(a,0));break d;case -536870867:if(!g){b=h.ej;if(b!=(-536870819)&&b!=(-536870821)&&d>=0){V(h);h=a.ei;i=h.ev;if(C_(h))break c;if(i<0){j=a.ei.ej;if(j!=(-536870819)&&j!=(-536870821)&&d>=0)break c;}e:{try{if(DM(i))break e;i=i&65535;break e;}catch($$e){$$je=BS($$e);if($$je instanceof Cr){break b;}else{throw $$e;}}}try{Bk(c,d,i);}catch($$e){$$je=BS($$e);if($$je instanceof Cr){break b;}
else{throw $$e;}}V(a.ei);d=(-1);break d;}}if(d>=0)Bv(c,d);d=45;V(a.ei);break d;case -536870821:if(d>=0){Bv(c,d);d=(-1);}V(a.ei);j=0;h=a.ei;if(h.ev==(-536870818)){V(h);j=1;}if(!e)Kz(c,C3(a,j));else Hm(c,C3(a,j));e=0;V(a.ei);break d;case -536870819:if(d>=0)Bv(c,d);d=93;V(a.ei);break d;case -536870818:if(d>=0)Bv(c,d);d=94;V(a.ei);break d;case 0:if(d>=0)Bv(c,d);h=a.ei.e0;if(h===null)d=0;else{RC(c,h);d=(-1);}V(a.ei);break d;default:}if(d>=0)Bv(c,d);d=V(a.ei);}g=0;}Q(Bl(I(13),Fv(a),a.ei.eL));}Q(Bl(I(13),Fv(a),a.ei.eL));}if
(!f){if(d>=0)Bv(c,d);return c;}Q(Bl(I(13),Fv(a),a.ei.eL-1|0));},IE=(a,b)=>{let c,d,e;c=EM(b);if(Bp(a,2)){a:{if(!(b>=97&&b<=122)){if(b<65)break a;if(b>90)break a;}return Iz(b&65535);}if(Bp(a,64)&&b>128){if(c){d=new FR;BV(d);d.eA=2;d.gs=CV(Dh(b));return d;}if(Fj(b))return Hj(b&65535);if(!Ei(b))return IZ(b&65535);return H5(b&65535);}}if(!c){if(Fj(b))return Hj(b&65535);if(!Ei(b))return Fx(b&65535);return H5(b&65535);}d=new B7;BV(d);d.eA=2;d.fg=b;e=(CI(b)).data;d.fz=e[0];d.fp=e[1];return d;},Fu=(a,b)=>{let c,d,e;if
(!KM(b)){if(!b.eq){if(b.cJ())return Hu(b);return GS(b);}if(!b.cJ())return IS(b);c=new Em;E2(c,b);return c;}c=Nf(b);d=new Ga;Bm(d);d.gg=c;d.gH=c.ew;if(!b.eq){if(b.cJ())return EZ(Hu(DU(b)),d);return EZ(GS(DU(b)),d);}if(!b.cJ())return EZ(IS(DU(b)),d);c=new Ex;e=new Em;E2(e,DU(b));Iy(c,e,d);return c;},RJ=b=>{return FT(b,0);},Da=b=>{if(b>=97&&b<=122)b=(b-32|0)&65535;else if(b>=65&&b<=90)b=(b+32|0)&65535;return b;},Bp=(a,b)=>{return (a.eQ&b)!=b?0:1;},EC=L(0);
function FL(){let a=this;J.call(a);a.ir=null;a.gS=null;a.fk=null;a.eI=null;a.fI=0;a.f9=0;a.gl=0;a.ho=null;a.fV=null;a.e2=null;}
let Gm=(a,b,c)=>{let d,e;a.fV=OB(a,c);c=a.fk;d=a.gl;e=C$(a);Fo(b,BW(c,d,e));B4(b,a.fV);a.gl=Dq(a);return a;},OB=(a,b)=>{let c,d,e,f,g,h,i,j,k,l,$$je;c=a.ho;if(c!==null&&C1(c,b)){if(a.e2===null)return a.fV;d=new W;Y(d);e=0;while(true){b=a.e2;if(e>=b.eC)break;T(d,BT(b,e));e=e+1|0;}return X(d);}a.ho=b;f=D4(b);c=new W;Y(c);a.e2=null;g=0;h=0;i=0;a:{b:while(true){j=f.data;e=j.length;if(g>=e){b=a.e2;if(b!==null){k=c.er;if(h!=k)C2(b,Jb(c,h,k));}return X(c);}if(j[g]==92&&!i){i=1;g=g+1|0;}c:{if(i){if(g>=e)break b;Bh(c,
j[g]);i=0;}else if(j[g]!=36)Bh(c,j[g]);else{if(a.e2===null)a.e2=HB();d:{try{b=new Br;g=g+1|0;Jy(b,f,g,1);k=RS(b);if(h==C5(c))break d;C2(a.e2,Jb(c,h,C5(c)));h=C5(c);break d;}catch($$e){$$je=BS($$e);if($$je instanceof Cr){break a;}else{throw $$e;}}}try{C2(a.e2,J8(a,k));l=Ep(a,k);h=h+Dz(l)|0;Lf(c,l);break c;}catch($$e){$$je=BS($$e);if($$je instanceof Cr){break a;}else{throw $$e;}}}}g=g+1|0;}b=new Bo;Z(b);Q(b);}b=new Bs;Bu(b,I(13));Q(b);},GK=(a,b)=>{let c,d;c=a.fk;d=a.gl;c=c;Fo(b,BW(c,d,c.eh.length));return b;},Ep
=(a,b)=>{let c,d,e;c=a.eI;if(D9(c,b)<0)c=null;else{d=c.fS;e=D9(c,b);b=Fn(c,b);c=BW(d,e,b);}return c;},F5=(a,b)=>{let c,d,e;c=a.fk.eh.length;if(b>=0&&b<=c){IG(a.eI,null,(-1),(-1));d=a.eI;d.fE=1;d.eW=b;c=d.fr;if(c<0)c=b;d.fr=c;b=a.gS.cU(b,a.fk,d);if(b==(-1))a.eI.eP=1;if(b>=0){d=a.eI;if(d.fQ){e=d.eS.data;if(e[0]==(-1)){c=d.eW;e[0]=c;e[1]=c;}d.fr=Ej(d);return 1;}}a.eI.eW=(-1);return 0;}d=new Bo;Bu(d,Hg(b));Q(d);},G6=a=>{let b,c,d;b=a.fk.eh.length;c=a.eI;if(!c.fG)b=a.f9;if(c.eW>=0&&c.fE==1){c.eW=Ej(c);if(Ej(a.eI)
==D9(a.eI,0)){c=a.eI;c.eW=c.eW+1|0;}d=a.eI.eW;return d<=b&&F5(a,d)?1:0;}return F5(a,a.fI);},Fq=(a,b)=>{return D9(a.eI,b);},Eu=(a,b)=>{return Fn(a.eI,b);},C$=a=>{return Fq(a,0);},Dq=a=>{return Eu(a,0);},DT=L(Cl),NH=a=>{Y(a);},QH=()=>{let a=new DT();NH(a);return a;},LI=(a,b,c,d,e)=>{GA(a,b,c,d,e);return a;},OJ=(a,b,c,d)=>{HQ(a,b,c,d);return a;},Ms=(a,b,c,d,e)=>{Gr(a,b,c,d,e);return a;},Q7=(a,b,c,d)=>{E3(a,b,c,d);return a;},I$=a=>{return a.er;},PM=(a,b)=>{EQ(a,b);},Ly=(a,b,c)=>{In(a,b,c);return a;},K2=(a,b,c)=>
{Eh(a,b,c);return a;},Bo=L(Bi);
function Jf(){let a=this;J.call(a);a.eS=null;a.fM=null;a.gv=null;a.fS=null;a.gz=0;a.fQ=0;a.eK=0;a.el=0;a.eW=0;a.fG=0;a.e8=0;a.eP=0;a.hW=0;a.fr=0;a.fE=0;a.ic=null;}
let Bf=(a,b,c)=>{a.fM.data[b]=c;},BU=(a,b)=>{return a.fM.data[b];},Ej=a=>{return Fn(a,0);},Fn=(a,b)=>{IO(a,b);return a.eS.data[(b*2|0)+1|0];},B0=(a,b,c)=>{a.eS.data[b*2|0]=c;},ES=(a,b,c)=>{a.eS.data[(b*2|0)+1|0]=c;},CL=(a,b)=>{return a.eS.data[b*2|0];},D6=(a,b)=>{return a.eS.data[(b*2|0)+1|0];},D9=(a,b)=>{IO(a,b);return a.eS.data[b*2|0];},Gf=(a,b)=>{return a.gv.data[b];},Cd=(a,b,c)=>{a.gv.data[b]=c;},IO=(a,b)=>{let c;if(!a.fQ){c=new GI;Z(c);Q(c);}if(b>=0&&b<a.gz)return;c=new Bo;Bu(c,Hg(b));Q(c);},IG=(a,b,c,
d)=>{a.fQ=0;a.fE=2;DS(a.eS,(-1));DS(a.fM,(-1));if(b!==null)a.fS=b;if(c>=0){a.eK=c;a.el=d;}a.eW=a.eK;},Bs=L(Bi);
function CB(){let a=this;Bs.call(a);a.gp=null;a.f3=null;a.fv=0;}
let DB=(a,b,c,d)=>{Z(a);a.fv=(-1);a.gp=b;a.f3=c;a.fv=d;},Bl=(a,b,c)=>{let d=new CB();DB(d,a,b,c);return d;},OC=L(),Bw=(b,c)=>{if(b<c)c=b;return c;},BR=(b,c)=>{if(b>c)c=b;return c;};
function H2(){J.call(this);this.fO=null;}
let LC=a=>{let b;b=new W;Y(b);a.fO=b;},FX=()=>{let a=new H2();LC(a);return a;},B5=(a,b)=>{let c;c=BY(a.fO,b.eh.length);Bh(c,58);B4(c,b);},ED=(a,b)=>{B5(a,C7(b));},FE=(a,b)=>{B5(a,!b?I(0):I(21));},HX=a=>{return X(a.fO);},Do=L(Bi);
function Bd(){let a=this;J.call(a);a.eg=null;a.eH=0;a.fW=null;a.fC=0;}
let CD=0,Dc=()=>{Dc=B3(Bd);QK();},Bm=a=>{let b;Dc();b=CD;CD=b+1|0;a.fW=C7(b);},Je=(a,b)=>{let c;Dc();c=CD;CD=c+1|0;a.fW=C7(c);a.eg=b;},Dg=(a,b,c,d)=>{let e;e=d.el;while(true){if(b>e)return (-1);if(a.cY(b,c,d)>=0)break;b=b+1|0;}return b;},CY=(a,b,c,d,e)=>{while(true){if(c<b)return (-1);if(a.cY(c,d,e)>=0)break;c=c+(-1)|0;}return c;},So=(a,b)=>{a.fC=b;},Pg=a=>{return a.fC;},Lu=a=>{let b,c,d;b=a.fW;c=a.L();d=new W;Y(d);Bh(d,60);b=T(d,b);Bh(b,58);Bh(T(b,c),62);return X(d);},Rl=a=>{return Lu(a);},Sg=a=>{return a.eg;},MU
=(a,b)=>{a.eg=b;},MT=(a,b)=>{return 1;},PA=a=>{return null;},Dm=a=>{let b;a.eH=1;b=a.eg;if(b!==null){if(!b.eH){b=b.c0();if(b!==null){a.eg.eH=1;a.eg=b;}a.eg.bP();}else if(b instanceof CM&&b.eX.gr)a.eg=b.eg;}},QK=()=>{CD=1;},GF=L(0),DQ=L(0),CU=L(),I4=L(0),Ge=L(0);
function C6(){CU.call(this);this.eZ=0;}
let CS=L(0),EV=L(0);
function HD(){let a=this;C6.call(a);a.eU=null;a.eC=0;}
let L5=a=>{a.eU=K(J,10);},HB=()=>{let a=new HD();L5(a);return a;},G3=(a,b)=>{let c,d;c=a.eU.data.length;if(c<b){d=c>=1073741823?2147483647:BR(b,BR(c*2|0,5));a.eU=H3(a.eU,d);}},BT=(a,b)=>{Gb(a,b);return a.eU.data[b];},C2=(a,b)=>{let c,d;G3(a,a.eC+1|0);c=a.eU.data;d=a.eC;a.eC=d+1|0;c[d]=b;a.eZ=a.eZ+1|0;return 1;},Gb=(a,b)=>{let c;if(b>=0&&b<a.eC)return;c=new Bo;Z(c);Q(c);};
function Gv(){let a=this;J.call(a);a.hy=0;a.ib=0;a.hJ=null;}
let Sa=(a,b,c)=>{a.hJ=b;a.ib=c;a.hy=c;},J8=(a,b)=>{let c=new Gv();Sa(c,a,b);return c;},R$=a=>{return Ep(a.hJ,a.hy);},PZ=L(Bi);
function By(){let a=this;Bd.call(a);a.gr=0;a.eT=0;}
let CW=null,Ct=()=>{Ct=B3(By);K7();},CE=(a,b)=>{Ct();Bm(a);a.eT=b;},RU=a=>{let b=new By();CE(b,a);return b;},Nn=(a,b,c,d)=>{let e,f;e=D6(d,a.eT);ES(d,a.eT,b);f=a.eg.cY(b,c,d);if(f<0)ES(d,a.eT,e);return f;},Sh=a=>{return a.eT;},NS=a=>{return I(41);},OM=(a,b)=>{return 0;},K7=()=>{let b;b=new HT;Bm(b);CW=b;};
function Hs(){let a=this;J.call(a);a.et=null;a.fe=0;a.eY=0;a.g1=0;a.gb=0;a.ev=0;a.ej=0;a.hr=null;a.gK=0;a.e0=null;a.e5=null;a.ek=0;a.fx=0;a.eL=0;a.fw=0;a.eE=null;}
let K8=null,OH=null,J3=0,IC=(a,b)=>{if(b>0&&b<3)a.eY=b;if(b==1){a.ej=a.ev;a.e5=a.e0;a.ek=a.fw;a.fw=a.eL;Cx(a);}},C_=a=>{return a.e0===null?0:1;},E1=a=>{return a.e5===null?0:1;},V=a=>{Cx(a);return a.gb;},CO=a=>{let b;b=a.e0;Cx(a);return b;},Cx=a=>{let b,c,d,e,f,g,h,i,$$je;a.gb=a.ev;a.ev=a.ej;a.e0=a.e5;a.eL=a.fw;a.fw=a.ek;a:{while(true){b=0;c=a.ek>=a.et.data.length?0:Fs(a);a.ej=c;a.e5=null;if(a.eY==4){if(c!=92)return;c=a.ek;d=a.et.data;c=c>=d.length?0:d[Bg(a)];a.ej=c;switch(c){case 69:break;default:a.ej=92;a.ek
=a.fx;return;}a.eY=a.g1;a.ej=a.ek>(a.et.data.length-2|0)?0:Fs(a);}b:{c=a.ej;if(c==92){c=a.ek>=(a.et.data.length-2|0)?(-1):Fs(a);c:{a.ej=c;switch(c){case -1:Q(Bl(I(13),Ca(a),a.ek));case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9:case 10:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 21:case 22:case 23:case 24:case 25:case 26:case 27:case 28:case 29:case 30:case 31:case 32:case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 118:break;case 48:a.ej
=Na(a);break b;case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:if(a.eY!=1)break b;a.ej=(-2147483648)|c;break b;case 65:a.ej=(-2147483583);break b;case 66:a.ej=(-2147483582);break b;case 67:case 69:case 70:case 72:case 73:case 74:case 75:case 76:case 77:case 78:case 79:case 82:case 84:case 85:case 86:case 88:case 89:case 103:case 104:case 105:case 106:case 107:case 108:case 109:case 111:case 113:case 121:Q(Bl(I(13),Ca(a),a.ek));case 68:case 83:case 87:case 100:case 115:case 119:a.e5=Ja(D3(a.et,
a.fx,1),0);a.ej=0;break b;case 71:a.ej=(-2147483577);break b;case 80:case 112:break c;case 81:a.g1=a.eY;a.eY=4;b=1;break b;case 90:a.ej=(-2147483558);break b;case 97:a.ej=7;break b;case 98:a.ej=(-2147483550);break b;case 99:c=a.ek;d=a.et.data;if(c>=(d.length-2|0))Q(Bl(I(13),Ca(a),a.ek));a.ej=d[Bg(a)]&31;break b;case 101:a.ej=27;break b;case 102:a.ej=12;break b;case 110:a.ej=10;break b;case 114:a.ej=13;break b;case 116:a.ej=9;break b;case 117:a.ej=FK(a,4);break b;case 120:a.ej=FK(a,2);break b;case 122:a.ej=(-2147483526);break b;default:}break b;}e
=L8(a);f=0;if(a.ej==80)f=1;try{a.e5=Ja(e,f);}catch($$e){$$je=BS($$e);if($$je instanceof Et){Q(Bl(I(13),Ca(a),a.ek));}else{throw $$e;}}a.ej=0;}else{g=a.eY;if(g==1)switch(c){case 36:a.ej=(-536870876);break b;case 40:if(a.et.data[a.ek]!=63){a.ej=(-2147483608);break b;}Bg(a);c=a.et.data[a.ek];g=0;h=null;while(true){d:{if(!g){switch(c){case 33:break;case 60:Bg(a);c=a.et.data[a.ek];g=1;break d;case 61:a.ej=(-536870872);Bg(a);break d;case 62:a.ej=(-33554392);Bg(a);break d;default:i=QQ(a);a.ej=i;if(i<256){a.fe=i;i=
i<<16;a.ej=i;a.ej=(-1073741784)|i;break d;}i=i&255;a.ej=i;a.fe=i;i=i<<16;a.ej=i;a.ej=(-16777176)|i;break d;}a.ej=(-268435416);Bg(a);}else{e:{switch(c){case 33:break;case 61:g=0;a.ej=(-134217688);Bg(a);break d;case 62:if(h===null)Q(Bl(I(13),Ca(a),a.ek));a.hr=C9(h);Bg(a);h=null;g=0;a.ej=(-2130706392);break d;default:break e;}g=0;a.ej=(-67108824);Bg(a);break d;}f:{if(!(c>=65&&c<=90)){if(c<97)break f;if(c>122)break f;}if(h===null)h=L7();F$(h,c);Bg(a);c=a.et.data[a.ek];break d;}if(c<48)break a;if(c>57)break a;if
(h===null)Q(Bl(I(42),Ca(a),a.ek));F$(h,c);Bg(a);c=a.et.data[a.ek];}}if(!g)break;}break b;case 41:a.ej=(-536870871);break b;case 42:case 43:case 63:g=a.ek;d=a.et.data;switch(g>=d.length?42:d[g]){case 43:a.ej=c|(-2147483648);Bg(a);break b;case 63:a.ej=c|(-1073741824);Bg(a);break b;default:}a.ej=c|(-536870912);break b;case 46:a.ej=(-536870866);break b;case 91:a.ej=(-536870821);IC(a,2);break b;case 93:if(g!=2)break b;a.ej=(-536870819);break b;case 94:a.ej=(-536870818);break b;case 123:a.e5=Ne(a,c);break b;case 124:a.ej
=(-536870788);break b;default:}else if(g==2)switch(c){case 38:a.ej=(-536870874);break b;case 45:a.ej=(-536870867);break b;case 91:a.ej=(-536870821);break b;case 93:a.ej=(-536870819);break b;case 94:a.ej=(-536870818);break b;default:}}}if(b)continue;else break;}return;}Q(Bl(I(13),Ca(a),a.ek));},L8=a=>{let b,c,d,e,f,g;b=new W;CX(b,10);c=a.ek;d=a.et;e=d.data;if(c<(e.length-2|0)){if(e[c]!=123){b=D3(d,Bg(a),1);f=new W;Y(f);T(T(f,I(43)),b);return X(f);}Bg(a);c=0;a:{while(true){g=a.ek;d=a.et.data;if(g>=(d.length-2
|0))break;c=d[Bg(a)];if(c==125)break a;Bh(b,c);}}if(c!=125)Q(Bl(I(13),a.eE,a.ek));}if(!b.er)Q(Bl(I(13),a.eE,a.ek));f=X(b);if(f.eh.length==1){b=new W;Y(b);T(T(b,I(43)),f);return X(b);}b:{c:{if(f.eh.length>3){if(II(f,I(43)))break c;if(II(f,I(44)))break c;}break b;}f=DV(f,2);}return f;},Ne=(a,b)=>{let c,d,e,f,g,$$je;c=new W;CX(c,4);d=(-1);e=2147483647;a:{while(true){f=a.ek;g=a.et.data;if(f>=g.length)break a;b=g[Bg(a)];if(b==125)break a;if(b==44&&d<0)try{d=DN(C9(c),10);N6(c,0,C5(c));continue;}catch($$e){$$je=BS($$e);if
($$je instanceof BK){break;}else{throw $$e;}}Bh(c,b&65535);}Q(Bl(I(13),a.eE,a.ek));}if(b!=125)Q(Bl(I(13),a.eE,a.ek));if(c.er>0)b:{try{e=DN(C9(c),10);if(d>=0)break b;d=e;break b;}catch($$e){$$je=BS($$e);if($$je instanceof BK){Q(Bl(I(13),a.eE,a.ek));}else{throw $$e;}}}else if(d<0)Q(Bl(I(13),a.eE,a.ek));if((d|e|(e-d|0))<0)Q(Bl(I(13),a.eE,a.ek));b=a.ek;g=a.et.data;f=b>=g.length?42:g[b];c:{switch(f){case 43:a.ej=(-2147483525);Bg(a);break c;case 63:a.ej=(-1073741701);Bg(a);break c;default:}a.ej=(-536870789);}c=new Gt;c.e7
=d;c.e4=e;return c;},Ca=a=>{return a.eE;},BQ=a=>{return !a.ev&&!a.ej&&a.ek==a.gK&&!C_(a)?1:0;},DM=b=>{return b<0?0:1;},Cq=a=>{return !BQ(a)&&!C_(a)&&DM(a.ev)?1:0;},HP=a=>{let b;b=a.ev;return b<=56319&&b>=55296?1:0;},Ji=a=>{let b;b=a.ev;return b<=57343&&b>=56320?1:0;},Ei=b=>{return b<=56319&&b>=55296?1:0;},Fj=b=>{return b<=57343&&b>=56320?1:0;},FK=(a,b)=>{let c,d,e,f,$$je;c=new W;CX(c,b);d=a.et.data.length-2|0;e=0;while(true){f=B1(e,b);if(f>=0)break;if(a.ek>=d)break;Bh(c,a.et.data[Bg(a)]);e=e+1|0;}if(!f)a:{try
{b=DN(C9(c),16);}catch($$e){$$je=BS($$e);if($$je instanceof BK){break a;}else{throw $$e;}}return b;}Q(Bl(I(13),a.eE,a.ek));},Na=a=>{let b,c,d,e,f,g;b=3;c=1;d=a.et.data;e=d.length-2|0;f=Fc(d[a.ek],8);switch(f){case -1:break;default:if(f>3)b=2;Bg(a);a:{while(true){if(c>=b)break a;g=a.ek;if(g>=e)break a;g=Fc(a.et.data[g],8);if(g<0)break;f=(f*8|0)+g|0;Bg(a);c=c+1|0;}}return f;}Q(Bl(I(13),a.eE,a.ek));},QQ=a=>{let b,c,d,e;b=1;c=a.fe;a:while(true){d=a.ek;e=a.et.data;if(d>=e.length)Q(Bl(I(13),a.eE,d));b:{c:{switch(e[d])
{case 41:Bg(a);return c|256;case 45:if(!b)Q(Bl(I(13),a.eE,d));b=0;break b;case 58:break a;case 100:break c;case 105:c=b?c|2:(c^2)&c;break b;case 109:c=b?c|8:(c^8)&c;break b;case 115:c=b?c|32:(c^32)&c;break b;case 117:c=b?c|64:(c^64)&c;break b;case 120:c=b?c|4:(c^4)&c;break b;default:}break b;}c=b?c|1:(c^1)&c;}Bg(a);}Bg(a);return c;},Bg=a=>{let b,c,d,e,f;b=a.ek;a.fx=b;if(!(a.fe&4))a.ek=b+1|0;else{c=a.et.data.length-2|0;a.ek=b+1|0;a:while(true){d=a.ek;if(d<c){d=a.et.data[d];Bb();if(HI(d)){a.ek=a.ek+1|0;continue;}}d
=a.ek;if(d>=c)break;e=a.et.data;if(e[d]!=35)break;a.ek=d+1|0;while(true){f=a.ek;if(f>=c)continue a;b=e[f];if(b!=10&&b!=13&&b!=133&&(b|1)!=8233?0:1)continue a;a.ek=f+1|0;}}}return a.fx;},GV=b=>{return K8.df(b);},Fs=a=>{let b,c,d,e;b=a.et.data[Bg(a)];if(BA(b)){c=a.fx+1|0;d=a.et.data;if(c<d.length){e=d[c];if(BI(e)){Bg(a);return Cm(b,e);}}}return b;},OF=a=>{return a.eL;},ND=L(),EK=(b,c)=>{let d,e,f,g;b=b.data;d=I0(c);e=d.data;f=Bw(c,b.length);g=0;while(g<f){e[g]=b[g];g=g+1|0;}return d;},H3=(b,c)=>{let d,e,f,g;d
=Ek(Co(b));if(d===null){d=new Do;Z(d);Q(d);}if(d===B9(N$)){d=new Bs;Z(d);Q(d);}if(c<0){d=new JD;Z(d);Q(d);}b=b.data;e=KP(d.eJ,c);f=Bw(c,b.length);g=0;while(g<f){e.data[g]=b[g];g=g+1|0;}return e;},Gi=(b,c,d,e)=>{let f,g,h;if(c>d){f=new Bs;Z(f);Q(f);}while(c<d){g=b.data;h=c+1|0;g[c]=e;c=h;}},DS=(b,c)=>{Gi(b,0,b.data.length,c);},CJ=L(Bo),OL=L(),Se=(b,c,d,e,f)=>{let g,h,i,j,k,l,m,n;if(b!==null&&d!==null){if(c>=0&&e>=0&&f>=0&&(c+f|0)<=DZ(b)&&(e+f|0)<=DZ(d)){a:{b:{if(b!==d){g=Ek(Co(b));h=Ek(Co(d));if(g!==null&&h!==
null){if(g===h)break b;if(!DI(g)&&!DI(h)){i=b;j=0;k=c;while(j<f){c:{l=i.data;m=k+1|0;n=l[k];if(n!==null){n=Co(n);if(Di(n.eJ,h.eJ)){k=1;break c;}}k=0;}if(!k){D8(b,c,d,e,j);b=new DJ;Z(b);Q(b);}j=j+1|0;k=m;}D8(b,c,d,e,f);return;}if(!DI(g))break a;if(DI(h))break b;else break a;}b=new DJ;Z(b);Q(b);}}D8(b,c,d,e,f);return;}b=new DJ;Z(b);Q(b);}b=new Bo;Z(b);Q(b);}d=new Do;Bu(d,I(45));Q(d);},G2=(b,c,d,e,f)=>{if(c>=0&&e>=0&&f>=0&&(c+f|0)<=DZ(b)&&(e+f|0)<=DZ(d)){D8(b,c,d,e,f);return;}b=new Bo;Z(b);Q(b);},D8=(b,c,d,e,f)=>
{if(f!==0){if(typeof b.data.buffer!=='undefined'){d.data.set(b.data.subarray(c,c+f),e);}else if(b!==d||e<c){for(let i=0;i<f;i=i+1|0){d.data[e++]=b.data[c++];}}else {c=c+f|0;e=e+f|0;for(let i=0;i<f;i=i+1|0){d.data[ --e]=b.data[ --c];}}}},IN=L(By),LP=(a,b,c,d)=>{let e;e=a.eT;Bf(d,e,b-BU(d,e)|0);return a.eg.cY(b,c,d);},Ra=a=>{return I(46);},R5=(a,b)=>{return 0;},IK=L(By),QI=(a,b,c,d)=>{return b;},P0=a=>{return I(47);},FQ=L(By),O$=(a,b,c,d)=>{if(BU(d,a.eT)!=b)b=(-1);return b;},MD=a=>{return I(48);};
function Gc(){By.call(this);this.g4=0;}
let L_=(a,b,c,d)=>{let e;e=a.eT;Bf(d,e,b-BU(d,e)|0);a.g4=b;return b;},KG=a=>{return I(49);},Or=(a,b)=>{return 0;},Cz=L(By),Nu=(a,b,c,d)=>{if(d.fE!=1&&b!=d.el)return (-1);d.fQ=1;ES(d,0,b);return b;},PN=a=>{return I(50);};
function Bn(){Bd.call(this);this.eA=0;}
let BV=a=>{Bm(a);a.eA=1;},QV=(a,b,c,d)=>{let e;if((b+a.dq()|0)>d.el){d.eP=1;return (-1);}e=a.dr(b,c);if(e<0)return (-1);return a.eg.cY(b+e|0,c,d);},LB=a=>{return a.eA;},PS=(a,b)=>{return 1;},Gp=L(Bn),Lg=(a,b)=>{Je(a,b);a.eA=1;a.fC=1;a.eA=0;},Cp=a=>{let b=new Gp();Lg(b,a);return b;},Kl=(a,b,c)=>{return 0;},Ls=(a,b,c,d)=>{let e,f,g,h;e=d.el;f=d.eK;while(true){g=B1(b,e);if(g>0)return (-1);if(g<0){h=c;if(BI(P(h,b))&&b>f&&BA(P(h,b-1|0))){b=b+1|0;continue;}}if(a.eg.cY(b,c,d)>=0)break;b=b+1|0;}return b;},Qp=(a,b,c,
d,e)=>{let f,g,h;f=e.el;g=e.eK;while(true){if(c<b)return (-1);if(c<f){h=d;if(BI(P(h,c))&&c>g&&BA(P(h,c-1|0))){c=c+(-1)|0;continue;}}if(a.eg.cY(c,d,e)>=0)break;c=c+(-1)|0;}return c;},NU=a=>{return I(51);},L3=(a,b)=>{return 0;};
function Bj(){let a=this;Bd.call(a);a.eB=null;a.eX=null;a.es=0;}
let Cn=(a,b,c)=>{Bm(a);a.eB=b;a.eX=c;a.es=c.eT;},QX=(a,b)=>{let c=new Bj();Cn(c,a,b);return c;},MY=(a,b,c,d)=>{let e,f,g,h;if(a.eB===null)return (-1);e=CL(d,a.es);B0(d,a.es,b);f=a.eB.eC;g=0;while(true){if(g>=f){B0(d,a.es,e);return (-1);}h=(BT(a.eB,g)).cY(b,c,d);if(h>=0)break;g=g+1|0;}return h;},Oh=(a,b)=>{a.eX.eg=b;},P9=a=>{return I(52);},RY=(a,b)=>{let c,d,e,f;a:{c=a.eB;if(c!==null){c=c;d=new He;d.hp=c;d.hf=c.eZ;d.hk=c.eC;d.gO=(-1);d=d;while(true){e=d.hd;if(!(e>=d.hk?0:1))break a;f=d.hf;c=d.hp;if(f!=c.eZ){b
=new GY;Z(b);Q(b);}d.gO=e;d.hd=e+1|0;if(!(BT(c,e)).ck(b))continue;else break;}return 1;}}return 0;},Qe=(a,b)=>{return D6(b,a.es)>=0&&CL(b,a.es)==D6(b,a.es)?0:1;},Qk=a=>{let b,c,d,e,f,g,h,i,j;a.eH=1;b=a.eX;if(b!==null&&!b.eH)Dm(b);a:{b=a.eB;if(b!==null){c=b.eC;d=0;while(true){if(d>=c)break a;b=BT(a.eB,d);e=b.c0();if(e===null)e=b;else{b.eH=1;f=a.eB;Gb(f,d);g=f.eC-1|0;f.eC=g;h=d;while(h<g){i=f.eU.data;j=h+1|0;i[h]=i[j];h=j;}f.eU.data[g]=null;f.eZ=f.eZ+1|0;f=a.eB;if(d<0)break;j=f.eC;if(d>j)break;G3(f,j+1|0);h=f.eC;g
=h;while(g>d){i=f.eU.data;i[g]=i[g-1|0];g=g+(-1)|0;}f.eU.data[d]=e;f.eC=h+1|0;f.eZ=f.eZ+1|0;}if(!e.eH)e.bP();d=d+1|0;}b=new Bo;Z(b);Q(b);}}if(a.eg!==null)Dm(a);},D7=L(Bj),NF=(a,b,c,d)=>{let e,f,g,h;e=BU(d,a.es);Bf(d,a.es,b);f=a.eB.eC;g=0;while(true){if(g>=f){Bf(d,a.es,e);return (-1);}h=(BT(a.eB,g)).cY(b,c,d);if(h>=0)break;g=g+1|0;}return h;},J6=a=>{return I(53);},Q9=(a,b)=>{return !BU(b,a.es)?0:1;},BZ=L(D7),RX=(a,b,c,d)=>{let e,f,g;e=BU(d,a.es);Bf(d,a.es,b);f=a.eB.eC;g=0;while(g<f){if((BT(a.eB,g)).cY(b,c,d)
>=0)return a.eg.cY(a.eX.g4,c,d);g=g+1|0;}Bf(d,a.es,e);return (-1);},Qm=(a,b)=>{a.eg=b;},LY=a=>{return I(53);},F4=L(BZ),N3=(a,b,c,d)=>{let e,f;e=a.eB.eC;f=0;while(f<e){if((BT(a.eB,f)).cY(b,c,d)>=0)return a.eg.cY(b,c,d);f=f+1|0;}return (-1);},M2=(a,b)=>{return 0;},Ps=a=>{return I(54);},Gx=L(BZ),Oz=(a,b,c,d)=>{let e,f;e=a.eB.eC;f=0;while(true){if(f>=e)return a.eg.cY(b,c,d);if((BT(a.eB,f)).cY(b,c,d)>=0)break;f=f+1|0;}return (-1);},LS=(a,b)=>{return 0;},MH=a=>{return I(55);},HK=L(BZ),Qb=(a,b,c,d)=>{let e,f,g,h;e
=a.eB.eC;f=d.fG?0:d.eK;a:{g=a.eg.cY(b,c,d);if(g>=0){Bf(d,a.es,b);h=0;while(true){if(h>=e)break a;if((BT(a.eB,h)).du(f,b,c,d)>=0){Bf(d,a.es,(-1));return g;}h=h+1|0;}}}return (-1);},RL=(a,b)=>{return 0;},M9=a=>{return I(56);},Hb=L(BZ),KH=(a,b,c,d)=>{let e,f;e=a.eB.eC;Bf(d,a.es,b);f=0;while(true){if(f>=e)return a.eg.cY(b,c,d);if((BT(a.eB,f)).du(0,b,c,d)>=0)break;f=f+1|0;}return (-1);},RD=(a,b)=>{return 0;},Pb=a=>{return I(57);};
function CM(){Bj.call(this);this.eM=null;}
let Jd=(a,b,c)=>{Bm(a);a.eM=b;a.eX=c;a.es=c.eT;},Ob=(a,b)=>{let c=new CM();Jd(c,a,b);return c;},Lm=(a,b,c,d)=>{let e,f;e=CL(d,a.es);B0(d,a.es,b);f=a.eM.cY(b,c,d);if(f>=0)return f;B0(d,a.es,e);return (-1);},Kh=(a,b,c,d)=>{let e;e=a.eM.cU(b,c,d);if(e>=0)B0(d,a.es,e);return e;},Sd=(a,b,c,d,e)=>{let f;f=a.eM.du(b,c,d,e);if(f>=0)B0(e,a.es,f);return f;},Rz=(a,b)=>{return a.eM.ck(b);},Om=a=>{let b;b=new GD;Jd(b,a.eM,a.eX);a.eg=b;return b;},PG=a=>{let b;a.eH=1;b=a.eX;if(b!==null&&!b.eH)Dm(b);b=a.eM;if(b!==null&&!b.eH)
{b=b.c0();if(b!==null){a.eM.eH=1;a.eM=b;}a.eM.bP();}},En=L(0),Cs=L(),NI=(a,b,c)=>{b=new EA;Z(b);Q(b);};
function D2(){let a=this;Cs.call(a);a.fu=0;a.e3=null;a.hA=0;a.hK=0.0;a.hu=0;}
let HO=b=>{let c;if(b>=1073741824)return 1073741824;if(!b)return 16;c=b-1|0;b=c|c>>1;b=b|b>>2;b=b|b>>4;b=b|b>>8;return (b|b>>16)+1|0;},H7=(a,b,c)=>{let d;if(b>=0&&c>0.0){b=HO(b);a.fu=0;a.e3=K(DG,b);a.hK=c;IJ(a);return;}d=new Bs;Z(d);Q(d);},Sr=(a,b)=>{let c=new D2();H7(c,a,b);return c;},IJ=a=>{a.hu=a.e3.data.length*a.hK|0;},G_=L(0);
function Hf(){let a=this;D2.call(a);a.g2=0;a.fN=null;a.fD=null;}
let NW=(a,b,c)=>{let d,e,f,g,h,i,j,k,l,m,n,o;d=a;e=d.fu;f=a.g2;if(!a.fu){a.fN=null;a.fD=null;}if(b===null)g=0;else{a:{h=b;if(!h.fF){i=0;while(true){if(i>=h.eh.length)break a;h.fF=(31*h.fF|0)+h.eh.charCodeAt(i)|0;i=i+1|0;}}}g=h.fF;}j=g&2147483647;k=a.e3.data;i=j%k.length|0;if(b===null){l=k[0];while(l!==null&&l.gx!==null){l=l.fB;}}else{l=k[i];while(l!==null){if(l.gc==g){h=l.gx;if(b!==h&&!C1(b,h)?0:1)break;}l=l.fB;}}l=l;if(l===null){a.hA=a.hA+1|0;f=a.fu+1|0;a.fu=f;if(f>a.hu){f=d.e3.data.length;f=HO(!f?1:f<<1);m
=K(DG,f);n=m.data;i=0;o=f-1|0;while(true){k=d.e3.data;if(i>=k.length)break;l=k[i];k[i]=null;while(l!==null){f=l.gc&o;h=l.fB;l.fB=n[f];n[f]=l;l=h;}i=i+1|0;}d.e3=m;IJ(d);i=j%a.e3.data.length|0;}h=new DG;l=null;h.gx=b;h.gf=l;h.gc=g;h.ff=null;h.fn=null;k=a.e3.data;h.fB=k[i];k[i]=h;b=a.fD;if(b===null)a.fN=h;else b.ff=h;h.fn=b;a.fD=h;l=h;}else if(f){b=l.ff;if(b!==null){h=l.fn;if(h===null)a.fN=b;else h.ff=b;b.fn=h;b=a.fD;if(b!==null)b.ff=l;l.fn=b;l.ff=null;a.fD=l;}}h=l.gf;l.gf=c;return h;},HF=L(),M5=null,GL=null,Kb
=null,JQ=null,Pi=null,Pc=null,DA=()=>{DA=B3(HF);LA();},LA=()=>{M5=new FF;GL=new FC;Kb=new FD;JQ=new FA;Pi=new FB;Pc=new Jj;},BK=L(Bs),L$=L(),DZ=b=>{let c;c=(Co(b)).eJ;if(c[Bc].itemType!==null)return Qn(b);b=new Bs;Z(b);Q(b);},DJ=L(Bi),C8=L();
function R(){let a=this;C8.call(a);a.ew=0;a.eF=0;a.ep=null;a.fK=null;a.gj=null;a.eq=0;}
let J$=null,CK=()=>{CK=B3(R);Rg();},Ba=a=>{let b;CK();b=new EN;b.em=BC(64);a.ep=b;},OG=a=>{return null;},MJ=a=>{return a.ep;},KM=a=>{let b,c,d,e,f;if(!a.eF)b=Db(a.ep,0)>=2048?0:1;else{a:{c=a.ep;b=0;d=c.ex;if(b<d){e=c.em.data;f=(e[0]^(-1))>>>0|0;if(f)b=DO(f)+b|0;else{b=(d+31|0)/32|0;f=1;while(f<b){if(e[f]!=(-1)){b=(f*32|0)+DO(e[f]^(-1))|0;break a;}f=f+1|0;}b=d;}}}b=b>=2048?0:1;}return b;},Sm=a=>{return a.eq;},Lo=a=>{return a;},Nf=a=>{let b,c;if(a.gj===null){b=a.dz();c=new GT;c.h5=a;c.hm=b;Ba(c);a.gj=c;Cb(c,a.eF);}return a.gj;},DU
=a=>{let b,c;if(a.fK===null){b=a.dz();c=new GP;c.il=a;c.gG=b;c.ha=a;Ba(c);a.fK=c;Cb(c,a.ew);a.fK.eq=a.eq;}return a.fK;},Pp=a=>{return 0;},Cb=(a,b)=>{let c;c=a.ew;if(c^b){a.ew=c?0:1;a.eF=a.eF?0:1;}if(!a.eq)a.eq=1;return a;},Sj=a=>{return a.ew;},Ez=(b,c)=>{CK();return b.dA(c);},DK=(b,c)=>{let d,e;CK();if(b.dB()!==null&&c.dB()!==null){b=b.dB();c=c.dB();d=Bw(b.em.data.length,c.em.data.length);e=0;a:{while(e<d){if(b.em.data[e]&c.em.data[e]){d=1;break a;}e=e+1|0;}d=0;}return d;}return 1;},Ja=(b,c)=>{let d,e,f;CK();d
=0;while(true){Eb();e=HZ.data;if(d>=e.length){f=new Et;Bu(f,I(13));f.ie=I(13);f.h7=b;Q(f);}e=e[d].data;if(C1(b,e[0]))break;d=d+1|0;}return Rm(e[1],c);},Rg=()=>{let b;b=new EG;Eb();J$=b;};
function Et(){let a=this;Bi.call(a);a.ie=null;a.h7=null;}
function Fg(){let a=this;R.call(a);a.f8=0;a.g3=0;a.fi=0;a.gq=0;a.eV=0;a.fc=0;a.en=null;a.ez=null;}
let Rc=a=>{Ba(a);a.en=Jw();},BE=()=>{let a=new Fg();Rc(a);return a;},QM=(a,b,c)=>{Ba(a);a.en=Jw();a.f8=b;a.g3=c;},Hk=(a,b)=>{let c=new Fg();QM(c,a,b);return c;},Bv=(a,b)=>{a:{if(a.f8){b:{if(!(b>=97&&b<=122)){if(b<65)break b;if(b>90)break b;}if(a.eV){Fw(a.en,Da(b&65535));break a;}Fe(a.en,Da(b&65535));break a;}if(a.g3&&b>128){a.fi=1;b=CV(Dh(b));}}}if(!(!Ei(b)&&!Fj(b))){if(a.gq)Fw(a.ep,b-55296|0);else Fe(a.ep,b-55296|0);}if(a.eV)Fw(a.en,b);else Fe(a.en,b);if(!a.eq&&EM(b))a.eq=1;return a;},RC=(a,b)=>{let c,d,e;if
(!a.eq&&b.eq)a.eq=1;if(a.gq){if(!b.eF)CP(a.ep,b.dz());else BL(a.ep,b.dz());}else if(!b.eF)CF(a.ep,b.dz());else{CQ(a.ep,b.dz());BL(a.ep,b.dz());a.eF=a.eF?0:1;a.gq=1;}if(!a.fc&&b.dB()!==null){if(a.eV){if(!b.ew)CP(a.en,b.dB());else BL(a.en,b.dB());}else if(!b.ew)CF(a.en,b.dB());else{CQ(a.en,b.dB());BL(a.en,b.dB());a.ew=a.ew?0:1;a.eV=1;}}else{c=a.ew;d=a.ez;if(d!==null){if(!c){e=new Im;e.im=a;e.hq=c;e.gU=d;e.gN=b;Ba(e);a.ez=e;}else{e=new Io;e.io=a;e.hO=c;e.hB=d;e.hl=b;Ba(e);a.ez=e;}}else{if(c&&!a.eV&&EX(a.en)){d
=new Ij;d.hY=a;d.hG=b;Ba(d);a.ez=d;}else if(!c){d=new Ih;d.gi=a;d.f0=c;d.hM=b;Ba(d);a.ez=d;}else{d=new Ii;d.gk=a;d.f_=c;d.gR=b;Ba(d);a.ez=d;}a.fc=1;}}return a;},Bk=(a,b,c)=>{let d,e,f,g,h;if(b>c){d=new Bs;Z(d);Q(d);}a:{b:{if(!a.f8){if(c<55296)break b;if(b>57343)break b;}c=c+1|0;while(true){if(b>=c)break a;Bv(a,b);b=b+1|0;}}if(!a.eV)DP(a.en,b,c+1|0);else{d=a.en;c=c+1|0;if(b>=0&&b<=c){e=d.ex;if(b<e){f=Bw(e,c);if(b!=f){g=b/32|0;c=f/32|0;if(g==c){h=d.em.data;h[g]=h[g]&(Dy(d,b)|Dp(d,f));}else{h=d.em.data;h[g]=h[g]
&Dy(d,b);e=g+1|0;while(e<c){d.em.data[e]=0;e=e+1|0;}if(f&31){h=d.em.data;h[c]=h[c]&Dp(d,f);}}CZ(d);}}}else{d=new Bo;Z(d);Q(d);}}}return a;},Kz=(a,b)=>{let c,d,e;if(!a.eq&&b.eq)a.eq=1;c=b;if(c.fi)a.fi=1;d=a.eF;if(!(d^b.eF)){if(!d)CF(a.ep,c.ep);else BL(a.ep,c.ep);}else if(d)CP(a.ep,c.ep);else{CQ(a.ep,c.ep);BL(a.ep,c.ep);a.eF=1;}if(!a.fc&&BD(c)!==null){d=a.ew;if(!(d^b.ew)){if(!d)CF(a.en,BD(c));else BL(a.en,BD(c));}else if(d)CP(a.en,BD(c));else{CQ(a.en,BD(c));BL(a.en,BD(c));a.ew=1;}}else{d=a.ew;e=a.ez;if(e!==null)
{if(!d){c=new Ic;c.h6=a;c.hF=d;c.gQ=e;c.hi=b;Ba(c);a.ez=c;}else{c=new I1;c.iu=a;c.hg=d;c.gL=e;c.g6=b;Ba(c);a.ez=c;}}else{if(!a.eV&&EX(a.en)){if(!d){e=new Ik;e.is=a;e.g_=b;Ba(e);a.ez=e;}else{e=new Il;e.hT=a;e.g9=b;Ba(e);a.ez=e;}}else if(!d){e=new Ip;e.hH=a;e.gD=b;e.hD=d;Ba(e);a.ez=e;}else{e=new Iq;e.gP=a;e.g8=b;e.hh=d;Ba(e);a.ez=e;}a.fc=1;}}},Hm=(a,b)=>{let c,d,e;if(!a.eq&&b.eq)a.eq=1;c=b;if(c.fi)a.fi=1;d=a.eF;if(!(d^b.eF)){if(!d)BL(a.ep,c.ep);else CF(a.ep,c.ep);}else if(!d)CP(a.ep,c.ep);else{CQ(a.ep,c.ep);BL(a.ep,
c.ep);a.eF=0;}if(!a.fc&&BD(c)!==null){d=a.ew;if(!(d^b.ew)){if(!d)BL(a.en,BD(c));else CF(a.en,BD(c));}else if(!d)CP(a.en,BD(c));else{CQ(a.en,BD(c));BL(a.en,BD(c));a.ew=0;}}else{d=a.ew;e=a.ez;if(e!==null){if(!d){c=new Ie;c.ik=a;c.hL=d;c.hj=e;c.hN=b;Ba(c);a.ez=c;}else{c=new If;c.hZ=a;c.hs=d;c.gI=e;c.hE=b;Ba(c);a.ez=c;}}else{if(!a.eV&&EX(a.en)){if(!d){e=new Ia;e.hV=a;e.gM=b;Ba(e);a.ez=e;}else{e=new Ib;e.ii=a;e.gT=b;Ba(e);a.ez=e;}}else if(!d){e=new Ig;e.hQ=a;e.hn=b;e.gZ=d;Ba(e);a.ez=e;}else{e=new H_;e.gV=a;e.hw=
b;e.hP=d;Ba(e);a.ez=e;}a.fc=1;}}},BH=(a,b)=>{let c;c=a.ez;if(c!==null)return a.ew^c.dA(b);return a.ew^BX(a.en,b);},BD=a=>{if(!a.fc)return a.en;return null;},K6=a=>{return a.ep;},Mp=a=>{let b,c;if(a.ez!==null)return a;b=BD(a);c=new Id;c.h2=a;c.fH=b;Ba(c);return Cb(c,a.ew);},Mi=a=>{let b,c,d;b=new W;Y(b);c=Db(a.en,0);while(c>=0){DR(b,CI(c));Bh(b,124);c=Db(a.en,c+1|0);}d=b.er;if(d>0)GB(b,d-1|0);return X(b);},LE=a=>{return a.fi;};
function B6(){Bd.call(this);this.eo=null;}
let BP=(a,b,c,d)=>{Je(a,c);a.eo=b;a.fC=d;},Sp=a=>{return a.eo;},JM=(a,b)=>{return !a.eo.ck(b)&&!a.eg.ck(b)?0:1;},Ng=(a,b)=>{return 1;},K5=a=>{let b;a.eH=1;b=a.eg;if(b!==null&&!b.eH){b=b.c0();if(b!==null){a.eg.eH=1;a.eg=b;}a.eg.bP();}b=a.eo;if(b!==null){if(!b.eH){b=b.c0();if(b!==null){a.eo.eH=1;a.eo=b;}a.eo.bP();}else if(b instanceof CM&&b.eX.gr)a.eo=b.eg;}};
function BF(){B6.call(this);this.eu=null;}
let Cc=(a,b,c,d)=>{BP(a,b,c,d);a.eu=b;},PI=(a,b,c)=>{let d=new BF();Cc(d,a,b,c);return d;},KJ=(a,b,c,d)=>{let e,f;e=0;a:{while((b+a.eu.dq()|0)<=d.el){f=a.eu.dr(b,c);if(f<=0)break a;b=b+f|0;e=e+1|0;}}while(true){if(e<0)return (-1);f=a.eg.cY(b,c,d);if(f>=0)break;b=b-a.eu.dq()|0;e=e+(-1)|0;}return f;},Qf=a=>{return I(58);};
function Ck(){BF.call(this);this.fo=null;}
let Fp=(a,b,c,d,e)=>{Cc(a,c,d,e);a.fo=b;},KA=(a,b,c,d)=>{let e=new Ck();Fp(e,a,b,c,d);return e;},Nv=(a,b,c,d)=>{let e,f,g,h,i;e=a.fo;f=e.e7;g=e.e4;h=0;while(true){if(h>=f){a:{while(h<g){if((b+a.eu.dq()|0)>d.el)break a;i=a.eu.dr(b,c);if(i<1)break a;b=b+i|0;h=h+1|0;}}while(true){if(h<f)return (-1);i=a.eg.cY(b,c,d);if(i>=0)break;b=b-a.eu.dq()|0;h=h+(-1)|0;}return i;}if((b+a.eu.dq()|0)>d.el){d.eP=1;return (-1);}i=a.eu.dr(b,c);if(i<1)break;b=b+i|0;h=h+1|0;}return (-1);},OK=a=>{return EE(a.fo);},BG=L(B6),Ll=(a,b,
c,d)=>{let e;if(!a.eo.dQ(d))return a.eg.cY(b,c,d);e=a.eo.cY(b,c,d);if(e>=0)return e;return a.eg.cY(b,c,d);},OP=a=>{return I(59);},Ci=L(BF),Kq=(a,b,c,d)=>{let e;e=a.eo.cY(b,c,d);if(e<0)e=a.eg.cY(b,c,d);return e;},R1=(a,b)=>{a.eg=b;a.eo.bY(b);},GW=L(BF),Qy=(a,b,c,d)=>{while((b+a.eu.dq()|0)<=d.el&&a.eu.dr(b,c)>0){b=b+a.eu.dq()|0;}return a.eg.cY(b,c,d);},L9=(a,b,c,d)=>{let e,f,g;e=a.eg.cU(b,c,d);if(e<0)return (-1);f=e-a.eu.dq()|0;while(f>=b&&a.eu.dr(f,c)>0){g=f-a.eu.dq()|0;e=f;f=g;}return e;};
function IF(){Cs.call(this);this.ip=null;}
let EG=L(),Ew=null,E6=null,HZ=null,Eb=()=>{Eb=B3(EG);J_();},J_=()=>{let b,c,d,e;Ew=Lp();E6=QG();b=K(CG(J),194);c=b.data;d=K(J,2);e=d.data;e[0]=I(60);e[1]=Qr();c[0]=d;d=K(J,2);e=d.data;e[0]=I(61);e[1]=KX();c[1]=d;d=K(J,2);e=d.data;e[0]=I(62);e[1]=KW();c[2]=d;d=K(J,2);e=d.data;e[0]=I(63);e[1]=Nl();c[3]=d;d=K(J,2);e=d.data;e[0]=I(64);e[1]=E6;c[4]=d;d=K(J,2);e=d.data;e[0]=I(65);e[1]=Kn();c[5]=d;d=K(J,2);e=d.data;e[0]=I(66);e[1]=P7();c[6]=d;d=K(J,2);e=d.data;e[0]=I(67);e[1]=Nx();c[7]=d;d=K(J,2);e=d.data;e[0]=I(68);e[1]
=Mo();c[8]=d;d=K(J,2);e=d.data;e[0]=I(69);e[1]=Op();c[9]=d;d=K(J,2);e=d.data;e[0]=I(70);e[1]=K0();c[10]=d;d=K(J,2);e=d.data;e[0]=I(71);e[1]=NO();c[11]=d;d=K(J,2);e=d.data;e[0]=I(72);e[1]=Pz();c[12]=d;d=K(J,2);e=d.data;e[0]=I(73);e[1]=Kr();c[13]=d;d=K(J,2);e=d.data;e[0]=I(74);e[1]=Mz();c[14]=d;d=K(J,2);e=d.data;e[0]=I(75);e[1]=KF();c[15]=d;d=K(J,2);e=d.data;e[0]=I(76);e[1]=Kd();c[16]=d;d=K(J,2);e=d.data;e[0]=I(77);e[1]=Ky();c[17]=d;d=K(J,2);e=d.data;e[0]=I(78);e[1]=Ki();c[18]=d;d=K(J,2);e=d.data;e[0]=I(79);e[1]
=O_();c[19]=d;d=K(J,2);e=d.data;e[0]=I(80);e[1]=Ot();c[20]=d;d=K(J,2);e=d.data;e[0]=I(81);e[1]=QL();c[21]=d;d=K(J,2);e=d.data;e[0]=I(82);e[1]=Lz();c[22]=d;d=K(J,2);e=d.data;e[0]=I(83);e[1]=KL();c[23]=d;d=K(J,2);e=d.data;e[0]=I(84);e[1]=Kw();c[24]=d;d=K(J,2);e=d.data;e[0]=I(85);e[1]=NY();c[25]=d;d=K(J,2);e=d.data;e[0]=I(86);e[1]=OS();c[26]=d;d=K(J,2);e=d.data;e[0]=I(87);e[1]=QB();c[27]=d;d=K(J,2);e=d.data;e[0]=I(88);e[1]=Ew;c[28]=d;d=K(J,2);e=d.data;e[0]=I(89);e[1]=MN();c[29]=d;d=K(J,2);e=d.data;e[0]=I(90);e[1]
=Nz();c[30]=d;d=K(J,2);e=d.data;e[0]=I(91);e[1]=Ew;c[31]=d;d=K(J,2);e=d.data;e[0]=I(92);e[1]=JP();c[32]=d;d=K(J,2);e=d.data;e[0]=I(93);e[1]=E6;c[33]=d;d=K(J,2);e=d.data;e[0]=I(94);e[1]=ME();c[34]=d;d=K(J,2);e=d.data;e[0]=I(95);e[1]=N(0,127);c[35]=d;d=K(J,2);e=d.data;e[0]=I(96);e[1]=N(128,255);c[36]=d;d=K(J,2);e=d.data;e[0]=I(97);e[1]=N(256,383);c[37]=d;d=K(J,2);e=d.data;e[0]=I(98);e[1]=N(384,591);c[38]=d;d=K(J,2);e=d.data;e[0]=I(99);e[1]=N(592,687);c[39]=d;d=K(J,2);e=d.data;e[0]=I(100);e[1]=N(688,767);c[40]
=d;d=K(J,2);e=d.data;e[0]=I(101);e[1]=N(768,879);c[41]=d;d=K(J,2);e=d.data;e[0]=I(102);e[1]=N(880,1023);c[42]=d;d=K(J,2);e=d.data;e[0]=I(103);e[1]=N(1024,1279);c[43]=d;d=K(J,2);e=d.data;e[0]=I(104);e[1]=N(1280,1327);c[44]=d;d=K(J,2);e=d.data;e[0]=I(105);e[1]=N(1328,1423);c[45]=d;d=K(J,2);e=d.data;e[0]=I(106);e[1]=N(1424,1535);c[46]=d;d=K(J,2);e=d.data;e[0]=I(107);e[1]=N(1536,1791);c[47]=d;d=K(J,2);e=d.data;e[0]=I(108);e[1]=N(1792,1871);c[48]=d;d=K(J,2);e=d.data;e[0]=I(109);e[1]=N(1872,1919);c[49]=d;d=K(J,2);e
=d.data;e[0]=I(110);e[1]=N(1920,1983);c[50]=d;d=K(J,2);e=d.data;e[0]=I(111);e[1]=N(2304,2431);c[51]=d;d=K(J,2);e=d.data;e[0]=I(112);e[1]=N(2432,2559);c[52]=d;d=K(J,2);e=d.data;e[0]=I(113);e[1]=N(2560,2687);c[53]=d;d=K(J,2);e=d.data;e[0]=I(114);e[1]=N(2688,2815);c[54]=d;d=K(J,2);e=d.data;e[0]=I(115);e[1]=N(2816,2943);c[55]=d;d=K(J,2);e=d.data;e[0]=I(116);e[1]=N(2944,3071);c[56]=d;d=K(J,2);e=d.data;e[0]=I(117);e[1]=N(3072,3199);c[57]=d;d=K(J,2);e=d.data;e[0]=I(118);e[1]=N(3200,3327);c[58]=d;d=K(J,2);e=d.data;e[0]
=I(119);e[1]=N(3328,3455);c[59]=d;d=K(J,2);e=d.data;e[0]=I(120);e[1]=N(3456,3583);c[60]=d;d=K(J,2);e=d.data;e[0]=I(121);e[1]=N(3584,3711);c[61]=d;d=K(J,2);e=d.data;e[0]=I(122);e[1]=N(3712,3839);c[62]=d;d=K(J,2);e=d.data;e[0]=I(123);e[1]=N(3840,4095);c[63]=d;d=K(J,2);e=d.data;e[0]=I(124);e[1]=N(4096,4255);c[64]=d;d=K(J,2);e=d.data;e[0]=I(125);e[1]=N(4256,4351);c[65]=d;d=K(J,2);e=d.data;e[0]=I(126);e[1]=N(4352,4607);c[66]=d;d=K(J,2);e=d.data;e[0]=I(127);e[1]=N(4608,4991);c[67]=d;d=K(J,2);e=d.data;e[0]=I(128);e[1]
=N(4992,5023);c[68]=d;d=K(J,2);e=d.data;e[0]=I(129);e[1]=N(5024,5119);c[69]=d;d=K(J,2);e=d.data;e[0]=I(130);e[1]=N(5120,5759);c[70]=d;d=K(J,2);e=d.data;e[0]=I(131);e[1]=N(5760,5791);c[71]=d;d=K(J,2);e=d.data;e[0]=I(132);e[1]=N(5792,5887);c[72]=d;d=K(J,2);e=d.data;e[0]=I(133);e[1]=N(5888,5919);c[73]=d;d=K(J,2);e=d.data;e[0]=I(134);e[1]=N(5920,5951);c[74]=d;d=K(J,2);e=d.data;e[0]=I(135);e[1]=N(5952,5983);c[75]=d;d=K(J,2);e=d.data;e[0]=I(136);e[1]=N(5984,6015);c[76]=d;d=K(J,2);e=d.data;e[0]=I(137);e[1]=N(6016,
6143);c[77]=d;d=K(J,2);e=d.data;e[0]=I(138);e[1]=N(6144,6319);c[78]=d;d=K(J,2);e=d.data;e[0]=I(139);e[1]=N(6400,6479);c[79]=d;d=K(J,2);e=d.data;e[0]=I(140);e[1]=N(6480,6527);c[80]=d;d=K(J,2);e=d.data;e[0]=I(141);e[1]=N(6528,6623);c[81]=d;d=K(J,2);e=d.data;e[0]=I(142);e[1]=N(6624,6655);c[82]=d;d=K(J,2);e=d.data;e[0]=I(143);e[1]=N(6656,6687);c[83]=d;d=K(J,2);e=d.data;e[0]=I(144);e[1]=N(7424,7551);c[84]=d;d=K(J,2);e=d.data;e[0]=I(145);e[1]=N(7552,7615);c[85]=d;d=K(J,2);e=d.data;e[0]=I(146);e[1]=N(7616,7679);c[86]
=d;d=K(J,2);e=d.data;e[0]=I(147);e[1]=N(7680,7935);c[87]=d;d=K(J,2);e=d.data;e[0]=I(148);e[1]=N(7936,8191);c[88]=d;d=K(J,2);e=d.data;e[0]=I(149);e[1]=N(8192,8303);c[89]=d;d=K(J,2);e=d.data;e[0]=I(150);e[1]=N(8304,8351);c[90]=d;d=K(J,2);e=d.data;e[0]=I(151);e[1]=N(8352,8399);c[91]=d;d=K(J,2);e=d.data;e[0]=I(152);e[1]=N(8400,8447);c[92]=d;d=K(J,2);e=d.data;e[0]=I(153);e[1]=N(8448,8527);c[93]=d;d=K(J,2);e=d.data;e[0]=I(154);e[1]=N(8528,8591);c[94]=d;d=K(J,2);e=d.data;e[0]=I(155);e[1]=N(8592,8703);c[95]=d;d=K(J,
2);e=d.data;e[0]=I(156);e[1]=N(8704,8959);c[96]=d;d=K(J,2);e=d.data;e[0]=I(157);e[1]=N(8960,9215);c[97]=d;d=K(J,2);e=d.data;e[0]=I(158);e[1]=N(9216,9279);c[98]=d;d=K(J,2);e=d.data;e[0]=I(159);e[1]=N(9280,9311);c[99]=d;d=K(J,2);e=d.data;e[0]=I(160);e[1]=N(9312,9471);c[100]=d;d=K(J,2);e=d.data;e[0]=I(161);e[1]=N(9472,9599);c[101]=d;d=K(J,2);e=d.data;e[0]=I(162);e[1]=N(9600,9631);c[102]=d;d=K(J,2);e=d.data;e[0]=I(163);e[1]=N(9632,9727);c[103]=d;d=K(J,2);e=d.data;e[0]=I(164);e[1]=N(9728,9983);c[104]=d;d=K(J,2);e
=d.data;e[0]=I(165);e[1]=N(9984,10175);c[105]=d;d=K(J,2);e=d.data;e[0]=I(166);e[1]=N(10176,10223);c[106]=d;d=K(J,2);e=d.data;e[0]=I(167);e[1]=N(10224,10239);c[107]=d;d=K(J,2);e=d.data;e[0]=I(168);e[1]=N(10240,10495);c[108]=d;d=K(J,2);e=d.data;e[0]=I(169);e[1]=N(10496,10623);c[109]=d;d=K(J,2);e=d.data;e[0]=I(170);e[1]=N(10624,10751);c[110]=d;d=K(J,2);e=d.data;e[0]=I(171);e[1]=N(10752,11007);c[111]=d;d=K(J,2);e=d.data;e[0]=I(172);e[1]=N(11008,11263);c[112]=d;d=K(J,2);e=d.data;e[0]=I(173);e[1]=N(11264,11359);c[113]
=d;d=K(J,2);e=d.data;e[0]=I(174);e[1]=N(11392,11519);c[114]=d;d=K(J,2);e=d.data;e[0]=I(175);e[1]=N(11520,11567);c[115]=d;d=K(J,2);e=d.data;e[0]=I(176);e[1]=N(11568,11647);c[116]=d;d=K(J,2);e=d.data;e[0]=I(177);e[1]=N(11648,11743);c[117]=d;d=K(J,2);e=d.data;e[0]=I(178);e[1]=N(11776,11903);c[118]=d;d=K(J,2);e=d.data;e[0]=I(179);e[1]=N(11904,12031);c[119]=d;d=K(J,2);e=d.data;e[0]=I(180);e[1]=N(12032,12255);c[120]=d;d=K(J,2);e=d.data;e[0]=I(181);e[1]=N(12272,12287);c[121]=d;d=K(J,2);e=d.data;e[0]=I(182);e[1]=N(12288,
12351);c[122]=d;d=K(J,2);e=d.data;e[0]=I(183);e[1]=N(12352,12447);c[123]=d;d=K(J,2);e=d.data;e[0]=I(184);e[1]=N(12448,12543);c[124]=d;d=K(J,2);e=d.data;e[0]=I(185);e[1]=N(12544,12591);c[125]=d;d=K(J,2);e=d.data;e[0]=I(186);e[1]=N(12592,12687);c[126]=d;d=K(J,2);e=d.data;e[0]=I(187);e[1]=N(12688,12703);c[127]=d;d=K(J,2);e=d.data;e[0]=I(188);e[1]=N(12704,12735);c[128]=d;d=K(J,2);e=d.data;e[0]=I(189);e[1]=N(12736,12783);c[129]=d;d=K(J,2);e=d.data;e[0]=I(190);e[1]=N(12784,12799);c[130]=d;d=K(J,2);e=d.data;e[0]=I(191);e[1]
=N(12800,13055);c[131]=d;d=K(J,2);e=d.data;e[0]=I(192);e[1]=N(13056,13311);c[132]=d;d=K(J,2);e=d.data;e[0]=I(193);e[1]=N(13312,19893);c[133]=d;d=K(J,2);e=d.data;e[0]=I(194);e[1]=N(19904,19967);c[134]=d;d=K(J,2);e=d.data;e[0]=I(195);e[1]=N(19968,40959);c[135]=d;d=K(J,2);e=d.data;e[0]=I(196);e[1]=N(40960,42127);c[136]=d;d=K(J,2);e=d.data;e[0]=I(197);e[1]=N(42128,42191);c[137]=d;d=K(J,2);e=d.data;e[0]=I(198);e[1]=N(42752,42783);c[138]=d;d=K(J,2);e=d.data;e[0]=I(199);e[1]=N(43008,43055);c[139]=d;d=K(J,2);e=d.data;e[0]
=I(200);e[1]=N(44032,55203);c[140]=d;d=K(J,2);e=d.data;e[0]=I(201);e[1]=N(55296,56191);c[141]=d;d=K(J,2);e=d.data;e[0]=I(202);e[1]=N(56192,56319);c[142]=d;d=K(J,2);e=d.data;e[0]=I(203);e[1]=N(56320,57343);c[143]=d;d=K(J,2);e=d.data;e[0]=I(204);e[1]=N(57344,63743);c[144]=d;d=K(J,2);e=d.data;e[0]=I(205);e[1]=N(63744,64255);c[145]=d;d=K(J,2);e=d.data;e[0]=I(206);e[1]=N(64256,64335);c[146]=d;d=K(J,2);e=d.data;e[0]=I(207);e[1]=N(64336,65023);c[147]=d;d=K(J,2);e=d.data;e[0]=I(208);e[1]=N(65024,65039);c[148]=d;d=K(J,
2);e=d.data;e[0]=I(209);e[1]=N(65040,65055);c[149]=d;d=K(J,2);e=d.data;e[0]=I(210);e[1]=N(65056,65071);c[150]=d;d=K(J,2);e=d.data;e[0]=I(211);e[1]=N(65072,65103);c[151]=d;d=K(J,2);e=d.data;e[0]=I(212);e[1]=N(65104,65135);c[152]=d;d=K(J,2);e=d.data;e[0]=I(213);e[1]=N(65136,65279);c[153]=d;d=K(J,2);e=d.data;e[0]=I(214);e[1]=N(65280,65519);c[154]=d;d=K(J,2);e=d.data;e[0]=I(215);e[1]=N(0,1114111);c[155]=d;d=K(J,2);e=d.data;e[0]=I(216);e[1]=NT();c[156]=d;d=K(J,2);e=d.data;e[0]=I(217);e[1]=Be(0,1);c[157]=d;d=K(J,
2);e=d.data;e[0]=I(218);e[1]=CC(62,1);c[158]=d;d=K(J,2);e=d.data;e[0]=I(219);e[1]=Be(1,1);c[159]=d;d=K(J,2);e=d.data;e[0]=I(220);e[1]=Be(2,1);c[160]=d;d=K(J,2);e=d.data;e[0]=I(221);e[1]=Be(3,0);c[161]=d;d=K(J,2);e=d.data;e[0]=I(222);e[1]=Be(4,0);c[162]=d;d=K(J,2);e=d.data;e[0]=I(223);e[1]=Be(5,1);c[163]=d;d=K(J,2);e=d.data;e[0]=I(224);e[1]=CC(448,1);c[164]=d;d=K(J,2);e=d.data;e[0]=I(225);e[1]=Be(6,1);c[165]=d;d=K(J,2);e=d.data;e[0]=I(226);e[1]=Be(7,0);c[166]=d;d=K(J,2);e=d.data;e[0]=I(227);e[1]=Be(8,1);c[167]
=d;d=K(J,2);e=d.data;e[0]=I(228);e[1]=CC(3584,1);c[168]=d;d=K(J,2);e=d.data;e[0]=I(229);e[1]=Be(9,1);c[169]=d;d=K(J,2);e=d.data;e[0]=I(230);e[1]=Be(10,1);c[170]=d;d=K(J,2);e=d.data;e[0]=I(231);e[1]=Be(11,1);c[171]=d;d=K(J,2);e=d.data;e[0]=I(232);e[1]=CC(28672,0);c[172]=d;d=K(J,2);e=d.data;e[0]=I(233);e[1]=Be(12,0);c[173]=d;d=K(J,2);e=d.data;e[0]=I(234);e[1]=Be(13,0);c[174]=d;d=K(J,2);e=d.data;e[0]=I(235);e[1]=Be(14,0);c[175]=d;d=K(J,2);e=d.data;e[0]=I(236);e[1]=O7(983040,1,1);c[176]=d;d=K(J,2);e=d.data;e[0]
=I(237);e[1]=Be(15,0);c[177]=d;d=K(J,2);e=d.data;e[0]=I(238);e[1]=Be(16,1);c[178]=d;d=K(J,2);e=d.data;e[0]=I(239);e[1]=Be(18,1);c[179]=d;d=K(J,2);e=d.data;e[0]=I(240);e[1]=RP(19,0,1);c[180]=d;d=K(J,2);e=d.data;e[0]=I(241);e[1]=CC(1643118592,1);c[181]=d;d=K(J,2);e=d.data;e[0]=I(242);e[1]=Be(20,0);c[182]=d;d=K(J,2);e=d.data;e[0]=I(243);e[1]=Be(21,0);c[183]=d;d=K(J,2);e=d.data;e[0]=I(244);e[1]=Be(22,0);c[184]=d;d=K(J,2);e=d.data;e[0]=I(245);e[1]=Be(23,0);c[185]=d;d=K(J,2);e=d.data;e[0]=I(246);e[1]=Be(24,1);c[186]
=d;d=K(J,2);e=d.data;e[0]=I(247);e[1]=CC(2113929216,1);c[187]=d;d=K(J,2);e=d.data;e[0]=I(248);e[1]=Be(25,1);c[188]=d;d=K(J,2);e=d.data;e[0]=I(249);e[1]=Be(26,0);c[189]=d;d=K(J,2);e=d.data;e[0]=I(250);e[1]=Be(27,0);c[190]=d;d=K(J,2);e=d.data;e[0]=I(251);e[1]=Be(28,1);c[191]=d;d=K(J,2);e=d.data;e[0]=I(252);e[1]=Be(29,0);c[192]=d;d=K(J,2);e=d.data;e[0]=I(253);e[1]=Be(30,0);c[193]=d;HZ=b;};
function U(){let a=this;J.call(a);a.gn=null;a.gh=null;}
let Rm=(a,b)=>{if(!b&&a.gn===null)a.gn=a.dT();else if(b&&a.gh===null)a.gh=Cb(a.dT(),1);if(b)return a.gh;return a.gn;};
function Gt(){let a=this;C8.call(a);a.e7=0;a.e4=0;}
let EE=a=>{let b,c,d,e,f;b=a.e7;c=a.e4;d=c!=2147483647?C7(c):I(13);e=new W;Y(e);Bh(e,123);f=BY(e,b);Bh(f,44);Bh(T(f,d),125);return X(e);},HT=L(Bd),Qu=(a,b,c,d)=>{return b;},N0=a=>{return I(254);},Oe=(a,b)=>{return 0;};
function EN(){let a=this;J.call(a);a.em=null;a.ex=0;}
let PR=a=>{a.em=BC(2);},Jw=()=>{let a=new EN();PR(a);return a;},Fe=(a,b)=>{let c,d,e;if(b<0){c=new Bo;Z(c);Q(c);}d=b/32|0;if(b>=a.ex){DW(a,d+1|0);a.ex=b+1|0;}e=a.em.data;e[d]=e[d]|1<<(b%32|0);},DP=(a,b,c)=>{let d,e,f,g,h;if(b>=0){d=B1(b,c);if(d<=0){if(!d)return;d=b/32|0;e=c/32|0;if(c>a.ex){DW(a,e+1|0);a.ex=c;}if(d==e){f=a.em.data;f[d]=f[d]|Dp(a,b)&Dy(a,c);}else{f=a.em.data;f[d]=f[d]|Dp(a,b);g=d+1|0;while(g<e){a.em.data[g]=(-1);g=g+1|0;}if(c&31){f=a.em.data;f[e]=f[e]|Dy(a,c);}}return;}}h=new Bo;Z(h);Q(h);},Dp
=(a,b)=>{return (-1)<<(b%32|0);},Dy=(a,b)=>{b=b%32|0;return !b?0:(-1)>>>(32-b|0)|0;},Fw=(a,b)=>{let c,d,e,f,g,h;if(b<0){c=new Bo;Z(c);Q(c);}d=b/32|0;e=a.em.data;if(d<e.length){f=e[d];g=b%32|0;BN();h=g&31;e[d]=f&((-2)<<h|((-2)>>>(32-h|0)|0));if(b==(a.ex-1|0))CZ(a);}},BX=(a,b)=>{let c,d,e;if(b<0){c=new Bo;Z(c);Q(c);}d=b/32|0;e=a.em.data;return d<e.length&&e[d]&1<<(b%32|0)?1:0;},Db=(a,b)=>{let c,d,e,f,g;if(b<0){c=new Bo;Z(c);Q(c);}d=a.ex;if(b>=d)return (-1);e=b/32|0;f=a.em.data;g=f[e]>>>(b%32|0)|0;if(g)return DO(g)
+b|0;d=(d+31|0)/32|0;g=e+1|0;while(g<d){if(f[g])return (g*32|0)+DO(f[g])|0;g=g+1|0;}return (-1);},DW=(a,b)=>{let c,d,e,f;c=a.em.data.length;if(c>=b)return;c=BR((b*3|0)/2|0,(c*2|0)+1|0);d=a.em.data;e=BC(c);f=e.data;b=Bw(c,d.length);c=0;while(c<b){f[c]=d[c];c=c+1|0;}a.em=e;},CZ=a=>{let b,c,d;b=(a.ex+31|0)/32|0;a.ex=b*32|0;c=b-1|0;a:{while(true){if(c<0)break a;d=Hp(a.em.data[c]);if(d<32)break;c=c+(-1)|0;a.ex=a.ex-32|0;}a.ex=a.ex-d|0;}},BL=(a,b)=>{let c,d,e,f;c=Bw(a.em.data.length,b.em.data.length);d=0;while(d<
c){e=a.em.data;e[d]=e[d]&b.em.data[d];d=d+1|0;}while(true){f=a.em.data;if(c>=f.length)break;f[c]=0;c=c+1|0;}a.ex=Bw(a.ex,b.ex);CZ(a);},CP=(a,b)=>{let c,d,e;c=Bw(a.em.data.length,b.em.data.length);d=0;while(d<c){e=a.em.data;e[d]=e[d]&(b.em.data[d]^(-1));d=d+1|0;}CZ(a);},CF=(a,b)=>{let c,d,e;c=BR(a.ex,b.ex);a.ex=c;DW(a,(c+31|0)/32|0);c=Bw(a.em.data.length,b.em.data.length);d=0;while(d<c){e=a.em.data;e[d]=e[d]|b.em.data[d];d=d+1|0;}},CQ=(a,b)=>{let c,d,e;c=BR(a.ex,b.ex);a.ex=c;DW(a,(c+31|0)/32|0);c=Bw(a.em.data.length,
b.em.data.length);d=0;while(d<c){e=a.em.data;e[d]=e[d]^b.em.data[d];d=d+1|0;}CZ(a);},EX=a=>{return a.ex?0:1;};
function Ga(){let a=this;Bj.call(a);a.gg=null;a.gH=0;}
let OA=a=>{let b,c,d;b=!a.gH?I(255):I(256);c=a.gg.g();d=new W;Y(d);T(T(T(d,I(257)),b),c);return X(d);};
function Ex(){let a=this;Bj.call(a);a.fJ=null;a.fL=null;}
let Iy=(a,b,c)=>{Bm(a);a.fJ=b;a.fL=c;},EZ=(a,b)=>{let c=new Ex();Iy(c,a,b);return c;},Nh=(a,b,c,d)=>{let e,f,g,h,i,j;e=a.fJ.cY(b,c,d);if(e<0)a:{f=a.fL;g=d.eK;e=d.el;h=b+1|0;e=B1(h,e);if(e>0){d.eP=1;e=(-1);}else{i=c;j=P(i,b);if(!f.gg.dA(j))e=(-1);else{if(BA(j)){if(e<0&&BI(P(i,h))){e=(-1);break a;}}else if(BI(j)&&b>g&&BA(P(i,b-1|0))){e=(-1);break a;}e=f.eg.cY(h,c,d);}}}if(e>=0)return e;return (-1);},NC=(a,b)=>{a.eg=b;a.fL.eg=b;a.fJ.bY(b);},Pl=a=>{let b,c,d;b=Df(a.fJ);c=Df(a.fL);d=new W;Y(d);T(T(T(T(d,I(258)),
b),I(259)),c);return X(d);},Pr=(a,b)=>{return 1;},OE=(a,b)=>{return 1;};
function BO(){let a=this;Bj.call(a);a.eN=null;a.gA=0;}
let E2=(a,b)=>{Bm(a);a.eN=b.dU();a.gA=b.ew;},IS=a=>{let b=new BO();E2(b,a);return b;},KK=(a,b,c,d)=>{let e,f,g,h,i;e=d.el;if(b<e){f=b+1|0;g=c;h=P(g,b);if(a.dA(h)){i=a.eg.cY(f,c,d);if(i>0)return i;}if(f<e){b=f+1|0;f=P(g,f);if(Dx(h,f)&&a.dA(Cm(h,f)))return a.eg.cY(b,c,d);}}return (-1);},O9=a=>{let b,c,d;b=!a.gA?I(255):I(256);c=a.eN.g();d=new W;Y(d);T(T(T(d,I(257)),b),c);return X(d);},L6=(a,b)=>{return a.eN.dA(b);},M1=(a,b)=>{if(b instanceof B7)return Ez(a.eN,b.fg);if(b instanceof B8)return Ez(a.eN,b.eO);if(b instanceof BO)return DK(a.eN,
b.eN);if(!(b instanceof B2))return 1;return DK(a.eN,b.e6);},Sn=a=>{return a.eN;},Lc=(a,b)=>{a.eg=b;},Lh=(a,b)=>{return 1;},Em=L(BO),PT=(a,b)=>{return a.eN.dA(CV(Dh(b)));},P6=a=>{let b,c,d;b=!a.gA?I(255):I(256);c=a.eN.g();d=new W;Y(d);T(T(T(d,I(260)),b),c);return X(d);};
function It(){let a=this;Bn.call(a);a.go=null;a.he=0;}
let MO=(a,b)=>{BV(a);a.go=b.dU();a.he=b.ew;},Hu=a=>{let b=new It();MO(b,a);return b;},Qz=(a,b,c)=>{return !a.go.dA(Cj(Ch(P(c,b))))?(-1):1;},OT=a=>{let b,c,d;b=!a.he?I(255):I(256);c=a.go.g();d=new W;Y(d);T(T(T(d,I(260)),b),c);return X(d);};
function B2(){let a=this;Bn.call(a);a.e6=null;a.g$=0;}
let Pv=(a,b)=>{BV(a);a.e6=b.dU();a.g$=b.ew;},GS=a=>{let b=new B2();Pv(b,a);return b;},Eg=(a,b,c)=>{return !a.e6.dA(P(c,b))?(-1):1;},QY=a=>{let b,c,d;b=!a.g$?I(255):I(256);c=a.e6.g();d=new W;Y(d);T(T(T(d,I(257)),b),c);return X(d);},Oj=(a,b)=>{if(b instanceof B8)return Ez(a.e6,b.eO);if(b instanceof B2)return DK(a.e6,b.e6);if(!(b instanceof BO)){if(!(b instanceof B7))return 1;return 0;}return DK(a.e6,b.eN);};
function Fa(){let a=this;Bj.call(a);a.fm=null;a.fX=null;a.fR=0;}
let Lq=(a,b,c)=>{Bm(a);a.fm=b;a.fR=c;},G$=(a,b)=>{let c=new Fa();Lq(c,a,b);return c;},Ko=(a,b)=>{a.eg=b;},EF=a=>{if(a.fX===null)a.fX=Dk(a.fm);return a.fX;},RH=a=>{let b,c;b=EF(a);c=new W;Y(c);T(T(c,I(261)),b);return X(c);},Kp=(a,b,c,d)=>{let e,f,g,h,i,j,k,l,m,n,o,p,q;e=d.el;f=BC(3);g=(-1);h=(-1);if(b>=e)return (-1);i=b+1|0;j=c;k=P(j,b);l=k-44032|0;if(l>=0&&l<11172){m=4352+(l/588|0)|0;n=4449+((l%588|0)/28|0)|0;b=l%28|0;o=!b?HV([m,n]):HV([m,n,4519+b|0]);}else o=null;if(o!==null){p=o.data;l=0;b=p.length;q=a.fR;if
(b!=q)return (-1);while(true){if(l>=q)return a.eg.cY(i,c,d);if(p[l]!=a.fm.data[l])break;l=l+1|0;}return (-1);}f=f.data;f[0]=k;n=k-4352|0;if(n>=0&&n<19){if(i<e){k=P(j,i);g=k-4449|0;}if(g>=0&&g<21){n=i+1|0;f[1]=k;if(n<e){k=P(j,n);h=k-4519|0;}if(h>=0&&h<28){a:{b=n+1|0;f[2]=k;if(a.fR==3){n=f[0];p=a.fm.data;if(n==p[0]&&f[1]==p[1]&&f[2]==p[2]){b=a.eg.cY(b,c,d);break a;}}b=(-1);}return b;}b:{if(a.fR==2){q=f[0];p=a.fm.data;if(q==p[0]&&f[1]==p[1]){b=a.eg.cY(n,c,d);break b;}}b=(-1);}return b;}return (-1);}return (-1);},O5
=(a,b)=>{return b instanceof Fa&&!C1(EF(b),EF(a))?0:1;},MG=(a,b)=>{return 1;};
function B8(){Bn.call(this);this.eO=0;}
let PO=(a,b)=>{BV(a);a.eO=b;},Fx=a=>{let b=new B8();PO(b,a);return b;},P$=a=>{return 1;},Nk=(a,b,c)=>{return a.eO!=P(c,b)?(-1):1;},KE=(a,b,c,d)=>{let e,f,g,h;if(!(c instanceof Br))return Dg(a,b,c,d);e=c;f=d.el;while(true){if(b>=f)return (-1);g=DH(e,a.eO,b);if(g<0)return (-1);h=a.eg;b=g+1|0;if(h.cY(b,c,d)>=0)break;}return g;},Ql=(a,b,c,d,e)=>{let f,g;if(!(d instanceof Br))return CY(a,b,c,d,e);f=d;a:{while(true){if(c<b)return (-1);g=C0(f,a.eO,c);if(g<0)break a;if(g<b)break a;if(a.eg.cY(g+1|0,d,e)>=0)break;c=g
+(-1)|0;}return g;}return (-1);},M8=a=>{let b,c;b=a.eO;c=new W;Y(c);Bh(c,b);return X(c);},Mg=(a,b)=>{if(b instanceof B8)return b.eO!=a.eO?0:1;if(!(b instanceof B2)){if(b instanceof BO)return b.dA(a.eO);if(!(b instanceof B7))return 1;return 0;}return Eg(b,0,HA(a.eO))<=0?0:1;};
function HJ(){Bn.call(this);this.fY=0;}
let L4=(a,b)=>{BV(a);a.fY=Cj(Ch(b));},IZ=a=>{let b=new HJ();L4(b,a);return b;},J1=(a,b,c)=>{return a.fY!=Cj(Ch(P(c,b)))?(-1):1;},NA=a=>{let b,c;b=a.fY;c=new W;Y(c);Bh(T(c,I(262)),b);return X(c);};
function F7(){let a=this;Bn.call(a);a.gm=0;a.hc=0;}
let Ro=(a,b)=>{BV(a);a.gm=b;a.hc=Da(b);},Iz=a=>{let b=new F7();Ro(b,a);return b;},K9=(a,b,c)=>{let d;d=a.gm;c=c;return d!=P(c,b)&&a.hc!=P(c,b)?(-1):1;},Of=a=>{let b,c;b=a.gm;c=new W;Y(c);Bh(T(c,I(263)),b);return X(c);};
function Cg(){let a=this;Bj.call(a);a.ft=0;a.fZ=null;a.f7=null;a.fU=0;}
let ER=(a,b,c)=>{Bm(a);a.ft=1;a.f7=b;a.fU=c;},Ry=(a,b)=>{let c=new Cg();ER(c,a,b);return c;},PF=(a,b)=>{a.eg=b;},NE=(a,b,c,d)=>{let e,f,g,h,i,j,k,l;e=BC(4);f=d.el;if(b>=f)return (-1);g=E5(a,b,c,f);h=b+a.ft|0;i=GV(g);if(i===null){i=e.data;b=1;i[0]=g;}else{b=i.data.length;G2(i,0,e,0,b);b=0+b|0;}a:{if(h<f){j=e.data;g=E5(a,h,c,f);while(b<4){if(!((g!=832?0:1)|(g!=833?0:1)|(g!=835?0:1)|(g!=836?0:1))){k=b+1|0;j[b]=g;}else{i=(GV(g)).data;if(i.length!=2){k=b+1|0;j[b]=i[0];}else{l=b+1|0;j[b]=i[0];k=l+1|0;j[l]=i[1];}}h
=h+a.ft|0;if(h>=f){b=k;break a;}g=E5(a,h,c,f);b=k;}}}if(b!=a.fU)return (-1);i=e.data;g=0;while(true){if(g>=b)return a.eg.cY(h,c,d);if(i[g]!=a.f7.data[g])break;g=g+1|0;}return (-1);},EJ=a=>{let b,c;if(a.fZ===null){b=new W;Y(b);c=0;while(c<a.fU){DR(b,CI(a.f7.data[c]));c=c+1|0;}a.fZ=X(b);}return a.fZ;},Nb=a=>{let b,c;b=EJ(a);c=new W;Y(c);T(T(c,I(264)),b);return X(c);},E5=(a,b,c,d)=>{let e,f,g;a.ft=1;if(b>=(d-1|0))e=P(c,b);else{d=b+1|0;c=c;e=P(c,b);f=P(c,d);if(Dx(e,f)){g=BJ(2).data;g[0]=e;g[1]=f;d=g.length;if(0
<d&&d<=d){e=0<(d-1|0)&&BA(g[0])&&BI(g[1])?Cm(g[0],g[1]):g[0];a.ft=2;}else{c=new Bo;Z(c);Q(c);}}}return e;},QA=(a,b)=>{return b instanceof Cg&&!C1(EJ(b),EJ(a))?0:1;},Qo=(a,b)=>{return 1;},H9=L(Cg),Hy=L(Cg),JF=L(BG),QU=(a,b,c,d)=>{let e;while(true){e=a.eo.cY(b,c,d);if(e<=0)break;b=e;}return a.eg.cY(b,c,d);},FG=L(BG),LD=(a,b,c,d)=>{let e;e=a.eo.cY(b,c,d);if(e<0)return (-1);if(e>b){while(true){b=a.eo.cY(e,c,d);if(b<=e)break;e=b;}b=e;}return a.eg.cY(b,c,d);},CN=L(BG),KU=(a,b,c,d)=>{let e;if(!a.eo.dQ(d))return a.eg.cY(b,
c,d);e=a.eo.cY(b,c,d);if(e>=0)return e;return a.eg.cY(b,c,d);},NM=(a,b)=>{a.eg=b;a.eo.bY(b);},Jh=L(CN),Qd=(a,b,c,d)=>{let e;e=a.eo.cY(b,c,d);if(e<=0)e=b;return a.eg.cY(e,c,d);},MB=(a,b)=>{a.eg=b;};
function Cf(){let a=this;BG.call(a);a.e9=null;a.eR=0;}
let Dr=(a,b,c,d,e,f)=>{BP(a,c,d,e);a.e9=b;a.eR=f;},Sq=(a,b,c,d,e)=>{let f=new Cf();Dr(f,a,b,c,d,e);return f;},Rw=(a,b,c,d)=>{let e,f;e=Gf(d,a.eR);if(!a.eo.dQ(d))return a.eg.cY(b,c,d);if(e>=a.e9.e4)return a.eg.cY(b,c,d);f=a.eR;e=e+1|0;Cd(d,f,e);f=a.eo.cY(b,c,d);if(f>=0){Cd(d,a.eR,0);return f;}f=a.eR;e=e+(-1)|0;Cd(d,f,e);if(e>=a.e9.e7)return a.eg.cY(b,c,d);Cd(d,a.eR,0);return (-1);},N4=a=>{return EE(a.e9);},GG=L(Cf),OQ=(a,b,c,d)=>{let e,f,g;e=0;f=a.e9.e4;a:{while(true){g=a.eo.cY(b,c,d);if(g<=b)break a;if(e>=f)break;e
=e+1|0;b=g;}}if(g<0&&e<a.e9.e7)return (-1);return a.eg.cY(b,c,d);},Iv=L(BG),P8=(a,b,c,d)=>{let e;if(!a.eo.dQ(d))return a.eg.cY(b,c,d);e=a.eg.cY(b,c,d);if(e>=0)return e;return a.eo.cY(b,c,d);},G8=L(CN),Pu=(a,b,c,d)=>{let e;if(!a.eo.dQ(d))return a.eg.cY(b,c,d);e=a.eg.cY(b,c,d);if(e<0)e=a.eo.cY(b,c,d);return e;},IP=L(Cf),Mk=(a,b,c,d)=>{let e,f,g;e=Gf(d,a.eR);if(!a.eo.dQ(d))return a.eg.cY(b,c,d);f=a.e9;if(e>=f.e4){Cd(d,a.eR,0);return a.eg.cY(b,c,d);}if(e<f.e7){Cd(d,a.eR,e+1|0);g=a.eo.cY(b,c,d);}else{g=a.eg.cY(b,
c,d);if(g>=0){Cd(d,a.eR,0);return g;}Cd(d,a.eR,e+1|0);g=a.eo.cY(b,c,d);}return g;},Iw=L(B6),Q5=(a,b,c,d)=>{let e;e=d.el;if(e>b)return a.eg.du(b,e,c,d);return a.eg.cY(b,c,d);},Lr=(a,b,c,d)=>{let e;e=d.el;if(a.eg.du(b,e,c,d)>=0)return b;return (-1);},PD=a=>{return I(265);};
function H$(){B6.call(this);this.ga=null;}
let Oo=(a,b,c,d)=>{let e,f;e=d.el;f=IV(a,b,e,c);if(f>=0)e=f;if(e>b)return a.eg.du(b,e,c,d);return a.eg.cY(b,c,d);},KC=(a,b,c,d)=>{let e,f,g,h;e=d.el;f=a.eg.cU(b,c,d);if(f<0)return (-1);g=IV(a,f,e,c);if(g>=0)e=g;g=BR(f,a.eg.du(f,e,c,d));if(g<=0)h=g?(-1):0;else{h=g-1|0;a:{while(true){if(h<b){h=(-1);break a;}if(a.ga.d7(P(c,h)))break;h=h+(-1)|0;}}}if(h>=b)b=h>=g?h:h+1|0;return b;},IV=(a,b,c,d)=>{while(true){if(b>=c)return (-1);if(a.ga.d7(P(d,b)))break;b=b+1|0;}return b;},R0=a=>{return I(266);},Dd=L(),IR=null,I2
=null,Fr=b=>{let c;if(!(b&1)){c=I2;if(c!==null)return c;c=new Jo;I2=c;return c;}c=IR;if(c!==null)return c;c=new Jn;IR=c;return c;},JH=L(BF),Mu=(a,b,c,d)=>{let e;a:{while(true){if((b+a.eu.dq()|0)>d.el)break a;e=a.eu.dr(b,c);if(e<1)break;b=b+e|0;}}return a.eg.cY(b,c,d);},GN=L(Ci),Lt=(a,b,c,d)=>{let e;if((b+a.eu.dq()|0)<=d.el){e=a.eu.dr(b,c);if(e>=1)b=b+e|0;}return a.eg.cY(b,c,d);},IX=L(Ck),JR=(a,b,c,d)=>{let e,f,g,h,i;e=a.fo;f=e.e7;g=e.e4;h=0;while(true){if(h>=f){a:{while(true){if(h>=g)break a;if((b+a.eu.dq()
|0)>d.el)break a;i=a.eu.dr(b,c);if(i<1)break;b=b+i|0;h=h+1|0;}}return a.eg.cY(b,c,d);}if((b+a.eu.dq()|0)>d.el){d.eP=1;return (-1);}i=a.eu.dr(b,c);if(i<1)break;b=b+i|0;h=h+1|0;}return (-1);},G4=L(BF),N8=(a,b,c,d)=>{let e;while(true){e=a.eg.cY(b,c,d);if(e>=0)break;if((b+a.eu.dq()|0)<=d.el){e=a.eu.dr(b,c);b=b+e|0;}if(e<1)return (-1);}return e;},HG=L(Ci),MR=(a,b,c,d)=>{let e;e=a.eg.cY(b,c,d);if(e>=0)return e;return a.eo.cY(b,c,d);},HL=L(Ck),Ks=(a,b,c,d)=>{let e,f,g,h,i,j;e=a.fo;f=e.e7;g=e.e4;h=0;while(true){if(h
>=f){a:{while(true){i=a.eg.cY(b,c,d);if(i>=0)break;if((b+a.eu.dq()|0)<=d.el){i=a.eu.dr(b,c);b=b+i|0;h=h+1|0;}if(i<1)break a;if(h>g)break a;}return i;}return (-1);}if((b+a.eu.dq()|0)>d.el){d.eP=1;return (-1);}j=a.eu.dr(b,c);if(j<1)break;b=b+j|0;h=h+1|0;}return (-1);},EU=L(Bd),RK=(a,b,c,d)=>{if(b&&!(d.e8&&b==d.eK))return (-1);return a.eg.cY(b,c,d);},Ph=(a,b)=>{return 0;},Lx=a=>{return I(267);};
function FM(){Bd.call(this);this.gX=0;}
let PX=(a,b)=>{Bm(a);a.gX=b;},GR=a=>{let b=new FM();PX(b,a);return b;},Ox=(a,b,c,d)=>{let e,f,g,h;e=b>=d.el?32:P(c,b);if(!b)f=32;else{g=b-1|0;f=P(c,g);}h=d.fG?0:d.eK;return (e!=32&&!Hd(a,e,b,h,c)?0:1)^(f!=32&&!Hd(a,f,b-1|0,h,c)?0:1)^a.gX?(-1):a.eg.cY(b,c,d);},OW=(a,b)=>{return 0;},Rp=a=>{return I(268);},Hd=(a,b,c,d,e)=>{let f;if(!HY(b)&&b!=95){a:{if(DD(b)==6)while(true){c=c+(-1)|0;if(c<d)break a;f=P(e,c);if(HY(f))return 0;if(DD(f)!=6)return 1;}}return 1;}return 0;},H1=L(Bd),PW=(a,b,c,d)=>{if(b!=d.fr)return (-1);return a.eg.cY(b,
c,d);},Ri=(a,b)=>{return 0;},NL=a=>{return I(269);};
function EO(){Bd.call(this);this.fh=0;}
let Gu=(a,b)=>{Bm(a);a.fh=b;},Kv=a=>{let b=new EO();Gu(b,a);return b;},P3=(a,b,c,d)=>{let e,f,g,h;e=d.e8?d.el:c.eh.length;if(b>=e){Bf(d,a.fh,0);return a.eg.cY(b,c,d);}f=e-b|0;if(f==2){g=c;if(P(g,b)==13&&P(g,b+1|0)==10){Bf(d,a.fh,0);return a.eg.cY(b,c,d);}}a:{if(f==1){h=P(c,b);if(h==10)break a;if(h==13)break a;if(h==133)break a;if((h|1)==8233)break a;}return (-1);}Bf(d,a.fh,0);return a.eg.cY(b,c,d);},Qw=(a,b)=>{let c;c=!BU(b,a.fh)?0:1;Bf(b,a.fh,(-1));return c;},KD=a=>{return I(270);},Hi=L(Bd),Nq=(a,b,c,d)=>{if
(b<(!d.fG?d.el:c.eh.length))return (-1);d.eP=1;d.hW=1;return a.eg.cY(b,c,d);},JX=(a,b)=>{return 0;},MX=a=>{return I(271);};
function GZ(){Bd.call(this);this.gB=null;}
let Qg=(a,b,c,d)=>{let e,f,g;a:{if(b!=d.el){if(!b)break a;if(d.e8&&b==d.eK)break a;e=a.gB;f=b-1|0;g=c;if(e.d_(P(g,f),P(g,b)))break a;}return (-1);}return a.eg.cY(b,c,d);},Mt=(a,b)=>{return 0;},M_=a=>{return I(272);},G9=L(Bj),M6=a=>{Bm(a);},Ov=()=>{let a=new G9();M6(a);return a;},Qi=(a,b,c,d)=>{let e,f,g,h;e=d.el;f=b+1|0;if(f>e){d.eP=1;return (-1);}g=c;h=P(g,b);if(BA(h)){b=b+2|0;if(b<=e&&Dx(h,P(g,f)))return a.eg.cY(b,c,d);}return a.eg.cY(f,c,d);},Sb=a=>{return I(273);},O8=(a,b)=>{a.eg=b;},MQ=a=>{return (-2147483602);},O6
=(a,b)=>{return 1;};
function IH(){Bj.call(this);this.gy=null;}
let Qs=(a,b)=>{Bm(a);a.gy=b;},Q0=a=>{let b=new IH();Qs(b,a);return b;},Nd=(a,b,c,d)=>{let e,f,g,h,i;e=d.el;f=b+1|0;if(f>e){d.eP=1;return (-1);}g=c;h=P(g,b);if(BA(h)){b=b+2|0;if(b<=e){i=P(g,f);if(Dx(h,i))return a.gy.d7(Cm(h,i))?(-1):a.eg.cY(b,c,d);}}return a.gy.d7(h)?(-1):a.eg.cY(f,c,d);},QR=a=>{return I(274);},RW=(a,b)=>{a.eg=b;},JI=a=>{return (-2147483602);},QT=(a,b)=>{return 1;};
function GO(){Bd.call(this);this.fA=0;}
let Mj=(a,b)=>{Bm(a);a.fA=b;},JZ=a=>{let b=new GO();Mj(b,a);return b;},QN=(a,b,c,d)=>{let e;e=d.e8?d.el:c.eh.length;if(b>=e){Bf(d,a.fA,0);return a.eg.cY(b,c,d);}if((e-b|0)==1&&P(c,b)==10){Bf(d,a.fA,1);return a.eg.cY(b+1|0,c,d);}return (-1);},Mf=(a,b)=>{let c;c=!BU(b,a.fA)?0:1;Bf(b,a.fA,(-1));return c;},JN=a=>{return I(270);};
function Gg(){Bd.call(this);this.fs=0;}
let Ns=(a,b)=>{Bm(a);a.fs=b;},Oc=a=>{let b=new Gg();Ns(b,a);return b;},Nm=(a,b,c,d)=>{if((d.e8?d.el-b|0:c.eh.length-b|0)<=0){Bf(d,a.fs,0);return a.eg.cY(b,c,d);}if(P(c,b)!=10)return (-1);Bf(d,a.fs,1);return a.eg.cY(b+1|0,c,d);},LU=(a,b)=>{let c;c=!BU(b,a.fs)?0:1;Bf(b,a.fs,(-1));return c;},LV=a=>{return I(275);};
function FZ(){Bd.call(this);this.e$=0;}
let RE=(a,b)=>{Bm(a);a.e$=b;},Lw=a=>{let b=new FZ();RE(b,a);return b;},O2=(a,b,c,d)=>{let e,f,g,h;e=d.e8?d.el-b|0:c.eh.length-b|0;if(!e){Bf(d,a.e$,0);return a.eg.cY(b,c,d);}if(e<2){f=P(c,b);g=97;}else{h=c;f=P(h,b);g=P(h,b+1|0);}switch(f){case 10:case 133:case 8232:case 8233:Bf(d,a.e$,0);return a.eg.cY(b,c,d);case 13:if(g!=10){Bf(d,a.e$,0);return a.eg.cY(b,c,d);}Bf(d,a.e$,0);return a.eg.cY(b,c,d);default:}return (-1);},QD=(a,b)=>{let c;c=!BU(b,a.e$)?0:1;Bf(b,a.e$,(-1));return c;},My=a=>{return I(276);};
function CA(){let a=this;Bj.call(a);a.g0=0;a.fj=0;}
let Fi=(a,b,c)=>{Bm(a);a.g0=b;a.fj=c;},Qq=(a,b)=>{let c=new CA();Fi(c,a,b);return c;},MF=(a,b,c,d)=>{let e,f,g,h,i;e=De(a,d);if(e!==null&&(b+e.eh.length|0)<=d.el){f=0;while(true){if(f>=e.eh.length){Bf(d,a.fj,e.eh.length);return a.eg.cY(b+e.eh.length|0,c,d);}g=P(e,f);h=b+f|0;i=c;if(g!=P(i,h)&&Da(P(e,f))!=P(i,h))break;f=f+1|0;}return (-1);}return (-1);},Qj=(a,b)=>{a.eg=b;},De=(a,b)=>{let c,d;c=a.g0;d=CL(b,c);c=D6(b,c);return (c|d|(c-d|0))>=0&&c<=b.fS.eh.length?BW(b.fS,d,c):null;},L0=a=>{let b,c;b=a.es;c=new W;Y(c);BY(T(c,
I(277)),b);return X(c);},Q_=(a,b)=>{let c;c=!BU(b,a.fj)?0:1;Bf(b,a.fj,(-1));return c;},G1=L(CA),Nj=(a,b,c)=>{Fi(a,b,c);},MI=(a,b)=>{let c=new G1();Nj(c,a,b);return c;},QS=(a,b,c,d)=>{let e,f;e=De(a,d);if(e!==null&&(b+e.eh.length|0)<=d.el){f=!H6(c,e,b)?(-1):e.eh.length;if(f<0)return (-1);Bf(d,a.fj,f);return a.eg.cY(b+f|0,c,d);}return (-1);},MW=(a,b,c,d)=>{let e,f,g;e=De(a,d);f=d.eK;if(e!==null&&(b+e.eh.length|0)<=f){g=c;while(true){if(b>f)return (-1);b=FJ(g,e,b);if(b<0)return (-1);if(a.eg.cY(b+e.eh.length|0,
c,d)>=0)break;b=b+1|0;}return b;}return (-1);},LW=(a,b,c,d,e)=>{let f,g,h;f=De(a,e);if(f===null)return (-1);g=d;a:{while(true){if(c<b)return (-1);c=Bw(c,g.eh.length-f.eh.length|0);b:{c:while(true){if(c<0){c=(-1);break b;}h=0;while(true){if(h>=f.eh.length)break c;if(P(g,c+h|0)!=P(f,h))break;h=h+1|0;}c=c+(-1)|0;}}if(c<0)break a;if(c<b)break a;if(a.eg.cY(c+f.eh.length|0,d,e)>=0)break;c=c+(-1)|0;}return c;}return (-1);},Lj=(a,b)=>{return 1;},NG=a=>{let b,c;b=a.es;c=new W;Y(c);BY(T(c,I(278)),b);return X(c);};
function HE(){CA.call(this);this.h_=0;}
let Md=(a,b,c)=>{Fi(a,b,c);},Od=(a,b)=>{let c=new HE();Md(c,a,b);return c;},Ka=(a,b,c,d)=>{let e,f,g,h;e=De(a,d);if(e!==null&&(b+e.eh.length|0)<=d.el){f=0;while(true){if(f>=e.eh.length){Bf(d,a.fj,e.eh.length);return a.eg.cY(b+e.eh.length|0,c,d);}g=Cj(Ch(P(e,f)));h=b+f|0;if(g!=Cj(Ch(P(c,h))))break;f=f+1|0;}return (-1);}return (-1);},OX=a=>{let b,c;b=a.h_;c=new W;Y(c);BY(T(c,I(279)),b);return X(c);};
function IA(){let a=this;Bn.call(a);a.eG=null;a.f6=null;a.f5=null;}
let RM=(a,b,c)=>{return !ET(a,c,b)?(-1):a.eA;},Ok=(a,b,c,d)=>{let e,f,g;e=d.el;while(true){if(b>e)return (-1);f=P(a.eG,a.eA-1|0);a:{while(true){g=a.eA;if(b>(e-g|0)){b=(-1);break a;}g=(b+g|0)-1|0;g=P(c,g);if(g==f&&ET(a,c,b))break;b=b+F1(a.f6,g)|0;}}if(b<0)return (-1);if(a.eg.cY(b+a.eA|0,c,d)>=0)break;b=b+1|0;}return b;},Mq=(a,b,c,d,e)=>{let f,g,h;while(true){if(c<b)return (-1);f=P(a.eG,0);g=d;h=(g.eh.length-c|0)-a.eA|0;if(h<=0)c=c+h|0;a:{while(true){if(c<b){c=(-1);break a;}h=P(g,c);if(h==f&&ET(a,d,c))break;c
=c-F1(a.f5,h)|0;}}if(c<0)return (-1);if(a.eg.cY(c+a.eA|0,d,e)>=0)break;c=c+(-1)|0;}return c;},NP=a=>{let b,c;b=a.eG;c=new W;Y(c);T(T(c,I(280)),b);return X(c);},NJ=(a,b)=>{let c;if(b instanceof B8)return b.eO!=P(a.eG,0)?0:1;if(b instanceof B2)return Eg(b,0,BW(a.eG,0,1))<=0?0:1;if(!(b instanceof BO)){if(!(b instanceof B7))return 1;return a.eG.eh.length>1&&b.fg==Cm(P(a.eG,0),P(a.eG,1))?1:0;}a:{b:{b=b;if(!b.dA(P(a.eG,0))){if(a.eG.eh.length<=1)break b;if(!b.dA(Cm(P(a.eG,0),P(a.eG,1))))break b;}c=1;break a;}c=0;}return c;},ET
=(a,b,c)=>{let d,e;d=0;while(d<a.eA){e=d+c|0;if(P(b,e)!=P(a.eG,d))return 0;d=d+1|0;}return 1;};
function FV(){Bn.call(this);this.fq=null;}
let Mh=(a,b)=>{let c,d;BV(a);c=new W;Y(c);d=0;while(d<b.er){Bh(c,Cj(Ch(Er(b,d))));d=d+1|0;}a.fq=X(c);a.eA=c.er;},Q8=a=>{let b=new FV();Mh(b,a);return b;},Kk=(a,b,c)=>{let d,e,f;d=0;while(true){if(d>=a.fq.eh.length)return a.fq.eh.length;e=P(a.fq,d);f=b+d|0;if(e!=Cj(Ch(P(c,f))))break;d=d+1|0;}return (-1);},Oi=a=>{let b,c;b=a.fq;c=new W;Y(c);T(T(c,I(281)),b);return X(c);};
function GQ(){Bn.call(this);this.fl=null;}
let No=(a,b)=>{BV(a);a.fl=X(b);a.eA=b.er;},Sc=a=>{let b=new GQ();No(b,a);return b;},JV=(a,b,c)=>{let d,e,f,g;d=0;while(true){if(d>=a.fl.eh.length)return a.fl.eh.length;e=P(a.fl,d);f=b+d|0;g=c;if(e!=P(g,f)&&Da(P(a.fl,d))!=P(g,f))break;d=d+1|0;}return (-1);},Mn=a=>{let b,c;b=a.fl;c=new W;Y(c);T(T(c,I(282)),b);return X(c);},IU=L(0),El=L(CU),Ey=L(El),FF=L(Ey),E$=L(Cs),KV=(a,b,c)=>{b=new EA;Z(b);Q(b);},FC=L(E$),EI=L(C6),FD=L(EI),D_=L(0),FA=L(),Ju=L(0),FB=L(),Jj=L(),RQ=L(),Pt=L(),Hx=b=>{let c,d,e,f,g,h,i;c=JC(D4(b));d
=DY(c);e=BC(d*2|0);f=e.data;g=0;h=0;while(h<d){g=g+DY(c)|0;i=h*2|0;f[i]=g;f[i+1|0]=Ev(c);h=h+1|0;}return e;},F6=b=>{let c,d,e,f,g,h,i,j,k,l;c=BC(65536);d=c.data;e=0;f=0;g=0;a:{while(true){h=b.data;if(g>=h.length)break a;i=h[g];j=h[g+1|0];k=d.length;if(i<k)k=i;else if(i==e)break;Gi(c,e,k,f);g=g+2|0;e=k;f=j;}}l=new F0;l.hS=b;l.gJ=c;return l;},D5=b=>{if(b>92)return ((b-32|0)-2|0)<<24>>24;if(b<=34)return (b-32|0)<<24>>24;return ((b-32|0)-1|0)<<24>>24;},RR=b=>{let c,d,e,f,g,h,i,j,k,l,m,n,o,p,q;c=K(DC,16384);d=c.data;e
=I0(16384);f=e.data;g=0;h=0;i=0;j=0;a:while(true){if(j>=b.eh.length){if(g<=0)k=h;else{k=h+1|0;d[h]=GC(i,i+g|0,EK(e,g));}return H3(c,k);}l=D5(P(b,j));if(l==64){j=j+1|0;l=D5(P(b,j));m=0;k=1;n=0;while(n<3){j=j+1|0;m=m|DF(k,D5(P(b,j)));k=k*64|0;n=n+1|0;}}else if(l<32)m=1;else{l=(l-32|0)<<24>>24;j=j+1|0;m=D5(P(b,j));}if(!l&&m>=128){if(g>0){k=h+1|0;d[h]=GC(i,i+g|0,EK(e,g));h=k;}i=i+(g+m|0)|0;g=0;}else while(m>0){k=f.length;if(g!=k){n=h;o=i;i=g;}else{n=h+1|0;p=new DC;o=i+g|0;Gl(p,i,o,EK(e,g));d[h]=p;i=0;}q=Bw(m,k-
i|0);g=i+q|0;if(i>g)break a;while(i<g){k=i+1|0;f[i]=l;i=k;}m=m-q|0;h=n;i=o;}j=j+1|0;}b=new Bs;Z(b);Q(b);};
function FR(){Bn.call(this);this.gs=0;}
let J2=(a,b,c)=>{let d,e;d=b+1|0;c=c;e=P(c,b);d=P(c,d);return a.gs!=CV(Dh(Cm(e,d)))?(-1):2;},Rk=a=>{let b,c;b=Dk(CI(a.gs));c=new W;Y(c);T(T(c,I(262)),b);return X(c);};
function Dn(){Bj.call(this);this.e_=0;}
let PC=(a,b)=>{Bm(a);a.e_=b;},Hj=a=>{let b=new Dn();PC(b,a);return b;},N2=(a,b)=>{a.eg=b;},Qx=(a,b,c,d)=>{let e,f,g;e=b+1|0;if(e>d.el){d.eP=1;return (-1);}f=c;g=P(f,b);if(b>d.eK&&BA(P(f,b-1|0)))return (-1);if(a.e_!=g)return (-1);return a.eg.cY(e,c,d);},NB=(a,b,c,d)=>{let e,f,g,h,i;if(!(c instanceof Br))return Dg(a,b,c,d);e=c;f=d.eK;g=d.el;while(true){if(b>=g)return (-1);h=DH(e,a.e_,b);if(h<0)return (-1);if(h>f&&BA(P(e,h-1|0))){b=h+1|0;continue;}i=a.eg;b=h+1|0;if(i.cY(b,c,d)>=0)break;}return h;},R9=(a,b,c,d,
e)=>{let f,g;if(!(d instanceof Br))return CY(a,b,c,d,e);f=e.eK;g=d;a:{while(true){if(c<b)return (-1);c=C0(g,a.e_,c);if(c<0)break a;if(c<b)break a;if(c>f&&BA(P(g,c-1|0))){c=c+(-2)|0;continue;}if(a.eg.cY(c+1|0,d,e)>=0)break;c=c+(-1)|0;}return c;}return (-1);},LF=a=>{let b,c;b=a.e_;c=new W;Y(c);Bh(c,b);return X(c);},LN=(a,b)=>{if(b instanceof B8)return 0;if(b instanceof B2)return 0;if(b instanceof BO)return 0;if(b instanceof B7)return 0;if(b instanceof DL)return 0;if(!(b instanceof Dn))return 1;return b.e_!=a.e_
?0:1;},LZ=(a,b)=>{return 1;};
function DL(){Bj.call(this);this.fd=0;}
let Ni=(a,b)=>{Bm(a);a.fd=b;},H5=a=>{let b=new DL();Ni(b,a);return b;},PK=(a,b)=>{a.eg=b;},KI=(a,b,c,d)=>{let e,f,g,h,i;e=d.el;f=b+1|0;g=B1(f,e);if(g>0){d.eP=1;return (-1);}h=c;i=P(h,b);if(g<0&&BI(P(h,f)))return (-1);if(a.fd!=i)return (-1);return a.eg.cY(f,c,d);},OU=(a,b,c,d)=>{let e,f,g;if(!(c instanceof Br))return Dg(a,b,c,d);e=c;f=d.el;while(true){if(b>=f)return (-1);g=DH(e,a.fd,b);if(g<0)return (-1);b=g+1|0;if(b<f&&BI(P(e,b))){b=g+2|0;continue;}if(a.eg.cY(b,c,d)>=0)break;}return g;},JS=(a,b,c,d,e)=>{let f,
g,h;if(!(d instanceof Br))return CY(a,b,c,d,e);f=d;g=e.el;a:{while(true){if(c<b)return (-1);c=C0(f,a.fd,c);if(c<0)break a;if(c<b)break a;h=c+1|0;if(h<g&&BI(P(f,h))){c=c+(-1)|0;continue;}if(a.eg.cY(h,d,e)>=0)break;c=c+(-1)|0;}return c;}return (-1);},Qc=a=>{let b,c;b=a.fd;c=new W;Y(c);Bh(c,b);return X(c);},RO=(a,b)=>{if(b instanceof B8)return 0;if(b instanceof B2)return 0;if(b instanceof BO)return 0;if(b instanceof B7)return 0;if(b instanceof Dn)return 0;if(!(b instanceof DL))return 1;return b.fd!=a.fd?0:1;},Pm
=(a,b)=>{return 1;};
function B7(){let a=this;Bn.call(a);a.fz=0;a.fp=0;a.fg=0;}
let Rb=(a,b,c)=>{let d,e;d=b+1|0;c=c;e=P(c,b);d=P(c,d);return a.fz==e&&a.fp==d?2:(-1);},Ml=(a,b,c,d)=>{let e,f,g;if(!(c instanceof Br))return Dg(a,b,c,d);e=c;f=d.el;while(b<f){b=DH(e,a.fz,b);if(b<0)return (-1);b=b+1|0;if(b>=f)continue;g=P(e,b);if(a.fp==g&&a.eg.cY(b+1|0,c,d)>=0)return b+(-1)|0;b=b+1|0;}return (-1);},PH=(a,b,c,d,e)=>{let f;if(!(d instanceof Br))return CY(a,b,c,d,e);f=d;a:{while(true){if(c<b)return (-1);c=C0(f,a.fp,c)+(-1)|0;if(c<0)break a;if(c<b)break a;if(a.fz==P(f,c)&&a.eg.cY(c+2|0,d,e)>=0)break;c
=c+(-1)|0;}return c;}return (-1);},NQ=a=>{let b,c,d;b=a.fz;c=a.fp;d=new W;Y(d);Bh(d,b);Bh(d,c);return X(d);},QE=(a,b)=>{if(b instanceof B7)return b.fg!=a.fg?0:1;if(b instanceof BO)return b.dA(a.fg);if(b instanceof B8)return 0;if(!(b instanceof B2))return 1;return 0;},Jn=L(Dd),P4=(a,b)=>{return b!=10?0:1;},QW=(a,b,c)=>{return b!=10?0:1;},Jo=L(Dd),KB=(a,b)=>{return b!=10&&b!=13&&b!=133&&(b|1)!=8233?0:1;},Pe=(a,b,c)=>{a:{b:{if(b!=10&&b!=133&&(b|1)!=8233){if(b!=13)break b;if(c==10)break b;}b=1;break a;}b=0;}return b;};
function IL(){let a=this;J.call(a);a.gu=null;a.f$=null;a.fy=0;a.hC=0;}
let PU=(a,b)=>{let c,d;while(true){c=a.fy;if(b<c)break;a.fy=c<<1|1;}d=c<<1|1;a.fy=d;d=d+1|0;a.gu=BC(d);a.f$=BC(d);a.hC=b;},Hc=a=>{let b=new IL();PU(b,a);return b;},HR=(a,b,c)=>{let d,e,f,g;d=0;e=a.fy;f=b&e;while(true){g=a.gu.data;if(!g[f])break;if(g[f]==b)break;d=(d+1|0)&e;f=(f+d|0)&e;}g[f]=b;a.f$.data[f]=c;},F1=(a,b)=>{let c,d,e,f;c=a.fy;d=b&c;e=0;while(true){f=a.gu.data[d];if(!f)break;if(f==b)return a.f$.data[d];e=(e+1|0)&c;d=(d+e|0)&c;}return a.hC;};
function Jg(){let a=this;J.call(a);a.hx=null;a.hz=0;}
let RZ=(a,b)=>{a.hx=b;},JC=a=>{let b=new Jg();RZ(b,a);return b;},Np=L(),DY=b=>{let c,d,e,f,g;c=0;d=1;while(true){e=b.hx.data;f=b.hz;b.hz=f+1|0;g=e[f];g=g<34?g-32|0:g>=92?(g-32|0)-2|0:(g-32|0)-1|0;f=(g%2|0)!=1?0:1;c=c+DF(d,g/2|0)|0;d=d*46|0;if(!f)break;}return c;},Ev=b=>{let c,d;c=DY(b);d=c/2|0;if(c%2|0)d= -d|0;return d;},Dj=L(U),OR=a=>{return;},Lp=()=>{let a=new Dj();OR(a);return a;},HM=a=>{return Bv(Bk(BE(),9,13),32);},Ed=L(U),LH=a=>{return;},QG=()=>{let a=new Ed();LH(a);return a;},FP=a=>{return Bk(BE(),48,
57);},IB=L(U),MZ=a=>{return;},Qr=()=>{let a=new IB();MZ(a);return a;},R3=a=>{return Bk(BE(),97,122);},Fy=L(U),P5=a=>{return;},KX=()=>{let a=new Fy();P5(a);return a;},L1=a=>{return Bk(BE(),65,90);},FI=L(U),Ol=a=>{return;},KW=()=>{let a=new FI();Ol(a);return a;},KO=a=>{return Bk(BE(),0,127);},Ee=L(U),QZ=a=>{return;},Nl=()=>{let a=new Ee();QZ(a);return a;},IW=a=>{return Bk(Bk(BE(),97,122),65,90);},D1=L(Ee),Pd=a=>{return;},Kn=()=>{let a=new D1();Pd(a);return a;},GM=a=>{return Bk(IW(a),48,57);},Jq=L(U),LK=a=>{return;},P7
=()=>{let a=new Jq();LK(a);return a;},Ny=a=>{return Bk(Bk(Bk(BE(),33,64),91,96),123,126);},Ef=L(D1),Qa=a=>{return;},Nx=()=>{let a=new Ef();Qa(a);return a;},FN=a=>{return Bk(Bk(Bk(GM(a),33,64),91,96),123,126);},Ix=L(Ef),Ln=a=>{return;},Mo=()=>{let a=new Ix();Ln(a);return a;},K_=a=>{return Bv(FN(a),32);},Jv=L(U),JW=a=>{return;},Op=()=>{let a=new Jv();JW(a);return a;},Ru=a=>{return Bv(Bv(BE(),32),9);},JE=L(U);
let ON=a=>{return;},K0=()=>{let a=new JE();ON(a);return a;},KZ=a=>{return Bv(Bk(BE(),0,31),127);},Jc=L(U),OV=a=>{return;},NO=()=>{let a=new Jc();OV(a);return a;},Po=a=>{return Bk(Bk(Bk(BE(),48,57),97,102),65,70);},FS=L(U),Nt=a=>{return;},Pz=()=>{let a=new FS();Nt(a);return a;},MK=a=>{let b;b=new Gq;b.iv=a;Ba(b);b.eq=1;return b;},JG=L(U),QO=a=>{return;},Kr=()=>{let a=new JG();QO(a);return a;},Kt=a=>{let b;b=new F9;b.h1=a;Ba(b);b.eq=1;return b;},IM=L(U),Ou=a=>{return;},Mz=()=>{let a=new IM();Ou(a);return a;},Pa
=a=>{let b;b=new I9;b.h8=a;Ba(b);return b;},H4=L(U),K3=a=>{return;},KF=()=>{let a=new H4();K3(a);return a;},Rj=a=>{let b;b=new I8;b.hX=a;Ba(b);return b;},Gw=L(U),QP=a=>{return;},Kd=()=>{let a=new Gw();QP(a);return a;},Rr=a=>{let b;b=new Hn;b.h4=a;Ba(b);DP(b.ep,0,2048);b.eq=1;return b;},Ht=L(U),PV=a=>{return;},Ky=()=>{let a=new Ht();PV(a);return a;},JY=a=>{let b;b=new IQ;b.ia=a;Ba(b);b.eq=1;return b;},Gz=L(U),Kg=a=>{return;},Ki=()=>{let a=new Gz();Kg(a);return a;},O4=a=>{let b;b=new HN;b.ih=a;Ba(b);b.eq=1;return b;},I5
=L(U),LM=a=>{return;},O_=()=>{let a=new I5();LM(a);return a;},J4=a=>{let b;b=new Hq;b.iw=a;Ba(b);return b;},Jt=L(U),Og=a=>{return;},Ot=()=>{let a=new Jt();Og(a);return a;},Q3=a=>{let b;b=new F3;b.hU=a;Ba(b);b.eq=1;return b;},Gk=L(U),L2=a=>{return;},QL=()=>{let a=new Gk();L2(a);return a;},Ke=a=>{let b;b=new F8;b.id=a;Ba(b);b.eq=1;return b;},Fz=L(U),P_=a=>{return;},Lz=()=>{let a=new Fz();P_(a);return a;};
let MA=a=>{let b;b=new Hv;b.h3=a;Ba(b);b.eq=1;return b;},Ir=L(U),Rq=a=>{return;},KL=()=>{let a=new Ir();Rq(a);return a;},Q$=a=>{let b;b=new Jp;b.h9=a;Ba(b);b.eq=1;return b;},Jm=L(U),Mv=a=>{return;},Kw=()=>{let a=new Jm();Mv(a);return a;},MM=a=>{let b;b=new JA;b.h$=a;Ba(b);return b;},Hz=L(U),PY=a=>{return;},NY=()=>{let a=new Hz();PY(a);return a;},PP=a=>{let b;b=new Hr;b.ig=a;Ba(b);return b;},Gj=L(U),Rn=a=>{return;},OS=()=>{let a=new Gj();Rn(a);return a;},Mm=a=>{let b;b=new FO;b.h0=a;Ba(b);b.eq=1;return b;},Jz
=L(U),N1=a=>{return;},QB=()=>{let a=new Jz();N1(a);return a;},RI=a=>{let b;b=new Gn;b.it=a;Ba(b);b.eq=1;return b;},DE=L(U),Ku=a=>{return;},MN=()=>{let a=new DE();Ku(a);return a;},Jx=a=>{return Bv(Bk(Bk(Bk(BE(),97,122),65,90),48,57),95);},GH=L(DE),Oq=a=>{return;},Nz=()=>{let a=new GH();Oq(a);return a;},LR=a=>{let b;b=Cb(Jx(a),1);b.eq=1;return b;},ID=L(Dj),NV=a=>{return;},JP=()=>{let a=new ID();NV(a);return a;},N7=a=>{let b;b=Cb(HM(a),1);b.eq=1;return b;},F_=L(Ed),Qt=a=>{return;},ME=()=>{let a=new F_();Qt(a);return a;},Nc
=a=>{let b;b=Cb(FP(a),1);b.eq=1;return b;};
function Jk(){let a=this;U.call(a);a.hv=0;a.hR=0;}
let O1=(a,b,c)=>{a.hv=b;a.hR=c;},N=(a,b)=>{let c=new Jk();O1(c,a,b);return c;},R6=a=>{return Bk(BE(),a.hv,a.hR);},F2=L(U),PJ=a=>{return;},NT=()=>{let a=new F2();PJ(a);return a;},Ow=a=>{return Bk(Bk(BE(),65279,65279),65520,65533);};
function E0(){let a=this;U.call(a);a.f4=0;a.fT=0;a.gC=0;}
let QC=(a,b,c)=>{a.fT=c;a.f4=b;},Be=(a,b)=>{let c=new E0();QC(c,a,b);return c;},O3=(a,b,c,d)=>{a.gC=d;a.fT=c;a.f4=b;},RP=(a,b,c)=>{let d=new E0();O3(d,a,b,c);return d;},LL=a=>{let b;b=PE(a.f4);if(a.gC)DP(b.ep,0,2048);b.eq=a.fT;return b;};
function E_(){let a=this;U.call(a);a.f1=0;a.ge=0;a.g5=0;}
let Kf=(a,b,c)=>{a.ge=c;a.f1=b;},CC=(a,b)=>{let c=new E_();Kf(c,a,b);return c;},J9=(a,b,c,d)=>{a.g5=d;a.ge=c;a.f1=b;},O7=(a,b,c)=>{let d=new E_();J9(d,a,b,c);return d;},J7=a=>{let b;b=new IT;Hl(b,a.f1);if(a.g5)DP(b.ep,0,2048);b.eq=a.ge;return b;},JD=L(Bi),Ma=L();
function F0(){let a=this;J.call(a);a.hS=null;a.gJ=null;}
function DC(){let a=this;J.call(a);a.gY=0;a.gF=0;a.g7=null;}
let Gl=(a,b,c,d)=>{a.gY=b;a.gF=c;a.g7=d;},GC=(a,b,c)=>{let d=new DC();Gl(d,a,b,c);return d;},EA=L(Bi);
function GT(){let a=this;R.call(a);a.hm=null;a.h5=null;}
let K4=(a,b)=>{let c;c=b-55296|0;return c>=0&&c<2048?a.eF^BX(a.hm,c):0;};
function GP(){let a=this;R.call(a);a.gG=null;a.ha=null;a.il=null;}
let Lk=(a,b)=>{let c,d;c=b-55296|0;d=c>=0&&c<2048?a.eF^BX(a.gG,c):0;return a.ha.dA(b)&&!d?1:0;};
function Id(){let a=this;R.call(a);a.fH=null;a.h2=null;}
let Rv=(a,b)=>{return a.ew^BX(a.fH,b);},MS=a=>{let b,c,d;b=new W;Y(b);c=Db(a.fH,0);while(c>=0){DR(b,CI(c));Bh(b,124);c=Db(a.fH,c+1|0);}d=b.er;if(d>0)GB(b,d-1|0);return X(b);};
function Ij(){let a=this;R.call(a);a.hG=null;a.hY=null;}
let Rh=(a,b)=>{return a.hG.dA(b);};
function Ih(){let a=this;R.call(a);a.f0=0;a.hM=null;a.gi=null;}
let J0=(a,b)=>{return !(a.f0^BX(a.gi.en,b))&&!(a.f0^a.gi.eV^a.hM.dA(b))?0:1;};
function Ii(){let a=this;R.call(a);a.f_=0;a.gR=null;a.gk=null;}
let Kj=(a,b)=>{return !(a.f_^BX(a.gk.en,b))&&!(a.f_^a.gk.eV^a.gR.dA(b))?1:0;};
function Im(){let a=this;R.call(a);a.hq=0;a.gU=null;a.gN=null;a.im=null;}
let J5=(a,b)=>{return a.hq^(!a.gU.dA(b)&&!a.gN.dA(b)?0:1);};
function Io(){let a=this;R.call(a);a.hO=0;a.hB=null;a.hl=null;a.io=null;}
let JJ=(a,b)=>{return a.hO^(!a.hB.dA(b)&&!a.hl.dA(b)?0:1)?0:1;};
function Ik(){let a=this;R.call(a);a.g_=null;a.is=null;}
let M0=(a,b)=>{return BH(a.g_,b);};
function Il(){let a=this;R.call(a);a.g9=null;a.hT=null;}
let Km=(a,b)=>{return BH(a.g9,b)?0:1;};
function Ip(){let a=this;R.call(a);a.gD=null;a.hD=0;a.hH=null;}
let M3=(a,b)=>{return !BH(a.gD,b)&&!(a.hD^BX(a.hH.en,b))?0:1;};
function Iq(){let a=this;R.call(a);a.g8=null;a.hh=0;a.gP=null;}
let Q4=(a,b)=>{return !BH(a.g8,b)&&!(a.hh^BX(a.gP.en,b))?1:0;};
function Ic(){let a=this;R.call(a);a.hF=0;a.gQ=null;a.hi=null;a.h6=null;}
let R_=(a,b)=>{return !(a.hF^a.gQ.dA(b))&&!BH(a.hi,b)?0:1;};
function I1(){let a=this;R.call(a);a.hg=0;a.gL=null;a.g6=null;a.iu=null;}
let M$=(a,b)=>{return !(a.hg^a.gL.dA(b))&&!BH(a.g6,b)?1:0;};
function Ia(){let a=this;R.call(a);a.gM=null;a.hV=null;}
let Q2=(a,b)=>{return BH(a.gM,b);};
function Ib(){let a=this;R.call(a);a.gT=null;a.ii=null;}
let Mc=(a,b)=>{return BH(a.gT,b)?0:1;};
function Ig(){let a=this;R.call(a);a.hn=null;a.gZ=0;a.hQ=null;}
let P1=(a,b)=>{return BH(a.hn,b)&&a.gZ^BX(a.hQ.en,b)?1:0;};
function H_(){let a=this;R.call(a);a.hw=null;a.hP=0;a.gV=null;}
let Mb=(a,b)=>{return BH(a.hw,b)&&a.hP^BX(a.gV.en,b)?0:1;};
function Ie(){let a=this;R.call(a);a.hL=0;a.hj=null;a.hN=null;a.ik=null;}
let OO=(a,b)=>{return a.hL^a.hj.dA(b)&&BH(a.hN,b)?1:0;};
function If(){let a=this;R.call(a);a.hs=0;a.gI=null;a.hE=null;a.hZ=null;}
let Pj=(a,b)=>{return a.hs^a.gI.dA(b)&&BH(a.hE,b)?0:1;},FH=L(0);
function Fd(){let a=this;J.call(a);a.gx=null;a.gf=null;}
function Eq(){let a=this;Fd.call(a);a.gc=0;a.fB=null;}
function DG(){let a=this;Eq.call(a);a.ff=null;a.fn=null;}
let GI=L(Bi),GD=L(CM),LT=(a,b,c,d)=>{let e,f,g;e=0;f=d.el;a:{while(true){if(b>f){b=e;break a;}g=CL(d,a.es);B0(d,a.es,b);e=a.eM.cY(b,c,d);if(e>=0)break;B0(d,a.es,g);b=b+1|0;}}return b;},Rs=(a,b,c,d,e)=>{let f,g;f=0;a:{while(true){if(c<b){c=f;break a;}g=CL(e,a.es);B0(e,a.es,c);f=a.eM.cY(c,d,e);if(f>=0)break;B0(e,a.es,g);c=c+(-1)|0;}}return c;},Qv=a=>{return null;};
function Gq(){R.call(this);this.iv=null;}
let Nr=(a,b)=>{Bb();return BB(b)!=2?0:1;};
function F9(){R.call(this);this.h1=null;}
let NR=(a,b)=>{Bb();return BB(b)!=1?0:1;};
function I9(){R.call(this);this.h8=null;}
let MP=(a,b)=>{return HI(b);};
function I8(){R.call(this);this.hX=null;}
let LJ=(a,b)=>{return 0;};
function Hn(){R.call(this);this.h4=null;}
let Py=(a,b)=>{Bb();return !BB(b)?0:1;};
function IQ(){R.call(this);this.ia=null;}
let Nw=(a,b)=>{Bb();return BB(b)!=9?0:1;};
function HN(){R.call(this);this.ih=null;}
let NN=(a,b)=>{return C4(b);};
function Hq(){R.call(this);this.iw=null;}
let Rx=(a,b)=>{a:{b:{Bb();if(!(b>=0&&b<=31)){if(b<127)break b;if(b>159)break b;}b=1;break a;}b=0;}return b;};
function F3(){R.call(this);this.hU=null;}
let QJ=(a,b)=>{a:{b:{Bb();switch(BB(b)){case 1:case 2:case 3:case 4:case 5:case 6:case 8:case 9:case 10:case 23:case 26:break;case 7:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 21:case 22:case 24:case 25:break b;default:break b;}b=1;break a;}b=C4(b);}return b;};
function F8(){R.call(this);this.id=null;}
let R2=(a,b)=>{a:{b:{Bb();switch(BB(b)){case 1:case 2:case 3:case 4:case 5:case 10:case 23:case 26:break;case 6:case 7:case 8:case 9:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 21:case 22:case 24:case 25:break b;default:break b;}b=1;break a;}b=C4(b);}return b;};
function Hv(){R.call(this);this.h3=null;}
let On=(a,b)=>{a:{Bb();switch(BB(b)){case 1:case 2:case 3:case 4:case 5:break;default:b=0;break a;}b=1;}return b;};
function Jp(){R.call(this);this.h9=null;}
let KQ=(a,b)=>{return Ho(b);};
function JA(){R.call(this);this.h$=null;}
let Qh=(a,b)=>{return JB(b);};
function Hr(){R.call(this);this.ig=null;}
let M4=(a,b)=>{Bb();return BB(b)!=3?0:1;};
function FO(){R.call(this);this.h0=null;}
let PQ=(a,b)=>{a:{b:{Bb();switch(BB(b)){case 1:case 2:case 3:case 4:case 5:case 6:case 8:case 9:case 10:case 23:break;case 7:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 21:case 22:break b;default:break b;}b=1;break a;}b=C4(b);}return b;};
function Gn(){R.call(this);this.it=null;}
let Rt=(a,b)=>{a:{b:{Bb();switch(BB(b)){case 1:case 2:case 3:case 4:case 5:case 10:break;case 6:case 7:case 8:case 9:break b;default:break b;}b=1;break a;}b=C4(b);}return b;};
function D0(){R.call(this);this.gd=0;}
let Hl=(a,b)=>{Ba(a);a.gd=b;},PE=a=>{let b=new D0();Hl(b,a);return b;},NX=(a,b)=>{return a.ew^(a.gd!=DD(b&65535)?0:1);},IT=L(D0),Le=(a,b)=>{return a.ew^(!(a.gd>>DD(b&65535)&1)?0:1);};
function He(){let a=this;J.call(a);a.hd=0;a.hf=0;a.hk=0;a.gO=0;a.hp=null;}
let Os=L(),GY=L(Bi);
LQ([-1,"java",0,"util",0,"lang"]);
Cu([J,0,0,[],1,0,0,["g",M(Is)],B$,0,J,[],1537,0,0,0,DX,0,J,[],1537,0,0,0,EY,0,J,[],1537,0,0,0,Br,0,J,[B$,DX,EY],17,0,()=>Ce(),["h",O(P),"E",M(Dz),"g",M(Px)],Fk,0,J,[B$],1025,0,0,0,Dw,0,Fk,[DX],1,0,()=>BN(),0,Cl,0,J,[B$,EY],0,0,0,["y",O(EQ),"g",M(X)],EL,0,J,[],1537,0,0,0,W,0,Cl,[EL],1,0,0,["G",Bz(QF),"H",S(JU),"D",Bz(OI),"F",S(RF),"h",O(MC),"E",M(C5),"g",M(C9),"y",O(Q6),"B",Bq(Kc),"x",Bq(RA)],Ff,0,J,[],1,0,0,0,Cr,0,Ff,[],1,0,0,0,Bi,0,Cr,[],1,0,0,0,CT,0,J,[],1025,0,0,0,Me,0,CT,[],17,0,0,0,LO,0,CT,[],17,0,0,0,GE,
0,J,[],1537,0,0,0,Jl,0,J,[GE],1537,0,0,0,FU,0,J,[],1537,0,0,0,GX,0,J,[Jl,FU],17,0,0,0,I_,0,J,[],17,0,()=>Bx(),0,Pw,0,J,[],17,0,0,0,Q1,0,J,[],17,0,0,0,RT,0,J,[],17,0,0,0,E7,0,J,[],1537,0,0,0,Hw,0,J,[E7],1,0,0,0,E9,0,J,[DX],1,0,()=>Bb(),0,LX,0,J,[],17,0,0,0,Rf,0,J,[],17,0,0,0,HH,0,J,[B$],17,0,0,0,EC,0,J,[],1537,0,0,0,FL,0,J,[EC],17,0,0,0,DT,0,Cl,[EL],1,0,0,["G",Bz(LI),"H",S(OJ),"D",Bz(Ms),"F",S(Q7),"y",O(PM),"B",Bq(Ly),"x",Bq(K2)],Bo,"IndexOutOfBoundsException",2,Bi,[],1,[0,0,0],0,0,Jf,0,J,[EC],0,0,0,0,Bs,"IllegalArgumentException",
2,Bi,[],1,[0,0,0],0,0,CB,0,Bs,[],1,0,0,0,OC,0,J,[],17,0,0,0,H2,0,J,[],16,0,0,0,Do,"NullPointerException",2,Bi,[],1,[0,0,0],0,0,Bd,0,J,[],1024,0,()=>Dc(),["cU",S(Dg),"du",Bz(CY),"cm",M(Pg),"g",M(Rl),"bY",O(MU),"ck",O(MT),"c0",M(PA),"bP",M(Dm)],GF,0,J,[],1537,0,0,0,DQ,0,J,[GF],1537,0,0,0,CU,0,J,[DQ],1025,0,0,0,I4,0,J,[DQ],1537,0,0,0,Ge,0,J,[I4],1537,0,0,0,C6,0,CU,[Ge],1025,0,0,0,CS,0,J,[],1537,0,0,0,EV,0,J,[],1537,0,0,0,HD,0,C6,[CS,B$,EV],1,0,0,0]);
Cu([Gv,0,J,[],0,0,0,["g",M(R$)],PZ,"ClassCastException",2,Bi,[],1,[0,0,0],0,0,By,0,Bd,[],0,0,()=>Ct(),["cY",S(Nn),"L",M(NS),"dQ",O(OM)],Hs,0,J,[],0,0,0,0,ND,0,J,[],1,0,0,0,CJ,"StringIndexOutOfBoundsException",2,Bo,[],1,[0,0,0],0,0,OL,0,J,[],17,0,0,0,IN,0,By,[],0,0,0,["cY",S(LP),"L",M(Ra),"dQ",O(R5)],IK,0,By,[],0,0,0,["cY",S(QI),"L",M(P0)],FQ,0,By,[],0,0,0,["cY",S(O$),"L",M(MD)],Gc,0,By,[],0,0,0,["cY",S(L_),"L",M(KG),"dQ",O(Or)],Cz,0,By,[],0,0,0,["cY",S(Nu),"L",M(PN)],Bn,0,Bd,[],1024,0,0,["cY",S(QV),"dq",M(LB),
"dQ",O(PS)],Gp,0,Bn,[],0,0,0,["dr",Bq(Kl),"cU",S(Ls),"du",Bz(Qp),"L",M(NU),"dQ",O(L3)],Bj,0,Bd,[],0,0,0,["cY",S(MY),"bY",O(Oh),"L",M(P9),"ck",O(RY),"dQ",O(Qe),"bP",M(Qk)],D7,0,Bj,[],0,0,0,["cY",S(NF),"L",M(J6),"dQ",O(Q9)],BZ,0,D7,[],0,0,0,["cY",S(RX),"bY",O(Qm),"L",M(LY)],F4,0,BZ,[],0,0,0,["cY",S(N3),"dQ",O(M2),"L",M(Ps)],Gx,0,BZ,[],0,0,0,["cY",S(Oz),"dQ",O(LS),"L",M(MH)],HK,0,BZ,[],0,0,0,["cY",S(Qb),"dQ",O(RL),"L",M(M9)],Hb,0,BZ,[],0,0,0,["cY",S(KH),"dQ",O(RD),"L",M(Pb)],CM,0,Bj,[],0,0,0,["cY",S(Lm),"cU",S(Kh),
"du",Bz(Sd),"ck",O(Rz),"c0",M(Om),"bP",M(PG)],En,0,J,[],1537,0,0,0,Cs,0,J,[En],1025,0,0,["bR",Bq(NI)],D2,0,Cs,[CS,B$],1,0,0,0,G_,0,J,[En],1537,0,0,0,Hf,0,D2,[G_],1,0,0,["bR",Bq(NW)],HF,0,J,[],17,0,()=>DA(),0,BK,0,Bs,[],1,0,0,0,L$,0,J,[],17,0,0,0,DJ,"ArrayStoreException",2,Bi,[],1,[0,0,0],0,0,C8,0,J,[],1024,0,0,0,R,0,C8,[],1024,0,()=>CK(),["dB",M(OG),"dz",M(MJ),"dU",M(Lo),"cJ",M(Pp)],Et,"MissingResourceException",1,Bi,[],1,[0,0,0],0,0,Fg,0,R,[],0,0,0,["dA",O(BH),"dB",M(BD),"dz",M(K6),"dU",M(Mp),"g",M(Mi),"cJ",
M(LE)],B6,0,Bd,[],1024,0,0,["ck",O(JM),"dQ",O(Ng),"bP",M(K5)],BF,0,B6,[],0,0,0,["cY",S(KJ),"L",M(Qf)],Ck,0,BF,[],0,0,0,["cY",S(Nv),"L",M(OK)],BG,0,B6,[],0,0,0,["cY",S(Ll),"L",M(OP)],Ci,0,BF,[],0,0,0,["cY",S(Kq),"bY",O(R1)],GW,0,BF,[],0,0,0,["cY",S(Qy),"cU",S(L9)],IF,0,Cs,[],0,0,0,0,EG,0,J,[],16,0,()=>Eb(),0,U,0,J,[],1024,0,0,0,Gt,0,C8,[CS],0,0,0,["g",M(EE)],HT,0,Bd,[],0,0,0,["cY",S(Qu),"L",M(N0),"dQ",O(Oe)],EN,0,J,[CS,B$],1,0,0,0,Ga,0,Bj,[],0,0,0,["L",M(OA)],Ex,0,Bj,[],0,0,0,["cY",S(Nh),"bY",O(NC),"L",M(Pl),
"dQ",O(Pr),"ck",O(OE)],BO,0,Bj,[],0,0,0,["cY",S(KK),"L",M(O9),"dA",O(L6),"ck",O(M1),"bY",O(Lc),"dQ",O(Lh)]]);
Cu([Em,0,BO,[],0,0,0,["dA",O(PT),"L",M(P6)],It,0,Bn,[],0,0,0,["dr",Bq(Qz),"L",M(OT)],B2,0,Bn,[],0,0,0,["dr",Bq(Eg),"L",M(QY),"ck",O(Oj)],Fa,0,Bj,[],0,0,0,["bY",O(Ko),"L",M(RH),"cY",S(Kp),"ck",O(O5),"dQ",O(MG)],B8,0,Bn,[],0,0,0,["dq",M(P$),"dr",Bq(Nk),"cU",S(KE),"du",Bz(Ql),"L",M(M8),"ck",O(Mg)],HJ,0,Bn,[],0,0,0,["dr",Bq(J1),"L",M(NA)],F7,0,Bn,[],0,0,0,["dr",Bq(K9),"L",M(Of)],Cg,0,Bj,[],0,0,0,["bY",O(PF),"cY",S(NE),"L",M(Nb),"ck",O(QA),"dQ",O(Qo)],H9,0,Cg,[],0,0,0,0,Hy,0,Cg,[],0,0,0,0,JF,0,BG,[],0,0,0,["cY",
S(QU)],FG,0,BG,[],0,0,0,["cY",S(LD)],CN,0,BG,[],0,0,0,["cY",S(KU),"bY",O(NM)],Jh,0,CN,[],0,0,0,["cY",S(Qd),"bY",O(MB)],Cf,0,BG,[],0,0,0,["cY",S(Rw),"L",M(N4)],GG,0,Cf,[],0,0,0,["cY",S(OQ)],Iv,0,BG,[],0,0,0,["cY",S(P8)],G8,0,CN,[],0,0,0,["cY",S(Pu)],IP,0,Cf,[],0,0,0,["cY",S(Mk)],Iw,0,B6,[],0,0,0,["cY",S(Q5),"cU",S(Lr),"L",M(PD)],H$,0,B6,[],0,0,0,["cY",S(Oo),"cU",S(KC),"L",M(R0)],Dd,0,J,[],1024,0,0,0,JH,0,BF,[],0,0,0,["cY",S(Mu)],GN,0,Ci,[],0,0,0,["cY",S(Lt)],IX,0,Ck,[],0,0,0,["cY",S(JR)],G4,0,BF,[],0,0,0,["cY",
S(N8)],HG,0,Ci,[],0,0,0,["cY",S(MR)],HL,0,Ck,[],0,0,0,["cY",S(Ks)],EU,0,Bd,[],16,0,0,["cY",S(RK),"dQ",O(Ph),"L",M(Lx)],FM,0,Bd,[],0,0,0,["cY",S(Ox),"dQ",O(OW),"L",M(Rp)],H1,0,Bd,[],0,0,0,["cY",S(PW),"dQ",O(Ri),"L",M(NL)],EO,0,Bd,[],16,0,0,["cY",S(P3),"dQ",O(Qw),"L",M(KD)],Hi,0,Bd,[],0,0,0,["cY",S(Nq),"dQ",O(JX),"L",M(MX)],GZ,0,Bd,[],0,0,0,["cY",S(Qg),"dQ",O(Mt),"L",M(M_)],G9,0,Bj,[],0,0,0,["cY",S(Qi),"L",M(Sb),"bY",O(O8),"cm",M(MQ),"dQ",O(O6)],IH,0,Bj,[],16,0,0,["cY",S(Nd),"L",M(QR),"bY",O(RW),"cm",M(JI),"dQ",
O(QT)],GO,0,Bd,[],16,0,0,["cY",S(QN),"dQ",O(Mf),"L",M(JN)],Gg,0,Bd,[],0,0,0,["cY",S(Nm),"dQ",O(LU),"L",M(LV)],FZ,0,Bd,[],0,0,0,["cY",S(O2),"dQ",O(QD),"L",M(My)],CA,0,Bj,[],0,0,0,["cY",S(MF),"bY",O(Qj),"L",M(L0),"dQ",O(Q_)],G1,0,CA,[],0,0,0,["cY",S(QS),"cU",S(MW),"du",Bz(LW),"ck",O(Lj),"L",M(NG)],HE,0,CA,[],0,0,0,["cY",S(Ka),"L",M(OX)],IA,0,Bn,[],0,0,0,["dr",Bq(RM),"cU",S(Ok),"du",Bz(Mq),"L",M(NP),"ck",O(NJ)],FV,0,Bn,[],0,0,0,["dr",Bq(Kk),"L",M(Oi)],GQ,0,Bn,[],0,0,0,["dr",Bq(JV),"L",M(Mn)],IU,0,J,[DQ],1537,0,
0,0,El,0,CU,[IU],1025,0,0,0,Ey,0,El,[],1024,0,0,0,FF,0,Ey,[],0,0,0,0,E$,0,Cs,[],1024,0,0,["bR",Bq(KV)]]);
Cu([FC,0,E$,[],0,0,0,0,EI,0,C6,[EV],1024,0,0,0,FD,0,EI,[],0,0,0,0,D_,0,J,[],1537,0,0,0,FA,0,J,[D_],0,0,0,0,Ju,0,J,[D_],1537,0,0,0,FB,0,J,[Ju],0,0,0,0,Jj,0,J,[E7],1,0,0,0,RQ,0,J,[],16,0,0,0,Pt,0,J,[],17,0,0,0,FR,0,Bn,[],0,0,0,["dr",Bq(J2),"L",M(Rk)],Dn,0,Bj,[],0,0,0,["bY",O(N2),"cY",S(Qx),"cU",S(NB),"du",Bz(R9),"L",M(LF),"ck",O(LN),"dQ",O(LZ)],DL,0,Bj,[],0,0,0,["bY",O(PK),"cY",S(KI),"cU",S(OU),"du",Bz(JS),"L",M(Qc),"ck",O(RO),"dQ",O(Pm)],B7,0,Bn,[],0,0,0,["dr",Bq(Rb),"cU",S(Ml),"du",Bz(PH),"L",M(NQ),"ck",O(QE)],Jn,
0,Dd,[],0,0,0,["d7",O(P4),"d_",Bq(QW)],Jo,0,Dd,[],0,0,0,["d7",O(KB),"d_",Bq(Pe)],IL,0,J,[],0,0,0,0,Jg,0,J,[],1,0,0,0,Np,0,J,[],17,0,0,0,Dj,0,U,[],0,0,0,["dT",M(HM)],Ed,0,U,[],0,0,0,["dT",M(FP)],IB,0,U,[],0,0,0,["dT",M(R3)],Fy,0,U,[],0,0,0,["dT",M(L1)],FI,0,U,[],0,0,0,["dT",M(KO)],Ee,0,U,[],0,0,0,["dT",M(IW)],D1,0,Ee,[],0,0,0,["dT",M(GM)],Jq,0,U,[],0,0,0,["dT",M(Ny)],Ef,0,D1,[],0,0,0,["dT",M(FN)],Ix,0,Ef,[],0,0,0,["dT",M(K_)],Jv,0,U,[],0,0,0,["dT",M(Ru)],JE,0,U,[],0,0,0,["dT",M(KZ)],Jc,0,U,[],0,0,0,["dT",M(Po)],FS,
0,U,[],0,0,0,["dT",M(MK)],JG,0,U,[],0,0,0,["dT",M(Kt)],IM,0,U,[],0,0,0,["dT",M(Pa)],H4,0,U,[],0,0,0,["dT",M(Rj)],Gw,0,U,[],0,0,0,["dT",M(Rr)],Ht,0,U,[],0,0,0,["dT",M(JY)],Gz,0,U,[],0,0,0,["dT",M(O4)],I5,0,U,[],0,0,0,["dT",M(J4)],Jt,0,U,[],0,0,0,["dT",M(Q3)],Gk,0,U,[],0,0,0,["dT",M(Ke)],Fz,0,U,[],0,0,0,["dT",M(MA)],Ir,0,U,[],0,0,0,["dT",M(Q$)],Jm,0,U,[],0,0,0,["dT",M(MM)],Hz,0,U,[],0,0,0,["dT",M(PP)],Gj,0,U,[],0,0,0,["dT",M(Mm)],Jz,0,U,[],0,0,0,["dT",M(RI)],DE,0,U,[],0,0,0,["dT",M(Jx)],GH,0,DE,[],0,0,0,["dT",
M(LR)]]);
Cu([ID,0,Dj,[],0,0,0,["dT",M(N7)],F_,0,Ed,[],0,0,0,["dT",M(Nc)],Jk,0,U,[],0,0,0,["dT",M(R6)],F2,0,U,[],0,0,0,["dT",M(Ow)],E0,0,U,[],0,0,0,["dT",M(LL)],E_,0,U,[],0,0,0,["dT",M(J7)],JD,"NegativeArraySizeException",2,Bi,[],1,[0,0,0],0,0,Ma,0,J,[],0,0,0,0,F0,0,J,[],1,0,0,0,DC,0,J,[],1,0,0,0,EA,"UnsupportedOperationException",2,Bi,[],1,[0,0,0],0,0,GT,0,R,[],0,0,0,["dA",O(K4)],GP,0,R,[],0,0,0,["dA",O(Lk)],Id,0,R,[],0,0,0,["dA",O(Rv),"g",M(MS)],Ij,0,R,[],0,0,0,["dA",O(Rh)],Ih,0,R,[],0,0,0,["dA",O(J0)],Ii,0,R,[],0,
0,0,["dA",O(Kj)],Im,0,R,[],0,0,0,["dA",O(J5)],Io,0,R,[],0,0,0,["dA",O(JJ)],Ik,0,R,[],0,0,0,["dA",O(M0)],Il,0,R,[],0,0,0,["dA",O(Km)],Ip,0,R,[],0,0,0,["dA",O(M3)],Iq,0,R,[],0,0,0,["dA",O(Q4)],Ic,0,R,[],0,0,0,["dA",O(R_)],I1,0,R,[],0,0,0,["dA",O(M$)],Ia,0,R,[],0,0,0,["dA",O(Q2)],Ib,0,R,[],0,0,0,["dA",O(Mc)],Ig,0,R,[],0,0,0,["dA",O(P1)],H_,0,R,[],0,0,0,["dA",O(Mb)],Ie,0,R,[],0,0,0,["dA",O(OO)],If,0,R,[],0,0,0,["dA",O(Pj)],FH,0,J,[],1537,0,0,0,Fd,0,J,[FH,CS],0,0,0,0,Eq,0,Fd,[],0,0,0,0,DG,0,Eq,[],16,0,0,0,GI,"IllegalStateException",
2,Bi,[],1,[0,0,0],0,0,GD,0,CM,[],0,0,0,["cU",S(LT),"du",Bz(Rs),"c0",M(Qv)],Gq,0,R,[],0,0,0,["dA",O(Nr)],F9,0,R,[],0,0,0,["dA",O(NR)],I9,0,R,[],0,0,0,["dA",O(MP)],I8,0,R,[],0,0,0,["dA",O(LJ)],Hn,0,R,[],0,0,0,["dA",O(Py)],IQ,0,R,[],0,0,0,["dA",O(Nw)],HN,0,R,[],0,0,0,["dA",O(NN)],Hq,0,R,[],0,0,0,["dA",O(Rx)],F3,0,R,[],0,0,0,["dA",O(QJ)],F8,0,R,[],0,0,0,["dA",O(R2)],Hv,0,R,[],0,0,0,["dA",O(On)],Jp,0,R,[],0,0,0,["dA",O(KQ)],JA,0,R,[],0,0,0,["dA",O(Qh)]]);
Cu([Hr,0,R,[],0,0,0,["dA",O(M4)],FO,0,R,[],0,0,0,["dA",O(PQ)],Gn,0,R,[],0,0,0,["dA",O(Rt)],D0,0,R,[],0,0,0,["dA",O(NX)],IT,0,D0,[],0,0,0,["dA",O(Le)],He,0,J,[D_],0,0,0,0,Os,0,J,[],0,0,0,0,GY,"ConcurrentModificationException",1,Bi,[],1,[0,0,0],0,0]);
let Pn=CG(HC),Pf=CG(JO),Iu=CG(Gs);
K$(["0","<java_object>@","null","String is null","String is empty","String contains invalid digits: ","String contains digits out of radix ",": ","The value is too big for integer type","The value is too big for int type: ","Illegal radix: ","[L","[]","","compile-error","TeaVM rejected the pattern.",", ","PatternSyntaxException","limit-error","CaptureGroupLimit","The pattern exceeds the 1,000 capture-group execution limit.","1","ok","replacement-error","execution-error","TeaVM Matcher rejected the replacement.",
"TeaVM failed while matching.","IllegalArgumentException","Result limits must be positive.","bridge-error","NullPointerException","Pattern and subject must not be null.","Error","(?<=a)(b)\\1","abb","<$1>","a<b>","Patter is null","\\Q","\\E","\\\\E\\Q","fSet","Name capturing group should start with letter","Is","In","Either src or dest is null","NonCapFSet","AheadFSet","BehindFSet","AtomicFSet","FinalSet","<Empty set>","JointSet","NonCapJointSet","PosLookaheadJointSet","NegLookaheadJointSet","PosBehindJointSet",
"NegBehindJointSet","<Quant>","<GroupQuant>","Lower","Upper","ASCII","Alpha","Digit","Alnum","Punct","Graph","Print","Blank","Cntrl","XDigit","javaLowerCase","javaUpperCase","javaWhitespace","javaMirrored","javaDefined","javaDigit","javaIdentifierIgnorable","javaISOControl","javaJavaIdentifierPart","javaJavaIdentifierStart","javaLetter","javaLetterOrDigit","javaSpaceChar","javaTitleCase","javaUnicodeIdentifierPart","javaUnicodeIdentifierStart","Space","w","W","s","S","d","D","BasicLatin","Latin-1Supplement",
"LatinExtended-A","LatinExtended-B","IPAExtensions","SpacingModifierLetters","CombiningDiacriticalMarks","Greek","Cyrillic","CyrillicSupplement","Armenian","Hebrew","Arabic","Syriac","ArabicSupplement","Thaana","Devanagari","Bengali","Gurmukhi","Gujarati","Oriya","Tamil","Telugu","Kannada","Malayalam","Sinhala","Thai","Lao","Tibetan","Myanmar","Georgian","HangulJamo","Ethiopic","EthiopicSupplement","Cherokee","UnifiedCanadianAboriginalSyllabics","Ogham","Runic","Tagalog","Hanunoo","Buhid","Tagbanwa","Khmer",
"Mongolian","Limbu","TaiLe","NewTaiLue","KhmerSymbols","Buginese","PhoneticExtensions","PhoneticExtensionsSupplement","CombiningDiacriticalMarksSupplement","LatinExtendedAdditional","GreekExtended","GeneralPunctuation","SuperscriptsandSubscripts","CurrencySymbols","CombiningMarksforSymbols","LetterlikeSymbols","NumberForms","Arrows","MathematicalOperators","MiscellaneousTechnical","ControlPictures","OpticalCharacterRecognition","EnclosedAlphanumerics","BoxDrawing","BlockElements","GeometricShapes","MiscellaneousSymbols",
"Dingbats","MiscellaneousMathematicalSymbols-A","SupplementalArrows-A","BraillePatterns","SupplementalArrows-B","MiscellaneousMathematicalSymbols-B","SupplementalMathematicalOperators","MiscellaneousSymbolsandArrows","Glagolitic","Coptic","GeorgianSupplement","Tifinagh","EthiopicExtended","SupplementalPunctuation","CJKRadicalsSupplement","KangxiRadicals","IdeographicDescriptionCharacters","CJKSymbolsandPunctuation","Hiragana","Katakana","Bopomofo","HangulCompatibilityJamo","Kanbun","BopomofoExtended","CJKStrokes",
"KatakanaPhoneticExtensions","EnclosedCJKLettersandMonths","CJKCompatibility","CJKUnifiedIdeographsExtensionA","YijingHexagramSymbols","CJKUnifiedIdeographs","YiSyllables","YiRadicals","ModifierToneLetters","SylotiNagri","HangulSyllables","HighSurrogates","HighPrivateUseSurrogates","LowSurrogates","PrivateUseArea","CJKCompatibilityIdeographs","AlphabeticPresentationForms","ArabicPresentationForms-A","VariationSelectors","VerticalForms","CombiningHalfMarks","CJKCompatibilityForms","SmallFormVariants","ArabicPresentationForms-B",
"HalfwidthandFullwidthForms","all","Specials","Cn","IsL","Lu","Ll","Lt","Lm","Lo","IsM","Mn","Me","Mc","N","Nd","Nl","No","IsZ","Zs","Zl","Zp","IsC","Cc","Cf","Co","Cs","IsP","Pd","Ps","Pe","Pc","Po","IsS","Sm","Sc","Sk","So","Pi","Pf","posFSet"," ","^ ","range:","CompositeRangeSet: <nonsurrogate> "," <surrogate> ","UCI range:","decomposed Hangul syllable:","UCI ","CI ","decomposed char:","<DotAllQuant>","<DotQuant>","<SOL>","WordBoundary","PreviousMatch","<EOL>","EOI","^","DotAll",".","<Unix MultiLine $>",
"<MultiLine $>","CI back reference: ","back reference: ","UCI back reference: ","sequence: ","UCI sequence: ","CI sequence: "]);
Br.prototype.toString=function(){return D$(this);};
Br.prototype.valueOf=Br.prototype.toString;J.prototype.toString=function(){return D$(Is(this));};
J.prototype.__teavm_class__=function(){return RB(this);};
let C=JT(O0);
C.javaException=N_;
let Sl=Symbol('jsoClass');
(()=>{let c;})();
export{C as main,D as bridgeAbiVersion,E as engineIdentity,F as selfTest,G as execute,H as replace};

View File

@@ -0,0 +1,277 @@
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations, which became
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see https://opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
Python software and documentation are licensed under the
Python Software Foundation License Version 2.
Starting with Python 3.8.6, examples, recipes, and other code in
the documentation are dual licensed under the PSF License Version 2
and the Zero-Clause BSD license.
Some software incorporated into Python is under different licenses.
The licenses are listed with code falling under that license.
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved"
are retained in Python alone or in any derivative version prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
----------------------------------------------------------------------
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -0,0 +1,8 @@
b0e25a78cffb43f4d92de8b61ccfa1f1f98ecbc22330b54b5251e7b6ba010231 LICENSE.cpython.txt
1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5 LICENSE.pyodide.txt
00267477b691c9f855375a6930f13a3a87fa6580d8fab949bc6cce0fb1d0a821 engine-metadata.json
c963d22858f6bcb8f41586a2142f03905ab370c88ea22a86a2736e95fac2a8f3 pyodide-lock.json
1a9775427ef6e8abaa7db88ece0515422d1886915ae5c9093776410c865dfd8d pyodide.asm.mjs
e7f8fac36f8bf11085309cbc5c829b3ec3057c18bf1d73b05a6741612d63cdbf pyodide.asm.wasm
640ac750bf7cf42ee58b524faa09f803732d2237753cd630d6878edda8a107bf pyodide.mjs
444c770dfd75a32097fc0a7d5c1413fd3140601f49c3a1f2e9af0376fcd124b4 python_stdlib.zip

View File

@@ -0,0 +1,99 @@
{
"schemaVersion": 1,
"engine": "python",
"engineVersion": "CPython 3.14.2 re",
"runtime": "Pyodide 314.0.3",
"status": "production-runtime",
"bridge": {
"abiVersion": 1,
"sourceFile": {
"path": "engines/python/bridge.py",
"sha256": "87cf2690368eab306eb6765d5e516f23c73d987711a2be704576b1c36cf8c6c0",
"bytes": 8509
},
"userValuesAreFunctionArguments": true,
"regexModule": "re",
"supportModules": {
"serialization": "json",
"runtimeIdentity": "sys"
},
"maximumPatternUtf16": 65536,
"maximumSubjectBytes": 16777216,
"maximumReplacementUtf16": 65536,
"maximumOutputBytes": 67108864,
"maximumMatches": 10000,
"maximumCaptureRows": 100000,
"maximumCaptureGroups": 1000,
"maximumNativeExpansionCodePoints": 16777216
},
"source": {
"pyodide": {
"repository": "https://github.com/pyodide/pyodide.git",
"tag": "314.0.3",
"commitSha1": "ac57031be7564f864d061cb37c5c152e59f83ad4",
"license": "MPL-2.0",
"npmPackage": "pyodide@314.0.3",
"npmResolved": "https://registry.npmjs.org/pyodide/-/pyodide-314.0.3.tgz",
"npmIntegrity": "sha512-sK40My6m8tmBUYtYH9au9rXUeh9x0wfahtHdOlGmJxZDsKBGKtP6KznyFB2+u/klbQTdDionR0uaVd176zVQzQ=="
},
"cpython": {
"repository": "https://github.com/python/cpython.git",
"tag": "v3.14.2",
"tagObjectSha1": "a1d0069daf8e85b25a0c3f96abc43182be6d429e",
"commitSha1": "df793163d5821791d4e7caf88885a2c11a107986",
"license": "Python-2.0"
}
},
"toolchain": {
"abiVersion": "2026_0",
"architecture": "wasm32",
"emscriptenVersion": "5.0.3",
"lockPythonVersion": "3.14.0",
"executablePythonVersion": "3.14.2",
"threads": false
},
"packaging": {
"loaderTransformation": "Removed only the upstream sourceMappingURL trailer; executable JavaScript is unchanged.",
"upstreamLoaderSha256": "5cfc46f5dcbaf2a16f26e2363f441873eb424762609cc03db00d6a2ace4d00e5",
"packedLoaderSha256": "640ac750bf7cf42ee58b524faa09f803732d2237753cd630d6878edda8a107bf",
"externalPackagesIncluded": false,
"lockPythonVersionNote": "The upstream lock's 3.14.0 field identifies its CPython 3.14 ABI baseline; the executable runtime self-identifies as 3.14.2."
},
"files": [
{
"path": "LICENSE.cpython.txt",
"sha256": "b0e25a78cffb43f4d92de8b61ccfa1f1f98ecbc22330b54b5251e7b6ba010231",
"bytes": 13804
},
{
"path": "LICENSE.pyodide.txt",
"sha256": "1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5",
"bytes": 16725
},
{
"path": "pyodide-lock.json",
"sha256": "c963d22858f6bcb8f41586a2142f03905ab370c88ea22a86a2736e95fac2a8f3",
"bytes": 113804
},
{
"path": "pyodide.asm.mjs",
"sha256": "1a9775427ef6e8abaa7db88ece0515422d1886915ae5c9093776410c865dfd8d",
"bytes": 1249447
},
{
"path": "pyodide.asm.wasm",
"sha256": "e7f8fac36f8bf11085309cbc5c829b3ec3057c18bf1d73b05a6741612d63cdbf",
"bytes": 9596462
},
{
"path": "pyodide.mjs",
"sha256": "640ac750bf7cf42ee58b524faa09f803732d2237753cd630d6878edda8a107bf",
"bytes": 17843
},
{
"path": "python_stdlib.zip",
"sha256": "444c770dfd75a32097fc0a7d5c1413fd3140601f49c3a1f2e9af0376fcd124b4",
"bytes": 2545106
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -3,7 +3,7 @@
"schemaVersion": 1, "schemaVersion": 1,
"id": "de.add-ideas.regex-tools", "id": "de.add-ideas.regex-tools",
"name": "Regex Tools", "name": "Regex Tools",
"version": "0.2.0", "version": "0.3.0",
"description": "Develop, explain, test and apply regular expressions locally in the browser.", "description": "Develop, explain, test and apply regular expressions locally in the browser.",
"entry": "./", "entry": "./",
"icon": "./favicon.svg", "icon": "./favicon.svg",
@@ -36,5 +36,15 @@
"label": "Source", "label": "Source",
"url": "https://git.add-ideas.de/zemion/regex-tools" "url": "https://git.add-ideas.de/zemion/regex-tools"
} }
],
"assets": [
"./engines/pcre2/pcre2.mjs",
"./engines/pcre2/pcre2.wasm",
"./engines/python/pyodide.mjs",
"./engines/python/pyodide.asm.mjs",
"./engines/python/pyodide.asm.wasm",
"./engines/python/pyodide-lock.json",
"./engines/python/python_stdlib.zip",
"./engines/java/java-regex.mjs"
] ]
} }

View File

@@ -40,15 +40,17 @@ const entries = (await readdir(engineDirectory)).sort();
if ( if (
typeof packageJson.version !== "string" || typeof packageJson.version !== "string" ||
entries.length !== 2 || entries.length !== 4 ||
entries[0] !== "README.md" || entries[0] !== "README.md" ||
entries[1] !== "pcre2" entries[1] !== "java" ||
entries[2] !== "pcre2" ||
entries[3] !== "python"
) { ) {
throw new Error( throw new Error(
"The production build requires the documented PCRE2 runtime pack.", "The production build requires the documented Java, PCRE2 and Python runtime packs.",
); );
} }
console.log( console.log(
"The checked-in PCRE2 runtime pack is present; no release-time download or engine compilation is needed.", "The checked-in Java, PCRE2 and Python runtime packs are present; no release-time download or engine compilation is needed.",
); );

View File

@@ -0,0 +1,16 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { installJavaPack } from "./java-engine-pack.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const arguments_ = process.argv.slice(2);
if (arguments_.length > 1) {
throw new Error(
"Usage: node scripts/install-java-engine.mjs [.engine-build/java]",
);
}
const source = path.resolve(root, arguments_[0] ?? ".engine-build/java");
const result = await installJavaPack(source, root);
console.log(
`Installed verified ${result.metadata.semanticIdentity} assets at ${result.output}.`,
);

View File

@@ -0,0 +1,16 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { installPythonPack } from "./python-engine-pack.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const arguments_ = process.argv.slice(2);
if (arguments_.length > 1) {
throw new Error(
"Usage: node scripts/install-python-engine.mjs [.engine-build/python]",
);
}
const source = path.resolve(root, arguments_[0] ?? ".engine-build/python");
const result = await installPythonPack(source, root);
console.log(
`Installed verified ${result.metadata.runtime} / ${result.metadata.engineVersion} assets at ${result.output}.`,
);

View File

@@ -0,0 +1,603 @@
import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import {
access,
constants as fsConstants,
copyFile,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
realpath,
rename,
rm,
stat,
writeFile,
} from "node:fs/promises";
import { homedir } from "node:os";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
export const JAVA_LOCK = Object.freeze({
bridgeAbi: 1,
engineIdentity: "TeaVM java.util.regex 0.15.0",
engineVersion: "0.15.0",
sourceDateEpoch: 1_780_759_630,
maximumPatternUtf16: 65_536,
maximumSubjectBytes: 16_777_216,
maximumReplacementUtf16: 65_536,
maximumMatches: 10_000,
maximumCaptureRows: 100_000,
maximumCaptureGroups: 1_000,
maximumOutputBytes: 67_108_864,
source: Object.freeze({
repository: "https://github.com/konsoletyper/teavm.git",
tag: "0.15.0",
commit: "ee91b03e616c4b45401cd11fb0cd7eb0daf6649b",
tree: "1cdc5c332d5809828c8952f96d73f9e7504a0cec",
license: "Apache-2.0",
licenseSha256:
"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
noticeSha256:
"6c2b3372a6beee7fbaad482248ac0f5ad2b099d92ff2a528c04261cc9c0636f4",
}),
toolchain: Object.freeze({
mavenVersion: "3.9.15",
javaVersion: "25.0.3",
artifacts: Object.freeze([
Object.freeze({
coordinate: "org.teavm:teavm-classlib:0.15.0",
relativePath:
"org/teavm/teavm-classlib/0.15.0/teavm-classlib-0.15.0.jar",
sha256:
"7d805be4670a892a34c89d9d750a8afd65e2f8201b7b3a10343634ea19d1c410",
}),
Object.freeze({
coordinate: "org.teavm:teavm-jso:0.15.0",
relativePath: "org/teavm/teavm-jso/0.15.0/teavm-jso-0.15.0.jar",
sha256:
"58b337b93924106bf819c45890c7c231a0c740145afe77795f1e0f099f621d71",
}),
Object.freeze({
coordinate: "org.teavm:teavm-maven-plugin:0.15.0",
relativePath:
"org/teavm/teavm-maven-plugin/0.15.0/teavm-maven-plugin-0.15.0.jar",
sha256:
"1133e16e45f1fd3cb93129fd96d142e1f5ef3608891d83b2273442d5446a3879",
}),
]),
}),
});
const PACK_FILES = Object.freeze([
"LICENSE.txt",
"NOTICE.txt",
"SHA256SUMS",
"engine-metadata.json",
"java-regex.mjs",
]);
const CHECKSUM_FILES = Object.freeze([
"LICENSE.txt",
"NOTICE.txt",
"engine-metadata.json",
"java-regex.mjs",
]);
const BRIDGE_SOURCE_FILES = Object.freeze([
"engines/java/pom.xml",
"engines/java/README.md",
"engines/java/LICENSE-TeaVM.txt",
"engines/java/NOTICE-TeaVM.txt",
"engines/java/src/main/java/de/addideas/regextools/javaengine/RegexBridge.java",
]);
const EXPORTED_FUNCTIONS = Object.freeze([
"bridgeAbiVersion",
"engineIdentity",
"execute",
"replace",
"selfTest",
]);
function sha256(data) {
return createHash("sha256").update(data).digest("hex");
}
async function sha256File(file) {
return sha256(await readFile(file));
}
async function assertRealDirectory(candidate, label) {
const details = await lstat(candidate).catch(() => null);
if (!details?.isDirectory() || details.isSymbolicLink()) {
throw new Error(`${label} must be a real directory: ${candidate}`);
}
return realpath(candidate);
}
async function assertRegularFile(candidate, label) {
const details = await lstat(candidate).catch(() => null);
if (!details?.isFile() || details.isSymbolicLink()) {
throw new Error(
`${label} must be a regular, non-symlink file: ${candidate}`,
);
}
return realpath(candidate);
}
async function resolveExecutable(name, environment = process.env) {
for (const directory of (environment.PATH ?? "").split(path.delimiter)) {
if (!directory) continue;
const candidate = path.join(directory, name);
try {
await access(candidate, fsConstants.X_OK);
if ((await stat(candidate)).isFile()) return realpath(candidate);
} catch {
// Continue through PATH.
}
}
throw new Error(`${name} is required on PATH for the TeaVM Java build.`);
}
function commandLabel(command, arguments_) {
return [command, ...arguments_]
.map((part) =>
/^[A-Za-z0-9_./:=,+-]+$/u.test(part) ? part : JSON.stringify(part),
)
.join(" ");
}
async function run(command, arguments_, options = {}) {
const child = spawn(command, arguments_, {
cwd: options.cwd,
env: options.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stdout = [];
const stderr = [];
child.stdout.on("data", (chunk) => stdout.push(chunk));
child.stderr.on("data", (chunk) => stderr.push(chunk));
const result = await new Promise((resolve, reject) => {
child.once("error", reject);
child.once("close", (code, signal) => resolve({ code, signal }));
});
const output = Buffer.concat(stdout).toString("utf8");
const errorOutput = Buffer.concat(stderr).toString("utf8");
if (result.code !== 0) {
const detail = [output, errorOutput].filter(Boolean).join("\n").trim();
throw new Error(
`${commandLabel(command, arguments_)} failed with ${
result.signal ? `signal ${result.signal}` : `exit code ${result.code}`
}${detail ? `:\n${detail}` : "."}`,
);
}
return { stdout: output, stderr: errorOutput };
}
async function verifyToolchain() {
const maven = await resolveExecutable("mvn");
const java = await resolveExecutable("java");
const mavenVersion = await run(maven, ["--version"]);
const javaVersion = await run(java, ["-version"]);
if (
!mavenVersion.stdout.startsWith(
`Apache Maven ${JAVA_LOCK.toolchain.mavenVersion} `,
)
) {
throw new Error(
`The Java pack requires Maven ${JAVA_LOCK.toolchain.mavenVersion}.`,
);
}
const javaText = `${javaVersion.stdout}\n${javaVersion.stderr}`;
if (
!javaText.includes(`openjdk version "${JAVA_LOCK.toolchain.javaVersion}`)
) {
throw new Error(
`The Java pack requires OpenJDK ${JAVA_LOCK.toolchain.javaVersion}.`,
);
}
const repository = path.join(homedir(), ".m2", "repository");
for (const artifact of JAVA_LOCK.toolchain.artifacts) {
const file = await assertRegularFile(
path.join(repository, artifact.relativePath),
artifact.coordinate,
);
const actual = await sha256File(file);
if (actual !== artifact.sha256) {
throw new Error(
`${artifact.coordinate} SHA-256 mismatch: expected ${artifact.sha256}, got ${actual}.`,
);
}
}
return { maven };
}
function cleanBuildEnvironment() {
const environment = { ...process.env };
for (const variable of [
"CLASSPATH",
"JAVA_TOOL_OPTIONS",
"MAVEN_ARGS",
"MAVEN_OPTS",
"_JAVA_OPTIONS",
]) {
delete environment[variable];
}
environment.LANG = "C";
environment.LC_ALL = "C";
environment.SOURCE_DATE_EPOCH = String(JAVA_LOCK.sourceDateEpoch);
environment.TZ = "UTC";
return environment;
}
async function sourceFiles(root) {
return Promise.all(
BRIDGE_SOURCE_FILES.map(async (relativePath) => {
const file = await assertRegularFile(
path.join(root, relativePath),
`Java bridge source ${relativePath}`,
);
const contents = await readFile(file);
return {
path: relativePath,
sha256: sha256(contents),
bytes: contents.byteLength,
};
}),
);
}
async function fileMetadata(pack, name) {
const contents = await readFile(path.join(pack, name));
return { path: name, sha256: sha256(contents), bytes: contents.byteLength };
}
async function expectedMetadata(root, pack) {
return {
schemaVersion: 1,
engine: "java",
engineName: "TeaVM java.util.regex",
engineVersion: JAVA_LOCK.engineVersion,
semanticIdentity:
"TeaVM 0.15.0 classlib java.util.regex; Apache Harmony-derived; not OpenJDK",
status: "production-compatibility-runtime",
bridge: {
abiVersion: JAVA_LOCK.bridgeAbi,
maximumPatternUtf16: JAVA_LOCK.maximumPatternUtf16,
maximumSubjectBytes: JAVA_LOCK.maximumSubjectBytes,
maximumReplacementUtf16: JAVA_LOCK.maximumReplacementUtf16,
maximumMatches: JAVA_LOCK.maximumMatches,
maximumCaptureRows: JAVA_LOCK.maximumCaptureRows,
maximumCaptureGroups: JAVA_LOCK.maximumCaptureGroups,
maximumOutputBytes: JAVA_LOCK.maximumOutputBytes,
offsetUnit: "utf16",
exports: [...EXPORTED_FUNCTIONS],
sourceFiles: await sourceFiles(root),
},
source: {
repository: JAVA_LOCK.source.repository,
tag: JAVA_LOCK.source.tag,
commitSha1: JAVA_LOCK.source.commit,
treeSha1: JAVA_LOCK.source.tree,
license: JAVA_LOCK.source.license,
upstreamLicenseSha256: JAVA_LOCK.source.licenseSha256,
upstreamNoticeSha256: JAVA_LOCK.source.noticeSha256,
},
toolchain: {
mavenVersion: JAVA_LOCK.toolchain.mavenVersion,
javaVersion: JAVA_LOCK.toolchain.javaVersion,
teaVmArtifacts: JAVA_LOCK.toolchain.artifacts.map(
({ coordinate, sha256: digest }) => ({
coordinate,
sha256: digest,
}),
),
},
configuration: {
target: "JavaScript ES2015 module",
minified: true,
optimizationLevel: "ADVANCED",
sourceMaps: false,
filesystem: false,
supportedApplicationFlags: ["g", "i", "d", "m", "s", "u", "x", "U"],
unixLines: "d maps to TeaVM Pattern.UNIX_LINES.",
unicodeCharacterClassCompatibility:
"TeaVM 0.15.0 lacks OpenJDK UNICODE_CHARACTER_CLASS; U implies Unicode case folding only.",
replacement:
"TeaVM 0.15.0 Matcher appendReplacement/appendTail semantics: only single-digit $n references; ${name} is rejected; not OpenJDK Matcher parity.",
},
sourceDateEpoch: JAVA_LOCK.sourceDateEpoch,
files: await Promise.all(
["LICENSE.txt", "NOTICE.txt", "java-regex.mjs"].map((name) =>
fileMetadata(pack, name),
),
),
};
}
async function expectedChecksums(pack) {
const lines = await Promise.all(
CHECKSUM_FILES.map(
async (name) => `${await sha256File(path.join(pack, name))} ${name}`,
),
);
return `${lines.join("\n")}\n`;
}
function equalJson(left, right) {
return JSON.stringify(left) === JSON.stringify(right);
}
function readField(payload, state) {
const colon = payload.indexOf(":", state.cursor);
if (colon < 0) throw new Error("Malformed Java smoke-test payload.");
const length = Number.parseInt(payload.slice(state.cursor, colon), 10);
if (!Number.isSafeInteger(length) || length < 0) {
throw new Error("Malformed Java smoke-test field length.");
}
const start = colon + 1;
const end = start + length;
if (end > payload.length) {
throw new Error("Truncated Java smoke-test payload.");
}
state.cursor = end;
return payload.slice(start, end);
}
async function smokeJavaPack(pack) {
const moduleFile = path.join(pack, "java-regex.mjs");
const imported = await import(
`${pathToFileURL(moduleFile).href}?verify=${await sha256File(moduleFile)}`
);
for (const name of EXPORTED_FUNCTIONS) {
if (typeof imported[name] !== "function") {
throw new Error(`The Java module is missing export ${name}.`);
}
}
if (
imported.bridgeAbiVersion() !== JAVA_LOCK.bridgeAbi ||
imported.engineIdentity() !== JAVA_LOCK.engineIdentity ||
imported.selfTest() !== 0
) {
throw new Error("The TeaVM Java bridge identity or self-test failed.");
}
const execution = imported.execute("(?<=a)(b)\\1", "abb", 0, true, 10, 10);
const executionState = { cursor: 0 };
const executionFields = Array.from({ length: 9 }, () =>
readField(execution, executionState),
);
if (
executionState.cursor !== execution.length ||
JSON.stringify(executionFields) !==
JSON.stringify(["1", "ok", "1", "0", "1", "1", "3", "1", "2"])
) {
throw new Error("The TeaVM Java matching smoke test failed.");
}
const replacement = imported.replace("(a)", "a a", "<$1>", 0, true, 10, 10);
const replacementState = { cursor: 0 };
const replacementFields = [];
while (replacementState.cursor < replacement.length) {
replacementFields.push(readField(replacement, replacementState));
}
if (
replacementFields.at(-2) !== "0" ||
replacementFields.at(-1) !== "<a> <a>"
) {
throw new Error("The TeaVM Java replacement smoke test failed.");
}
}
export async function verifyJavaPack(packDirectory, root) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const pack = await assertRealDirectory(packDirectory, "Java engine pack");
const entries = (await readdir(pack, { withFileTypes: true })).sort(
(left, right) =>
left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
);
if (
entries.length !== PACK_FILES.length ||
entries.some(
(entry, index) =>
entry.name !== PACK_FILES[index] ||
!entry.isFile() ||
entry.isSymbolicLink(),
)
) {
throw new Error(
`The staged Java pack must contain only: ${PACK_FILES.join(", ")}.`,
);
}
if (
(await sha256File(path.join(pack, "LICENSE.txt"))) !==
JAVA_LOCK.source.licenseSha256
) {
throw new Error("The staged TeaVM licence does not match tag 0.15.0.");
}
const moduleText = await readFile(path.join(pack, "java-regex.mjs"), "utf8");
if (
moduleText.includes("sourceMappingURL") ||
moduleText.includes("/mnt/") ||
moduleText.includes("/home/") ||
!moduleText.includes("as bridgeAbiVersion") ||
!moduleText.includes("as engineIdentity") ||
!moduleText.includes("as execute") ||
!moduleText.includes("as replace") ||
!moduleText.includes("as selfTest")
) {
throw new Error(
"java-regex.mjs leaks a build path, has a source map, or lacks bridge exports.",
);
}
let metadata;
try {
metadata = JSON.parse(
await readFile(path.join(pack, "engine-metadata.json"), "utf8"),
);
} catch (error) {
throw new Error("Java engine-metadata.json is not valid JSON.", {
cause: error,
});
}
const expected = await expectedMetadata(repositoryRoot, pack);
if (!equalJson(metadata, expected)) {
throw new Error(
"The staged Java metadata does not match the pinned build contract.",
);
}
if (
(await readFile(path.join(pack, "SHA256SUMS"), "utf8")) !==
(await expectedChecksums(pack))
) {
throw new Error("The staged Java SHA256SUMS file is incorrect.");
}
await smokeJavaPack(pack);
return metadata;
}
async function replaceDirectory(stage, target, label) {
const targetDetails = await lstat(target).catch(() => null);
if (
targetDetails &&
(!targetDetails.isDirectory() || targetDetails.isSymbolicLink())
) {
throw new Error(`${label} must be a real directory.`);
}
if (!targetDetails) {
await rename(stage, target);
return;
}
const backup = `${target}.replaced-${process.pid}`;
await rename(target, backup);
try {
await rename(stage, target);
} catch (error) {
await rename(backup, target);
throw error;
}
await rm(backup, { recursive: true });
}
export async function buildJavaPack(root, outputDirectory) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const engine = await assertRealDirectory(
path.join(repositoryRoot, "engines", "java"),
"Java engine source",
);
const { maven } = await verifyToolchain();
const environment = cleanBuildEnvironment();
const buildArguments = [
"--batch-mode",
"--no-transfer-progress",
"--offline",
"clean",
"package",
];
await run(maven, buildArguments, { cwd: engine, env: environment });
const generated = await assertRegularFile(
path.join(engine, "target", "generated", "java-regex.mjs"),
"generated TeaVM module",
);
const firstBuild = await readFile(generated);
await run(maven, buildArguments, { cwd: engine, env: environment });
const secondBuild = await readFile(generated);
if (sha256(firstBuild) !== sha256(secondBuild)) {
throw new Error(
"Two clean TeaVM builds produced different Java module hashes.",
);
}
const output = path.resolve(
repositoryRoot,
outputDirectory ?? path.join(".engine-build", "java"),
);
await mkdir(path.dirname(output), { recursive: true });
const stage = await mkdtemp(path.join(path.dirname(output), ".java-build-"));
try {
await copyFile(
path.join(engine, "LICENSE-TeaVM.txt"),
path.join(stage, "LICENSE.txt"),
fsConstants.COPYFILE_EXCL,
);
await copyFile(
path.join(engine, "NOTICE-TeaVM.txt"),
path.join(stage, "NOTICE.txt"),
fsConstants.COPYFILE_EXCL,
);
await writeFile(path.join(stage, "java-regex.mjs"), secondBuild, {
flag: "wx",
});
const metadata = await expectedMetadata(repositoryRoot, stage);
await writeFile(
path.join(stage, "engine-metadata.json"),
`${JSON.stringify(metadata, null, 2)}\n`,
{ encoding: "utf8", flag: "wx" },
);
await writeFile(
path.join(stage, "SHA256SUMS"),
await expectedChecksums(stage),
{ encoding: "utf8", flag: "wx" },
);
await verifyJavaPack(stage, repositoryRoot);
await replaceDirectory(stage, output, "Java build output");
return {
output,
metadata,
moduleSha256: sha256(secondBuild),
moduleBytes: secondBuild.byteLength,
};
} finally {
await rm(stage, { recursive: true, force: true });
}
}
export async function installJavaPack(packDirectory, root) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const source = await assertRealDirectory(packDirectory, "verified Java pack");
const metadata = await verifyJavaPack(source, repositoryRoot);
const engineRoot = await assertRealDirectory(
path.join(repositoryRoot, "public", "engines"),
"public engine directory",
);
const target = path.join(engineRoot, "java");
const stage = await mkdtemp(path.join(engineRoot, ".java-install-"));
try {
for (const name of PACK_FILES) {
await copyFile(
path.join(source, name),
path.join(stage, name),
fsConstants.COPYFILE_EXCL,
);
}
await verifyJavaPack(stage, repositoryRoot);
await replaceDirectory(stage, target, "public/engines/java");
return { output: target, metadata };
} finally {
await rm(stage, { recursive: true, force: true });
}
}
const invokedFile = process.argv[1] ? path.resolve(process.argv[1]) : "";
if (invokedFile === fileURLToPath(import.meta.url)) {
if (process.argv.length > 3) {
throw new Error(
"Usage: node scripts/java-engine-pack.mjs [.engine-build/java]",
);
}
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const result = await buildJavaPack(root, process.argv[2]);
console.log(
`Built deterministic ${result.metadata.semanticIdentity} module: ${result.moduleSha256} (${result.moduleBytes.toLocaleString()} bytes).`,
);
}

View File

@@ -18,7 +18,7 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const zipEpoch = new Date(1980, 0, 1, 0, 0, 0); const zipEpoch = new Date(1980, 0, 1, 0, 0, 0);
const gplVersion3TextSha256 = const gplVersion3TextSha256 =
"fb981668c18a279e285fc4d83fba1e836cc84dd4daa73c9697d3cfd2d8aca6e0"; "fb981668c18a279e285fc4d83fba1e836cc84dd4daa73c9697d3cfd2d8aca6e0";
const expectedApplicationVersion = "0.2.0"; const expectedApplicationVersion = "0.3.0";
const requiredRootFiles = [ const requiredRootFiles = [
"LICENSE", "LICENSE",
"README.md", "README.md",
@@ -387,11 +387,25 @@ async function collectReleaseEntries() {
"SOURCE.md", "SOURCE.md",
"THIRD_PARTY_NOTICES.md", "THIRD_PARTY_NOTICES.md",
"engines/README.md", "engines/README.md",
"engines/java/LICENSE.txt",
"engines/java/NOTICE.txt",
"engines/java/SHA256SUMS",
"engines/java/engine-metadata.json",
"engines/java/java-regex.mjs",
"engines/pcre2/LICENSE.txt", "engines/pcre2/LICENSE.txt",
"engines/pcre2/SHA256SUMS", "engines/pcre2/SHA256SUMS",
"engines/pcre2/engine-metadata.json", "engines/pcre2/engine-metadata.json",
"engines/pcre2/pcre2.mjs", "engines/pcre2/pcre2.mjs",
"engines/pcre2/pcre2.wasm", "engines/pcre2/pcre2.wasm",
"engines/python/LICENSE.cpython.txt",
"engines/python/LICENSE.pyodide.txt",
"engines/python/SHA256SUMS",
"engines/python/engine-metadata.json",
"engines/python/pyodide-lock.json",
"engines/python/pyodide.asm.mjs",
"engines/python/pyodide.asm.wasm",
"engines/python/pyodide.mjs",
"engines/python/python_stdlib.zip",
"LICENSES/Emscripten-MIT.txt", "LICENSES/Emscripten-MIT.txt",
"LICENSES/PCRE2.txt", "LICENSES/PCRE2.txt",
"LICENSES/build/rolldown-1.1.5/LICENSE", "LICENSES/build/rolldown-1.1.5/LICENSE",

View File

@@ -0,0 +1,572 @@
import { createHash } from "node:crypto";
import {
constants as fsConstants,
copyFile,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
realpath,
rename,
rm,
writeFile,
} from "node:fs/promises";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
export const PYTHON_ENGINE_LOCK = Object.freeze({
bridgeAbi: 1,
pythonVersion: "3.14.2",
pyodideVersion: "314.0.3",
maximumPatternUtf16: 65_536,
maximumSubjectBytes: 16_777_216,
maximumReplacementUtf16: 65_536,
maximumOutputBytes: 67_108_864,
maximumMatches: 10_000,
maximumCaptureRows: 100_000,
maximumCaptureGroups: 1_000,
maximumNativeExpansionCodePoints: 16_777_216,
npm: Object.freeze({
package: "pyodide@314.0.3",
resolved: "https://registry.npmjs.org/pyodide/-/pyodide-314.0.3.tgz",
integrity:
"sha512-sK40My6m8tmBUYtYH9au9rXUeh9x0wfahtHdOlGmJxZDsKBGKtP6KznyFB2+u/klbQTdDionR0uaVd176zVQzQ==",
packageJsonSha256:
"0393eb01a0707cddc251f1a66090998773fe1aa3cfde66eedc96e83e43861845",
}),
pyodide: Object.freeze({
repository: "https://github.com/pyodide/pyodide.git",
tag: "314.0.3",
commit: "ac57031be7564f864d061cb37c5c152e59f83ad4",
license: "MPL-2.0",
licenseUrl:
"https://raw.githubusercontent.com/pyodide/pyodide/314.0.3/LICENSE",
licenseSha256:
"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5",
}),
cpython: Object.freeze({
repository: "https://github.com/python/cpython.git",
tag: "v3.14.2",
tagObject: "a1d0069daf8e85b25a0c3f96abc43182be6d429e",
commit: "df793163d5821791d4e7caf88885a2c11a107986",
license: "Python-2.0",
licenseUrl:
"https://raw.githubusercontent.com/python/cpython/v3.14.2/LICENSE",
licenseSha256:
"b0e25a78cffb43f4d92de8b61ccfa1f1f98ecbc22330b54b5251e7b6ba010231",
}),
emscriptenVersion: "5.0.3",
abiVersion: "2026_0",
lockPythonVersion: "3.14.0",
assets: Object.freeze({
"pyodide-lock.json":
"c963d22858f6bcb8f41586a2142f03905ab370c88ea22a86a2736e95fac2a8f3",
"pyodide.asm.mjs":
"1a9775427ef6e8abaa7db88ece0515422d1886915ae5c9093776410c865dfd8d",
"pyodide.asm.wasm":
"e7f8fac36f8bf11085309cbc5c829b3ec3057c18bf1d73b05a6741612d63cdbf",
"pyodide.mjs":
"5cfc46f5dcbaf2a16f26e2363f441873eb424762609cc03db00d6a2ace4d00e5",
"python_stdlib.zip":
"444c770dfd75a32097fc0a7d5c1413fd3140601f49c3a1f2e9af0376fcd124b4",
}),
strippedLoaderSha256:
"640ac750bf7cf42ee58b524faa09f803732d2237753cd630d6878edda8a107bf",
});
const PACK_FILES = Object.freeze(
[
"LICENSE.cpython.txt",
"LICENSE.pyodide.txt",
"SHA256SUMS",
"engine-metadata.json",
...Object.keys(PYTHON_ENGINE_LOCK.assets),
].sort(),
);
const CHECKSUM_FILES = Object.freeze(
PACK_FILES.filter((name) => name !== "SHA256SUMS"),
);
function sha256(data) {
return createHash("sha256").update(data).digest("hex");
}
async function sha256File(file) {
return sha256(await readFile(file));
}
async function assertRealDirectory(candidate, label) {
const details = await lstat(candidate).catch(() => null);
if (!details?.isDirectory() || details.isSymbolicLink()) {
throw new Error(`${label} must be a real directory: ${candidate}`);
}
return realpath(candidate);
}
async function assertRegularFile(candidate, label) {
const details = await lstat(candidate).catch(() => null);
if (!details?.isFile() || details.isSymbolicLink()) {
throw new Error(
`${label} must be a regular, non-symlink file: ${candidate}`,
);
}
return realpath(candidate);
}
async function assertHash(file, expected, label) {
const actual = await sha256File(file);
if (actual !== expected) {
throw new Error(
`${label} SHA-256 mismatch: expected ${expected}, got ${actual}.`,
);
}
}
function withoutSourceMapReference(source) {
const result = source.replace(
/\n\/\/# sourceMappingURL=pyodide\.mjs\.map\s*$/u,
"\n",
);
if (result === source) {
throw new Error(
"The pinned Pyodide loader no longer has the expected source-map trailer.",
);
}
return result;
}
async function verifyPyodideSource(sourceDirectory) {
const source = await assertRealDirectory(
sourceDirectory,
"Pyodide npm package",
);
for (const [name, expected] of Object.entries(PYTHON_ENGINE_LOCK.assets)) {
await assertRegularFile(path.join(source, name), `Pyodide ${name}`);
await assertHash(path.join(source, name), expected, `Pyodide ${name}`);
}
const packageFile = path.join(source, "package.json");
await assertHash(
packageFile,
PYTHON_ENGINE_LOCK.npm.packageJsonSha256,
"Pyodide package.json",
);
const packageJson = JSON.parse(await readFile(packageFile, "utf8"));
if (
packageJson.name !== "pyodide" ||
packageJson.version !== PYTHON_ENGINE_LOCK.pyodideVersion ||
packageJson.license !== PYTHON_ENGINE_LOCK.pyodide.license ||
packageJson.repository?.url !== "git+https://github.com/pyodide/pyodide.git"
) {
throw new Error("The Pyodide npm package identity is not pinned.");
}
const lock = JSON.parse(
await readFile(path.join(source, "pyodide-lock.json"), "utf8"),
);
if (
lock.info?.abi_version !== PYTHON_ENGINE_LOCK.abiVersion ||
lock.info?.arch !== "wasm32" ||
lock.info?.python !== PYTHON_ENGINE_LOCK.lockPythonVersion ||
lock.info?.platform !==
`emscripten_${PYTHON_ENGINE_LOCK.emscriptenVersion.replaceAll(".", "_")}`
) {
throw new Error("The Pyodide lock-file platform identity is not pinned.");
}
return source;
}
async function fetchPinned(url, expectedHash, label) {
const response = await fetch(url, {
redirect: "follow",
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) {
throw new Error(`${label} download failed with HTTP ${response.status}.`);
}
const value = Buffer.from(await response.arrayBuffer());
const actual = sha256(value);
if (actual !== expectedHash) {
throw new Error(
`${label} SHA-256 mismatch: expected ${expectedHash}, got ${actual}.`,
);
}
return value;
}
async function fileRecord(directory, name) {
const file = path.join(directory, name);
const value = await readFile(file);
return {
path: name,
sha256: sha256(value),
bytes: value.byteLength,
};
}
async function expectedMetadata(root, pack) {
const bridgePath = path.join(root, "engines", "python", "bridge.py");
const bridge = await readFile(bridgePath);
const files = [];
for (const name of CHECKSUM_FILES) {
if (name !== "engine-metadata.json") {
files.push(await fileRecord(pack, name));
}
}
return {
schemaVersion: 1,
engine: "python",
engineVersion: `CPython ${PYTHON_ENGINE_LOCK.pythonVersion} re`,
runtime: `Pyodide ${PYTHON_ENGINE_LOCK.pyodideVersion}`,
status: "production-runtime",
bridge: {
abiVersion: PYTHON_ENGINE_LOCK.bridgeAbi,
sourceFile: {
path: "engines/python/bridge.py",
sha256: sha256(bridge),
bytes: bridge.byteLength,
},
userValuesAreFunctionArguments: true,
regexModule: "re",
supportModules: {
serialization: "json",
runtimeIdentity: "sys",
},
maximumPatternUtf16: PYTHON_ENGINE_LOCK.maximumPatternUtf16,
maximumSubjectBytes: PYTHON_ENGINE_LOCK.maximumSubjectBytes,
maximumReplacementUtf16: PYTHON_ENGINE_LOCK.maximumReplacementUtf16,
maximumOutputBytes: PYTHON_ENGINE_LOCK.maximumOutputBytes,
maximumMatches: PYTHON_ENGINE_LOCK.maximumMatches,
maximumCaptureRows: PYTHON_ENGINE_LOCK.maximumCaptureRows,
maximumCaptureGroups: PYTHON_ENGINE_LOCK.maximumCaptureGroups,
maximumNativeExpansionCodePoints:
PYTHON_ENGINE_LOCK.maximumNativeExpansionCodePoints,
},
source: {
pyodide: {
repository: PYTHON_ENGINE_LOCK.pyodide.repository,
tag: PYTHON_ENGINE_LOCK.pyodide.tag,
commitSha1: PYTHON_ENGINE_LOCK.pyodide.commit,
license: PYTHON_ENGINE_LOCK.pyodide.license,
npmPackage: PYTHON_ENGINE_LOCK.npm.package,
npmResolved: PYTHON_ENGINE_LOCK.npm.resolved,
npmIntegrity: PYTHON_ENGINE_LOCK.npm.integrity,
},
cpython: {
repository: PYTHON_ENGINE_LOCK.cpython.repository,
tag: PYTHON_ENGINE_LOCK.cpython.tag,
tagObjectSha1: PYTHON_ENGINE_LOCK.cpython.tagObject,
commitSha1: PYTHON_ENGINE_LOCK.cpython.commit,
license: PYTHON_ENGINE_LOCK.cpython.license,
},
},
toolchain: {
abiVersion: PYTHON_ENGINE_LOCK.abiVersion,
architecture: "wasm32",
emscriptenVersion: PYTHON_ENGINE_LOCK.emscriptenVersion,
lockPythonVersion: PYTHON_ENGINE_LOCK.lockPythonVersion,
executablePythonVersion: PYTHON_ENGINE_LOCK.pythonVersion,
threads: false,
},
packaging: {
loaderTransformation:
"Removed only the upstream sourceMappingURL trailer; executable JavaScript is unchanged.",
upstreamLoaderSha256: PYTHON_ENGINE_LOCK.assets["pyodide.mjs"],
packedLoaderSha256: PYTHON_ENGINE_LOCK.strippedLoaderSha256,
externalPackagesIncluded: false,
lockPythonVersionNote:
"The upstream lock's 3.14.0 field identifies its CPython 3.14 ABI baseline; the executable runtime self-identifies as 3.14.2.",
},
files,
};
}
function equalJson(left, right) {
return JSON.stringify(left) === JSON.stringify(right);
}
async function expectedChecksums(pack) {
const lines = [];
for (const name of CHECKSUM_FILES) {
lines.push(`${await sha256File(path.join(pack, name))} ${name}`);
}
return `${lines.join("\n")}\n`;
}
async function smokePythonPack(pack) {
await WebAssembly.compile(
await readFile(path.join(pack, "pyodide.asm.wasm")),
);
const moduleUrl = pathToFileURL(path.join(pack, "pyodide.mjs"));
moduleUrl.searchParams.set("regex-tools-verification", String(Date.now()));
const imported = await import(moduleUrl.href);
if (
imported.version !== PYTHON_ENGINE_LOCK.pyodideVersion ||
typeof imported.loadPyodide !== "function"
) {
throw new Error("The staged Pyodide module identity is invalid.");
}
const runtime = await imported.loadPyodide({
indexURL: `${pack}${path.sep}`,
stdout: () => undefined,
stderr: () => undefined,
});
const result = JSON.parse(
runtime.runPython(`
import json, re, sys
expression = re.compile(r"(?P<emoji>😀)|(?P<word>[a-z]+)", re.ASCII)
matches = [
[match.span(0), match.lastgroup]
for match in expression.finditer("😀 abc")
]
replacement_match = re.compile(r"(?P<word>[a-z]+)").search("abc")
json.dumps({
"python": ".".join(str(part) for part in sys.version_info[:3]),
"implementation": sys.implementation.name,
"matches": matches,
"replacement": replacement_match.expand(r"[\\g<word>]"),
})
`),
);
if (
result.python !== PYTHON_ENGINE_LOCK.pythonVersion ||
result.implementation !== "cpython" ||
JSON.stringify(result.matches) !==
JSON.stringify([
[[0, 1], "emoji"],
[[2, 5], "word"],
]) ||
result.replacement !== "[abc]"
) {
throw new Error("The staged CPython re runtime smoke test failed.");
}
}
export async function verifyPythonPack(packDirectory, root) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const pack = await assertRealDirectory(packDirectory, "Python engine pack");
const entries = (await readdir(pack, { withFileTypes: true })).sort(
(left, right) =>
left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
);
if (
entries.length !== PACK_FILES.length ||
entries.some(
(entry, index) =>
entry.name !== PACK_FILES[index] ||
!entry.isFile() ||
entry.isSymbolicLink(),
)
) {
throw new Error(
`The staged Python pack must contain only: ${PACK_FILES.join(", ")}.`,
);
}
await assertHash(
path.join(pack, "LICENSE.pyodide.txt"),
PYTHON_ENGINE_LOCK.pyodide.licenseSha256,
"staged Pyodide licence",
);
await assertHash(
path.join(pack, "LICENSE.cpython.txt"),
PYTHON_ENGINE_LOCK.cpython.licenseSha256,
"staged CPython licence",
);
await assertHash(
path.join(pack, "pyodide.mjs"),
PYTHON_ENGINE_LOCK.strippedLoaderSha256,
"staged Pyodide loader",
);
for (const [name, expected] of Object.entries(PYTHON_ENGINE_LOCK.assets)) {
if (name !== "pyodide.mjs") {
await assertHash(path.join(pack, name), expected, `staged ${name}`);
}
}
const loader = await readFile(path.join(pack, "pyodide.mjs"), "utf8");
if (
loader.includes("sourceMappingURL") ||
loader.includes("/mnt/DATA/") ||
loader.includes("/home/zemion/")
) {
throw new Error(
"The staged Pyodide loader leaks a source-map or local build path.",
);
}
let metadata;
try {
metadata = JSON.parse(
await readFile(path.join(pack, "engine-metadata.json"), "utf8"),
);
} catch (error) {
throw new Error("engine-metadata.json is not valid JSON.", {
cause: error,
});
}
const expected = await expectedMetadata(repositoryRoot, pack);
if (!equalJson(metadata, expected)) {
throw new Error(
"The staged Python metadata does not match the pinned pack contract.",
);
}
if (
(await readFile(path.join(pack, "SHA256SUMS"), "utf8")) !==
(await expectedChecksums(pack))
) {
throw new Error("The staged Python SHA256SUMS file is incorrect.");
}
await smokePythonPack(pack);
return metadata;
}
async function replaceDirectory(stage, target, label) {
const targetDetails = await lstat(target).catch(() => null);
if (
targetDetails &&
(!targetDetails.isDirectory() || targetDetails.isSymbolicLink())
) {
throw new Error(`${label} must be a real directory.`);
}
if (!targetDetails) {
await rename(stage, target);
return;
}
const backup = `${target}.replaced-${process.pid}-${Date.now()}`;
await rename(target, backup);
try {
await rename(stage, target);
} catch (error) {
await rename(backup, target);
throw error;
}
await rm(backup, { recursive: true });
}
export async function installPythonPack(packDirectory, root) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const sourcePack = await assertRealDirectory(
packDirectory,
"verified Python engine pack",
);
const metadata = await verifyPythonPack(sourcePack, repositoryRoot);
const engineRoot = await assertRealDirectory(
path.join(repositoryRoot, "public", "engines"),
"public engine directory",
);
const target = path.join(engineRoot, "python");
const stage = await mkdtemp(path.join(engineRoot, ".python-install-"));
try {
for (const name of PACK_FILES) {
await copyFile(
path.join(sourcePack, name),
path.join(stage, name),
fsConstants.COPYFILE_EXCL,
);
}
await verifyPythonPack(stage, repositoryRoot);
await replaceDirectory(stage, target, "public/engines/python");
return { output: target, metadata };
} finally {
await rm(stage, { recursive: true, force: true });
}
}
export async function buildPythonPack(options, root) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const source = await verifyPyodideSource(options.sourceDirectory);
const output = path.resolve(options.outputDirectory);
await mkdir(path.dirname(output), { recursive: true });
const stage = await mkdtemp(
path.join(path.dirname(output), ".python-pack-build-"),
);
try {
const [pyodideLicense, cpythonLicense] = await Promise.all([
fetchPinned(
PYTHON_ENGINE_LOCK.pyodide.licenseUrl,
PYTHON_ENGINE_LOCK.pyodide.licenseSha256,
"Pyodide licence",
),
fetchPinned(
PYTHON_ENGINE_LOCK.cpython.licenseUrl,
PYTHON_ENGINE_LOCK.cpython.licenseSha256,
"CPython licence",
),
]);
await writeFile(path.join(stage, "LICENSE.pyodide.txt"), pyodideLicense, {
flag: "wx",
});
await writeFile(path.join(stage, "LICENSE.cpython.txt"), cpythonLicense, {
flag: "wx",
});
for (const name of Object.keys(PYTHON_ENGINE_LOCK.assets)) {
if (name === "pyodide.mjs") {
const upstream = await readFile(path.join(source, name), "utf8");
const packed = withoutSourceMapReference(upstream);
if (sha256(packed) !== PYTHON_ENGINE_LOCK.strippedLoaderSha256) {
throw new Error("The deterministic Pyodide loader output changed.");
}
await writeFile(path.join(stage, name), packed, { flag: "wx" });
} else {
await copyFile(
path.join(source, name),
path.join(stage, name),
fsConstants.COPYFILE_EXCL,
);
}
}
const metadata = await expectedMetadata(repositoryRoot, stage);
await writeFile(
path.join(stage, "engine-metadata.json"),
`${JSON.stringify(metadata, null, 2)}\n`,
{ flag: "wx" },
);
await writeFile(
path.join(stage, "SHA256SUMS"),
await expectedChecksums(stage),
{ flag: "wx" },
);
await verifyPythonPack(stage, repositoryRoot);
await replaceDirectory(stage, output, "Python pack output");
return { output, metadata };
} finally {
await rm(stage, { recursive: true, force: true });
}
}
const invokedFile = process.argv[1]
? pathToFileURL(path.resolve(process.argv[1])).href
: "";
if (import.meta.url === invokedFile) {
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const arguments_ = process.argv.slice(2);
if (arguments_.length > 2) {
throw new Error(
"Usage: node scripts/python-engine-pack.mjs [node_modules/pyodide] [.engine-build/python]",
);
}
const result = await buildPythonPack(
{
sourceDirectory: path.resolve(
root,
arguments_[0] ?? "node_modules/pyodide",
),
outputDirectory: path.resolve(
root,
arguments_[1] ?? ".engine-build/python",
),
},
root,
);
console.log(
`Built and verified ${result.metadata.runtime} / ${result.metadata.engineVersion} at ${result.output}.`,
);
}

View File

@@ -26,6 +26,14 @@ const testCatalogue = {
theme: { mode: "system", brand: "add·ideas" }, theme: { mode: "system", brand: "add·ideas" },
apps: [{ manifest: "./toolbox-app.json", enabled: true }], apps: [{ manifest: "./toolbox-app.json", enabled: true }],
}; };
const productionSecurityHeaders = {
"Content-Security-Policy":
"default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; manifest-src 'self'",
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Resource-Policy": "same-origin",
"X-Content-Type-Options": "nosniff",
};
function safeFile(requestPath) { function safeFile(requestPath) {
const decoded = decodeURIComponent(requestPath); const decoded = decodeURIComponent(requestPath);
@@ -50,6 +58,7 @@ const server = createServer(async (request, response) => {
response.writeHead(200, { response.writeHead(200, {
"Content-Type": "application/json; charset=utf-8", "Content-Type": "application/json; charset=utf-8",
"Cache-Control": "no-cache", "Cache-Control": "no-cache",
...productionSecurityHeaders,
}); });
response.end(JSON.stringify(testCatalogue)); response.end(JSON.stringify(testCatalogue));
return; return;
@@ -66,7 +75,7 @@ const server = createServer(async (request, response) => {
"Content-Type": "Content-Type":
mediaTypes.get(path.extname(file)) ?? "application/octet-stream", mediaTypes.get(path.extname(file)) ?? "application/octet-stream",
"Cache-Control": "no-cache", "Cache-Control": "no-cache",
"Cross-Origin-Resource-Policy": "same-origin", ...productionSecurityHeaders,
}); });
response.end(content); response.end(content);
} catch { } catch {

View File

@@ -1,22 +1,28 @@
import { lstat, readFile, readdir } from "node:fs/promises"; import { lstat, readFile, readdir } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { verifyJavaPack } from "./java-engine-pack.mjs";
import { verifyPcre2Pack } from "./pcre2-engine-pack.mjs"; import { verifyPcre2Pack } from "./pcre2-engine-pack.mjs";
import { verifyPythonPack } from "./python-engine-pack.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const arguments_ = process.argv.slice(2); const arguments_ = process.argv.slice(2);
if (arguments_.length > 0) { if (arguments_.length > 0) {
if (arguments_.length !== 2 || arguments_[0] !== "--pcre2-pack") { const verifiers = new Map([
["--java-pack", verifyJavaPack],
["--pcre2-pack", verifyPcre2Pack],
["--python-pack", verifyPythonPack],
]);
const verifier = verifiers.get(arguments_[0]);
if (arguments_.length !== 2 || !verifier) {
throw new Error( throw new Error(
"Usage: node scripts/verify-engine-assets.mjs [--pcre2-pack PATH]", "Usage: node scripts/verify-engine-assets.mjs [--java-pack|--pcre2-pack|--python-pack PATH]",
); );
} }
const pack = path.resolve(root, arguments_[1]); const pack = path.resolve(root, arguments_[1]);
const metadata = await verifyPcre2Pack(pack, root); const metadata = await verifier(pack, root);
console.log( console.log(`Verified staged ${metadata.engineVersion} engine pack.`);
`Verified staged PCRE2 ${metadata.engineVersion} pack and WebAssembly bridge smoke test.`,
);
process.exit(0); process.exit(0);
} }
@@ -31,21 +37,34 @@ const entries = (await readdir(engineDirectory, { withFileTypes: true })).sort(
left.name < right.name ? -1 : left.name > right.name ? 1 : 0, left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
); );
if ( if (
entries.length !== 2 || entries.length !== 4 ||
!entries[0]?.isFile() || !entries[0]?.isFile() ||
entries[0].name !== "README.md" || entries[0].name !== "README.md" ||
!entries[1]?.isDirectory() || !entries[1]?.isDirectory() ||
entries[1].name !== "pcre2" entries[1].name !== "java" ||
!entries[2]?.isDirectory() ||
entries[2].name !== "pcre2" ||
!entries[3]?.isDirectory() ||
entries[3].name !== "python"
) { ) {
throw new Error( throw new Error(
"The production build must contain only README.md and the declared PCRE2 pack.", "The production build must contain only README.md and the declared Java, PCRE2 and Python packs.",
); );
} }
const notice = await readFile(path.join(engineDirectory, "README.md"), "utf8"); const notice = await readFile(path.join(engineDirectory, "README.md"), "utf8");
if (!notice.includes("native `RegExp`") || !notice.includes("PCRE2 10.47")) { if (
!notice.includes("native `RegExp`") ||
!notice.includes("PCRE2 10.47") ||
!notice.includes("Pyodide 314.0.3") ||
!notice.includes("TeaVM 0.15.0")
) {
throw new Error("The engine asset notice does not describe this release."); throw new Error("The engine asset notice does not describe this release.");
} }
await verifyPcre2Pack(path.join(engineDirectory, "pcre2"), root); await verifyPcre2Pack(path.join(engineDirectory, "pcre2"), root);
await verifyPythonPack(path.join(engineDirectory, "python"), root);
await verifyJavaPack(path.join(engineDirectory, "java"), root);
console.log("Verified native ECMAScript and bundled PCRE2 engine assets."); console.log(
"Verified native ECMAScript and bundled PCRE2, CPython/Pyodide and TeaVM Java engine assets.",
);

View File

@@ -100,7 +100,7 @@ describe("CapabilityPanel", () => {
); );
}); });
it("does not advertise ECMAScript generation for the partial PCRE2 syntax provider", () => { it("keeps PCRE2-only trace and code generation separate from ECMAScript-only tools", () => {
render( render(
<CapabilityPanel <CapabilityPanel
syntax={syntax} syntax={syntax}
@@ -110,21 +110,79 @@ describe("CapabilityPanel", () => {
...execution.engine, ...execution.engine,
flavour: "pcre2", flavour: "pcre2",
engineName: "PCRE2", engineName: "PCRE2",
capabilities: {
...execution.engine.capabilities,
actualTrace: true,
},
}, },
}} }}
/>, />,
); );
const trace = screen.getByText("Actual execution trace").closest("div");
expect(trace).toHaveTextContent("Available");
const generatedCases = screen.getByText("Generated cases").closest("div"); const generatedCases = screen.getByText("Generated cases").closest("div");
expect(generatedCases).toHaveTextContent( expect(generatedCases).toHaveTextContent(
"Unavailable — the partial PCRE2 provider does not expose a complete generation AST", "Unavailable — the ECMAScript case generator is not applied to PCRE2",
); );
const formatting = screen.getByText("Pattern formatting").closest("div"); const formatting = screen.getByText("Pattern formatting").closest("div");
expect(formatting).toHaveTextContent( expect(formatting).toHaveTextContent(
"Unavailable — no complete PCRE2 grammar-backed formatter is implemented", "Unavailable — the ECMAScript formatter is not applied to PCRE2",
);
const codeGeneration = screen.getByText("Code generation").closest("div");
expect(codeGeneration).toHaveTextContent(
"Available — reviewed PCRE2 10.47 8-bit C17 target",
); );
}); });
it.each([
["python", "Python re"],
["java", "Java Pattern"],
] as const)(
"does not borrow ECMAScript or PCRE2 feature semantics for %s",
(flavour, label) => {
render(
<CapabilityPanel
syntax={syntax}
execution={{
...execution,
engine: {
...execution.engine,
flavour,
engineName: `${label} fixture`,
},
}}
/>,
);
expect(
screen.getByText("Actual execution trace").closest("div"),
).toHaveTextContent(
"Unavailable — automatic-callout tracing is implemented only for PCRE2",
);
expect(
screen.getByText("Static risk analysis").closest("div"),
).toHaveTextContent(
`Unavailable — ECMAScript heuristics are not applied to ${label}`,
);
expect(
screen.getByText("Generated cases").closest("div"),
).toHaveTextContent(
`Unavailable — the ECMAScript case generator is not applied to ${label}`,
);
expect(
screen.getByText("Pattern formatting").closest("div"),
).toHaveTextContent(
`Unavailable — the ECMAScript formatter is not applied to ${label}`,
);
expect(
screen.getByText("Code generation").closest("div"),
).toHaveTextContent(
"Unavailable — C17 code generation is implemented only for PCRE2",
);
},
);
it("offers an accessible close control when used as a modal panel", async () => { it("offers an accessible close control when used as a modal panel", async () => {
const onClose = vi.fn(); const onClose = vi.fn();
const user = userEvent.setup(); const user = userEvent.setup();

View File

@@ -1,7 +1,21 @@
import { SYNTAX_PROFILE } from "../version"; import { SYNTAX_PROFILE } from "../version";
import type { RegexExecutionResult } from "../regex/model/match"; import type { RegexExecutionResult } from "../regex/model/match";
import type { RegexSyntaxResult } from "../regex/model/syntax"; import type { RegexSyntaxResult } from "../regex/model/syntax";
import type { RegexEngineCapabilities } from "../regex/model/flavour"; import type {
RegexEngineCapabilities,
RegexFlavourId,
} from "../regex/model/flavour";
const FLAVOUR_LABELS: Readonly<Record<RegexFlavourId, string>> = {
ecmascript: "ECMAScript",
pcre2: "PCRE2",
pcre: "PCRE",
python: "Python re",
go: "Go regexp",
rust: "Rust regex",
dotnet: ".NET Regex",
java: "Java Pattern",
};
function capability( function capability(
capabilities: RegexEngineCapabilities | undefined, capabilities: RegexEngineCapabilities | undefined,
@@ -44,6 +58,9 @@ export function CapabilityPanel({
const capabilities = execution?.engine.capabilities; const capabilities = execution?.engine.capabilities;
const activeFlavour = const activeFlavour =
execution?.engine.flavour ?? syntax?.root.support.flavour; execution?.engine.flavour ?? syntax?.root.support.flavour;
const activeFlavourLabel = activeFlavour
? FLAVOUR_LABELS[activeFlavour]
: undefined;
const rows = [ const rows = [
[ [
"Flavour", "Flavour",
@@ -98,35 +115,47 @@ export function CapabilityPanel({
], ],
[ [
"Actual execution trace", "Actual execution trace",
capability( activeFlavour === "pcre2"
? capability(
capabilities, capabilities,
"actualTrace", "actualTrace",
"the adapter exposes no engine trace", "this PCRE2 adapter exposes no bounded automatic-callout trace",
), )
: activeFlavour
? "Unavailable — automatic-callout tracing is implemented only for PCRE2"
: "Awaiting flavour worker result",
], ],
["Benchmark", capability(capabilities, "benchmark")], ["Benchmark", capability(capabilities, "benchmark")],
[ [
"Static risk analysis", "Static risk analysis",
activeFlavour === "ecmascript" activeFlavour === "ecmascript"
? "Available — advisory ECMAScript 2025 heuristics" ? "Available — advisory ECMAScript 2025 heuristics"
: activeFlavour === "pcre2" : activeFlavourLabel
? "Unavailable — ECMAScript heuristics are not applied to PCRE2" ? `Unavailable — ECMAScript heuristics are not applied to ${activeFlavourLabel}`
: "Awaiting flavour worker result", : "Awaiting flavour worker result",
], ],
[ [
"Generated cases", "Generated cases",
activeFlavour === "ecmascript" activeFlavour === "ecmascript"
? "Available — deterministic AST candidates, retained only after actual-engine verification" ? "Available — deterministic AST candidates, retained only after actual-engine verification"
: activeFlavour === "pcre2" : activeFlavourLabel
? "Unavailable — the partial PCRE2 provider does not expose a complete generation AST" ? `Unavailable — the ECMAScript case generator is not applied to ${activeFlavourLabel}`
: "Awaiting flavour worker result", : "Awaiting flavour worker result",
], ],
[ [
"Pattern formatting", "Pattern formatting",
activeFlavour === "ecmascript" activeFlavour === "ecmascript"
? "Available — grammar-backed literal/control escaping with mandatory exact-snapshot validation" ? "Available — grammar-backed literal/control escaping with mandatory exact-snapshot validation"
: activeFlavour === "pcre2" : activeFlavourLabel
? "Unavailable — no complete PCRE2 grammar-backed formatter is implemented" ? `Unavailable — the ECMAScript formatter is not applied to ${activeFlavourLabel}`
: "Awaiting flavour worker result",
],
[
"Code generation",
activeFlavour === "pcre2"
? "Available — reviewed PCRE2 10.47 8-bit C17 target"
: activeFlavour
? "Unavailable — C17 code generation is implemented only for PCRE2"
: "Awaiting flavour worker result", : "Awaiting flavour worker result",
], ],
[ [
@@ -172,11 +201,11 @@ export function CapabilityPanel({
))} ))}
</dl> </dl>
<p className="capability-roadmap"> <p className="capability-roadmap">
PCRE2 10.47 matching and substitution run in the bundled WebAssembly Availability is never inferred by substituting one flavour for another.
worker. Its separate trace worker exposes bounded reported automatic Static analysis, generated cases and formatting are ECMAScript-only;
callouts; movement classifications remain explicitly derived. Python, automatic-callout tracing and reviewed C17 generation are PCRE2-only.
Go, Rust, .NET and Java remain unavailable until their named runtimes Python and Java retain the engine identity and compatibility boundaries
pass the same worker, offset, conformance and licensing gates. reported by their own workers.
</p> </p>
</section> </section>
); );

View File

@@ -47,11 +47,19 @@ export function HelpDialog({
<section> <section>
<h3>What runs where</h3> <h3>What runs where</h3>
<p> <p>
Syntax parsing and actual ECMAScript or PCRE2 execution run in Syntax parsing and active-engine execution run in separate, killable
separate, killable browser workers. PCRE2 is the bundled, browser workers. ECMAScript uses the browser RegExp implementation;
self-hosted 10.47 WebAssembly engine. Patterns, subjects and PCRE2 uses the bundled 10.47 WebAssembly engine; Python uses CPython
projects are never uploaded. There is no backend, account, 3.14 through Pyodide; and Java uses TeaVM&apos;s java.util.regex
telemetry, CDN dependency, or executable replacement function. compatibility implementation. The result metadata names the runtime
actually used. Patterns, subjects and projects are never uploaded.
There is no backend, account, telemetry, CDN dependency, or
executable replacement function.
</p>
<p>
TeaVM 0.15.0 is not OpenJDK. Its replacement implementation accepts
single-digit $n references, so $12 means $1 followed by literal 2.
Java SE ${"{name}"} references are reported as unavailable.
</p> </p>
</section> </section>
<section> <section>
@@ -87,9 +95,9 @@ export function HelpDialog({
and negative subject runs through the selected actual engine before and negative subject runs through the selected actual engine before
it can become a test; candidates with the wrong outcome are it can become a test; candidates with the wrong outcome are
discarded. The coverage report names unsupported constructs. discarded. The coverage report names unsupported constructs.
Sampling is not proof of complete language coverage. PCRE2 remains Sampling is not proof of complete language coverage. Other flavours
unavailable until its structural provider exposes the required AST; remain unavailable until their structural providers expose the
its syntax is never reinterpreted as ECMAScript. required AST; their syntax is never reinterpreted as ECMAScript.
</p> </p>
</section> </section>
<section> <section>
@@ -97,11 +105,11 @@ export function HelpDialog({
<p> <p>
The ECMAScript formatter makes only grammar-backed literal/control The ECMAScript formatter makes only grammar-backed literal/control
escape changes; it does not insert layout whitespace or reinterpret escape changes; it does not insert layout whitespace or reinterpret
PCRE2 syntax. Applying a preview requires independent source and another flavour&apos;s syntax. Applying a preview requires
candidate reparsing, actual-engine match/replacement comparison, independent source and candidate reparsing, actual-engine
every applicable exact unit test, and an explicit confirmation. match/replacement comparison, every applicable exact unit test, and
Passing bounded checks is evidence for those snapshots, not proof an explicit confirmation. Passing bounded checks is evidence for
for every possible subject. those snapshots, not proof for every possible subject.
</p> </p>
</section> </section>
<section> <section>

View File

@@ -211,6 +211,8 @@ export function MinimizePanel({
() => optionsFor(activeFlavour, activeOptions, "pcre2"), () => optionsFor(activeFlavour, activeOptions, "pcre2"),
[activeFlavour, activeOptions], [activeFlavour, activeOptions],
); );
const activeEngineSupported =
activeFlavour === "ecmascript" || activeFlavour === "pcre2";
const unitExpectation = (): RegexTestExpectation => { const unitExpectation = (): RegexTestExpectation => {
switch (unitKind) { switch (unitKind) {
@@ -244,10 +246,14 @@ export function MinimizePanel({
const activeSide = { const activeSide = {
flavour: flavour:
activeFlavour === "pcre2" ? ("pcre2" as const) : ("ecmascript" as const), activeFlavour === "pcre2" ? ("pcre2" as const) : ("ecmascript" as const),
flavourVersion: activeFlavourVersion, flavourVersion: activeEngineSupported
? activeFlavourVersion
: ECMASCRIPT.defaultVersion,
pattern: initialPattern, pattern: initialPattern,
flags: activeFlags, flags: activeEngineSupported ? activeFlags : ECMASCRIPT.defaultFlags,
options: activeOptions, options: activeEngineSupported
? activeOptions
: defaultRegexOptions(ECMASCRIPT),
...(unitKind === "replacement" ? { replacement: initialReplacement } : {}), ...(unitKind === "replacement" ? { replacement: initialReplacement } : {}),
}; };
@@ -324,6 +330,12 @@ export function MinimizePanel({
}; };
const run = async () => { const run = async () => {
if (mode !== "comparison-mismatch" && !activeEngineSupported) {
setStatus(
`Subject minimization is not yet implemented for ${activeFlavour}; select ECMAScript or PCRE2, or use the explicit ECMAScript ↔ PCRE2 comparison target.`,
);
return;
}
const currentRevision = ++revision.current; const currentRevision = ++revision.current;
setRunning(true); setRunning(true);
setResult(undefined); setResult(undefined);
@@ -384,6 +396,13 @@ export function MinimizePanel({
replacement. It does not claim a globally or semantically minimal replacement. It does not claim a globally or semantically minimal
example. example.
</p> </p>
{!activeEngineSupported ? (
<p className="minimizer-advisory" role="status">
Python and Java execution remain available in the main workbench,
corpus and unit-test modes. This reducer currently has verified
failure oracles only for ECMAScript and PCRE2.
</p>
) : null}
<section className="minimizer-config" aria-labelledby="minimize-target"> <section className="minimizer-config" aria-labelledby="minimize-target">
<header> <header>
@@ -716,7 +735,10 @@ export function MinimizePanel({
<button <button
type="button" type="button"
className="primary-button" className="primary-button"
disabled={running} disabled={
running ||
(mode !== "comparison-mismatch" && !activeEngineSupported)
}
onClick={() => void run()} onClick={() => void run()}
> >
{running ? "Minimizing…" : "Verify & minimize"} {running ? "Minimizing…" : "Verify & minimize"}

View File

@@ -53,4 +53,67 @@ describe("QuickReference", () => {
screen.getByRole("link", { name: "PCRE2 pattern documentation" }), screen.getByRole("link", { name: "PCRE2 pattern documentation" }),
).toHaveAttribute("href", expect.stringContaining("pcre.org")); ).toHaveAttribute("href", expect.stringContaining("pcre.org"));
}); });
it("shows an original Python 3.14 re reference without borrowing ECMAScript syntax", async () => {
const user = userEvent.setup();
render(<QuickReference flavour="python" onUsePattern={vi.fn()} />);
expect(
screen.getByText("Original Python 3.14 re reference"),
).toBeInTheDocument();
expect(screen.getByText("8 of 8 constructs")).toBeInTheDocument();
await user.type(screen.getByLabelText("Filter constructs"), "absolute end");
expect(screen.getByText("Absolute end anchor")).toBeInTheDocument();
expect(screen.getByText("\\z")).toBeInTheDocument();
expect(screen.getByText("python")).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Python 3.14 re documentation" }),
).toHaveAttribute("href", "https://docs.python.org/3.14/library/re.html");
});
it("shows an original Java Pattern reference from the official Java API", async () => {
const user = userEvent.setup();
render(<QuickReference flavour="java" onUsePattern={vi.fn()} />);
expect(
screen.getByText("Original Java Pattern reference"),
).toBeInTheDocument();
expect(screen.getByText("9 of 9 constructs")).toBeInTheDocument();
expect(
screen.getByText(
/bundled TeaVM 0\.15\.0 adapter is a tested compatibility subset/u,
),
).toBeInTheDocument();
await user.type(screen.getByLabelText("Filter constructs"), "intersection");
expect(
screen.getByText("Character-class intersection"),
).toBeInTheDocument();
expect(screen.getByText("[class&&class]")).toBeInTheDocument();
expect(screen.getByText("java")).toBeInTheDocument();
expect(
screen.getByRole("link", {
name: "Java SE 25 Pattern documentation",
}),
).toHaveAttribute(
"href",
"https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/regex/Pattern.html",
);
});
it("does not relabel an unreviewed flavour as ECMAScript", () => {
render(<QuickReference flavour="go" onUsePattern={vi.fn()} />);
expect(
screen.getByText("Original Go regexp reference"),
).toBeInTheDocument();
expect(screen.getByText("0 of 0 constructs")).toBeInTheDocument();
expect(screen.getByRole("status")).toHaveTextContent(
"No reviewed quick reference is available for Go regexp.",
);
expect(screen.queryByText("Any character")).not.toBeInTheDocument();
});
}); });

View File

@@ -1,9 +1,6 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import type { RegexFlavourId } from "../regex/model/flavour"; import type { RegexFlavourId } from "../regex/model/flavour";
import { import { REFERENCE_BY_FLAVOUR } from "../regex/reference/token-reference";
ECMASCRIPT_REFERENCE,
PCRE2_REFERENCE,
} from "../regex/reference/token-reference";
export function QuickReference({ export function QuickReference({
onUsePattern, onUsePattern,
@@ -15,8 +12,8 @@ export function QuickReference({
readonly flavour?: RegexFlavourId; readonly flavour?: RegexFlavourId;
}) { }) {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const reference = const referenceSet = REFERENCE_BY_FLAVOUR[flavour];
flavour === "pcre2" ? PCRE2_REFERENCE : ECMASCRIPT_REFERENCE; const reference = referenceSet.entries;
const entries = useMemo(() => { const entries = useMemo(() => {
const normalizeSearchText = (value: string) => const normalizeSearchText = (value: string) =>
value value
@@ -46,9 +43,7 @@ export function QuickReference({
> >
<header className="panel-heading"> <header className="panel-heading">
<div> <div>
<p className="eyebrow"> <p className="eyebrow">Original {referenceSet.label} reference</p>
Original {flavour === "pcre2" ? "PCRE2" : "ECMAScript"} reference
</p>
<h2 id="reference-heading">Quick reference</h2> <h2 id="reference-heading">Quick reference</h2>
</div> </div>
<div className="panel-heading-actions"> <div className="panel-heading-actions">
@@ -76,6 +71,9 @@ export function QuickReference({
onChange={(event) => setQuery(event.target.value)} onChange={(event) => setQuery(event.target.value)}
/> />
</label> </label>
{referenceSet.note ? (
<p className="reference-note">{referenceSet.note}</p>
) : null}
{entries.length > 0 ? ( {entries.length > 0 ? (
<ul className="reference-list"> <ul className="reference-list">
{entries.map((entry) => ( {entries.map((entry) => (
@@ -124,7 +122,9 @@ export function QuickReference({
</ul> </ul>
) : ( ) : (
<p className="empty-state" role="status"> <p className="empty-state" role="status">
No reference constructs match {query.trim()}. {reference.length === 0
? `No reviewed quick reference is available for ${referenceSet.label}.`
: `No reference constructs match “${query.trim()}”.`}
</p> </p>
)} )}
</section> </section>

View File

@@ -51,6 +51,29 @@ function renderResult(result: RegexReplacementResult) {
} }
describe("ReplacementPanel completeness", () => { describe("ReplacementPanel completeness", () => {
it.each([
["python", "Python re"],
["java", "Java Pattern"],
] as const)(
"labels %s templates without relabelling them",
(flavour, label) => {
render(
<ReplacementPanel
flavour={flavour}
replacement=""
onReplacementChange={() => undefined}
onSelectToken={() => undefined}
onSelectContribution={() => undefined}
/>,
);
expect(screen.getByText(`${label} template syntax`)).toBeInTheDocument();
expect(
screen.getByText(`Native matches · bounded ${label} substitution`),
).toBeInTheDocument();
},
);
it("labels a match-limited replacement as partial", () => { it("labels a match-limited replacement as partial", () => {
renderResult({ renderResult({
execution: { ...execution, truncated: true }, execution: { ...execution, truncated: true },

View File

@@ -17,6 +17,17 @@ import {
import { handleTreeNavigation } from "./tree-navigation"; import { handleTreeNavigation } from "./tree-navigation";
import { ReplacementPreviewPanel } from "./ReplacementPreviewPanel"; import { ReplacementPreviewPanel } from "./ReplacementPreviewPanel";
const TEMPLATE_FLAVOUR_LABELS: Readonly<Record<RegexFlavourId, string>> = {
ecmascript: "ECMAScript",
pcre2: "PCRE2",
pcre: "PCRE",
python: "Python re",
go: "Go regexp",
rust: "Rust regex",
dotnet: ".NET Regex",
java: "Java Pattern",
};
function ReplacementTokenRow({ function ReplacementTokenRow({
token, token,
selected, selected,
@@ -79,7 +90,7 @@ export function ReplacementPanel({
}) { }) {
const activeFlavour = const activeFlavour =
flavour ?? result?.execution.engine.flavour ?? "ecmascript"; flavour ?? result?.execution.engine.flavour ?? "ecmascript";
const engineLabel = activeFlavour === "pcre2" ? "PCRE2" : "ECMAScript"; const engineLabel = TEMPLATE_FLAVOUR_LABELS[activeFlavour];
const retainedTokens = syntax?.tokens ?? []; const retainedTokens = syntax?.tokens ?? [];
const editorTokens = retainedTokens.slice( const editorTokens = retainedTokens.slice(
0, 0,

View File

@@ -225,7 +225,7 @@ describe("project import and export", () => {
).toThrow(/Test 1 subject exceeds.*UTF-8 byte limit/u); ).toThrow(/Test 1 subject exceeds.*UTF-8 byte limit/u);
}); });
it("accepts PCRE2 and rejects unsupported flavour or option semantics without rewriting", () => { it("accepts implemented flavours and rejects unsupported flavour or option semantics without rewriting", () => {
expect( expect(
importProjectDocument( importProjectDocument(
JSON.stringify({ JSON.stringify({
@@ -258,14 +258,30 @@ describe("project import and export", () => {
JSON.stringify({ ...project, flavourVersion: "ECMAScript 2026" }), JSON.stringify({ ...project, flavourVersion: "ECMAScript 2026" }),
), ),
).toThrow(/flavour version.*unsupported/iu); ).toThrow(/flavour version.*unsupported/iu);
expect(() => expect(
importProjectDocument( importProjectDocument(
JSON.stringify({ JSON.stringify({
...project, ...project,
tests: [{ ...project.tests[0], flavour: "python" }], tests: [{ ...project.tests[0], flavour: "python" }],
}), }),
), ),
).toThrow(/only supports ECMAScript and PCRE2/u); ).toEqual(
expect.objectContaining({
tests: [
expect.objectContaining({
flavour: "python",
}),
],
}),
);
expect(() =>
importProjectDocument(
JSON.stringify({
...project,
tests: [{ ...project.tests[0], flavour: "go" }],
}),
),
).toThrow(/only supports ECMAScript, PCRE2, Python re and Java/u);
expect(() => expect(() =>
importProjectDocument( importProjectDocument(
JSON.stringify({ JSON.stringify({

View File

@@ -71,12 +71,22 @@ class ResponsiveWorker implements WorkerLike {
onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null; onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;
readonly operations: EngineWorkerOperation[] = []; readonly operations: EngineWorkerOperation[] = [];
terminateCalls = 0; terminateCalls = 0;
private readonly flavour: RegexFlavourId;
constructor(flavour: RegexFlavourId) {
this.flavour = flavour;
}
postMessage(message: unknown): void { postMessage(message: unknown): void {
const request = message as WorkerRequest<EngineWorkerOperation>; const request = message as WorkerRequest<EngineWorkerOperation>;
this.operations.push(request.payload); this.operations.push(request.payload);
const payload: EngineWorkerResult = const payload: EngineWorkerResult =
request.payload.kind === "execute" request.payload.kind === "load"
? {
kind: "load",
info: result(this.flavour).engine,
}
: request.payload.kind === "execute"
? { ? {
kind: "execute", kind: "execute",
result: result(request.payload.request.flavour), result: result(request.payload.request.flavour),
@@ -106,14 +116,34 @@ class ResponsiveWorker implements WorkerLike {
} }
} }
class StallingWorker implements WorkerLike {
onmessage: ((event: MessageEvent<unknown>) => void) | null = null;
onerror: ((event: ErrorEvent) => void) | null = null;
onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;
readonly operations: EngineWorkerOperation[] = [];
terminateCalls = 0;
postMessage(message: unknown): void {
const request = message as WorkerRequest<EngineWorkerOperation>;
this.operations.push(request.payload);
}
terminate(): void {
this.terminateCalls += 1;
}
}
describe("engine worker selection", () => { describe("engine worker selection", () => {
it("registers the shipped ECMAScript and PCRE2 execution workers", () => { it("registers every shipped execution worker with a startup budget", () => {
expect( expect(
ENGINE_WORKER_REGISTRY.registrations.map( ENGINE_WORKER_REGISTRY.registrations.map(
(registration) => registration.flavour, (registration) => registration.flavour,
), ),
).toEqual(["ecmascript", "pcre2"]); ).toEqual(["ecmascript", "pcre2", "python", "java"]);
expect(ENGINE_WORKER_REGISTRY.require("pcre2").label).toBe("PCRE2 engine"); expect(ENGINE_WORKER_REGISTRY.require("pcre2").label).toBe("PCRE2 engine");
expect(ENGINE_WORKER_REGISTRY.require("python").startupTimeoutMs).toBe(
30_000,
);
}); });
it("selects and reuses a dedicated supervisor per registered flavour", async () => { it("selects and reuses a dedicated supervisor per registered flavour", async () => {
@@ -121,8 +151,9 @@ describe("engine worker selection", () => {
const registration = (flavour: RegexFlavourId) => ({ const registration = (flavour: RegexFlavourId) => ({
flavour, flavour,
label: `${flavour} fixture`, label: `${flavour} fixture`,
startupTimeoutMs: 100,
createWorker: () => { createWorker: () => {
const worker = new ResponsiveWorker(); const worker = new ResponsiveWorker(flavour);
workers.set(flavour, [...(workers.get(flavour) ?? []), worker]); workers.set(flavour, [...(workers.get(flavour) ?? []), worker]);
return worker; return worker;
}, },
@@ -150,7 +181,14 @@ describe("engine worker selection", () => {
expect(workers.get("ecmascript")).toHaveLength(1); expect(workers.get("ecmascript")).toHaveLength(1);
expect(workers.get("pcre2")).toHaveLength(1); expect(workers.get("pcre2")).toHaveLength(1);
expect(workers.get("ecmascript")?.[0]?.operations).toHaveLength(2); expect(
workers
.get("ecmascript")?.[0]
?.operations.map((operation) => operation.kind),
).toEqual(["load", "execute", "execute"]);
expect(
workers.get("pcre2")?.[0]?.operations.map((operation) => operation.kind),
).toEqual(["load", "execute"]);
supervisor.dispose(); supervisor.dispose();
expect(workers.get("ecmascript")?.[0]?.terminateCalls).toBe(1); expect(workers.get("ecmascript")?.[0]?.terminateCalls).toBe(1);
@@ -165,4 +203,72 @@ describe("engine worker selection", () => {
); );
supervisor.dispose(); supervisor.dispose();
}); });
it("lets the newest request supersede a shared ready-engine continuation", async () => {
const worker = new ResponsiveWorker("python");
const supervisor = new EngineSupervisor(
new EngineWorkerRegistry([
{
flavour: "python",
label: "Python fixture",
startupTimeoutMs: 100,
createWorker: () => worker,
},
]),
);
await supervisor.load("python");
const older = supervisor.execute(request("python"), 100);
const newer = supervisor.execute(request("python"), 100);
await expect(older).rejects.toMatchObject({ kind: "cancelled" });
await expect(newer).resolves.toEqual(
expect.objectContaining({
engine: expect.objectContaining({ flavour: "python" }),
}),
);
expect(worker.operations.map((operation) => operation.kind)).toEqual([
"load",
"execute",
]);
supervisor.dispose();
});
it("restarts a superseded cold runtime under the startup budget before execution", async () => {
const coldWorker = new StallingWorker();
const readyWorker = new ResponsiveWorker("python");
let factoryCalls = 0;
const supervisor = new EngineSupervisor(
new EngineWorkerRegistry([
{
flavour: "python",
label: "Python fixture",
startupTimeoutMs: 100,
createWorker: () => {
factoryCalls += 1;
return factoryCalls === 1 ? coldWorker : readyWorker;
},
},
]),
);
const older = supervisor.execute(request("python"), 1);
const newer = supervisor.execute(request("python"), 1);
await expect(older).rejects.toMatchObject({ kind: "cancelled" });
await expect(newer).resolves.toEqual(
expect.objectContaining({
engine: expect.objectContaining({ flavour: "python" }),
}),
);
expect(coldWorker.operations.map((operation) => operation.kind)).toEqual([
"load",
]);
expect(coldWorker.terminateCalls).toBe(1);
expect(readyWorker.operations.map((operation) => operation.kind)).toEqual([
"load",
"execute",
]);
supervisor.dispose();
});
}); });

View File

@@ -4,7 +4,7 @@ import type {
RegexReplacementRequest, RegexReplacementRequest,
RegexReplacementResult, RegexReplacementResult,
} from "../model/match"; } from "../model/match";
import { WorkerSupervisor } from "./WorkerSupervisor"; import { WorkerRequestError, WorkerSupervisor } from "./WorkerSupervisor";
import type { import type {
EngineWorkerOperation, EngineWorkerOperation,
EngineWorkerResult, EngineWorkerResult,
@@ -13,7 +13,7 @@ import {
ENGINE_WORKER_REGISTRY, ENGINE_WORKER_REGISTRY,
type EngineWorkerRegistry, type EngineWorkerRegistry,
} from "./engine-registry"; } from "./engine-registry";
import type { RegexFlavourId } from "../model/flavour"; import type { RegexEngineInfo, RegexFlavourId } from "../model/flavour";
export type EngineRuntimeState = export type EngineRuntimeState =
| { readonly status: "unloaded" } | { readonly status: "unloaded" }
@@ -28,6 +28,18 @@ export class EngineSupervisor {
RegexFlavourId, RegexFlavourId,
WorkerSupervisor<EngineWorkerOperation, EngineWorkerResult> WorkerSupervisor<EngineWorkerOperation, EngineWorkerResult>
>(); >();
private readonly loaded = new Map<
RegexFlavourId,
{
readonly generation: number;
readonly info: RegexEngineInfo;
}
>();
private readonly loading = new Map<
RegexFlavourId,
Promise<RegexEngineInfo>
>();
private readonly operationRevisions = new Map<RegexFlavourId, number>();
private disposed = false; private disposed = false;
constructor(registry: EngineWorkerRegistry = ENGINE_WORKER_REGISTRY) { constructor(registry: EngineWorkerRegistry = ENGINE_WORKER_REGISTRY) {
@@ -51,14 +63,76 @@ export class EngineSupervisor {
return supervisor; return supervisor;
} }
async load(flavour: RegexFlavourId): Promise<RegexEngineInfo> {
const registration = this.registry.require(flavour);
const supervisor = this.supervisorFor(flavour);
const loaded = this.loaded.get(flavour);
if (
loaded &&
supervisor.hasWorker &&
loaded.generation === supervisor.currentGeneration
) {
return loaded.info;
}
const current = this.loading.get(flavour);
if (current && supervisor.hasWorker) return current;
const loading: Promise<RegexEngineInfo> = supervisor
.run({ kind: "load" }, registration.startupTimeoutMs)
.then((response) => {
if (response.kind !== "load") {
throw new Error(
"Engine worker returned the wrong load response kind",
);
}
if (response.info.flavour !== flavour) {
throw new Error(
`Engine worker for ${flavour} loaded a ${response.info.flavour} runtime.`,
);
}
this.loaded.set(flavour, {
generation: supervisor.currentGeneration,
info: response.info,
});
return response.info;
})
.finally(() => {
if (this.loading.get(flavour) === loading) {
this.loading.delete(flavour);
}
});
this.loading.set(flavour, loading);
return loading;
}
private async prepareOperation(flavour: RegexFlavourId): Promise<{
readonly revision: number;
readonly supervisor: WorkerSupervisor<
EngineWorkerOperation,
EngineWorkerResult
>;
}> {
const revision = (this.operationRevisions.get(flavour) ?? 0) + 1;
this.operationRevisions.set(flavour, revision);
const supervisor = this.supervisorFor(flavour);
if (supervisor.isRunning) supervisor.cancel();
await this.load(flavour);
if (this.operationRevisions.get(flavour) !== revision) {
throw new WorkerRequestError(
"cancelled",
`${this.registry.require(flavour).label} request was superseded`,
);
}
return { revision, supervisor };
}
async execute( async execute(
request: RegexExecutionRequest, request: RegexExecutionRequest,
timeoutMs: number, timeoutMs: number,
): Promise<RegexExecutionResult> { ): Promise<RegexExecutionResult> {
const response = await this.supervisorFor(request.flavour).run( const { supervisor } = await this.prepareOperation(request.flavour);
const response = await supervisor.run(
{ kind: "execute", request }, { kind: "execute", request },
timeoutMs, timeoutMs,
{ supersede: true },
); );
if (response.kind !== "execute") { if (response.kind !== "execute") {
throw new Error("Engine worker returned the wrong response kind"); throw new Error("Engine worker returned the wrong response kind");
@@ -75,10 +149,10 @@ export class EngineSupervisor {
request: RegexReplacementRequest, request: RegexReplacementRequest,
timeoutMs: number, timeoutMs: number,
): Promise<RegexReplacementResult> { ): Promise<RegexReplacementResult> {
const response = await this.supervisorFor(request.flavour).run( const { supervisor } = await this.prepareOperation(request.flavour);
const response = await supervisor.run(
{ kind: "replace", request }, { kind: "replace", request },
timeoutMs, timeoutMs,
{ supersede: true },
); );
if (response.kind !== "replace") { if (response.kind !== "replace") {
throw new Error("Engine worker returned the wrong response kind"); throw new Error("Engine worker returned the wrong response kind");
@@ -92,7 +166,13 @@ export class EngineSupervisor {
} }
cancel(): void { cancel(): void {
for (const supervisor of this.supervisors.values()) supervisor.cancel(); for (const [flavour, supervisor] of this.supervisors) {
this.operationRevisions.set(
flavour,
(this.operationRevisions.get(flavour) ?? 0) + 1,
);
supervisor.cancel();
}
} }
dispose(): void { dispose(): void {
@@ -100,5 +180,8 @@ export class EngineSupervisor {
this.disposed = true; this.disposed = true;
for (const supervisor of this.supervisors.values()) supervisor.dispose(); for (const supervisor of this.supervisors.values()) supervisor.dispose();
this.supervisors.clear(); this.supervisors.clear();
this.loaded.clear();
this.loading.clear();
this.operationRevisions.clear();
} }
} }

View File

@@ -58,6 +58,10 @@ export class WorkerSupervisor<TOperation, TResult> {
return this.active !== null; return this.active !== null;
} }
get hasWorker(): boolean {
return this.worker !== null;
}
run( run(
operation: TOperation, operation: TOperation,
timeoutMs: number, timeoutMs: number,

View File

@@ -0,0 +1,263 @@
import { describe, expect, it, vi } from "vitest";
import { APPLICATION_VERSION } from "../../../../version";
import type { RegexExecutionRequest } from "../../../model/match";
import {
JavaEngineAdapter,
JAVA_BRIDGE_ABI_VERSION,
JAVA_ENGINE_IDENTITY,
JAVA_ENGINE_VERSION,
javaApplicationFlags,
normalizedJavaFlags,
} from "./JavaEngineAdapter";
import type { TeaVmJavaRegexModule } from "./java-module";
function fields(...values: readonly (string | number | boolean)[]): string {
return values
.map((value) => {
const encoded =
typeof value === "boolean" ? (value ? "1" : "0") : String(value);
return `${encoded.length}:${encoded}`;
})
.join("");
}
function module(
overrides: Partial<TeaVmJavaRegexModule> = {},
): TeaVmJavaRegexModule {
return {
bridgeAbiVersion: () => JAVA_BRIDGE_ABI_VERSION,
engineIdentity: () => JAVA_ENGINE_IDENTITY,
selfTest: () => 0,
execute: () => fields("1", "ok", 0, false, 0),
replace: () => fields("1", "ok", 0, false, 0, false, ""),
...overrides,
};
}
function request(
overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
return {
flavour: "java",
flavourVersion: "TeaVM java.util.regex 0.15.0",
pattern: "(?<letter>a)(b)?",
flags: ["g"],
subject: "ab a",
captureMetadata: [
{
number: 1,
name: "letter",
range: { startUtf16: 0, endUtf16: 12 },
repeated: false,
},
{
number: 2,
range: { startUtf16: 12, endUtf16: 16 },
repeated: false,
},
],
scanAll: false,
maximumMatches: 100,
maximumCaptureRows: 1_000,
...overrides,
};
}
describe("TeaVM Java engine adapter", () => {
it("loads the exact compatibility runtime identity", async () => {
const adapter = new JavaEngineAdapter(module(), "unit-test TeaVM runtime");
await expect(adapter.load()).resolves.toEqual(
expect.objectContaining({
flavour: "java",
adapterVersion: APPLICATION_VERSION,
engineName: "TeaVM java.util.regex",
engineVersion: JAVA_ENGINE_VERSION,
runtimeVersion: "unit-test TeaVM runtime",
offsetUnit: "utf16",
}),
);
});
it("rejects a module that fails ABI identity or self-test", async () => {
await expect(
new JavaEngineAdapter(module({ bridgeAbiVersion: () => 99 })).load(),
).rejects.toThrow(/identity or bridge self-test/u);
await expect(
new JavaEngineAdapter(module({ selfTest: () => 1 })).load(),
).rejects.toThrow(/identity or bridge self-test/u);
});
it("normalizes adapter flags and maps only native TeaVM bits", () => {
const normalized = normalizedJavaFlags(["U", "i"], true);
expect(normalized).toEqual({
userFlags: "iU",
effectiveFlags: "giuU",
internallyAddedIndicesFlag: false,
internallyAddedGlobalFlag: true,
});
expect(javaApplicationFlags(normalized)).toBe(
(1 << 0) | (1 << 3) | (1 << 5),
);
expect(javaApplicationFlags(normalizedJavaFlags(["d"], false))).toBe(
1 << 6,
);
expect(() => normalizedJavaFlags(["i", "i"], false)).toThrow(
/unsupported Java flags/u,
);
expect(() => normalizedJavaFlags(["y"], false)).toThrow(
/unsupported Java flags/u,
);
});
it("materializes UTF-16 offsets and numbered/named capture metadata", async () => {
const execute = vi.fn(() =>
fields("1", "ok", 2, false, 1, 0, 3, 0, 1, -1, -1),
);
const adapter = new JavaEngineAdapter(module({ execute }));
const result = await adapter.execute(
request({ pattern: "(?<letter>a)(😀)?", subject: "a😀" }),
);
expect(result.accepted).toBe(true);
expect(result.matches[0]).toEqual(
expect.objectContaining({
value: "a😀",
range: { startUtf16: 0, endUtf16: 3 },
nativeRange: { start: 0, end: 3, unit: "utf16" },
}),
);
expect(result.matches[0]?.captures).toEqual([
expect.objectContaining({
groupNumber: 1,
groupName: "letter",
value: "a",
status: "participated",
range: { startUtf16: 0, endUtf16: 1 },
}),
{
groupNumber: 2,
status: "did-not-participate",
},
]);
expect(execute).toHaveBeenCalledWith(
"(?<letter>a)(😀)?",
"a😀",
0,
true,
100,
1_000,
);
});
it("reports authoritative compile failures at pattern UTF-16 offsets", async () => {
const adapter = new JavaEngineAdapter(
module({
execute: () =>
fields(
"1",
"compile-error",
"PatternSyntaxException",
"Unclosed group",
8,
),
}),
);
const result = await adapter.execute(
request({ pattern: "abcdefgh(", subject: "" }),
);
expect(result.accepted).toBe(false);
expect(result.diagnostics[0]).toEqual(
expect.objectContaining({
code: "compile-error",
message: "PatternSyntaxException: Unclosed group",
range: { startUtf16: 8, endUtf16: 9 },
provenance: "reported",
}),
);
});
it("surfaces result caps and the U semantic boundary", async () => {
const adapter = new JavaEngineAdapter(
module({
execute: () => fields("1", "ok", 0, true, 1, 0, 0),
}),
);
const result = await adapter.execute(
request({
pattern: "(?=.)",
flags: ["U"],
subject: "x",
captureMetadata: [],
}),
);
expect(result.truncated).toBe(true);
expect(result.diagnostics.map(({ code }) => code)).toEqual([
"engine-compatibility",
"result-limit",
]);
});
it("uses bounded UTF-8 replacement output and reports native errors", async () => {
const adapter = new JavaEngineAdapter(
module({
replace: () => fields("1", "ok", 0, false, 0, false, "é😀x"),
}),
);
const bounded = await adapter.replace({
...request({ pattern: "x", subject: "x", captureMetadata: [] }),
replacement: "é😀x",
maximumOutputBytes: 3,
});
expect(bounded.output).toBe("é");
expect(bounded.outputBytes).toBe(2);
expect(bounded.outputTruncated).toBe(true);
expect(bounded.truncated).toBe(true);
expect(bounded.execution.diagnostics.at(-1)?.code).toBe(
"replacement-output-limit",
);
const failing = new JavaEngineAdapter(
module({
replace: () =>
fields(
"1",
"replacement-error",
"IllegalArgumentException",
"TeaVM Matcher rejected the replacement.",
-1,
),
}),
);
const error = await failing.replace({
...request(),
replacement: "${name}",
maximumOutputBytes: 100,
});
expect(error.execution.accepted).toBe(false);
expect(error.execution.diagnostics[0]?.code).toBe("replacement-error");
});
it("rejects malformed bridge data and requests outside shared bounds", async () => {
const malformed = new JavaEngineAdapter(
module({ execute: () => fields("1", "ok", 1, false, 1, 0, 1, -1, 0) }),
);
await expect(malformed.execute(request({ subject: "a" }))).rejects.toThrow(
/inconsistent capture offsets/u,
);
const adapter = new JavaEngineAdapter(module());
await expect(
adapter.execute(request({ maximumMatches: 10_001 })),
).rejects.toThrow(/bounded contract/u);
await expect(
adapter.replace({
...request(),
replacement: "x",
maximumOutputBytes: 0,
}),
).rejects.toThrow(/bounded contract/u);
});
});

View File

@@ -0,0 +1,657 @@
import type { RegexEngineAdapter } from "../../RegexEngineAdapter";
import { DEFAULT_REGEX_LIMITS, utf8ByteLength } from "../../request-limits";
import type { RegexDiagnostic } from "../../../model/diagnostics";
import type { RegexEngineInfo } from "../../../model/flavour";
import type {
CaptureResult,
RegexExecutionFlags,
RegexExecutionRequest,
RegexExecutionResult,
RegexMatchResult,
RegexReplacementRequest,
RegexReplacementResult,
} from "../../../model/match";
import type { TeaVmJavaRegexModule } from "./java-module";
import { APPLICATION_VERSION } from "../../../../version";
export const JAVA_ADAPTER_VERSION = APPLICATION_VERSION;
export const JAVA_ENGINE_VERSION = "0.15.0";
export const JAVA_ENGINE_IDENTITY = "TeaVM java.util.regex 0.15.0";
export const JAVA_BRIDGE_ABI_VERSION = 1;
const FLAG_ORDER = "gidmsuxU";
const CAPTURE_VALUE_PREVIEW_UTF16 = 64 * 1024;
const TOTAL_VALUE_PREVIEW_UTF16 = 8 * 1024 * 1024;
const FLAG_BITS = {
i: 1 << 0,
m: 1 << 1,
s: 1 << 2,
u: 1 << 3,
x: 1 << 4,
U: 1 << 5,
d: 1 << 6,
} as const;
interface BridgeFailure {
readonly status:
| "bridge-error"
| "compile-error"
| "execution-error"
| "limit-error"
| "replacement-error";
readonly name: string;
readonly message: string;
readonly index: number;
}
interface RawCapture {
readonly start: number;
readonly end: number;
}
interface RawMatch {
readonly start: number;
readonly end: number;
readonly captures: readonly RawCapture[];
}
interface BridgeSuccess {
readonly status: "ok";
readonly groupCount: number;
readonly resultsTruncated: boolean;
readonly matches: readonly RawMatch[];
readonly outputHardTruncated?: boolean;
readonly output?: string;
}
type BridgeResult = BridgeFailure | BridgeSuccess;
class FieldReader {
readonly #encoded: string;
#cursor = 0;
constructor(encoded: string) {
this.#encoded = encoded;
}
field(): string {
const colon = this.#encoded.indexOf(":", this.#cursor);
if (colon < 0) {
throw new Error("TeaVM Java bridge returned a malformed field length.");
}
const lengthText = this.#encoded.slice(this.#cursor, colon);
if (!/^(?:0|[1-9][0-9]*)$/u.test(lengthText)) {
throw new Error("TeaVM Java bridge returned an invalid field length.");
}
const length = Number.parseInt(lengthText, 10);
const start = colon + 1;
const end = start + length;
if (!Number.isSafeInteger(length) || end > this.#encoded.length) {
throw new Error(
"TeaVM Java bridge returned a field outside its payload.",
);
}
this.#cursor = end;
return this.#encoded.slice(start, end);
}
integer(minimum: number, maximum: number): number {
const value = this.field();
if (!/^-?(?:0|[1-9][0-9]*)$/u.test(value)) {
throw new Error("TeaVM Java bridge returned an invalid integer.");
}
const parsed = Number.parseInt(value, 10);
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new Error("TeaVM Java bridge returned an out-of-range integer.");
}
return parsed;
}
boolean(): boolean {
const value = this.field();
if (value !== "0" && value !== "1") {
throw new Error("TeaVM Java bridge returned an invalid boolean.");
}
return value === "1";
}
finish(): void {
if (this.#cursor !== this.#encoded.length) {
throw new Error("TeaVM Java bridge returned trailing payload data.");
}
}
}
class ValuePreviewBudget {
#remaining = TOTAL_VALUE_PREVIEW_UTF16;
truncated = false;
take(value: string): { readonly value: string; readonly truncated: boolean } {
const maximum = Math.min(CAPTURE_VALUE_PREVIEW_UTF16, this.#remaining);
const preview = value.slice(0, maximum);
this.#remaining -= preview.length;
const truncated = preview.length !== value.length;
this.truncated ||= truncated;
return { value: preview, truncated };
}
}
export function javaEngineInfo(runtimeVersion?: string): RegexEngineInfo {
return {
flavour: "java",
adapterVersion: JAVA_ADAPTER_VERSION,
engineName: "TeaVM java.util.regex",
engineVersion: JAVA_ENGINE_VERSION,
runtimeVersion: runtimeVersion ?? "TeaVM JavaScript · not OpenJDK",
offsetUnit: "utf16",
capabilities: {
compilation: true,
matching: true,
replacement: true,
namedCaptures: true,
captureHistory: false,
actualTrace: false,
benchmark: false,
},
};
}
export function normalizedJavaFlags(
userFlags: readonly string[],
scanAll: boolean,
): RegexExecutionFlags {
if (
new Set(userFlags).size !== userFlags.length ||
userFlags.some((flag) => !FLAG_ORDER.includes(flag))
) {
throw new RangeError("The request contains unsupported Java flags.");
}
const selected = new Set(userFlags);
const user = [...FLAG_ORDER].filter((flag) => selected.has(flag)).join("");
const internallyAddedGlobalFlag = scanAll && !selected.has("g");
if (internallyAddedGlobalFlag) selected.add("g");
if (selected.has("U")) selected.add("u");
return {
userFlags: user,
effectiveFlags: [...FLAG_ORDER]
.filter((flag) => selected.has(flag))
.join(""),
internallyAddedIndicesFlag: false,
internallyAddedGlobalFlag,
};
}
export function javaApplicationFlags(flags: RegexExecutionFlags): number {
let result = 0;
for (const flag of flags.effectiveFlags) {
result |= FLAG_BITS[flag as keyof typeof FLAG_BITS] ?? 0;
}
return result;
}
function validateRequest(
request: RegexExecutionRequest,
replacement?: RegexReplacementRequest,
): void {
if (request.flavour !== "java") {
throw new Error(`Java adapter cannot execute ${request.flavour}.`);
}
if (request.pattern.length > DEFAULT_REGEX_LIMITS.patternHardLengthUtf16) {
throw new RangeError(
`Java patterns are limited to ${DEFAULT_REGEX_LIMITS.patternHardLengthUtf16.toLocaleString()} UTF-16 units.`,
);
}
if (
utf8ByteLength(request.subject) >
DEFAULT_REGEX_LIMITS.interactiveSubjectHardBytes
) {
throw new RangeError("Java subjects are limited to 16 MiB of UTF-8.");
}
if (
!Number.isSafeInteger(request.maximumMatches) ||
request.maximumMatches < 1 ||
request.maximumMatches > DEFAULT_REGEX_LIMITS.maximumMatches ||
!Number.isSafeInteger(request.maximumCaptureRows) ||
request.maximumCaptureRows < 1 ||
request.maximumCaptureRows > DEFAULT_REGEX_LIMITS.maximumCaptureRows
) {
throw new RangeError(
"Java result limits are outside the bounded contract.",
);
}
if (
replacement &&
(replacement.replacement.length >
DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16 ||
!Number.isSafeInteger(replacement.maximumOutputBytes) ||
replacement.maximumOutputBytes < 1 ||
replacement.maximumOutputBytes >
DEFAULT_REGEX_LIMITS.maximumReplacementOutputBytes)
) {
throw new RangeError(
"Java replacement or output limits are outside the bounded contract.",
);
}
}
function decodeBridgeResult(
encoded: string,
patternLength: number,
subjectLength: number,
maximumMatches: number,
replacement: boolean,
): BridgeResult {
const reader = new FieldReader(encoded);
if (reader.field() !== "1") {
throw new Error("TeaVM Java bridge ABI payload version is unsupported.");
}
const status = reader.field();
if (status !== "ok") {
if (
status !== "bridge-error" &&
status !== "compile-error" &&
status !== "execution-error" &&
status !== "limit-error" &&
status !== "replacement-error"
) {
throw new Error("TeaVM Java bridge returned an unknown failure status.");
}
const failure: BridgeFailure = {
status,
name: reader.field(),
message: reader.field(),
index: reader.integer(-1, patternLength),
};
reader.finish();
return failure;
}
const groupCount = reader.integer(
0,
DEFAULT_REGEX_LIMITS.maximumCaptureGroups,
);
const resultsTruncated = reader.boolean();
const matchCount = reader.integer(0, maximumMatches);
const matches: RawMatch[] = [];
for (let matchNumber = 0; matchNumber < matchCount; matchNumber += 1) {
const start = reader.integer(0, subjectLength);
const end = reader.integer(start, subjectLength);
const captures: RawCapture[] = [];
for (let group = 1; group <= groupCount; group += 1) {
const captureStart = reader.integer(-1, subjectLength);
const captureEnd = reader.integer(-1, subjectLength);
if (
(captureStart === -1) !== (captureEnd === -1) ||
(captureStart >= 0 && captureEnd < captureStart)
) {
throw new Error(
"TeaVM Java bridge returned inconsistent capture offsets.",
);
}
captures.push({ start: captureStart, end: captureEnd });
}
matches.push({ start, end, captures });
}
const result: BridgeSuccess = replacement
? {
status: "ok",
groupCount,
resultsTruncated,
matches,
outputHardTruncated: reader.boolean(),
output: reader.field(),
}
: {
status: "ok",
groupCount,
resultsTruncated,
matches,
};
reader.finish();
return result;
}
function bridgeFailureDiagnostic(
failure: BridgeFailure,
request: RegexExecutionRequest,
): RegexDiagnostic {
const code =
failure.status === "compile-error"
? "compile-error"
: failure.status === "replacement-error"
? "replacement-error"
: failure.status === "limit-error"
? "resource-limit"
: "execution-error";
const index = failure.index;
return {
id: `java-engine-${failure.status}`,
source: "execution-engine",
severity: "error",
code,
message: failure.message
? `${failure.name}: ${failure.message}`
: failure.name,
...(index >= 0 && index <= request.pattern.length
? {
range: {
startUtf16: index,
endUtf16: Math.min(request.pattern.length, index + 1),
},
}
: {}),
flavour: "java",
provenance: "reported",
};
}
function emptyExecution(
info: RegexEngineInfo,
flags: RegexExecutionFlags,
start: number,
diagnostic: RegexDiagnostic,
): RegexExecutionResult {
return {
accepted: false,
engine: info,
flags,
matches: [],
diagnostics: [diagnostic],
elapsedMs: performance.now() - start,
truncated: false,
};
}
function materializeMatches(
raw: BridgeSuccess,
request: RegexExecutionRequest,
): {
readonly matches: readonly RegexMatchResult[];
readonly valuesTruncated: boolean;
} {
const names = new Map(
request.captureMetadata
.filter(
(capture) => capture.number >= 1 && capture.number <= raw.groupCount,
)
.map((capture) => [capture.number, capture.name]),
);
const previews = new ValuePreviewBudget();
const matches = raw.matches.map((match, matchIndex) => {
const value = request.subject.slice(match.start, match.end);
const matchPreview = previews.take(value);
const captures: CaptureResult[] = match.captures.map(
(capture, captureIndex) => {
const groupNumber = captureIndex + 1;
const groupName = names.get(groupNumber);
if (capture.start < 0) {
return {
groupNumber,
...(groupName ? { groupName } : {}),
status: "did-not-participate" as const,
};
}
const captureValue = request.subject.slice(capture.start, capture.end);
const preview = previews.take(captureValue);
return {
groupNumber,
...(groupName ? { groupName } : {}),
value: preview.value,
status: preview.truncated
? ("truncated" as const)
: capture.start === capture.end
? ("matched-empty" as const)
: ("participated" as const),
range: {
startUtf16: capture.start,
endUtf16: capture.end,
},
nativeRange: {
start: capture.start,
end: capture.end,
unit: "utf16" as const,
},
};
},
);
return {
matchNumber: matchIndex + 1,
value: matchPreview.value,
valueStatus: matchPreview.truncated
? ("truncated" as const)
: ("complete" as const),
range: { startUtf16: match.start, endUtf16: match.end },
nativeRange: {
start: match.start,
end: match.end,
unit: "utf16" as const,
},
captures,
};
});
return { matches, valuesTruncated: previews.truncated };
}
function compatibilityDiagnostics(
flags: RegexExecutionFlags,
): RegexDiagnostic[] {
return flags.effectiveFlags.includes("U")
? [
{
id: "java-teavm-unicode-character-class-compatibility",
source: "execution-engine",
severity: "warning",
code: "engine-compatibility",
message:
"TeaVM 0.15.0 does not implement OpenJDK Pattern.UNICODE_CHARACTER_CLASS. U implies Unicode case folding here, while predefined classes and word boundaries retain TeaVM class-library behaviour.",
flavour: "java",
provenance: "reported",
},
]
: [];
}
function utf8Prefix(
value: string,
maximumBytes: number,
): {
readonly value: string;
readonly bytes: number;
readonly complete: boolean;
} {
let bytes = 0;
let index = 0;
while (index < value.length) {
const first = value.charCodeAt(index);
let width: number;
let units = 1;
if (first <= 0x7f) {
width = 1;
} else if (first <= 0x7ff) {
width = 2;
} else if (
first >= 0xd800 &&
first <= 0xdbff &&
index + 1 < value.length &&
value.charCodeAt(index + 1) >= 0xdc00 &&
value.charCodeAt(index + 1) <= 0xdfff
) {
width = 4;
units = 2;
} else {
width = 3;
}
if (bytes + width > maximumBytes) break;
bytes += width;
index += units;
}
return {
value: value.slice(0, index),
bytes,
complete: index === value.length,
};
}
export class JavaEngineAdapter implements RegexEngineAdapter {
readonly flavour = "java" as const;
readonly #module: TeaVmJavaRegexModule;
readonly #info: RegexEngineInfo;
constructor(module: TeaVmJavaRegexModule, runtimeVersion?: string) {
this.#module = module;
this.#info = javaEngineInfo(runtimeVersion);
}
async load(): Promise<RegexEngineInfo> {
if (
this.#module.bridgeAbiVersion() !== JAVA_BRIDGE_ABI_VERSION ||
this.#module.engineIdentity() !== JAVA_ENGINE_IDENTITY ||
this.#module.selfTest() !== 0
) {
throw new Error(
"The bundled TeaVM Java regex engine failed its identity or bridge self-test.",
);
}
return this.#info;
}
async execute(request: RegexExecutionRequest): Promise<RegexExecutionResult> {
const result = this.#run(request);
if (!("execution" in result)) return result;
throw new Error("Java execution returned a replacement result.");
}
async replace(
request: RegexReplacementRequest,
): Promise<RegexReplacementResult> {
const result = this.#run(request, request);
if ("execution" in result) return result;
throw new Error("Java replacement returned an execution-only result.");
}
#run(
request: RegexExecutionRequest,
replacementRequest?: RegexReplacementRequest,
): RegexExecutionResult | RegexReplacementResult {
const start = performance.now();
const flags = normalizedJavaFlags(request.flags, request.scanAll);
validateRequest(request, replacementRequest);
const applicationFlags = javaApplicationFlags(flags);
const iterate = flags.effectiveFlags.includes("g");
const encoded = replacementRequest
? this.#module.replace(
request.pattern,
request.subject,
replacementRequest.replacement,
applicationFlags,
iterate,
request.maximumMatches,
request.maximumCaptureRows,
)
: this.#module.execute(
request.pattern,
request.subject,
applicationFlags,
iterate,
request.maximumMatches,
request.maximumCaptureRows,
);
const bridge = decodeBridgeResult(
encoded,
request.pattern.length,
request.subject.length,
request.maximumMatches,
replacementRequest !== undefined,
);
if (bridge.status !== "ok") {
const execution = emptyExecution(
this.#info,
flags,
start,
bridgeFailureDiagnostic(bridge, request),
);
return replacementRequest
? {
execution,
output: "",
outputBytes: 0,
outputTruncated: false,
truncated: false,
}
: execution;
}
const materialized = materializeMatches(bridge, request);
const diagnostics = compatibilityDiagnostics(flags);
if (bridge.resultsTruncated) {
diagnostics.push({
id: "java-results-truncated",
source: "execution-engine",
severity: "warning",
code: "result-limit",
message: `Results reached the configured limit (${request.maximumMatches.toLocaleString()} matches or ${request.maximumCaptureRows.toLocaleString()} capture rows).`,
flavour: "java",
provenance: "derived",
});
}
if (materialized.valuesTruncated) {
diagnostics.push({
id: "java-value-previews-truncated",
source: "execution-engine",
severity: "warning",
code: "result-value-preview-limit",
message:
"Displayed TeaVM Java result values were clipped to bounded previews; exact UTF-16 ranges are retained.",
flavour: "java",
provenance: "derived",
});
}
const execution: RegexExecutionResult = {
accepted: true,
engine: this.#info,
flags,
matches: materialized.matches,
diagnostics,
elapsedMs: performance.now() - start,
truncated: bridge.resultsTruncated,
};
if (!replacementRequest) return execution;
const bounded = utf8Prefix(
bridge.output ?? "",
replacementRequest.maximumOutputBytes,
);
const outputTruncated =
bridge.outputHardTruncated === true || !bounded.complete;
const executionWithOutputDiagnostic: RegexExecutionResult = outputTruncated
? {
...execution,
diagnostics: [
...execution.diagnostics,
{
id: "java-output-truncated",
source: "execution-engine",
severity: "warning",
code: "replacement-output-limit",
message: `Replacement preview was truncated at ${replacementRequest.maximumOutputBytes.toLocaleString()} UTF-8 bytes or the bounded TeaVM bridge cap.`,
flavour: "java",
provenance: "derived",
},
],
}
: execution;
return {
execution: executionWithOutputDiagnostic,
output: bounded.value,
outputBytes: bounded.bytes,
outputTruncated,
truncated: outputTruncated || bridge.resultsTruncated,
};
}
terminate(): void {
// Lifecycle termination is performed by killing the containing worker.
}
}

View File

@@ -0,0 +1,48 @@
export interface TeaVmJavaRegexModule {
readonly bridgeAbiVersion: () => number;
readonly engineIdentity: () => string;
readonly selfTest: () => number;
readonly execute: (
pattern: string,
subject: string,
applicationFlags: number,
iterate: boolean,
maximumMatches: number,
maximumCaptureRows: number,
) => string;
readonly replace: (
pattern: string,
subject: string,
replacement: string,
applicationFlags: number,
iterate: boolean,
maximumMatches: number,
maximumCaptureRows: number,
) => string;
}
function isJavaRegexModule(
imported: Partial<TeaVmJavaRegexModule>,
): imported is TeaVmJavaRegexModule {
return (
typeof imported.bridgeAbiVersion === "function" &&
typeof imported.engineIdentity === "function" &&
typeof imported.selfTest === "function" &&
typeof imported.execute === "function" &&
typeof imported.replace === "function"
);
}
export async function importJavaRegexModule(
moduleUrl: URL,
): Promise<TeaVmJavaRegexModule> {
const imported = (await import(
/* @vite-ignore */ moduleUrl.href
)) as Partial<TeaVmJavaRegexModule>;
if (!isJavaRegexModule(imported)) {
throw new Error(
"The bundled TeaVM Java regex module is missing its bridge exports.",
);
}
return imported;
}

View File

@@ -0,0 +1,215 @@
import { describe, expect, it } from "vitest";
import type {
RegexExecutionRequest,
RegexReplacementRequest,
} from "../../../model/match";
import {
PythonEngineAdapter,
PYODIDE_ENGINE_VERSION,
PYTHON_BRIDGE_ABI_VERSION,
PYTHON_ENGINE_VERSION,
} from "./PythonEngineAdapter";
import type { PyodideRuntime, PythonCallableProxy } from "./python-module";
function request(
overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
return {
flavour: "python",
flavourVersion: PYTHON_ENGINE_VERSION,
pattern: "(?P<emoji>😀)(x)?",
flags: ["g"],
subject: "a😀b",
captureMetadata: [],
scanAll: false,
maximumMatches: 100,
maximumCaptureRows: 1_000,
...overrides,
};
}
function callable(
responder: (
operation: "identity" | "execute" | "replace",
) => Record<string, unknown>,
): PythonCallableProxy & { destroyed: boolean } {
const bridge = ((operation: "identity" | "execute" | "replace") =>
JSON.stringify(responder(operation))) as unknown as PythonCallableProxy & {
destroyed: boolean;
};
bridge.destroyed = false;
bridge.destroy = () => {
bridge.destroyed = true;
};
return bridge;
}
class FakePyodide implements PyodideRuntime {
readonly version = PYODIDE_ENGINE_VERSION;
readonly bridge: PythonCallableProxy & { destroyed: boolean };
runPythonCalls = 0;
constructor(
responder: (
operation: "identity" | "execute" | "replace",
) => Record<string, unknown>,
) {
this.bridge = callable(responder);
}
runPython(source: string): unknown {
void source;
this.runPythonCalls += 1;
return this.bridge;
}
}
function identity(): Record<string, unknown> {
return {
abi: PYTHON_BRIDGE_ABI_VERSION,
ok: true,
identity: {
implementation: "cpython",
pythonVersion: "3.14.2",
reModule: "re",
},
};
}
describe("Python Pyodide adapter", () => {
it("normalizes authoritative code-point ranges and capture names", async () => {
const runtime = new FakePyodide((operation) =>
operation === "identity"
? identity()
: {
abi: PYTHON_BRIDGE_ABI_VERSION,
ok: true,
groupCount: 2,
groupNames: [[1, "emoji"]],
matches: [
{
span: [1, 2],
captures: [[1, 2], null],
},
],
resultsTruncated: false,
},
);
const adapter = new PythonEngineAdapter(runtime, "unit-test runtime");
await expect(adapter.load()).resolves.toEqual(
expect.objectContaining({
flavour: "python",
engineVersion: PYTHON_ENGINE_VERSION,
offsetUnit: "code-point",
}),
);
const result = await adapter.execute(request());
expect(result.accepted).toBe(true);
expect(result.matches[0]).toEqual(
expect.objectContaining({
value: "😀",
range: { startUtf16: 1, endUtf16: 3 },
nativeRange: { start: 1, end: 2, unit: "code-point" },
}),
);
expect(result.matches[0]?.captures).toEqual([
expect.objectContaining({
groupNumber: 1,
groupName: "emoji",
value: "😀",
range: { startUtf16: 1, endUtf16: 3 },
}),
{
groupNumber: 2,
status: "did-not-participate",
},
]);
expect(runtime.runPythonCalls).toBe(1);
});
it("normalizes compile positions and scan-all flags", async () => {
const runtime = new FakePyodide((operation) =>
operation === "identity"
? identity()
: {
abi: PYTHON_BRIDGE_ABI_VERSION,
ok: false,
phase: "compile",
code: "compile-error",
message: "missing )",
position: 2,
},
);
const adapter = new PythonEngineAdapter(runtime);
const result = await adapter.execute(
request({ pattern: "😀(", flags: ["i"], scanAll: true }),
);
expect(result.accepted).toBe(false);
expect(result.flags).toEqual({
userFlags: "i",
effectiveFlags: "gi",
internallyAddedIndicesFlag: false,
internallyAddedGlobalFlag: true,
});
expect(result.diagnostics[0]).toEqual(
expect.objectContaining({
code: "compile-error",
range: { startUtf16: 3, endUtf16: 3 },
}),
);
});
it("returns bounded native replacement output and disposes its proxy", async () => {
const runtime = new FakePyodide((operation) => {
if (operation === "identity") return identity();
return {
abi: PYTHON_BRIDGE_ABI_VERSION,
ok: true,
groupCount: 1,
groupNames: [[1, "emoji"]],
matches: [{ span: [1, 2], captures: [[1, 2]] }],
resultsTruncated: false,
output: "a[😀]",
outputBytes: 7,
outputTruncated: true,
};
});
const adapter = new PythonEngineAdapter(runtime);
const replacementRequest: RegexReplacementRequest = {
...request(),
replacement: "[\\g<emoji>]",
maximumOutputBytes: 7,
};
const result = await adapter.replace(replacementRequest);
expect(result.output).toBe("a[😀]");
expect(result.outputBytes).toBe(7);
expect(result.outputTruncated).toBe(true);
expect(result.execution.diagnostics).toEqual([
expect.objectContaining({ code: "replacement-output-limit" }),
]);
adapter.terminate();
expect(runtime.bridge.destroyed).toBe(true);
await expect(adapter.execute(request())).rejects.toThrow("terminated");
});
it("rejects unsupported flags and unbounded output requests", async () => {
const runtime = new FakePyodide(identity);
const adapter = new PythonEngineAdapter(runtime);
await expect(adapter.execute(request({ flags: ["u"] }))).rejects.toThrow(
"unsupported Python re flags",
);
await expect(
adapter.replace({
...request(),
replacement: "x",
maximumOutputBytes: 0,
}),
).rejects.toThrow("outside the bounded contract");
});
});

View File

@@ -0,0 +1,734 @@
import type { RegexEngineAdapter } from "../../RegexEngineAdapter";
import { DEFAULT_REGEX_LIMITS, utf8ByteLength } from "../../request-limits";
import type { RegexDiagnostic } from "../../../model/diagnostics";
import type { RegexEngineInfo } from "../../../model/flavour";
import type {
CaptureResult,
RegexExecutionFlags,
RegexExecutionRequest,
RegexExecutionResult,
RegexMatchResult,
RegexReplacementRequest,
RegexReplacementResult,
} from "../../../model/match";
import { APPLICATION_VERSION } from "../../../../version";
import PYTHON_BRIDGE_SOURCE from "../../../../../engines/python/bridge.py?raw";
import type { PyodideRuntime, PythonCallableProxy } from "./python-module";
export const PYTHON_BRIDGE_ABI_VERSION = 1;
export const PYTHON_ENGINE_VERSION = "CPython 3.14.2 re";
export const PYODIDE_ENGINE_VERSION = "314.0.3";
const FLAG_ORDER = "gaimsx";
const CAPTURE_VALUE_PREVIEW_UTF16 = 64 * 1024;
const TOTAL_VALUE_PREVIEW_UTF16 = 8 * 1024 * 1024;
const EXECUTION_RESULT_JSON_UTF16_LIMIT = 32 * 1024 * 1024;
interface NativeError {
readonly abi: number;
readonly ok: false;
readonly phase: "bridge" | "compile" | "match" | "replacement";
readonly code: string;
readonly message: string;
readonly position?: number;
}
interface NativeIdentity {
readonly abi: number;
readonly ok: true;
readonly identity: {
readonly implementation: string;
readonly pythonVersion: string;
readonly reModule: string;
};
}
type NativeSpan = readonly [number, number];
interface NativeMatch {
readonly span: NativeSpan;
readonly captures: readonly (NativeSpan | null)[];
}
interface NativeExecution {
readonly abi: number;
readonly ok: true;
readonly groupCount: number;
readonly groupNames: readonly (readonly [number, string])[];
readonly matches: readonly NativeMatch[];
readonly resultsTruncated: boolean;
readonly output?: string;
readonly outputBytes?: number;
readonly outputTruncated?: boolean;
}
class ValuePreviewBudget {
#remaining = TOTAL_VALUE_PREVIEW_UTF16;
truncated = false;
take(value: string): { readonly value: string; readonly truncated: boolean } {
const maximum = Math.min(CAPTURE_VALUE_PREVIEW_UTF16, this.#remaining);
const preview = value.slice(0, maximum);
this.#remaining -= preview.length;
const truncated = preview.length !== value.length;
this.truncated ||= truncated;
return { value: preview, truncated };
}
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function codePointLength(value: string): number {
let length = 0;
for (let index = 0; index < value.length; index += 1) {
const first = value.charCodeAt(index);
if (first >= 0xd800 && first <= 0xdbff && index + 1 < value.length) {
const second = value.charCodeAt(index + 1);
if (second >= 0xdc00 && second <= 0xdfff) index += 1;
}
length += 1;
}
return length;
}
function codePointOffsetToUtf16(value: string, target: number): number {
if (!Number.isSafeInteger(target) || target < 0) {
throw new RangeError(`Invalid code-point offset ${target}.`);
}
let codePointOffset = 0;
let utf16Offset = 0;
if (target === 0) return 0;
for (const character of value) {
codePointOffset += 1;
utf16Offset += character.length;
if (codePointOffset === target) return utf16Offset;
}
throw new RangeError(`Code-point offset ${target} is outside the input.`);
}
function selectedCodePointOffsetsToUtf16(
value: string,
selected: ReadonlySet<number>,
): ReadonlyMap<number, number> {
const result = new Map<number, number>();
let codePointOffset = 0;
let utf16Offset = 0;
if (selected.has(0)) result.set(0, 0);
for (const character of value) {
codePointOffset += 1;
utf16Offset += character.length;
if (selected.has(codePointOffset)) {
result.set(codePointOffset, utf16Offset);
}
}
if (result.size !== selected.size) {
throw new Error("A Python native range is outside the subject.");
}
return result;
}
function parseBridgeJson(value: unknown, maximumUtf16: number): unknown {
if (typeof value !== "string") {
throw new Error("The Python bridge returned a non-string result.");
}
if (value.length > maximumUtf16) {
throw new Error("The Python bridge result exceeds its bounded contract.");
}
try {
return JSON.parse(value) as unknown;
} catch {
throw new Error("The Python bridge returned invalid JSON.");
}
}
function parseNativeError(value: unknown): NativeError | undefined {
if (!isRecord(value) || value.ok !== false) return undefined;
if (
value.abi !== PYTHON_BRIDGE_ABI_VERSION ||
!["bridge", "compile", "match", "replacement"].includes(
String(value.phase),
) ||
typeof value.code !== "string" ||
typeof value.message !== "string" ||
(value.position !== undefined &&
(!Number.isSafeInteger(value.position) || Number(value.position) < 0))
) {
throw new Error("The Python bridge returned an invalid error record.");
}
return value as unknown as NativeError;
}
function requireIdentity(value: unknown): NativeIdentity {
if (
!isRecord(value) ||
value.abi !== PYTHON_BRIDGE_ABI_VERSION ||
value.ok !== true ||
!isRecord(value.identity) ||
typeof value.identity.implementation !== "string" ||
typeof value.identity.pythonVersion !== "string" ||
typeof value.identity.reModule !== "string"
) {
throw new Error("The Python bridge returned an invalid identity record.");
}
return value as unknown as NativeIdentity;
}
function isNativeSpan(
value: unknown,
maximumCodePoint: number,
): value is NativeSpan {
return (
Array.isArray(value) &&
value.length === 2 &&
Number.isSafeInteger(value[0]) &&
Number.isSafeInteger(value[1]) &&
Number(value[0]) >= 0 &&
Number(value[0]) <= Number(value[1]) &&
Number(value[1]) <= maximumCodePoint
);
}
function requireExecution(
value: unknown,
request: RegexExecutionRequest,
subjectCodePointLength: number,
replacementRequest?: RegexReplacementRequest,
): NativeExecution {
if (
!isRecord(value) ||
value.abi !== PYTHON_BRIDGE_ABI_VERSION ||
value.ok !== true ||
!Number.isSafeInteger(value.groupCount) ||
Number(value.groupCount) < 0 ||
Number(value.groupCount) > DEFAULT_REGEX_LIMITS.maximumCaptureGroups ||
!Array.isArray(value.groupNames) ||
!Array.isArray(value.matches) ||
typeof value.resultsTruncated !== "boolean" ||
value.matches.length > request.maximumMatches
) {
throw new Error("The Python bridge returned an invalid result record.");
}
const groupCount = Number(value.groupCount);
const names = new Set<number>();
for (const entry of value.groupNames) {
if (
!Array.isArray(entry) ||
entry.length !== 2 ||
!Number.isSafeInteger(entry[0]) ||
Number(entry[0]) < 1 ||
Number(entry[0]) > groupCount ||
names.has(Number(entry[0])) ||
typeof entry[1] !== "string"
) {
throw new Error("The Python bridge returned invalid capture names.");
}
names.add(Number(entry[0]));
}
let captureRows = 0;
for (const entry of value.matches) {
if (
!isRecord(entry) ||
!isNativeSpan(entry.span, subjectCodePointLength) ||
!Array.isArray(entry.captures) ||
entry.captures.length !== groupCount
) {
throw new Error("The Python bridge returned an invalid match record.");
}
captureRows += Math.max(1, groupCount);
if (captureRows > request.maximumCaptureRows) {
throw new Error("The Python bridge exceeded the capture-row limit.");
}
for (const capture of entry.captures) {
if (capture !== null && !isNativeSpan(capture, subjectCodePointLength)) {
throw new Error("The Python bridge returned an invalid capture range.");
}
}
}
if (replacementRequest) {
if (
typeof value.output !== "string" ||
!Number.isSafeInteger(value.outputBytes) ||
Number(value.outputBytes) < 0 ||
Number(value.outputBytes) > replacementRequest.maximumOutputBytes ||
utf8ByteLength(value.output) !== value.outputBytes ||
typeof value.outputTruncated !== "boolean"
) {
throw new Error(
"The Python bridge returned invalid bounded replacement output.",
);
}
} else if (
value.output !== undefined ||
value.outputBytes !== undefined ||
value.outputTruncated !== undefined
) {
throw new Error(
"The Python bridge returned replacement data for an execution request.",
);
}
return value as unknown as NativeExecution;
}
function normalizedPythonFlags(
userFlags: readonly string[],
scanAll: boolean,
): RegexExecutionFlags {
const selected = new Set(userFlags);
const internallyAddedGlobalFlag = scanAll && !selected.has("g");
if (internallyAddedGlobalFlag) selected.add("g");
return {
userFlags: [...FLAG_ORDER]
.filter((flag) => userFlags.includes(flag))
.join(""),
effectiveFlags: [...FLAG_ORDER]
.filter((flag) => selected.has(flag))
.join(""),
internallyAddedIndicesFlag: false,
internallyAddedGlobalFlag,
};
}
function validateRequest(
request: RegexExecutionRequest,
replacementRequest?: RegexReplacementRequest,
): void {
if (request.flavour !== "python") {
throw new Error(`Python adapter cannot execute ${request.flavour}.`);
}
if (
new Set(request.flags).size !== request.flags.length ||
request.flags.some(
(flag) => flag.length !== 1 || !FLAG_ORDER.includes(flag),
)
) {
throw new RangeError("The request contains unsupported Python re flags.");
}
if (request.pattern.length > DEFAULT_REGEX_LIMITS.patternHardLengthUtf16) {
throw new RangeError(
`Python re patterns are limited to ${DEFAULT_REGEX_LIMITS.patternHardLengthUtf16.toLocaleString()} UTF-16 units.`,
);
}
if (
utf8ByteLength(request.subject) >
DEFAULT_REGEX_LIMITS.interactiveSubjectHardBytes
) {
throw new RangeError("Python re subjects are limited to 16 MiB of UTF-8.");
}
if (
!Number.isSafeInteger(request.maximumMatches) ||
request.maximumMatches < 1 ||
request.maximumMatches > DEFAULT_REGEX_LIMITS.maximumMatches ||
!Number.isSafeInteger(request.maximumCaptureRows) ||
request.maximumCaptureRows < 1 ||
request.maximumCaptureRows > DEFAULT_REGEX_LIMITS.maximumCaptureRows
) {
throw new RangeError(
"Python re result limits are outside the bounded contract.",
);
}
if (request.options && Object.keys(request.options).length > 0) {
throw new RangeError(
"The Python re engine does not accept engine options.",
);
}
if (
replacementRequest &&
(replacementRequest.replacement.length >
DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16 ||
!Number.isSafeInteger(replacementRequest.maximumOutputBytes) ||
replacementRequest.maximumOutputBytes < 1 ||
replacementRequest.maximumOutputBytes >
DEFAULT_REGEX_LIMITS.maximumReplacementOutputBytes)
) {
throw new RangeError(
"Python replacement or output limits are outside the bounded contract.",
);
}
}
export function pythonEngineInfo(runtimeIdentity?: string): RegexEngineInfo {
return {
flavour: "python",
adapterVersion: APPLICATION_VERSION,
engineName: "CPython re via Pyodide",
engineVersion: PYTHON_ENGINE_VERSION,
runtimeVersion: [
`Pyodide ${PYODIDE_ENGINE_VERSION}`,
"WebAssembly",
runtimeIdentity,
]
.filter(Boolean)
.join(" · "),
offsetUnit: "code-point",
capabilities: {
compilation: true,
matching: true,
replacement: true,
namedCaptures: true,
captureHistory: false,
actualTrace: false,
benchmark: false,
},
};
}
function diagnosticForError(
error: NativeError,
pattern: string,
): RegexDiagnostic {
let range:
{ readonly startUtf16: number; readonly endUtf16: number } | undefined;
if (
error.phase === "compile" &&
error.position !== undefined &&
error.position <= codePointLength(pattern)
) {
const offset = codePointOffsetToUtf16(pattern, error.position);
range = { startUtf16: offset, endUtf16: offset };
}
return {
id: `python-${error.phase}-${error.code}`,
source: "execution-engine",
severity: "error",
code: error.code,
message: `Python re ${error.phase} error: ${error.message}`,
...(range ? { range } : {}),
flavour: "python",
provenance: "reported",
};
}
function materializeMatches(
native: NativeExecution,
request: RegexExecutionRequest,
): {
readonly matches: readonly RegexMatchResult[];
readonly valuesTruncated: boolean;
} {
const names = new Map(native.groupNames);
const previews = new ValuePreviewBudget();
const selectedOffsets = new Set<number>();
for (const match of native.matches) {
selectedOffsets.add(match.span[0]);
selectedOffsets.add(match.span[1]);
for (const capture of match.captures) {
if (capture !== null) {
selectedOffsets.add(capture[0]);
selectedOffsets.add(capture[1]);
}
}
}
const editorOffsets = selectedCodePointOffsetsToUtf16(
request.subject,
selectedOffsets,
);
const editorOffset = (nativeOffset: number): number => {
const value = editorOffsets.get(nativeOffset);
if (value === undefined) {
throw new Error("A Python native offset was not normalized.");
}
return value;
};
const matches = native.matches.map((match, index): RegexMatchResult => {
const startUtf16 = editorOffset(match.span[0]);
const endUtf16 = editorOffset(match.span[1]);
const value = request.subject.slice(startUtf16, endUtf16);
const preview = previews.take(value);
const captures = match.captures.map((span, captureIndex): CaptureResult => {
const groupNumber = captureIndex + 1;
const groupName = names.get(groupNumber);
if (span === null) {
return {
groupNumber,
...(groupName ? { groupName } : {}),
status: "did-not-participate",
};
}
const captureStartUtf16 = editorOffset(span[0]);
const captureEndUtf16 = editorOffset(span[1]);
const captureValue = request.subject.slice(
captureStartUtf16,
captureEndUtf16,
);
const capturePreview = previews.take(captureValue);
return {
groupNumber,
...(groupName ? { groupName } : {}),
value: capturePreview.value,
status: capturePreview.truncated
? "truncated"
: captureValue.length === 0
? "matched-empty"
: "participated",
range: {
startUtf16: captureStartUtf16,
endUtf16: captureEndUtf16,
},
nativeRange: {
start: span[0],
end: span[1],
unit: "code-point",
},
};
});
return {
matchNumber: index + 1,
value: preview.value,
valueStatus: preview.truncated ? "truncated" : "complete",
range: { startUtf16, endUtf16 },
nativeRange: {
start: match.span[0],
end: match.span[1],
unit: "code-point",
},
captures,
};
});
return { matches, valuesTruncated: previews.truncated };
}
function resultDiagnostics(
request: RegexExecutionRequest,
native: NativeExecution,
valuesTruncated: boolean,
replacementRequest?: RegexReplacementRequest,
): readonly RegexDiagnostic[] {
const diagnostics: RegexDiagnostic[] = [];
if (native.resultsTruncated) {
diagnostics.push({
id: "python-results-truncated",
source: "execution-engine",
severity: "warning",
code: "result-limit",
message: `Results reached the configured limit (${request.maximumMatches.toLocaleString()} matches or ${request.maximumCaptureRows.toLocaleString()} capture rows).`,
flavour: "python",
provenance: "derived",
});
}
if (valuesTruncated) {
diagnostics.push({
id: "python-value-previews-truncated",
source: "execution-engine",
severity: "warning",
code: "result-value-preview-limit",
message:
"Displayed Python re result values were clipped to bounded previews; exact code-point and UTF-16 ranges are retained.",
flavour: "python",
provenance: "derived",
});
}
if (replacementRequest && native.outputTruncated) {
diagnostics.push({
id: "python-output-truncated",
source: "execution-engine",
severity: "warning",
code: "replacement-output-limit",
message: `Replacement preview was truncated at ${replacementRequest.maximumOutputBytes.toLocaleString()} UTF-8 bytes.`,
flavour: "python",
provenance: "derived",
});
}
return diagnostics;
}
function failedExecution(
info: RegexEngineInfo,
flags: RegexExecutionFlags,
start: number,
diagnostic: RegexDiagnostic,
): RegexExecutionResult {
return {
accepted: false,
engine: info,
flags,
matches: [],
diagnostics: [diagnostic],
elapsedMs: performance.now() - start,
truncated: false,
};
}
export class PythonEngineAdapter implements RegexEngineAdapter {
readonly flavour = "python" as const;
readonly #runtime: PyodideRuntime;
readonly #info: RegexEngineInfo;
#bridge: PythonCallableProxy | undefined;
#loadPromise: Promise<RegexEngineInfo> | undefined;
#terminated = false;
constructor(runtime: PyodideRuntime, runtimeIdentity?: string) {
this.#runtime = runtime;
this.#info = pythonEngineInfo(runtimeIdentity);
}
load(): Promise<RegexEngineInfo> {
this.#loadPromise ??= Promise.resolve().then(() => {
if (this.#terminated) {
throw new Error("The Python engine adapter has been terminated.");
}
if (this.#runtime.version !== PYODIDE_ENGINE_VERSION) {
throw new Error(
`Expected Pyodide ${PYODIDE_ENGINE_VERSION}, received ${this.#runtime.version}.`,
);
}
const bridge = this.#bridgeProxy();
const identity = requireIdentity(
parseBridgeJson(bridge("identity", "", "", "", 1, 1, "", 1), 16 * 1024),
);
if (
identity.identity.implementation !== "cpython" ||
identity.identity.pythonVersion !== "3.14.2" ||
identity.identity.reModule !== "re"
) {
throw new Error(
"The bundled Python runtime failed its identity self-test.",
);
}
return this.#info;
});
return this.#loadPromise;
}
async execute(request: RegexExecutionRequest): Promise<RegexExecutionResult> {
await this.load();
validateRequest(request);
const start = performance.now();
const flags = normalizedPythonFlags(request.flags, request.scanAll);
const subjectCodePoints = codePointLength(request.subject);
const parsed = parseBridgeJson(
this.#bridgeProxy()(
"execute",
request.pattern,
flags.effectiveFlags,
request.subject,
request.maximumMatches,
request.maximumCaptureRows,
"",
1,
),
EXECUTION_RESULT_JSON_UTF16_LIMIT,
);
const error = parseNativeError(parsed);
if (error) {
return failedExecution(
this.#info,
flags,
start,
diagnosticForError(error, request.pattern),
);
}
const native = requireExecution(parsed, request, subjectCodePoints);
const materialized = materializeMatches(native, request);
return {
accepted: true,
engine: this.#info,
flags,
matches: materialized.matches,
diagnostics: resultDiagnostics(
request,
native,
materialized.valuesTruncated,
),
elapsedMs: performance.now() - start,
truncated: native.resultsTruncated,
};
}
async replace(
request: RegexReplacementRequest,
): Promise<RegexReplacementResult> {
await this.load();
validateRequest(request, request);
const start = performance.now();
const flags = normalizedPythonFlags(request.flags, request.scanAll);
const subjectCodePoints = codePointLength(request.subject);
const maximumJsonUtf16 =
request.maximumOutputBytes * 6 + EXECUTION_RESULT_JSON_UTF16_LIMIT;
const parsed = parseBridgeJson(
this.#bridgeProxy()(
"replace",
request.pattern,
flags.effectiveFlags,
request.subject,
request.maximumMatches,
request.maximumCaptureRows,
request.replacement,
request.maximumOutputBytes,
),
maximumJsonUtf16,
);
const error = parseNativeError(parsed);
if (error) {
return {
execution: failedExecution(
this.#info,
flags,
start,
diagnosticForError(error, request.pattern),
),
output: "",
outputBytes: 0,
outputTruncated: false,
truncated: false,
};
}
const native = requireExecution(
parsed,
request,
subjectCodePoints,
request,
);
const materialized = materializeMatches(native, request);
const execution: RegexExecutionResult = {
accepted: true,
engine: this.#info,
flags,
matches: materialized.matches,
diagnostics: resultDiagnostics(
request,
native,
materialized.valuesTruncated,
request,
),
elapsedMs: performance.now() - start,
truncated: native.resultsTruncated,
};
return {
execution,
output: native.output ?? "",
outputBytes: native.outputBytes ?? 0,
outputTruncated: native.outputTruncated ?? false,
truncated: native.resultsTruncated || (native.outputTruncated ?? false),
};
}
terminate(): void {
if (this.#terminated) return;
this.#terminated = true;
this.#bridge?.destroy();
this.#bridge = undefined;
}
#bridgeProxy(): PythonCallableProxy {
if (this.#terminated) {
throw new Error("The Python engine adapter has been terminated.");
}
if (!this.#bridge) {
const bridge = this.#runtime.runPython(PYTHON_BRIDGE_SOURCE);
if (
typeof bridge !== "function" ||
!("destroy" in bridge) ||
typeof bridge.destroy !== "function"
) {
throw new Error("The fixed Python bridge did not return a callable.");
}
this.#bridge = bridge as PythonCallableProxy;
}
return this.#bridge;
}
}

View File

@@ -0,0 +1,46 @@
export interface PythonCallableProxy {
(
operation: "identity" | "execute" | "replace",
pattern: string,
flags: string,
subject: string,
maximumMatches: number,
maximumCaptureRows: number,
replacement: string,
maximumOutputBytes: number,
): unknown;
destroy(): void;
}
export interface PyodideRuntime {
readonly version: string;
runPython(source: string): unknown;
}
export interface PyodideLoaderOptions {
readonly indexURL: string;
readonly stdout?: (line: string) => void;
readonly stderr?: (line: string) => void;
}
export interface PyodideModule {
readonly version: string;
loadPyodide(options: PyodideLoaderOptions): Promise<PyodideRuntime>;
}
export async function importPyodideModule(
moduleUrl: URL,
): Promise<PyodideModule> {
const imported: unknown = await import(/* @vite-ignore */ moduleUrl.href);
if (
typeof imported !== "object" ||
imported === null ||
!("version" in imported) ||
typeof imported.version !== "string" ||
!("loadPyodide" in imported) ||
typeof imported.loadPyodide !== "function"
) {
throw new Error("The Python runtime module has an invalid shape.");
}
return imported as PyodideModule;
}

View File

@@ -4,6 +4,7 @@ import type { WorkerFactory } from "./WorkerSupervisor";
export interface EngineWorkerRegistration { export interface EngineWorkerRegistration {
readonly flavour: RegexFlavourId; readonly flavour: RegexFlavourId;
readonly label: string; readonly label: string;
readonly startupTimeoutMs: number;
readonly createWorker: WorkerFactory; readonly createWorker: WorkerFactory;
} }
@@ -45,6 +46,7 @@ export const ENGINE_WORKER_REGISTRY = new EngineWorkerRegistry([
{ {
flavour: "ecmascript", flavour: "ecmascript",
label: "ECMAScript engine", label: "ECMAScript engine",
startupTimeoutMs: 1_000,
createWorker: () => createWorker: () =>
new Worker( new Worker(
new URL("../../workers/ecmascript.worker.ts", import.meta.url), new URL("../../workers/ecmascript.worker.ts", import.meta.url),
@@ -57,10 +59,31 @@ export const ENGINE_WORKER_REGISTRY = new EngineWorkerRegistry([
{ {
flavour: "pcre2", flavour: "pcre2",
label: "PCRE2 engine", label: "PCRE2 engine",
startupTimeoutMs: 10_000,
createWorker: () => createWorker: () =>
new Worker(new URL("../../workers/pcre2.worker.ts", import.meta.url), { new Worker(new URL("../../workers/pcre2.worker.ts", import.meta.url), {
type: "module", type: "module",
name: "regex-tools-pcre2", name: "regex-tools-pcre2",
}), }),
}, },
{
flavour: "python",
label: "Python re engine",
startupTimeoutMs: 30_000,
createWorker: () =>
new Worker(new URL("../../workers/python.worker.ts", import.meta.url), {
type: "module",
name: "regex-tools-python",
}),
},
{
flavour: "java",
label: "Java regex engine",
startupTimeoutMs: 15_000,
createWorker: () =>
new Worker(new URL("../../workers/java.worker.ts", import.meta.url), {
type: "module",
name: "regex-tools-java",
}),
},
]); ]);

View File

@@ -9,10 +9,11 @@ import type {
RegexSyntaxResult, RegexSyntaxResult,
ReplacementSyntaxResult, ReplacementSyntaxResult,
} from "../model/syntax"; } from "../model/syntax";
import type { RegexEngineInfo } from "../model/flavour";
import type { ReplacementSyntaxRequest } from "../syntax/SyntaxProvider"; import type { ReplacementSyntaxRequest } from "../syntax/SyntaxProvider";
import type { Pcre2TraceRequest, Pcre2TraceResult } from "../model/trace"; import type { Pcre2TraceRequest, Pcre2TraceResult } from "../model/trace";
export const WORKER_PROTOCOL_VERSION = 1; export const WORKER_PROTOCOL_VERSION = 2;
export type SyntaxWorkerOperation = export type SyntaxWorkerOperation =
| { | {
@@ -35,6 +36,9 @@ export type SyntaxWorkerResult =
}; };
export type EngineWorkerOperation = export type EngineWorkerOperation =
| {
readonly kind: "load";
}
| { | {
readonly kind: "execute"; readonly kind: "execute";
readonly request: RegexExecutionRequest; readonly request: RegexExecutionRequest;
@@ -45,6 +49,10 @@ export type EngineWorkerOperation =
}; };
export type EngineWorkerResult = export type EngineWorkerResult =
| {
readonly kind: "load";
readonly info: RegexEngineInfo;
}
| { | {
readonly kind: "execute"; readonly kind: "execute";
readonly result: RegexExecutionResult; readonly result: RegexExecutionResult;

View File

@@ -44,18 +44,18 @@ const fixturePcre2: RegexFlavourDefinition = {
}; };
describe("available regex flavour contracts", () => { describe("available regex flavour contracts", () => {
it("publishes the two implemented production flavour boundaries", () => { it("publishes the four implemented production flavour boundaries", () => {
expect( expect(
AVAILABLE_REGEX_FLAVOURS.definitions.map((definition) => definition.id), AVAILABLE_REGEX_FLAVOURS.definitions.map((definition) => definition.id),
).toEqual(["ecmascript", "pcre2"]); ).toEqual(["ecmascript", "pcre2", "python", "java"]);
expect( expect(
AVAILABLE_REGEX_FLAVOURS.require("pcre2").options.map( AVAILABLE_REGEX_FLAVOURS.require("pcre2").options.map(
(option) => option.name, (option) => option.name,
), ),
).toEqual(["matchLimit", "depthLimit", "heapLimitKib"]); ).toEqual(["matchLimit", "depthLimit", "heapLimitKib"]);
expect(() => expect(() =>
AVAILABLE_REGEX_FLAVOURS.parse("python", "Project flavour"), AVAILABLE_REGEX_FLAVOURS.parse("go", "Project flavour"),
).toThrow(/only supports ECMAScript and PCRE2/u); ).toThrow(/ECMAScript, PCRE2, Python re and Java/u);
}); });
it("validates ECMAScript flags through the flavour contract", () => { it("validates ECMAScript flags through the flavour contract", () => {
@@ -150,6 +150,29 @@ describe("available regex flavour contracts", () => {
).toThrow(/integer from 1 to 100000000/u); ).toThrow(/integer from 1 to 100000000/u);
}); });
it("validates Python and Java flavour identities and flags", () => {
const python = AVAILABLE_REGEX_FLAVOURS.require("python");
const java = AVAILABLE_REGEX_FLAVOURS.require("java");
expect(parseRegexFlags(["g", "a", "x"], python, "Flags")).toEqual([
"g",
"a",
"x",
]);
expect(
resolveRegexFlavourVersion("Python 3.14", python, "Version").value,
).toBe("Python 3.14");
expect(parseRegexFlags(["g", "d", "U"], java, "Flags")).toEqual([
"g",
"d",
"U",
]);
expect(
resolveRegexFlavourVersion("TeaVM 0.15.0", java, "Version").value,
).toBe("TeaVM 0.15.0");
expect(() => parseRegexFlags(["a"], java, "Flags")).toThrow(/unsupported/u);
});
it("rejects ambiguous registrations at construction time", () => { it("rejects ambiguous registrations at construction time", () => {
expect( expect(
() => new RegexFlavourRegistry([fixturePcre2, fixturePcre2]), () => new RegexFlavourRegistry([fixturePcre2, fixturePcre2]),

View File

@@ -383,7 +383,59 @@ export const PCRE2_FLAVOUR: RegexFlavourDefinition = {
], ],
}; };
export const PYTHON_FLAVOUR: RegexFlavourDefinition = {
id: "python",
label: "Python re",
versions: [
{
value: "CPython 3.14 via Pyodide 314.0.3",
label: "CPython 3.14 · Pyodide 314.0.3",
syntaxVersion: "Python 3.14 re",
legacyValues: ["Python 3.14", "Pyodide 314.0.3"],
},
],
defaultVersion: "CPython 3.14 via Pyodide 314.0.3",
flags: [
{ value: "g", label: "Global" },
{ value: "a", label: "ASCII character classes" },
{ value: "i", label: "Ignore case" },
{ value: "m", label: "Multiline" },
{ value: "s", label: "Dot all" },
{ value: "x", label: "Verbose" },
],
defaultFlags: ["g"],
options: [],
};
export const JAVA_FLAVOUR: RegexFlavourDefinition = {
id: "java",
label: "Java",
versions: [
{
value: "TeaVM java.util.regex 0.15.0",
label: "java.util.regex · TeaVM 0.15.0",
syntaxVersion: "Java Pattern / TeaVM 0.15.0",
legacyValues: ["Java Pattern", "TeaVM 0.15.0"],
},
],
defaultVersion: "TeaVM java.util.regex 0.15.0",
flags: [
{ value: "g", label: "Global" },
{ value: "i", label: "Case insensitive" },
{ value: "d", label: "Unix lines" },
{ value: "m", label: "Multiline" },
{ value: "s", label: "Dot all" },
{ value: "u", label: "Unicode case" },
{ value: "x", label: "Comments" },
{ value: "U", label: "Unicode character classes" },
],
defaultFlags: ["g"],
options: [],
};
export const AVAILABLE_REGEX_FLAVOURS = new RegexFlavourRegistry([ export const AVAILABLE_REGEX_FLAVOURS = new RegexFlavourRegistry([
ECMASCRIPT_FLAVOUR, ECMASCRIPT_FLAVOUR,
PCRE2_FLAVOUR, PCRE2_FLAVOUR,
PYTHON_FLAVOUR,
JAVA_FLAVOUR,
]); ]);

View File

@@ -242,3 +242,265 @@ export const PCRE2_REFERENCE: readonly ReferenceEntry[] = [
sourceUrl: PCRE2_REFERENCE_SOURCE, sourceUrl: PCRE2_REFERENCE_SOURCE,
}, },
]; ];
const PYTHON_REFERENCE_SOURCE = "https://docs.python.org/3.14/library/re.html";
export const PYTHON_REFERENCE: readonly ReferenceEntry[] = [
{
id: "python-named-capture",
construct: "Named capture",
syntax: "(?P<name>...)",
explanation:
"Captures a group under both a number and a Python identifier.",
flavours: ["python"],
flags: [],
example: "(?P<year>\\d{4})",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
{
id: "python-named-backreference",
construct: "Named backreference",
syntax: "(?P=name)",
explanation: "Matches the text retained by an earlier named capture.",
flavours: ["python"],
flags: [],
example: "(?P<quote>['\"]).*?(?P=quote)",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
{
id: "python-scoped-flags",
construct: "Scoped inline flags",
syntax: "(?aiLmsux-imsx:...)",
explanation:
"Applies selected matching modes only inside the enclosing group.",
flavours: ["python"],
flags: ["a", "i", "m", "s", "x"],
example: "(?i:invoice)-(?a:\\w+)",
caveat:
"The a, L and u character-mode flags are mutually exclusive within one inline group.",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
{
id: "python-atomic",
construct: "Atomic group",
syntax: "(?>...)",
explanation:
"Discards the group's internal backtracking points after it succeeds.",
flavours: ["python"],
flags: [],
example: "(?>\\d+)\\d",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
{
id: "python-possessive",
construct: "Possessive quantifier",
syntax: "*+ ++ ?+ {m,n}+",
explanation:
"Repeats without allowing later pattern parts to backtrack into the repetition.",
flavours: ["python"],
flags: [],
example: "\\w++:",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
{
id: "python-lookbehind",
construct: "Fixed-length lookbehind",
syntax: "(?<=...) (?<!...)",
explanation:
"Checks immediately preceding text without consuming it; the contained pattern must have a fixed length.",
flavours: ["python"],
flags: [],
example: "(?<=€)\\d+",
caveat:
"Alternatives and repetitions are accepted only when the lookbehind still has one fixed width.",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
{
id: "python-conditional",
construct: "Capture conditional",
syntax: "(?(id/name)yes|no)",
explanation:
"Selects a branch according to whether an earlier capture participated.",
flavours: ["python"],
flags: [],
example: "(<)?\\w+@(\\w+\\.)+\\w+(?(1)>|$)",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
{
id: "python-absolute-end",
construct: "Absolute end anchor",
syntax: "\\z",
explanation: "Asserts the absolute end of the input.",
flavours: ["python"],
flags: [],
example: "\\d+\\z",
caveat: "The \\z spelling was added in Python 3.14.",
source: "Python 3.14 re documentation",
sourceUrl: PYTHON_REFERENCE_SOURCE,
},
];
const JAVA_REFERENCE_SOURCE =
"https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/regex/Pattern.html";
export const JAVA_REFERENCE: readonly ReferenceEntry[] = [
{
id: "java-named-capture",
construct: "Named capture",
syntax: "(?<name>...)",
explanation:
"Captures a group under both a number and a Latin-letter-led name.",
flavours: ["java"],
flags: [],
example: "(?<year>\\d{4})",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-named-backreference",
construct: "Named backreference",
syntax: "\\k<name>",
explanation: "Matches the text retained by an earlier named capture.",
flavours: ["java"],
flags: [],
example: "(?<word>\\w+)\\s+\\k<word>",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-class-intersection",
construct: "Character-class intersection",
syntax: "[class&&class]",
explanation:
"Keeps only characters present in both nested character classes.",
flavours: ["java"],
flags: [],
example: "[a-z&&[^aeiou]]+",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-scoped-flags",
construct: "Scoped embedded flags",
syntax: "(?idmsuxU-idmsuxU:...)",
explanation: "Enables and disables selected Pattern modes for one group.",
flavours: ["java"],
flags: ["i", "d", "m", "s", "u", "x", "U"],
example: "(?i:invoice)-(?-i:[A-Z]+)",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-independent",
construct: "Independent group",
syntax: "(?>...)",
explanation:
"Prevents the engine from revisiting choices made inside the group.",
flavours: ["java"],
flags: [],
example: "(?>\\d+)\\d",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-possessive",
construct: "Possessive quantifier",
syntax: "*+ ++ ?+ {m,n}+",
explanation:
"Repeats without returning matched characters during later backtracking.",
flavours: ["java"],
flags: [],
example: "\\w++:",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-linebreak",
construct: "Unicode linebreak",
syntax: "\\R",
explanation:
"Matches one Unicode linebreak sequence, including a CRLF pair as one sequence.",
flavours: ["java"],
flags: [],
example: "^.+\\R.+$",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-grapheme",
construct: "Extended grapheme cluster",
syntax: "\\X",
explanation:
"Matches one extended Unicode grapheme cluster rather than one code point.",
flavours: ["java"],
flags: [],
example: "\\X",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
{
id: "java-unicode-property",
construct: "Unicode property",
syntax: "\\p{Property}",
explanation:
"Matches characters selected by a supported Unicode category, script, block or binary property.",
flavours: ["java"],
flags: ["U"],
example: "\\p{IsLatin}+",
caveat:
"The bundled TeaVM adapter reports compatibility warnings where its Pattern implementation differs from the documented Java SE language.",
source: "Java SE 25 Pattern documentation",
sourceUrl: JAVA_REFERENCE_SOURCE,
},
];
export interface FlavourReference {
readonly label: string;
readonly entries: readonly ReferenceEntry[];
readonly note?: string;
}
export const REFERENCE_BY_FLAVOUR: Readonly<
Record<RegexFlavourId, FlavourReference>
> = {
ecmascript: {
label: "ECMAScript",
entries: ECMASCRIPT_REFERENCE,
},
pcre2: {
label: "PCRE2",
entries: PCRE2_REFERENCE,
},
python: {
label: "Python 3.14 re",
entries: PYTHON_REFERENCE,
},
java: {
label: "Java Pattern",
entries: JAVA_REFERENCE,
note: "This reference describes the Java Pattern language. The bundled TeaVM 0.15.0 adapter is a tested compatibility subset, not OpenJDK; replacement supports only single-digit $n references and not Java SE ${name}. Its diagnostics and engine metadata are authoritative for this build.",
},
pcre: {
label: "PCRE",
entries: [],
},
go: {
label: "Go regexp",
entries: [],
},
rust: {
label: "Rust regex",
entries: [],
},
dotnet: {
label: ".NET Regex",
entries: [],
},
};

View File

@@ -0,0 +1,84 @@
import { describe, expect, it } from "vitest";
import { JavaSyntaxProvider } from "./JavaSyntaxProvider";
const provider = new JavaSyntaxProvider();
describe("Java lexical syntax provider", () => {
it("finds ordinary and Java-named captures without counting lookbehind", async () => {
const result = await provider.parsePattern({
flavour: "java",
pattern: String.raw`(?<word>\w+)(?:\s+)(?<=(\d))`,
flags: [],
options: {},
});
expect(result.accepted).toBe(true);
expect(result.coverage.status).toBe("partial");
expect(result.captures).toEqual([
expect.objectContaining({ number: 1, name: "word" }),
expect.objectContaining({ number: 2 }),
]);
});
it("ignores quoted literals, classes and comments-mode text", async () => {
const result = await provider.parsePattern({
flavour: "java",
pattern: String.raw`\Q(captured-looking)\E [(] # (ignored)
(kept)`,
flags: ["x"],
options: {},
});
expect(result.captures).toHaveLength(1);
});
it("tokenizes TeaVM replacements and rejects its known named-reference gap", async () => {
const result = await provider.parseReplacement({
flavour: "java",
replacement: "${word}-$1-\\$",
captureMetadata: [
{
number: 1,
name: "word",
range: { startUtf16: 0, endUtf16: 9 },
repeated: false,
},
],
});
expect(result.accepted).toBe(false);
expect(result.tokens.map((token) => token.kind)).toEqual([
"named-capture",
"literal",
"numbered-capture",
"literal",
"engine-special",
]);
expect(result.diagnostics).toEqual([
expect.objectContaining({
code: "engine-compatibility",
severity: "error",
}),
]);
});
it("treats only one digit after $ as a TeaVM capture reference", async () => {
const result = await provider.parseReplacement({
flavour: "java",
replacement: "$12",
captureMetadata: [
{
number: 1,
range: { startUtf16: 0, endUtf16: 3 },
repeated: false,
},
],
});
expect(result.accepted).toBe(true);
expect(result.tokens).toEqual([
expect.objectContaining({ kind: "numbered-capture", raw: "$1" }),
expect.objectContaining({ kind: "literal", raw: "2" }),
]);
});
});

View File

@@ -0,0 +1,61 @@
import type { RegexFlavourId } from "../../../model/flavour";
import type {
RegexSyntaxRequest,
RegexSyntaxResult,
ReplacementSyntaxResult,
SyntaxCoverage,
} from "../../../model/syntax";
import type {
RegexSyntaxProvider,
ReplacementSyntaxRequest,
} from "../../SyntaxProvider";
import {
lexicalCoverage,
parseLexicalPattern,
type LexicalCaptureDialect,
} from "../lexical-captures";
import { parseJavaReplacement } from "./replacement";
const DIALECT: LexicalCaptureDialect = {
flavour: "java",
providerId: "regex-tools-java-lexical",
providerVersion: "0.3.0",
engineLabel: "TeaVM java.util.regex",
extendedFlag: "x",
quotedLiterals: true,
namedCapture: (pattern, opener) => {
const match = /^\(\?<([A-Za-z][A-Za-z0-9]*)>/u.exec(pattern.slice(opener));
return match?.[1]
? { name: match[1], end: opener + match[0].length }
: undefined;
},
unsupportedConstructs: [
"complete structural nesting and quantifier ownership",
"inline flag scope in normalized explanations",
"character-class intersections and Unicode properties as structural nodes",
"conditionals, assertions and backreferences as structural nodes",
],
};
export class JavaSyntaxProvider implements RegexSyntaxProvider {
readonly id = DIALECT.providerId;
readonly version = DIALECT.providerVersion;
readonly supportedFlavours = ["java"] as const;
async parsePattern(request: RegexSyntaxRequest): Promise<RegexSyntaxResult> {
return parseLexicalPattern(request, DIALECT);
}
async parseReplacement(
request: ReplacementSyntaxRequest,
): Promise<ReplacementSyntaxResult> {
if (request.flavour !== "java") {
throw new Error(`Unsupported replacement flavour: ${request.flavour}`);
}
return parseJavaReplacement(request.replacement, request.captureMetadata);
}
getCoverage(flavour: RegexFlavourId): SyntaxCoverage {
return lexicalCoverage(flavour, DIALECT);
}
}

View File

@@ -0,0 +1,169 @@
import { DEFAULT_REGEX_LIMITS } from "../../../execution/request-limits";
import type { RegexDiagnostic } from "../../../model/diagnostics";
import type {
CaptureDefinition,
ReplacementSyntaxResult,
ReplacementToken,
} from "../../../model/syntax";
const REPLACEMENT_TOKEN = /\\.|\$\{[^}]*\}|\$[0-9]/gu;
function literalToken(
id: number,
raw: string,
start: number,
): ReplacementToken {
return {
id: `java-replacement-${id}`,
kind: "literal",
raw,
range: { startUtf16: start, endUtf16: start + raw.length },
explanation: `Insert literal text ${JSON.stringify(raw.slice(0, 160))}${
raw.length > 160 ? "…" : ""
}`,
};
}
function specialToken(
id: number,
raw: string,
start: number,
): ReplacementToken {
const range = { startUtf16: start, endUtf16: start + raw.length };
const number = /^\$([0-9]+)$/u.exec(raw)?.[1];
if (number !== undefined) {
const captureNumber = Number.parseInt(number, 10);
return {
id: `java-replacement-${id}`,
kind: captureNumber === 0 ? "full-match" : "numbered-capture",
raw,
range,
explanation:
captureNumber === 0
? "Insert the complete match"
: `Insert numbered capture ${captureNumber}`,
...(captureNumber === 0 ? {} : { captureNumber }),
};
}
const name = /^\$\{([A-Za-z][A-Za-z0-9]*)\}$/u.exec(raw)?.[1];
if (name) {
return {
id: `java-replacement-${id}`,
kind: "named-capture",
raw,
range,
explanation: `Java SE named capture “${name}” (unavailable in the bundled TeaVM 0.15.0 replacement runtime)`,
captureName: name,
};
}
return {
id: `java-replacement-${id}`,
kind: "engine-special",
raw,
range,
explanation:
"Java Matcher evaluates this quoted replacement character; the engine output is authoritative",
};
}
export function parseJavaReplacement(
replacement: string,
captures: readonly CaptureDefinition[],
): ReplacementSyntaxResult {
if (
replacement.length > DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16
) {
return {
accepted: false,
tokens: [],
diagnostics: [
{
id: "java-replacement-template-input-limit",
source: "syntax-provider",
severity: "error",
code: "replacement-template-limit",
message: `Replacement template exceeds the ${DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16.toLocaleString()} UTF-16 unit limit; it was not parsed or executed.`,
flavour: "java",
provenance: "derived",
},
],
totalDiagnostics: 1,
diagnosticsTruncated: false,
};
}
const numbers = new Set(captures.map((capture) => capture.number));
const names = new Set(
captures.flatMap((capture) => (capture.name ? [capture.name] : [])),
);
const tokens: ReplacementToken[] = [];
const diagnostics: RegexDiagnostic[] = [];
let cursor = 0;
for (const match of replacement.matchAll(REPLACEMENT_TOKEN)) {
const start = match.index;
const raw = match[0];
if (start > cursor) {
tokens.push(
literalToken(tokens.length, replacement.slice(cursor, start), cursor),
);
}
const token = specialToken(tokens.length, raw, start);
tokens.push(token);
if (
token.kind === "numbered-capture" &&
token.captureNumber !== undefined &&
!numbers.has(token.captureNumber)
) {
diagnostics.push({
id: `java-replacement-missing-number-${start}`,
source: "syntax-provider",
severity: "warning",
code: "replacement-unknown-capture",
message: `Replacement references capture ${token.captureNumber}, which the partial lexical provider did not find. Java Matcher remains authoritative at execution.`,
range: token.range,
flavour: "java",
provenance: "derived",
});
} else if (token.kind === "named-capture") {
diagnostics.push({
id: `java-replacement-named-unsupported-${start}`,
source: "syntax-provider",
severity: "error",
code: "engine-compatibility",
message:
"TeaVM 0.15.0 Matcher replacement implements only single-digit $n references. Java SE ${name} replacement is not available in this compatibility runtime.",
range: token.range,
flavour: "java",
provenance: "reported",
});
if (
token.captureName !== undefined &&
names.size > 0 &&
!names.has(token.captureName)
) {
diagnostics.push({
id: `java-replacement-missing-name-${start}`,
source: "syntax-provider",
severity: "warning",
code: "replacement-unknown-capture",
message: `Replacement references unknown named capture “${token.captureName}”.`,
range: token.range,
flavour: "java",
provenance: "derived",
});
}
}
cursor = start + raw.length;
}
if (cursor < replacement.length) {
tokens.push(literalToken(tokens.length, replacement.slice(cursor), cursor));
}
return {
accepted: !diagnostics.some(
(diagnostic) => diagnostic.severity === "error",
),
tokens,
diagnostics,
totalDiagnostics: diagnostics.length,
diagnosticsTruncated: false,
};
}

View File

@@ -0,0 +1,285 @@
import { DEFAULT_REGEX_LIMITS } from "../../execution/request-limits";
import type { RegexDiagnostic } from "../../model/diagnostics";
import type { RegexFlavourId } from "../../model/flavour";
import type {
CaptureDefinition,
NormalizedRegexNode,
NormalizedRegexToken,
RegexNodeKind,
RegexSyntaxRequest,
RegexSyntaxResult,
SyntaxCoverage,
} from "../../model/syntax";
interface NamedCapture {
readonly name: string;
readonly end: number;
}
export interface LexicalCaptureDialect {
readonly flavour: RegexFlavourId;
readonly providerId: string;
readonly providerVersion: string;
readonly engineLabel: string;
readonly extendedFlag: string;
readonly quotedLiterals: boolean;
readonly namedCapture: (
pattern: string,
opener: number,
) => NamedCapture | undefined;
readonly unsupportedConstructs: readonly string[];
}
interface CaptureOpener {
readonly start: number;
readonly end: number;
readonly name?: string;
}
function scanCaptureOpeners(
pattern: string,
dialect: LexicalCaptureDialect,
extended: boolean,
): readonly CaptureOpener[] {
const openers: CaptureOpener[] = [];
let escaped = false;
let characterClass = false;
let quoted = false;
for (let index = 0; index < pattern.length; index += 1) {
const current = pattern[index];
if (quoted) {
if (current === "\\" && pattern[index + 1] === "E") {
quoted = false;
index += 1;
}
continue;
}
if (escaped) {
escaped = false;
continue;
}
if (current === "\\") {
if (dialect.quotedLiterals && pattern[index + 1] === "Q") {
quoted = true;
index += 1;
} else {
escaped = true;
}
continue;
}
if (current === "[" && !characterClass) {
characterClass = true;
continue;
}
if (current === "]" && characterClass) {
characterClass = false;
continue;
}
if (extended && current === "#" && !characterClass) {
const lineEnd = pattern.indexOf("\n", index + 1);
if (lineEnd < 0) break;
index = lineEnd;
continue;
}
if (characterClass || current !== "(") continue;
if (pattern.startsWith("(?#", index)) {
let commentEnd = index + 3;
let commentEscaped = false;
while (commentEnd < pattern.length) {
const character = pattern[commentEnd];
if (!commentEscaped && character === ")") break;
if (!commentEscaped && character === "\\") {
commentEscaped = true;
} else {
commentEscaped = false;
}
commentEnd += 1;
}
index = commentEnd;
continue;
}
if (pattern[index + 1] !== "?") {
openers.push({ start: index, end: index + 1 });
continue;
}
const named = dialect.namedCapture(pattern, index);
if (named) {
openers.push({
start: index,
end: named.end,
name: named.name,
});
}
}
return openers;
}
function construct(
pattern: string,
opener: CaptureOpener,
number: number,
dialect: LexicalCaptureDialect,
): {
readonly node: NormalizedRegexNode;
readonly token: NormalizedRegexToken;
readonly capture: CaptureDefinition;
} {
const kind: RegexNodeKind = opener.name
? "named-capture-group"
: "capture-group";
const range = { startUtf16: opener.start, endUtf16: opener.end };
const raw = pattern.slice(opener.start, opener.end);
return {
node: {
id: `${dialect.flavour}-capture-${number}`,
kind,
range,
raw,
explanation: opener.name
? `Open capture group ${number} named “${opener.name}`
: `Open capture group ${number}`,
children: [],
capture: { number, ...(opener.name ? { name: opener.name } : {}) },
properties: {
zeroWidth: false,
nullable: "unknown",
consumesInput: "conditional",
},
support: {
flavour: dialect.flavour,
status: "partial",
notes: [
`The lexical provider identifies this opener; ${dialect.engineLabel} compilation is authoritative.`,
],
},
provenance: {
provider: dialect.providerId,
providerVersion: dialect.providerVersion,
source: "parsed",
},
},
token: {
id: `${dialect.flavour}-token-${number}`,
kind,
range,
raw,
captureNumber: number,
},
capture: {
number,
...(opener.name ? { name: opener.name } : {}),
range,
repeated: false,
},
};
}
function coverage(dialect: LexicalCaptureDialect): SyntaxCoverage {
return {
status: "partial",
summary: `Application-owned lexical capture structure; ${dialect.engineLabel} is authoritative for syntax acceptance and matching.`,
unsupportedConstructs: dialect.unsupportedConstructs,
};
}
function rootNode(
pattern: string,
children: readonly NormalizedRegexNode[],
dialect: LexicalCaptureDialect,
): NormalizedRegexNode {
const providerCoverage = coverage(dialect);
return {
id: `${dialect.flavour}-pattern-root`,
kind: "pattern",
range: { startUtf16: 0, endUtf16: pattern.length },
raw: pattern,
explanation: `${dialect.engineLabel} pattern; the runtime compiler decides final acceptance`,
children,
properties: {
zeroWidth: false,
nullable: "unknown",
consumesInput: "conditional",
},
support: {
flavour: dialect.flavour,
status: "partial",
notes: [providerCoverage.summary],
},
provenance: {
provider: dialect.providerId,
providerVersion: dialect.providerVersion,
source: "parsed",
},
};
}
export function parseLexicalPattern(
request: RegexSyntaxRequest,
dialect: LexicalCaptureDialect,
): RegexSyntaxResult {
if (request.flavour !== dialect.flavour) {
throw new Error(`Unsupported syntax flavour: ${request.flavour}`);
}
const providerCoverage = coverage(dialect);
if (request.pattern.length > DEFAULT_REGEX_LIMITS.patternHardLengthUtf16) {
const diagnostics: RegexDiagnostic[] = [
{
id: `${dialect.flavour}-pattern-input-limit`,
source: "syntax-provider",
severity: "error",
code: "pattern-limit",
message: `Pattern exceeds the ${DEFAULT_REGEX_LIMITS.patternHardLengthUtf16.toLocaleString()} UTF-16 unit limit.`,
flavour: dialect.flavour,
provenance: "derived",
},
];
return {
accepted: false,
root: rootNode(request.pattern, [], dialect),
tokens: [],
captures: [],
diagnostics,
provider: {
id: dialect.providerId,
version: dialect.providerVersion,
},
coverage: providerCoverage,
};
}
const constructs = scanCaptureOpeners(
request.pattern,
dialect,
request.flags.includes(dialect.extendedFlag),
).map((opener, index) =>
construct(request.pattern, opener, index + 1, dialect),
);
return {
accepted: true,
root: rootNode(
request.pattern,
constructs.map((entry) => entry.node),
dialect,
),
tokens: constructs.map((entry) => entry.token),
captures: constructs.map((entry) => entry.capture),
diagnostics: [],
provider: {
id: dialect.providerId,
version: dialect.providerVersion,
},
coverage: providerCoverage,
};
}
export function lexicalCoverage(
flavour: RegexFlavourId,
dialect: LexicalCaptureDialect,
): SyntaxCoverage {
return flavour === dialect.flavour
? coverage(dialect)
: {
status: "unavailable",
summary: `${dialect.engineLabel} lexical provider does not parse ${flavour}.`,
unsupportedConstructs: ["all"],
};
}

View File

@@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import { PythonSyntaxProvider } from "./PythonSyntaxProvider";
const provider = new PythonSyntaxProvider();
describe("Python lexical syntax provider", () => {
it("finds ordinary and Python-named captures without counting lookarounds", async () => {
const result = await provider.parsePattern({
flavour: "python",
pattern: String.raw`(?P<word>\w+)(?:\s+)(?=(\d+))`,
flags: [],
options: {},
});
expect(result.accepted).toBe(true);
expect(result.coverage.status).toBe("partial");
expect(result.captures).toEqual([
expect.objectContaining({ number: 1, name: "word" }),
expect.objectContaining({ number: 2 }),
]);
});
it("ignores verbose comments, classes and escaped parentheses", async () => {
const result = await provider.parsePattern({
flavour: "python",
pattern: String.raw`[(] \( # (ignored)
(kept)`,
flags: ["x"],
options: {},
});
expect(result.captures).toHaveLength(1);
});
it("tokenizes Python capture replacement forms and engine escapes", async () => {
const result = await provider.parseReplacement({
flavour: "python",
replacement: String.raw`\g<word>-\1-\n`,
captureMetadata: [
{
number: 1,
name: "word",
range: { startUtf16: 0, endUtf16: 10 },
repeated: false,
},
],
});
expect(result.accepted).toBe(true);
expect(result.tokens.map((token) => token.kind)).toEqual([
"named-capture",
"literal",
"numbered-capture",
"literal",
"engine-special",
]);
});
});

View File

@@ -0,0 +1,63 @@
import type { RegexFlavourId } from "../../../model/flavour";
import type {
RegexSyntaxRequest,
RegexSyntaxResult,
ReplacementSyntaxResult,
SyntaxCoverage,
} from "../../../model/syntax";
import type {
RegexSyntaxProvider,
ReplacementSyntaxRequest,
} from "../../SyntaxProvider";
import {
lexicalCoverage,
parseLexicalPattern,
type LexicalCaptureDialect,
} from "../lexical-captures";
import { parsePythonReplacement } from "./replacement";
const DIALECT: LexicalCaptureDialect = {
flavour: "python",
providerId: "regex-tools-python-lexical",
providerVersion: "0.3.0",
engineLabel: "CPython 3.14 re",
extendedFlag: "x",
quotedLiterals: false,
namedCapture: (pattern, opener) => {
const match = /^\(\?P<([A-Za-z_][A-Za-z0-9_]*)>/u.exec(
pattern.slice(opener),
);
return match?.[1]
? { name: match[1], end: opener + match[0].length }
: undefined;
},
unsupportedConstructs: [
"complete structural nesting and quantifier ownership",
"inline flag scope in normalized explanations",
"conditionals, assertions and backreferences as structural nodes",
"non-ASCII Python identifier capture names in lexical metadata",
],
};
export class PythonSyntaxProvider implements RegexSyntaxProvider {
readonly id = DIALECT.providerId;
readonly version = DIALECT.providerVersion;
readonly supportedFlavours = ["python"] as const;
async parsePattern(request: RegexSyntaxRequest): Promise<RegexSyntaxResult> {
return parseLexicalPattern(request, DIALECT);
}
async parseReplacement(
request: ReplacementSyntaxRequest,
): Promise<ReplacementSyntaxResult> {
if (request.flavour !== "python") {
throw new Error(`Unsupported replacement flavour: ${request.flavour}`);
}
return parsePythonReplacement(request.replacement, request.captureMetadata);
}
getCoverage(flavour: RegexFlavourId): SyntaxCoverage {
return lexicalCoverage(flavour, DIALECT);
}
}

View File

@@ -0,0 +1,166 @@
import { DEFAULT_REGEX_LIMITS } from "../../../execution/request-limits";
import type { RegexDiagnostic } from "../../../model/diagnostics";
import type {
CaptureDefinition,
ReplacementSyntaxResult,
ReplacementToken,
} from "../../../model/syntax";
const REPLACEMENT_TOKEN = /\\g<[^>]*>|\\[1-9][0-9]*|\\./gu;
function literalToken(
id: number,
raw: string,
start: number,
): ReplacementToken {
return {
id: `python-replacement-${id}`,
kind: "literal",
raw,
range: { startUtf16: start, endUtf16: start + raw.length },
explanation: `Insert literal text ${JSON.stringify(raw.slice(0, 160))}${
raw.length > 160 ? "…" : ""
}`,
};
}
function specialToken(
id: number,
raw: string,
start: number,
): ReplacementToken {
const range = { startUtf16: start, endUtf16: start + raw.length };
const explicit = /^\\g<([^>]+)>$/u.exec(raw)?.[1];
if (explicit && /^[0-9]+$/u.test(explicit)) {
const captureNumber = Number.parseInt(explicit, 10);
return {
id: `python-replacement-${id}`,
kind: captureNumber === 0 ? "full-match" : "numbered-capture",
raw,
range,
explanation:
captureNumber === 0
? "Insert the complete match"
: `Insert numbered capture ${captureNumber}`,
...(captureNumber === 0 ? {} : { captureNumber }),
};
}
if (explicit && /^[A-Za-z_][A-Za-z0-9_]*$/u.test(explicit)) {
return {
id: `python-replacement-${id}`,
kind: "named-capture",
raw,
range,
explanation: `Insert named capture “${explicit}`,
captureName: explicit,
};
}
const shortNumber = /^\\([1-9][0-9]?)$/u.exec(raw)?.[1];
if (shortNumber) {
const captureNumber = Number.parseInt(shortNumber, 10);
return {
id: `python-replacement-${id}`,
kind: "numbered-capture",
raw,
range,
explanation: `Insert numbered capture ${captureNumber}`,
captureNumber,
};
}
return {
id: `python-replacement-${id}`,
kind: "engine-special",
raw,
range,
explanation:
"Python re evaluates this replacement escape; the engine output is authoritative",
};
}
export function parsePythonReplacement(
replacement: string,
captures: readonly CaptureDefinition[],
): ReplacementSyntaxResult {
if (
replacement.length > DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16
) {
return {
accepted: false,
tokens: [],
diagnostics: [
{
id: "python-replacement-template-input-limit",
source: "syntax-provider",
severity: "error",
code: "replacement-template-limit",
message: `Replacement template exceeds the ${DEFAULT_REGEX_LIMITS.maximumReplacementTemplateUtf16.toLocaleString()} UTF-16 unit limit; it was not parsed or executed.`,
flavour: "python",
provenance: "derived",
},
],
totalDiagnostics: 1,
diagnosticsTruncated: false,
};
}
const numbers = new Set(captures.map((capture) => capture.number));
const names = new Set(
captures.flatMap((capture) => (capture.name ? [capture.name] : [])),
);
const tokens: ReplacementToken[] = [];
const diagnostics: RegexDiagnostic[] = [];
let cursor = 0;
for (const match of replacement.matchAll(REPLACEMENT_TOKEN)) {
const start = match.index;
const raw = match[0];
if (start > cursor) {
tokens.push(
literalToken(tokens.length, replacement.slice(cursor, start), cursor),
);
}
const token = specialToken(tokens.length, raw, start);
tokens.push(token);
if (
token.kind === "numbered-capture" &&
token.captureNumber !== undefined &&
!numbers.has(token.captureNumber)
) {
diagnostics.push({
id: `python-replacement-missing-number-${start}`,
source: "syntax-provider",
severity: "warning",
code: "replacement-unknown-capture",
message: `Replacement references capture ${token.captureNumber}, which the partial lexical provider did not find. Python re remains authoritative at execution.`,
range: token.range,
flavour: "python",
provenance: "derived",
});
} else if (
token.kind === "named-capture" &&
token.captureName !== undefined &&
names.size > 0 &&
!names.has(token.captureName)
) {
diagnostics.push({
id: `python-replacement-missing-name-${start}`,
source: "syntax-provider",
severity: "warning",
code: "replacement-unknown-capture",
message: `Replacement references unknown named capture “${token.captureName}”.`,
range: token.range,
flavour: "python",
provenance: "derived",
});
}
cursor = start + raw.length;
}
if (cursor < replacement.length) {
tokens.push(literalToken(tokens.length, replacement.slice(cursor), cursor));
}
return {
accepted: true,
tokens,
diagnostics,
totalDiagnostics: diagnostics.length,
diagnosticsTruncated: false,
};
}

View File

@@ -3,7 +3,7 @@
"schemaVersion": 1, "schemaVersion": 1,
"id": "de.add-ideas.regex-tools", "id": "de.add-ideas.regex-tools",
"name": "Regex Tools", "name": "Regex Tools",
"version": "0.2.0", "version": "0.3.0",
"description": "Develop, explain, test and apply regular expressions locally in the browser.", "description": "Develop, explain, test and apply regular expressions locally in the browser.",
"entry": "./", "entry": "./",
"icon": "./favicon.svg", "icon": "./favicon.svg",
@@ -36,5 +36,15 @@
"label": "Source", "label": "Source",
"url": "https://git.add-ideas.de/zemion/regex-tools" "url": "https://git.add-ideas.de/zemion/regex-tools"
} }
],
"assets": [
"./engines/pcre2/pcre2.mjs",
"./engines/pcre2/pcre2.wasm",
"./engines/python/pyodide.mjs",
"./engines/python/pyodide.asm.mjs",
"./engines/python/pyodide.asm.wasm",
"./engines/python/pyodide-lock.json",
"./engines/python/python_stdlib.zip",
"./engines/java/java-regex.mjs"
] ]
} }

View File

@@ -1,3 +1,3 @@
export const APPLICATION_VERSION = "0.2.0"; export const APPLICATION_VERSION = "0.3.0";
export const SYNTAX_PROFILE = "community"; export const SYNTAX_PROFILE = "community";
export const REGEXPP_VERSION = "4.12.2"; export const REGEXPP_VERSION = "4.12.2";

View File

@@ -1,5 +1,6 @@
/// <reference lib="webworker" /> /// <reference lib="webworker" />
import { import {
EcmaScriptEngineAdapter,
executeEcmaScript, executeEcmaScript,
replaceEcmaScript, replaceEcmaScript,
} from "../regex/execution/adapters/ecmascript/EcmaScriptEngineAdapter"; } from "../regex/execution/adapters/ecmascript/EcmaScriptEngineAdapter";
@@ -13,8 +14,9 @@ import {
} from "../regex/execution/worker-protocol"; } from "../regex/execution/worker-protocol";
const scope = self as DedicatedWorkerGlobalScope; const scope = self as DedicatedWorkerGlobalScope;
const adapter = new EcmaScriptEngineAdapter();
scope.onmessage = ( scope.onmessage = async (
event: MessageEvent<WorkerRequest<EngineWorkerOperation>>, event: MessageEvent<WorkerRequest<EngineWorkerOperation>>,
) => { ) => {
const request = event.data; const request = event.data;
@@ -22,7 +24,12 @@ scope.onmessage = (
let response: WorkerResponse<EngineWorkerResult>; let response: WorkerResponse<EngineWorkerResult>;
try { try {
const payload: EngineWorkerResult = const payload: EngineWorkerResult =
request.payload.kind === "execute" request.payload.kind === "load"
? {
kind: "load",
info: await adapter.load(),
}
: request.payload.kind === "execute"
? { ? {
kind: "execute", kind: "execute",
result: executeEcmaScript(request.payload.request), result: executeEcmaScript(request.payload.request),

View File

@@ -0,0 +1,88 @@
/// <reference lib="webworker" />
import {
JavaEngineAdapter,
JAVA_ENGINE_VERSION,
} from "../regex/execution/adapters/java/JavaEngineAdapter";
import { importJavaRegexModule } from "../regex/execution/adapters/java/java-module";
import {
WORKER_PROTOCOL_VERSION,
workerError,
type EngineWorkerOperation,
type EngineWorkerResult,
type WorkerRequest,
type WorkerResponse,
} from "../regex/execution/worker-protocol";
const scope = self as DedicatedWorkerGlobalScope;
let adapterPromise: Promise<JavaEngineAdapter> | undefined;
function applicationRoot(): URL {
const worker = new URL(scope.location.href);
const assets = worker.pathname.lastIndexOf("/assets/");
if (assets >= 0) {
worker.pathname = worker.pathname.slice(0, assets + 1);
} else {
const sourceWorker = worker.pathname.lastIndexOf("/src/workers/");
worker.pathname =
sourceWorker >= 0 ? worker.pathname.slice(0, sourceWorker + 1) : "/";
}
worker.search = "";
worker.hash = "";
return worker;
}
async function loadAdapter(): Promise<JavaEngineAdapter> {
const moduleUrl = new URL("engines/java/java-regex.mjs", applicationRoot());
const module = await importJavaRegexModule(moduleUrl);
const adapter = new JavaEngineAdapter(
module,
`TeaVM JavaScript · ${scope.navigator.userAgent} · not OpenJDK`,
);
const info = await adapter.load();
if (info.engineVersion !== JAVA_ENGINE_VERSION) {
throw new Error("Loaded TeaVM Java runtime identity does not match.");
}
return adapter;
}
scope.onmessage = async (
event: MessageEvent<WorkerRequest<EngineWorkerOperation>>,
) => {
const request = event.data;
if (request.protocolVersion !== WORKER_PROTOCOL_VERSION) return;
let response: WorkerResponse<EngineWorkerResult>;
try {
adapterPromise ??= loadAdapter();
const adapter = await adapterPromise;
let payload: EngineWorkerResult;
if (request.payload.kind === "load") {
payload = { kind: "load", info: await adapter.load() };
} else if (request.payload.kind === "execute") {
payload = {
kind: "execute",
result: await adapter.execute(request.payload.request),
};
} else {
payload = {
kind: "replace",
result: await adapter.replace(request.payload.request),
};
}
response = {
protocolVersion: WORKER_PROTOCOL_VERSION,
requestId: request.requestId,
generation: request.generation,
ok: true,
payload,
};
} catch (error) {
response = {
protocolVersion: WORKER_PROTOCOL_VERSION,
requestId: request.requestId,
generation: request.generation,
ok: false,
error: workerError(error),
};
}
scope.postMessage(response);
};

View File

@@ -62,7 +62,12 @@ scope.onmessage = async (
adapterPromise ??= loadAdapter(); adapterPromise ??= loadAdapter();
const adapter = await adapterPromise; const adapter = await adapterPromise;
const payload: EngineWorkerResult = const payload: EngineWorkerResult =
request.payload.kind === "execute" request.payload.kind === "load"
? {
kind: "load",
info: await adapter.load(),
}
: request.payload.kind === "execute"
? { ? {
kind: "execute", kind: "execute",
result: await adapter.execute(request.payload.request), result: await adapter.execute(request.payload.request),

View File

@@ -0,0 +1,99 @@
/// <reference lib="webworker" />
import {
PythonEngineAdapter,
PYODIDE_ENGINE_VERSION,
PYTHON_ENGINE_VERSION,
} from "../regex/execution/adapters/python/PythonEngineAdapter";
import { importPyodideModule } from "../regex/execution/adapters/python/python-module";
import {
WORKER_PROTOCOL_VERSION,
workerError,
type EngineWorkerOperation,
type EngineWorkerResult,
type WorkerRequest,
type WorkerResponse,
} from "../regex/execution/worker-protocol";
const scope = self as DedicatedWorkerGlobalScope;
let adapterPromise: Promise<PythonEngineAdapter> | undefined;
function applicationRoot(): URL {
const worker = new URL(scope.location.href);
const assets = worker.pathname.lastIndexOf("/assets/");
if (assets >= 0) {
worker.pathname = worker.pathname.slice(0, assets + 1);
} else {
const sourceWorker = worker.pathname.lastIndexOf("/src/workers/");
worker.pathname =
sourceWorker >= 0 ? worker.pathname.slice(0, sourceWorker + 1) : "/";
}
worker.search = "";
worker.hash = "";
return worker;
}
async function loadAdapter(): Promise<PythonEngineAdapter> {
const moduleUrl = new URL("engines/python/pyodide.mjs", applicationRoot());
const imported = await importPyodideModule(moduleUrl);
if (imported.version !== PYODIDE_ENGINE_VERSION) {
throw new Error(
"Loaded Pyodide module identity does not match the adapter.",
);
}
const runtime = await imported.loadPyodide({
indexURL: new URL(".", moduleUrl).href,
stdout: () => undefined,
stderr: (line) => console.warn(`Python ${line}`),
});
const adapter = new PythonEngineAdapter(runtime, scope.navigator.userAgent);
const info = await adapter.load();
if (info.engineVersion !== PYTHON_ENGINE_VERSION) {
throw new Error(
"Loaded Python runtime identity does not match the adapter.",
);
}
return adapter;
}
scope.onmessage = async (
event: MessageEvent<WorkerRequest<EngineWorkerOperation>>,
) => {
const request = event.data;
if (request.protocolVersion !== WORKER_PROTOCOL_VERSION) return;
let response: WorkerResponse<EngineWorkerResult>;
try {
adapterPromise ??= loadAdapter();
const adapter = await adapterPromise;
const payload: EngineWorkerResult =
request.payload.kind === "load"
? {
kind: "load",
info: await adapter.load(),
}
: request.payload.kind === "execute"
? {
kind: "execute",
result: await adapter.execute(request.payload.request),
}
: {
kind: "replace",
result: await adapter.replace(request.payload.request),
};
response = {
protocolVersion: WORKER_PROTOCOL_VERSION,
requestId: request.requestId,
generation: request.generation,
ok: true,
payload,
};
} catch (error) {
response = {
protocolVersion: WORKER_PROTOCOL_VERSION,
requestId: request.requestId,
generation: request.generation,
ok: false,
error: workerError(error),
};
}
scope.postMessage(response);
};

View File

@@ -1,6 +1,8 @@
/// <reference lib="webworker" /> /// <reference lib="webworker" />
import { EcmaScriptSyntaxProvider } from "../regex/syntax/providers/ecmascript/EcmaScriptSyntaxProvider"; import { EcmaScriptSyntaxProvider } from "../regex/syntax/providers/ecmascript/EcmaScriptSyntaxProvider";
import { JavaSyntaxProvider } from "../regex/syntax/providers/java/JavaSyntaxProvider";
import { Pcre2SyntaxProvider } from "../regex/syntax/providers/pcre2/Pcre2SyntaxProvider"; import { Pcre2SyntaxProvider } from "../regex/syntax/providers/pcre2/Pcre2SyntaxProvider";
import { PythonSyntaxProvider } from "../regex/syntax/providers/python/PythonSyntaxProvider";
import { import {
WORKER_PROTOCOL_VERSION, WORKER_PROTOCOL_VERSION,
workerError, workerError,
@@ -13,6 +15,8 @@ import {
const providers = { const providers = {
ecmascript: new EcmaScriptSyntaxProvider(), ecmascript: new EcmaScriptSyntaxProvider(),
pcre2: new Pcre2SyntaxProvider(), pcre2: new Pcre2SyntaxProvider(),
python: new PythonSyntaxProvider(),
java: new JavaSyntaxProvider(),
} as const; } as const;
const scope = self as DedicatedWorkerGlobalScope; const scope = self as DedicatedWorkerGlobalScope;
@@ -29,6 +33,10 @@ scope.onmessage = async (
? providers.ecmascript ? providers.ecmascript
: flavour === "pcre2" : flavour === "pcre2"
? providers.pcre2 ? providers.pcre2
: flavour === "python"
? providers.python
: flavour === "java"
? providers.java
: undefined; : undefined;
if (!provider) { if (!provider) {
throw new Error(`No syntax provider is registered for ${flavour}.`); throw new Error(`No syntax provider is registered for ${flavour}.`);

View File

@@ -0,0 +1,123 @@
import { expect, test, type Page } from "@playwright/test";
async function setEditor(page: Page, testId: string, value: string) {
await page.getByTestId(testId).locator(".cm-content").fill(value);
}
async function waitForReady(page: Page, timeout = 15_000) {
await expect(page.locator(".run-status")).toHaveClass(/status-ready/u, {
timeout,
});
}
test("CPython re executes and replaces with code-point-native offsets under the production CSP", async ({
page,
}) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
page.on("console", (message) => {
if (message.type() === "error") consoleErrors.push(message.text());
});
const response = await page.goto("/deep/nested/regex/");
expect(response?.headers()["content-security-policy"]).toContain(
"script-src 'self' 'wasm-unsafe-eval'",
);
expect(response?.headers()["content-security-policy"]).not.toContain(
"'unsafe-eval'",
);
await waitForReady(page);
await page.getByLabel("Live").uncheck();
await page.getByRole("combobox", { name: "Flavour" }).selectOption("python");
await setEditor(page, "editor-regular-expression-pattern", "(?P<word>\\w+)");
await setEditor(page, "editor-test-text", "😀 café");
await page.getByRole("button", { name: "Run", exact: true }).click();
await waitForReady(page, 45_000);
await expect(page.locator(".run-status")).toContainText(
"native offsets code-point",
);
const match = page.locator(".extraction-row").first();
await expect(match).toContainText("UTF-16 range: 3…7");
await expect(match).toContainText("native range: 2…6 code-point");
await page.getByRole("button", { name: "Replace", exact: true }).click();
await setEditor(page, "editor-replacement-template", "[\\g<word>]");
await page.getByRole("button", { name: "Run", exact: true }).click();
await waitForReady(page, 45_000);
await expect(
page.getByTestId("editor-replacement-output").locator(".cm-content"),
).toHaveText("😀 [café]");
await page.getByRole("button", { name: "Quick reference" }).click();
await expect(
page.getByRole("dialog", { name: "Quick reference" }),
).toContainText("Python 3.14 re documentation");
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "Capabilities" }).click();
const capabilities = page.getByRole("dialog", { name: "Capabilities" });
await expect(capabilities).toContainText("CPython re via Pyodide");
await expect(capabilities).toContainText("CPython 3.14.2 re");
expect(pageErrors).toEqual([]);
expect(consoleErrors).toEqual([]);
});
test("TeaVM java.util.regex executes Java-only syntax and numeric replacement with UTF-16 offsets", async ({
page,
}) => {
const pageErrors: string[] = [];
page.on("pageerror", (error) => pageErrors.push(error.message));
await page.goto("/deep/nested/regex/");
await waitForReady(page);
await page.getByLabel("Live").uncheck();
await page.getByRole("combobox", { name: "Flavour" }).selectOption("java");
await setEditor(
page,
"editor-regular-expression-pattern",
"(?<word>[a-z]++)",
);
await setEditor(page, "editor-test-text", "😀 abc");
await page.getByRole("button", { name: "Run", exact: true }).click();
await waitForReady(page);
await expect(page.locator(".run-status")).toContainText(
"native offsets utf16",
);
const match = page.locator(".extraction-row").first();
await expect(match).toContainText("UTF-16 range: 3…6");
await expect(match).toContainText("native range: 3…6 utf16");
await page.getByRole("button", { name: "Replace", exact: true }).click();
await setEditor(page, "editor-replacement-template", "<$1>");
await page.getByRole("button", { name: "Run", exact: true }).click();
await waitForReady(page);
await expect(
page.getByTestId("editor-replacement-output").locator(".cm-content"),
).toHaveText("😀 <abc>");
await page.getByRole("button", { name: "Capabilities" }).click();
const capabilities = page.getByRole("dialog", { name: "Capabilities" });
await expect(capabilities).toContainText("TeaVM java.util.regex");
await expect(capabilities).toContainText("not OpenJDK");
expect(pageErrors).toEqual([]);
});
test("serves the self-hosted Python and Java runtime assets with executable MIME types", async ({
request,
}) => {
const pythonModule = await request.get("./engines/python/pyodide.asm.mjs");
expect(pythonModule.ok()).toBe(true);
expect(pythonModule.headers()["content-type"]).toContain("text/javascript");
const pythonWasm = await request.get("./engines/python/pyodide.asm.wasm");
expect(pythonWasm.ok()).toBe(true);
expect(pythonWasm.headers()["content-type"]).toContain("application/wasm");
expect((await pythonWasm.body()).subarray(0, 4)).toEqual(
Buffer.from([0x00, 0x61, 0x73, 0x6d]),
);
const javaModule = await request.get("./engines/java/java-regex.mjs");
expect(javaModule.ok()).toBe(true);
expect(javaModule.headers()["content-type"]).toContain("text/javascript");
});

View File

@@ -0,0 +1,294 @@
// @vitest-environment node
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { beforeAll, describe, expect, it } from "vitest";
import {
JavaEngineAdapter,
JAVA_ENGINE_IDENTITY,
JAVA_ENGINE_VERSION,
} from "../../src/regex/execution/adapters/java/JavaEngineAdapter";
import type { TeaVmJavaRegexModule } from "../../src/regex/execution/adapters/java/java-module";
import type {
RegexExecutionRequest,
RegexReplacementRequest,
} from "../../src/regex/model/match";
const pack = path.resolve("public/engines/java");
let adapter: JavaEngineAdapter;
function request(
pattern: string,
subject: string,
overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
return {
flavour: "java",
flavourVersion: JAVA_ENGINE_IDENTITY,
pattern,
flags: ["g"],
subject,
captureMetadata: [],
scanAll: false,
maximumMatches: 100,
maximumCaptureRows: 1_000,
...overrides,
};
}
function replacement(
pattern: string,
subject: string,
replacementText: string,
overrides: Partial<RegexReplacementRequest> = {},
): RegexReplacementRequest {
return {
...request(pattern, subject),
replacement: replacementText,
maximumOutputBytes: 1_000,
...overrides,
};
}
beforeAll(async () => {
const imported = (await import(
`${pathToFileURL(path.join(pack, "java-regex.mjs")).href}?conformance`
)) as unknown as TeaVmJavaRegexModule;
adapter = new JavaEngineAdapter(
imported,
"TeaVM JavaScript · Node.js conformance · not OpenJDK",
);
await adapter.load();
});
describe("pinned TeaVM 0.15.0 java.util.regex conformance", () => {
it("loads the exact compatibility engine identity", async () => {
await expect(adapter.load()).resolves.toEqual(
expect.objectContaining({
engineName: "TeaVM java.util.regex",
engineVersion: JAVA_ENGINE_VERSION,
runtimeVersion: expect.stringContaining("not OpenJDK"),
offsetUnit: "utf16",
}),
);
});
it("returns native UTF-16 offsets and named/numbered metadata", async () => {
const result = await adapter.execute(
request("(?<emoji>😀)([A-Z]+)?", "😀AB 😀", {
captureMetadata: [
{
number: 1,
name: "emoji",
range: { startUtf16: 0, endUtf16: 12 },
repeated: false,
},
{
number: 2,
name: "tail",
range: { startUtf16: 12, endUtf16: 21 },
repeated: false,
},
],
}),
);
expect(result.accepted).toBe(true);
expect(result.matches.map(({ range }) => range)).toEqual([
{ startUtf16: 0, endUtf16: 4 },
{ startUtf16: 5, endUtf16: 7 },
]);
expect(result.matches[0]?.captures).toEqual([
expect.objectContaining({
groupNumber: 1,
groupName: "emoji",
value: "😀",
range: { startUtf16: 0, endUtf16: 2 },
}),
expect.objectContaining({
groupNumber: 2,
groupName: "tail",
value: "AB",
range: { startUtf16: 2, endUtf16: 4 },
}),
]);
expect(result.matches[1]?.captures[1]).toEqual({
groupNumber: 2,
groupName: "tail",
status: "did-not-participate",
});
});
it("supports TeaVM lookbehind, numeric backreferences and atomic groups", async () => {
const lookbehind = await adapter.execute(request("(?<=a)(b)\\1", "abb ab"));
expect(lookbehind.matches.map(({ value }) => value)).toEqual(["bb"]);
const atomic = await adapter.execute(request("(?>a|ab)c", "abc"));
expect(atomic.matches).toHaveLength(0);
});
it("advances global zero-length matches in UTF-16 without looping", async () => {
const result = await adapter.execute(request("(?=.)", "😀x"));
expect(result.matches.map(({ range }) => range)).toEqual([
{ startUtf16: 0, endUtf16: 0 },
{ startUtf16: 1, endUtf16: 1 },
{ startUtf16: 2, endUtf16: 2 },
]);
});
it("maps i, m, s, u and x to TeaVM Pattern flags", async () => {
const cases = [
["a", "A", ["i"]],
["ä", "Ä", ["i", "u"]],
["^b$", "a\nb\nc", ["m"]],
["a.b", "a\nb", ["s"]],
["a b # comment", "ab", ["x"]],
] as const;
for (const [pattern, subject, flags] of cases) {
const result = await adapter.execute(
request(pattern, subject, { flags: [...flags] }),
);
expect(result.matches, `${flags.join("")}: ${pattern}`).toHaveLength(1);
}
const normalized = await adapter.execute(
request("a", "A", { flags: ["i", "m", "s", "u", "x"] }),
);
expect(normalized.flags.effectiveFlags).toBe("imsux");
});
it("maps d to UNIX_LINES and reports the honest U compatibility boundary", async () => {
const ordinaryLines = await adapter.execute(
request(".", "\r", { flags: [] }),
);
const unixLines = await adapter.execute(
request(".", "\r", { flags: ["d"] }),
);
expect(ordinaryLines.matches).toHaveLength(0);
expect(unixLines.matches).toHaveLength(1);
const result = await adapter.execute(
request("\\w+", "é", { flags: ["d", "U"] }),
);
expect(result.accepted).toBe(true);
expect(result.matches).toHaveLength(0);
expect(result.flags.userFlags).toBe("dU");
expect(result.flags.effectiveFlags).toBe("duU");
expect(result.flags.internallyAddedIndicesFlag).toBe(false);
expect(result.diagnostics[0]).toEqual(
expect.objectContaining({
code: "engine-compatibility",
message: expect.stringContaining("does not implement OpenJDK"),
}),
);
});
it("uses TeaVM Matcher native single-digit $n replacement", async () => {
const result = await adapter.replace(replacement("(a)", "a a", "<$1>"));
expect(result.output).toBe("<a> <a>");
expect(result.outputTruncated).toBe(false);
expect(result.execution.matches).toHaveLength(2);
const escaped = await adapter.replace(replacement("(a)", "a a", "\\$1"));
expect(escaped.output).toBe("$1 $1");
});
it("rejects ${name}, which TeaVM 0.15.0 does not support", async () => {
const unsupportedNamed = await adapter.replace(
replacement("(?<name>a)", "a", "${name}"),
);
expect(unsupportedNamed.execution.accepted).toBe(false);
expect(unsupportedNamed.execution.diagnostics[0]).toEqual(
expect.objectContaining({
code: "replacement-error",
message: expect.stringMatching(/IllegalArgumentException/u),
}),
);
});
it("returns compile diagnostics and enforces match/capture/output caps", async () => {
const invalid = await adapter.execute(request("(", "x"));
expect(invalid.accepted).toBe(false);
expect(invalid.diagnostics[0]).toEqual(
expect.objectContaining({
code: "compile-error",
provenance: "reported",
}),
);
const matchLimited = await adapter.execute(
request("a", "aaa", { maximumMatches: 1 }),
);
expect(matchLimited.matches).toHaveLength(1);
expect(matchLimited.truncated).toBe(true);
const captureLimited = await adapter.execute(
request("(a)(b)", "ab", {
maximumCaptureRows: 1,
captureMetadata: [
{
number: 1,
range: { startUtf16: 0, endUtf16: 3 },
repeated: false,
},
{
number: 2,
range: { startUtf16: 3, endUtf16: 6 },
repeated: false,
},
],
}),
);
expect(captureLimited.matches).toHaveLength(0);
expect(captureLimited.truncated).toBe(true);
const outputLimited = await adapter.replace(
replacement("a", "aaaa", "long", { maximumOutputBytes: 5 }),
);
expect(outputLimited.output).toBe("longl");
expect(outputLimited.outputBytes).toBe(5);
expect(outputLimited.outputTruncated).toBe(true);
});
it("ships internally consistent source identity, licence and checksums", async () => {
const metadata = JSON.parse(
await readFile(path.join(pack, "engine-metadata.json"), "utf8"),
) as {
engineName: string;
engineVersion: string;
semanticIdentity: string;
source: {
tag: string;
commitSha1: string;
license: string;
};
};
expect(metadata).toEqual(
expect.objectContaining({
engineName: "TeaVM java.util.regex",
engineVersion: "0.15.0",
semanticIdentity: expect.stringContaining("not OpenJDK"),
source: expect.objectContaining({
tag: "0.15.0",
commitSha1: "ee91b03e616c4b45401cd11fb0cd7eb0daf6649b",
license: "Apache-2.0",
}),
}),
);
const checksumText = await readFile(path.join(pack, "SHA256SUMS"), "utf8");
for (const line of checksumText.trim().split("\n")) {
const [expected, name] = line.split(/ {2}/u);
const contents = await readFile(path.join(pack, name ?? ""));
expect(createHash("sha256").update(contents).digest("hex"), name).toBe(
expected,
);
}
expect(await readFile(path.join(pack, "LICENSE.txt"), "utf8")).toContain(
"Apache License",
);
});
});

View File

@@ -0,0 +1,240 @@
// @vitest-environment node
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
PythonEngineAdapter,
PYODIDE_ENGINE_VERSION,
PYTHON_ENGINE_VERSION,
} from "../../src/regex/execution/adapters/python/PythonEngineAdapter";
import type {
PyodideModule,
PyodideRuntime,
} from "../../src/regex/execution/adapters/python/python-module";
import type {
RegexExecutionRequest,
RegexReplacementRequest,
} from "../../src/regex/model/match";
const engineDirectory = path.resolve(
process.cwd(),
"public",
"engines",
"python",
);
function request(
overrides: Partial<RegexExecutionRequest> = {},
): RegexExecutionRequest {
return {
flavour: "python",
flavourVersion: PYTHON_ENGINE_VERSION,
pattern: "a",
flags: ["g"],
subject: "a",
captureMetadata: [],
scanAll: false,
maximumMatches: 100,
maximumCaptureRows: 1_000,
...overrides,
};
}
describe("CPython re 3.14.2 conformance through self-hosted Pyodide", () => {
let runtime: PyodideRuntime;
let adapter: PythonEngineAdapter;
beforeAll(async () => {
const moduleUrl = pathToFileURL(path.join(engineDirectory, "pyodide.mjs"));
const imported = (await import(
/* @vite-ignore */ moduleUrl.href
)) as PyodideModule;
expect(imported.version).toBe(PYODIDE_ENGINE_VERSION);
runtime = await imported.loadPyodide({
indexURL: `${engineDirectory}${path.sep}`,
stdout: () => undefined,
stderr: () => undefined,
});
adapter = new PythonEngineAdapter(runtime, "Node conformance runtime");
await adapter.load();
}, 30_000);
afterAll(() => {
adapter?.terminate();
});
it("reports the exact engine, runtime and native offset identity", async () => {
await expect(adapter.load()).resolves.toEqual(
expect.objectContaining({
flavour: "python",
engineName: "CPython re via Pyodide",
engineVersion: "CPython 3.14.2 re",
runtimeVersion: expect.stringContaining("Pyodide 314.0.3"),
offsetUnit: "code-point",
}),
);
});
it("uses native Python named groups and preserves optional captures", async () => {
const result = await adapter.execute(
request({
pattern: "(?P<word>[A-Za-z]+)(?:-(\\d+))?",
subject: "abc-12 xyz",
}),
);
expect(result.accepted).toBe(true);
expect(result.matches).toHaveLength(2);
expect(result.matches[0]?.captures).toEqual([
expect.objectContaining({
groupNumber: 1,
groupName: "word",
value: "abc",
}),
expect.objectContaining({ groupNumber: 2, value: "12" }),
]);
expect(result.matches[1]?.captures).toEqual([
expect.objectContaining({
groupNumber: 1,
groupName: "word",
value: "xyz",
}),
{ groupNumber: 2, status: "did-not-participate" },
]);
});
it("normalizes astral and zero-length code-point offsets to UTF-16", async () => {
const result = await adapter.execute(
request({
pattern: "(?=.)",
subject: "😀a",
}),
);
expect(
result.matches.map((match) => ({
native: match.nativeRange,
editor: match.range,
status: match.valueStatus,
})),
).toEqual([
{
native: { start: 0, end: 0, unit: "code-point" },
editor: { startUtf16: 0, endUtf16: 0 },
status: "complete",
},
{
native: { start: 1, end: 1, unit: "code-point" },
editor: { startUtf16: 2, endUtf16: 2 },
status: "complete",
},
]);
});
it("implements g, a, i, m, s and x with scan-all normalization", async () => {
const combined = await adapter.execute(
request({
pattern: "^ a . b $",
flags: ["i", "m", "s", "x"],
subject: "z\nA\nB\nq",
scanAll: true,
}),
);
expect(combined.accepted).toBe(true);
expect(combined.flags.effectiveFlags).toBe("gimsx");
expect(combined.flags.internallyAddedGlobalFlag).toBe(true);
expect(combined.matches.map((match) => match.value)).toEqual(["A\nB"]);
const ascii = await adapter.execute(
request({
pattern: "\\w+",
flags: ["g", "a"],
subject: "é az",
}),
);
expect(ascii.matches.map((match) => match.value)).toEqual(["az"]);
});
it("uses native match.expand syntax for named, numbered and zero-length replacement", async () => {
const replacementRequest: RegexReplacementRequest = {
...request({
pattern: "(?P<word>[a-z]+)-(\\d+)",
subject: "a-1 b-22",
}),
replacement: "\\g<word>[\\2]",
maximumOutputBytes: 1_024,
};
const replaced = await adapter.replace(replacementRequest);
expect(replaced.execution.accepted).toBe(true);
expect(replaced.output).toBe("a[1] b[22]");
expect(replaced.outputTruncated).toBe(false);
const zeroLength = await adapter.replace({
...request({
pattern: "(?=.)",
subject: "😀a",
}),
replacement: "|",
maximumOutputBytes: 1_024,
});
expect(zeroLength.output).toBe("|😀|a");
});
it("bounds UTF-8 output without splitting an astral scalar", async () => {
const result = await adapter.replace({
...request({ pattern: ".", subject: "abc" }),
replacement: "😀",
maximumOutputBytes: 5,
});
expect(result.execution.accepted).toBe(true);
expect(result.output).toBe("😀");
expect(result.outputBytes).toBe(4);
expect(result.outputTruncated).toBe(true);
expect(result.truncated).toBe(true);
});
it("reports native compile and replacement errors", async () => {
const compileError = await adapter.execute(request({ pattern: "😀(" }));
expect(compileError.accepted).toBe(false);
expect(compileError.diagnostics[0]).toEqual(
expect.objectContaining({
code: "compile-error",
range: { startUtf16: 2, endUtf16: 2 },
}),
);
const replacementError = await adapter.replace({
...request({ pattern: "(?P<word>a)" }),
replacement: "\\g<missing>",
maximumOutputBytes: 1_024,
});
expect(replacementError.execution.accepted).toBe(false);
expect(replacementError.execution.diagnostics[0]?.code).toBe(
"replacement-error",
);
});
it("enforces authoritative match and capture-row collection caps", async () => {
const matchCap = await adapter.execute(
request({
pattern: "a",
subject: "aaa",
maximumMatches: 1,
}),
);
expect(matchCap.matches).toHaveLength(1);
expect(matchCap.truncated).toBe(true);
const rowCap = await adapter.execute(
request({
pattern: "(a)(b)",
subject: "ab",
maximumCaptureRows: 1,
}),
);
expect(rowCap.matches).toHaveLength(0);
expect(rowCap.truncated).toBe(true);
});
});