Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7079cde15f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@ node_modules/
|
||||
dist/
|
||||
release/
|
||||
.engine-build/
|
||||
engines/java/target/
|
||||
coverage/
|
||||
playwright-report/
|
||||
test-results/
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
public/engines/pcre2/pcre2.mjs
|
||||
public/engines/java/
|
||||
public/engines/python/
|
||||
|
||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -2,6 +2,31 @@
|
||||
|
||||
## 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
|
||||
|
||||
- Open Quick reference and Capabilities as populated, accessible in-viewport
|
||||
|
||||
103
README.md
103
README.md
@@ -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,
|
||||
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
|
||||
PCRE2 10.47.
|
||||
The v0.3.0 release contains four executable engine slices:
|
||||
|
||||
- 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
|
||||
grammar; application-owned structural explanations are partial and
|
||||
feature-matrix tested.
|
||||
- Execution engine: the current browser's native ECMAScript `RegExp`.
|
||||
- Native and editor offsets: UTF-16 code units.
|
||||
- ECMAScript native and editor offsets are UTF-16 code units.
|
||||
- PCRE2 execution and substitution: pinned official PCRE2 10.47 compiled to a
|
||||
self-hosted 8-bit WebAssembly pack with UTF/UCP always enabled.
|
||||
- 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
|
||||
displayed from a dedicated killable trace worker under 50,000-event and
|
||||
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.
|
||||
- Actual execution can be terminated by killing its worker.
|
||||
- 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.
|
||||
- Match, replacement, typed list/extraction, corpus/apply and unit-test modes
|
||||
are available.
|
||||
@@ -73,7 +92,8 @@ as a complete record of every internal engine action.
|
||||
## Workbench modes
|
||||
|
||||
- **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
|
||||
original/result, changed-range and token-contribution previews that
|
||||
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
|
||||
`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
|
||||
represented safely in the editor. The optional **Scan all** action adds only an
|
||||
internal iteration flag to that request and does not change saved flags.
|
||||
ECMAScript may add internal `d` to obtain exact indices without changing match
|
||||
semantics.
|
||||
represented safely in the editor. Python exposes application-level `g` plus
|
||||
CPython `re` flags `a`, `i`, `m`, `s` and `x`. Java exposes application-level
|
||||
`g` plus TeaVM `Pattern` flags `i`, `d`, `m`, `s`, `u`, `x` and the documented
|
||||
`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
|
||||
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:verify
|
||||
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 toolbox: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; they do not build Regex Tools source.
|
||||
|
||||
The PCRE2 worker loads self-hosted JavaScript and WebAssembly only. Deployment
|
||||
must serve `.wasm` as `application/wasm` and allow WebAssembly compilation in
|
||||
`script-src`; see [`docs/PORTAL_REQUIREMENTS.md`](docs/PORTAL_REQUIREMENTS.md).
|
||||
The PCRE2 and Python workers load only self-hosted JavaScript, WebAssembly and
|
||||
Python-standard-library assets; Java loads its self-hosted ES module.
|
||||
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
|
||||
provides bounded compilation, matching, native substitution and separate
|
||||
automatic-callout tracing, copied caller-owned records, deterministic
|
||||
allocation cleanup and explicit result completeness. The exact
|
||||
source/toolchain build is reproducible offline and the verified pack is bundled
|
||||
with its metadata, checksums and licence.
|
||||
PCRE2's application-owned ABI provides bounded compilation, matching, native
|
||||
substitution and separate automatic-callout tracing, copied caller-owned
|
||||
records, deterministic allocation cleanup and explicit result completeness.
|
||||
The exact source/toolchain build is reproducible offline and the verified pack
|
||||
is bundled 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
|
||||
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.
|
||||
|
||||
Pattern formatting is likewise ECMAScript-only. It consumes the complete
|
||||
regexpp token model and does not reinterpret the partial PCRE2 structure.
|
||||
PCRE2 formatting remains unavailable until a complete grammar-backed
|
||||
implementation passes equivalent idempotence, reparse, engine and test gates.
|
||||
regexpp token model and does not reinterpret the partial PCRE2, Python or Java
|
||||
structures. Those flavours remain unavailable until complete grammar-backed
|
||||
implementations pass equivalent idempotence, reparse, engine and test gates.
|
||||
|
||||
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
|
||||
the exact official release. See
|
||||
[`docs/COMPARISON_AND_CODEGEN.md`](docs/COMPARISON_AND_CODEGEN.md).
|
||||
|
||||
Python, Go, Rust, .NET, Java and legacy PCRE follow only through their actual
|
||||
named runtimes. No flavour is emulated through JavaScript and relabelled.
|
||||
Go, Rust, .NET and legacy PCRE follow only through their actual named runtimes.
|
||||
No flavour is emulated through JavaScript and relabelled.
|
||||
|
||||
## Licensing
|
||||
|
||||
@@ -308,7 +346,7 @@ notices; see [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) and
|
||||
No regex101 application source, branding, generated explanation prose,
|
||||
reference text or visual design is copied. RegexLib and RGXP.RU fixtures are not
|
||||
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
|
||||
|
||||
@@ -332,6 +370,14 @@ RegexHub fixture is shipped in v0.2.0.
|
||||
recognizes common capture forms and PCRE2-only constructs for reference, but
|
||||
the actual PCRE2 compiler is authoritative. Branch-reset capture numbering is
|
||||
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
|
||||
bounded separately.
|
||||
- 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
|
||||
source/candidate and unit-test validation is evidence for the checked
|
||||
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
|
||||
[`docs/`](docs/).
|
||||
|
||||
38
SOURCE.md
38
SOURCE.md
@@ -1,10 +1,10 @@
|
||||
# Source identity
|
||||
|
||||
- Project: Regex Tools
|
||||
- Release version: `0.2.0`
|
||||
- Release tag: `v0.2.0`
|
||||
- Release version: `0.3.0`
|
||||
- Release tag: `v0.3.0`
|
||||
- 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
|
||||
- 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,
|
||||
source-identity and attribution documents.
|
||||
|
||||
The explicit offline PCRE2 build is documented in `docs/ENGINE_BUILDS.md`.
|
||||
PCRE2 source is not vendored: the gate accepts only the pinned, clean official
|
||||
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 engine-pack inputs and rebuild gates are documented in
|
||||
`docs/ENGINE_BUILDS.md`.
|
||||
|
||||
The v0.2.0 release includes PCRE2. Python, Go, Rust, .NET, Java and legacy PCRE
|
||||
remain unimplemented. The v0.1.0 release remains available under its historical
|
||||
tag and artifact coordinates. A future public artifact must bump package,
|
||||
manifest, tag and source identity together; it must not reuse `v0.2.0`.
|
||||
- PCRE2 source is not vendored: the gate accepts only the pinned, clean
|
||||
official 10.47 checkout and Emscripten 6.0.4 compiler.
|
||||
- The Python pack is copied from the exact pinned Pyodide 314.0.3 npm package
|
||||
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`.
|
||||
|
||||
@@ -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> |
|
||||
| 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> |
|
||||
| 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> |
|
||||
| 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
|
||||
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
|
||||
release package additionally carries the exact Vite and Rolldown legal files
|
||||
under `LICENSES/build/`, including Vite's bundled-dependency notices for code
|
||||
emitted by the production build.
|
||||
Licence texts and copyright notices for the ordinary bundled dependencies are
|
||||
in `LICENSES/`. Each engine pack carries its exact upstream licence/notice
|
||||
files alongside the runtime. The release package additionally carries the exact
|
||||
Vite and Rolldown legal files under `LICENSES/build/`, including Vite's
|
||||
bundled-dependency notices for code emitted by the production build.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Regex Tools provides one deliberately scoped analysis implementation:
|
||||
ECMAScript 2025 patterns parsed into the application-owned normalized AST and
|
||||
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
|
||||
under selected limits”. It never turns an empty finding list into “safe”,
|
||||
|
||||
@@ -7,11 +7,15 @@ boundaries.
|
||||
React workbench
|
||||
├─ Pattern SyntaxSupervisor → syntax.worker
|
||||
│ ├─ 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
|
||||
├─ EngineSupervisor
|
||||
│ ├─ 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
|
||||
│ └─ separate ABI-v3 PCRE2_AUTO_CALLOUT operation → bounded trace DTOs
|
||||
├─ AnalysisPanel
|
||||
@@ -42,7 +46,18 @@ provenance. React never imports or traverses regexpp's cyclic AST.
|
||||
Every engine request carries a protocol version, monotonically increasing
|
||||
request ID and worker generation. A supervisor allows one active request,
|
||||
rejects stale responses, terminates on timeout/crash/cancel, and lazily creates
|
||||
a new generation. Timeout is a distinct error state.
|
||||
a 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
|
||||
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.
|
||||
|
||||
Subject minimization is a main-thread bounded reducer whose expensive predicate
|
||||
checks stay in independently terminable engine workers. Fixed pattern syntax
|
||||
is parsed once for capture metadata. The baseline and every accepted candidate
|
||||
retain the exact target and engine identity. Timeout, crash, cancellation,
|
||||
worker failure and incomplete output are separate outcomes. Deterministic
|
||||
ddmin deletion is followed by a fixed-point local sweep over Unicode-scalar
|
||||
deletion and lower-rank canonical replacement; only completion of that sweep
|
||||
permits a transform-local minimality claim.
|
||||
checks stay in independently terminable ECMAScript or PCRE2 workers. Fixed
|
||||
pattern syntax is parsed once for capture metadata. Python and Java are blocked
|
||||
at this feature boundary rather than routed to a different engine. The baseline
|
||||
and every accepted candidate retain the exact target and engine identity.
|
||||
Timeout, crash, cancellation, worker failure and incomplete output are separate
|
||||
outcomes. Deterministic ddmin deletion is followed by a fixed-point local sweep
|
||||
over Unicode-scalar deletion and lower-rank canonical replacement; only
|
||||
completion of that sweep permits a transform-local minimality claim.
|
||||
|
||||
Generated-case synthesis and verification are separate trust boundaries. The
|
||||
dedicated generation worker receives only a validated, complete ECMAScript
|
||||
@@ -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
|
||||
bounded per-group participation counts and samples before the next document.
|
||||
|
||||
Native ranges are retained in the engine's declared unit. PCRE2 always uses
|
||||
UTF/UCP and exposes UTF-8 byte ranges; its ABI copies scalar records and names
|
||||
out of WebAssembly before returning and retains no code pointer. Browser editor ranges
|
||||
are half-open UTF-16 code-unit ranges. Reusable converters cover UTF-8 byte and
|
||||
Unicode code-point offsets, including invalid-boundary rejection and
|
||||
lone-surrogate detection.
|
||||
Native ranges are retained in the engine's declared unit. ECMAScript and Java
|
||||
use UTF-16 code units. PCRE2 always uses UTF/UCP and exposes UTF-8 byte ranges;
|
||||
its ABI copies scalar records and names out of WebAssembly before returning and
|
||||
retains no code pointer. CPython `re` exposes Unicode code-point indices.
|
||||
Browser editor ranges are half-open UTF-16 code-unit ranges. Reusable
|
||||
converters cover UTF-8 byte and Unicode code-point offsets, including
|
||||
invalid-boundary rejection and lone-surrogate detection.
|
||||
|
||||
The release boundary is `dist/` plus checked-in legal/source documents. The
|
||||
Portal consumes the resulting immutable ZIP and never imports React source.
|
||||
|
||||
@@ -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
|
||||
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:
|
||||
|
||||
- **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
|
||||
|
||||
The first reviewed code-generation target is the PCRE2 10.47 8-bit C API.
|
||||
Other languages remain unavailable until their generators have equivalent
|
||||
escaping, semantic and toolchain gates.
|
||||
Python, Java and other targets remain unavailable until their generators have
|
||||
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:
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Engine builds
|
||||
|
||||
The production application uses the browser's native `RegExp` for ECMAScript
|
||||
and bundles a verified PCRE2 10.47 WebAssembly pack. Normal application builds
|
||||
never download or compile an engine: the reviewed files under
|
||||
`public/engines/pcre2/` are copied into `dist/` and verified there.
|
||||
and bundles verified PCRE2, Python and Java runtime packs. Normal application
|
||||
builds never download or compile an engine: reviewed files under
|
||||
`public/engines/` are copied into `dist/` and verified there.
|
||||
|
||||
## 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
|
||||
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
|
||||
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,
|
||||
cancellation or supersession and lazily creates a clean generation for the
|
||||
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.
|
||||
|
||||
@@ -5,6 +5,10 @@ results, but no V8, SpiderMonkey or JavaScriptCore execution trace. Regex Tools
|
||||
does not relabel structural explanations, static findings or timing
|
||||
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
|
||||
compiles the requested pattern with `PCRE2_AUTO_CALLOUT`, registers an
|
||||
application callback, and copies only:
|
||||
|
||||
@@ -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
|
||||
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
|
||||
trace. Selecting a node selects and scrolls its exact editor range; selecting
|
||||
pattern text chooses the smallest enclosing node. PCRE2’s separate trace viewer
|
||||
contains actual bounded automatic callouts; its movement labels remain a
|
||||
distinct derived layer and are never copied into the structural explanation.
|
||||
Python and Java expose no actual execution trace.
|
||||
|
||||
@@ -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
|
||||
would consume an incomplete value.
|
||||
|
||||
Repeated ECMAScript captures expose only the final retained capture. The UI
|
||||
labels this limitation and does not invent history. Later .NET support may add
|
||||
actual engine capture-history nodes without degrading them to this model.
|
||||
Repeated ECMAScript, PCRE2, Python and Java captures expose only the final
|
||||
retained capture. The UI labels this limitation and does not invent history.
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Flavour support
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 UTF-8 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 |
|
||||
| Java | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned UTF-16 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| Go `regexp` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned bytes |
|
||||
| Rust `regex` | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Unavailable | Planned bytes |
|
||||
| .NET | Unavailable | Unavailable | Unavailable | Planned capture history | Unavailable | Unavailable | Unavailable | Planned UTF-16 |
|
||||
|
||||
ECMAScript tests cover its complete exposed flag matrix, internal indices,
|
||||
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,
|
||||
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
|
||||
advisory. They describe only the selected ECMAScript 2025 pattern, browser
|
||||
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
|
||||
AST, an explicit deterministic seed and bounded sampling strategies. Every
|
||||
retained positive or negative label is confirmed by the actual selected
|
||||
browser engine. PCRE2 generation is unavailable because its current lexical
|
||||
provider is intentionally partial; ECMAScript behavior is never relabelled as
|
||||
PCRE2.
|
||||
browser engine. PCRE2, Python and Java generation are unavailable because
|
||||
their current lexical providers are intentionally partial; ECMAScript behavior
|
||||
is never relabelled as another flavour.
|
||||
|
||||
The PCRE2 explanation provider deliberately does not invent full structural
|
||||
coverage. In particular, branch-reset numbering is omitted from the syntax tree
|
||||
while actual capture numbers and names still come from PCRE2 results.
|
||||
The PCRE2, Python and Java explanation providers deliberately do not invent
|
||||
full structural coverage. PCRE2 branch-reset numbering is omitted from the
|
||||
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,
|
||||
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
|
||||
snapshot. It canonicalizes literal slashes and raw control/line-separator
|
||||
representations, then requires paired actual-engine and applicable exact-test
|
||||
validation before apply. PCRE2 formatting is unavailable because the current
|
||||
provider is deliberately partial; its syntax is never treated as ECMAScript.
|
||||
validation before apply. PCRE2, Python and Java formatting is unavailable
|
||||
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
|
||||
pinned to the PCRE2 10.47 8-bit API; other language generators remain
|
||||
|
||||
@@ -23,8 +23,9 @@ pretty-printer. The transform is idempotent.
|
||||
Formatting requires the exact current, accepted, non-recovered syntax snapshot
|
||||
from the bundled `@eslint-community/regexpp` provider. Transformations are
|
||||
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
|
||||
other flavour is reported as unavailable and is never silently rewritten.
|
||||
text scan. A stale parse, a malformed pattern, a partial provider, PCRE2,
|
||||
Python, Java or any other flavour is reported as unavailable and is never
|
||||
silently rewritten.
|
||||
|
||||
## Mandatory validation before apply
|
||||
|
||||
|
||||
@@ -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
|
||||
language was enumerated.
|
||||
|
||||
PCRE2 generation is unavailable in version 1. Its actual engine is complete for
|
||||
the advertised execution operations, but its current structural syntax
|
||||
provider is intentionally partial. Regex Tools does not feed PCRE2 syntax to
|
||||
the ECMAScript generator or relabel ECMAScript behavior.
|
||||
PCRE2, Python and Java generation is unavailable in version 1. Their actual
|
||||
engines are complete for the advertised execution operations, but their
|
||||
current structural syntax providers are intentionally partial. Regex Tools
|
||||
does not feed their syntax to the ECMAScript generator or relabel ECMAScript
|
||||
behaviour.
|
||||
|
||||
## Unsupported and partial constructs
|
||||
|
||||
@@ -75,7 +76,9 @@ removes the generation provenance.
|
||||
Candidate synthesis runs in a dedicated killable worker. The verifier then
|
||||
sends each exact candidate, pattern, flavour version, flags, engine options and
|
||||
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
|
||||
at least one match.
|
||||
|
||||
@@ -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
|
||||
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 target identity. Syntax is parsed once to obtain capture metadata. Every
|
||||
accepted subject candidate is then verified through the selected execution
|
||||
|
||||
@@ -9,11 +9,17 @@ community
|
||||
It uses only reviewed open-source dependencies and builds without private
|
||||
registry credentials. ECMAScript uses regexpp 4.12.2. PCRE2 uses an
|
||||
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`
|
||||
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
|
||||
interface. Partial coverage is exposed as metadata and never presented as a
|
||||
complete parser.
|
||||
|
||||
@@ -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
|
||||
safety claim.
|
||||
|
||||
PCRE2 cold execution includes loading and instantiating the self-hosted
|
||||
WebAssembly module inside its worker; warm requests reuse that instance.
|
||||
PCRE2 cold worker startup includes loading and instantiating the self-hosted
|
||||
WebAssembly module; warm requests reuse that instance.
|
||||
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
|
||||
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
|
||||
compiles an independently instrumented PCRE2 pattern in a separate worker and
|
||||
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
|
||||
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
|
||||
retain the bounded engine value. List export is disabled when a source value is incomplete, so a bounded
|
||||
preview cannot be mistaken for the full value. List generation additionally
|
||||
stops at 250,000 template-token evaluations, a 64 Ki UTF-16 row preview, or an
|
||||
8 Mi-unit aggregate materialization bound. The UI shows 500 list rows while an
|
||||
eligible export still contains every generated row; any incomplete result
|
||||
disables export.
|
||||
retain the bounded engine value. List export is disabled when a source value is
|
||||
incomplete, so a bounded preview cannot be mistaken for the full value. List
|
||||
generation additionally stops at 250,000 template-token evaluations, a 64 Ki
|
||||
UTF-16 row preview, or an 8 Mi-unit aggregate materialization bound. The UI
|
||||
shows 500 list rows while an eligible export still contains every generated
|
||||
row; any incomplete result disables export.
|
||||
Replacement output has a separate 64 MiB bound. Match/capture-limit
|
||||
incompleteness and output-byte truncation are tracked separately; either makes
|
||||
the aggregate replacement result incomplete.
|
||||
|
||||
@@ -9,17 +9,23 @@ Required delivery behavior:
|
||||
revalidation/no-cache resources.
|
||||
- Hashed Vite assets may use immutable long-term caching.
|
||||
- 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
|
||||
`'wasm-unsafe-eval'`; broad `'unsafe-eval'` is neither needed nor recommended.
|
||||
- `engines/pcre2/pcre2.mjs`, metadata, checksums, licence and WASM should use
|
||||
revalidation or a release-scoped immutable cache. They are not
|
||||
- `connect-src 'self'` must allow the Pyodide worker to fetch its own
|
||||
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.
|
||||
|
||||
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/`.
|
||||
|
||||
The package, manifest, tag, artifact URL and checksum must all use the v0.2.0
|
||||
identity. The historical v0.1.0 artifact coordinates remain immutable and must
|
||||
not be overwritten or reused.
|
||||
The package, manifest, tag, artifact URL and checksum must all use the v0.3.0
|
||||
identity. Historical artifact coordinates remain immutable and must not be
|
||||
overwritten or reused.
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# Reference implementations and sources
|
||||
|
||||
Inspection date: 2026-07-24.
|
||||
Inspection date: 2026-07-27.
|
||||
|
||||
| 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 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. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
@@ -17,5 +20,5 @@ Inspection date: 2026-07-24.
|
||||
| RegexLib | 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`.
|
||||
|
||||
@@ -18,9 +18,13 @@ on publication failure, replaces an existing exact version only with
|
||||
`--force`, and produces:
|
||||
|
||||
```text
|
||||
release/regex-tools-0.2.0.zip
|
||||
release/regex-tools-0.2.0.zip.sha256
|
||||
release/regex-tools-0.3.0.zip
|
||||
release/regex-tools-0.3.0.zip.sha256
|
||||
```
|
||||
|
||||
The existing `release/regex-tools-0.1.0.zip` and matching sidecar are historical
|
||||
immutable artifacts. Creating v0.2.0 must not replace, rename or remove them.
|
||||
Before packaging, the engine verifier checks the closed PCRE2, Python and Java
|
||||
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.
|
||||
|
||||
@@ -19,6 +19,18 @@ untrusted.
|
||||
- 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
|
||||
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/list token decorations, rows, chips, diagnostics and 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,
|
||||
cancellation and worker error remain distinct; truncated results fail
|
||||
closed. Subjects and results are ephemeral and never uploaded or persisted.
|
||||
- No backend, uploads, telemetry, remote corpus URL, CDN, remote font, `eval`,
|
||||
`Function`, arbitrary command line or unsafe HTML rendering exists.
|
||||
- No backend, uploads, telemetry, remote corpus URL, CDN, remote font,
|
||||
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
|
||||
default-src 'self';
|
||||
@@ -91,9 +107,11 @@ base-uri 'none';
|
||||
form-action 'none';
|
||||
```
|
||||
|
||||
The Toolbox shell currently requires inline styles. Corpus ZIP generation and
|
||||
PCRE2 use self-hosted modules and local workers under the existing
|
||||
`worker-src` policy. Broad `unsafe-eval` is not required.
|
||||
The Toolbox shell currently requires inline styles. Corpus ZIP generation,
|
||||
PCRE2, Pyodide and TeaVM use self-hosted modules/assets and local workers under
|
||||
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
|
||||
details are posted.
|
||||
|
||||
202
engines/java/LICENSE-TeaVM.txt
Normal file
202
engines/java/LICENSE-TeaVM.txt
Normal 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.
|
||||
11
engines/java/NOTICE-TeaVM.txt
Normal file
11
engines/java/NOTICE-TeaVM.txt
Normal 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
25
engines/java/README.md
Normal 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
84
engines/java/pom.xml
Normal 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>
|
||||
@@ -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
277
engines/python/bridge.py
Normal 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
48
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "regex-tools",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "regex-tools",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@add-ideas/toolbox-contract": "0.2.2",
|
||||
@@ -39,6 +39,7 @@
|
||||
"globals": "17.7.0",
|
||||
"jsdom": "29.1.1",
|
||||
"prettier": "3.9.5",
|
||||
"pyodide": "314.0.3",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.64.0",
|
||||
"vite": "8.1.5",
|
||||
@@ -1385,6 +1386,13 @@
|
||||
"dev": true,
|
||||
"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": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
||||
@@ -3405,6 +3413,20 @@
|
||||
"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": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
@@ -4101,6 +4123,28 @@
|
||||
"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": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "regex-tools",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "Develop, explain, test and apply regular expressions locally in the browser.",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"author": "Albrecht Degering",
|
||||
@@ -35,6 +35,12 @@
|
||||
"engines:pcre2:build": "node scripts/build-engines.mjs --pcre2",
|
||||
"engines:pcre2:install": "node scripts/install-pcre2-engine.mjs",
|
||||
"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",
|
||||
"manifest:generate": "node scripts/generate-toolbox-manifest.mjs",
|
||||
"manifest:check": "node scripts/generate-toolbox-manifest.mjs --check",
|
||||
@@ -75,6 +81,7 @@
|
||||
"globals": "17.7.0",
|
||||
"jsdom": "29.1.1",
|
||||
"prettier": "3.9.5",
|
||||
"pyodide": "314.0.3",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.64.0",
|
||||
"vite": "8.1.5",
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
# Engine assets
|
||||
|
||||
Regex Tools executes ECMAScript through the browser's native `RegExp` engine.
|
||||
It also ships the declared, self-hosted PCRE2 10.47 WebAssembly pack in
|
||||
`pcre2/`.
|
||||
It also ships three declared, self-hosted runtime packs:
|
||||
|
||||
The PCRE2 pack is built from pinned, documented open-source code and
|
||||
toolchain revisions. `engine-metadata.json`, `SHA256SUMS` and `LICENSE.txt`
|
||||
travel with the runtime files and are verified by the release gate.
|
||||
- `pcre2/`: official PCRE2 10.47, 8-bit WebAssembly;
|
||||
- `python/`: Pyodide 314.0.3 with CPython 3.14.2 `re`; and
|
||||
- `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.
|
||||
|
||||
202
public/engines/java/LICENSE.txt
Normal file
202
public/engines/java/LICENSE.txt
Normal 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.
|
||||
11
public/engines/java/NOTICE.txt
Normal file
11
public/engines/java/NOTICE.txt
Normal 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/).
|
||||
4
public/engines/java/SHA256SUMS
Normal file
4
public/engines/java/SHA256SUMS
Normal file
@@ -0,0 +1,4 @@
|
||||
cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 LICENSE.txt
|
||||
71bbe79ff78d0e5e64096acdd418589abfe886b8f827048dc655df438a57e477 NOTICE.txt
|
||||
719cb99761c8f0964835bf7b954fcf6e0df30ec14d2c06050c07c23a978d6d22 engine-metadata.json
|
||||
fd7974e7bde911ef0682cdc432d28e93c5db9d56f7268482a62e40a2ad86d62f java-regex.mjs
|
||||
118
public/engines/java/engine-metadata.json
Normal file
118
public/engines/java/engine-metadata.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
441
public/engines/java/java-regex.mjs
Normal file
441
public/engines/java/java-regex.mjs
Normal 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};
|
||||
277
public/engines/python/LICENSE.cpython.txt
Normal file
277
public/engines/python/LICENSE.cpython.txt
Normal 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.
|
||||
373
public/engines/python/LICENSE.pyodide.txt
Normal file
373
public/engines/python/LICENSE.pyodide.txt
Normal 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.
|
||||
8
public/engines/python/SHA256SUMS
Normal file
8
public/engines/python/SHA256SUMS
Normal 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
|
||||
99
public/engines/python/engine-metadata.json
Normal file
99
public/engines/python/engine-metadata.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
1
public/engines/python/pyodide-lock.json
Normal file
1
public/engines/python/pyodide-lock.json
Normal file
File diff suppressed because one or more lines are too long
2
public/engines/python/pyodide.asm.mjs
Normal file
2
public/engines/python/pyodide.asm.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
public/engines/python/pyodide.asm.wasm
Normal file
BIN
public/engines/python/pyodide.asm.wasm
Normal file
Binary file not shown.
3
public/engines/python/pyodide.mjs
Normal file
3
public/engines/python/pyodide.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
public/engines/python/python_stdlib.zip
Normal file
BIN
public/engines/python/python_stdlib.zip
Normal file
Binary file not shown.
@@ -3,7 +3,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "de.add-ideas.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.",
|
||||
"entry": "./",
|
||||
"icon": "./favicon.svg",
|
||||
@@ -36,5 +36,15 @@
|
||||
"label": "Source",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -40,15 +40,17 @@ const entries = (await readdir(engineDirectory)).sort();
|
||||
|
||||
if (
|
||||
typeof packageJson.version !== "string" ||
|
||||
entries.length !== 2 ||
|
||||
entries.length !== 4 ||
|
||||
entries[0] !== "README.md" ||
|
||||
entries[1] !== "pcre2"
|
||||
entries[1] !== "java" ||
|
||||
entries[2] !== "pcre2" ||
|
||||
entries[3] !== "python"
|
||||
) {
|
||||
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(
|
||||
"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.",
|
||||
);
|
||||
|
||||
16
scripts/install-java-engine.mjs
Normal file
16
scripts/install-java-engine.mjs
Normal 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}.`,
|
||||
);
|
||||
16
scripts/install-python-engine.mjs
Normal file
16
scripts/install-python-engine.mjs
Normal 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}.`,
|
||||
);
|
||||
603
scripts/java-engine-pack.mjs
Normal file
603
scripts/java-engine-pack.mjs
Normal 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).`,
|
||||
);
|
||||
}
|
||||
@@ -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 gplVersion3TextSha256 =
|
||||
"fb981668c18a279e285fc4d83fba1e836cc84dd4daa73c9697d3cfd2d8aca6e0";
|
||||
const expectedApplicationVersion = "0.2.0";
|
||||
const expectedApplicationVersion = "0.3.0";
|
||||
const requiredRootFiles = [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
@@ -387,11 +387,25 @@ async function collectReleaseEntries() {
|
||||
"SOURCE.md",
|
||||
"THIRD_PARTY_NOTICES.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/SHA256SUMS",
|
||||
"engines/pcre2/engine-metadata.json",
|
||||
"engines/pcre2/pcre2.mjs",
|
||||
"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/PCRE2.txt",
|
||||
"LICENSES/build/rolldown-1.1.5/LICENSE",
|
||||
|
||||
572
scripts/python-engine-pack.mjs
Normal file
572
scripts/python-engine-pack.mjs
Normal 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}.`,
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,14 @@ const testCatalogue = {
|
||||
theme: { mode: "system", brand: "add·ideas" },
|
||||
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) {
|
||||
const decoded = decodeURIComponent(requestPath);
|
||||
@@ -50,6 +58,7 @@ const server = createServer(async (request, response) => {
|
||||
response.writeHead(200, {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"Cache-Control": "no-cache",
|
||||
...productionSecurityHeaders,
|
||||
});
|
||||
response.end(JSON.stringify(testCatalogue));
|
||||
return;
|
||||
@@ -66,7 +75,7 @@ const server = createServer(async (request, response) => {
|
||||
"Content-Type":
|
||||
mediaTypes.get(path.extname(file)) ?? "application/octet-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
"Cross-Origin-Resource-Policy": "same-origin",
|
||||
...productionSecurityHeaders,
|
||||
});
|
||||
response.end(content);
|
||||
} catch {
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import { lstat, readFile, readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { verifyJavaPack } from "./java-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 arguments_ = process.argv.slice(2);
|
||||
|
||||
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(
|
||||
"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 metadata = await verifyPcre2Pack(pack, root);
|
||||
console.log(
|
||||
`Verified staged PCRE2 ${metadata.engineVersion} pack and WebAssembly bridge smoke test.`,
|
||||
);
|
||||
const metadata = await verifier(pack, root);
|
||||
console.log(`Verified staged ${metadata.engineVersion} engine pack.`);
|
||||
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,
|
||||
);
|
||||
if (
|
||||
entries.length !== 2 ||
|
||||
entries.length !== 4 ||
|
||||
!entries[0]?.isFile() ||
|
||||
entries[0].name !== "README.md" ||
|
||||
!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(
|
||||
"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");
|
||||
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.");
|
||||
}
|
||||
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.",
|
||||
);
|
||||
|
||||
@@ -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(
|
||||
<CapabilityPanel
|
||||
syntax={syntax}
|
||||
@@ -110,21 +110,79 @@ describe("CapabilityPanel", () => {
|
||||
...execution.engine,
|
||||
flavour: "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");
|
||||
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");
|
||||
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 () => {
|
||||
const onClose = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
import { SYNTAX_PROFILE } from "../version";
|
||||
import type { RegexExecutionResult } from "../regex/model/match";
|
||||
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(
|
||||
capabilities: RegexEngineCapabilities | undefined,
|
||||
@@ -44,6 +58,9 @@ export function CapabilityPanel({
|
||||
const capabilities = execution?.engine.capabilities;
|
||||
const activeFlavour =
|
||||
execution?.engine.flavour ?? syntax?.root.support.flavour;
|
||||
const activeFlavourLabel = activeFlavour
|
||||
? FLAVOUR_LABELS[activeFlavour]
|
||||
: undefined;
|
||||
const rows = [
|
||||
[
|
||||
"Flavour",
|
||||
@@ -98,35 +115,47 @@ export function CapabilityPanel({
|
||||
],
|
||||
[
|
||||
"Actual execution trace",
|
||||
capability(
|
||||
capabilities,
|
||||
"actualTrace",
|
||||
"the adapter exposes no engine trace",
|
||||
),
|
||||
activeFlavour === "pcre2"
|
||||
? capability(
|
||||
capabilities,
|
||||
"actualTrace",
|
||||
"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")],
|
||||
[
|
||||
"Static risk analysis",
|
||||
activeFlavour === "ecmascript"
|
||||
? "Available — advisory ECMAScript 2025 heuristics"
|
||||
: activeFlavour === "pcre2"
|
||||
? "Unavailable — ECMAScript heuristics are not applied to PCRE2"
|
||||
: activeFlavourLabel
|
||||
? `Unavailable — ECMAScript heuristics are not applied to ${activeFlavourLabel}`
|
||||
: "Awaiting flavour worker result",
|
||||
],
|
||||
[
|
||||
"Generated cases",
|
||||
activeFlavour === "ecmascript"
|
||||
? "Available — deterministic AST candidates, retained only after actual-engine verification"
|
||||
: activeFlavour === "pcre2"
|
||||
? "Unavailable — the partial PCRE2 provider does not expose a complete generation AST"
|
||||
: activeFlavourLabel
|
||||
? `Unavailable — the ECMAScript case generator is not applied to ${activeFlavourLabel}`
|
||||
: "Awaiting flavour worker result",
|
||||
],
|
||||
[
|
||||
"Pattern formatting",
|
||||
activeFlavour === "ecmascript"
|
||||
? "Available — grammar-backed literal/control escaping with mandatory exact-snapshot validation"
|
||||
: activeFlavour === "pcre2"
|
||||
? "Unavailable — no complete PCRE2 grammar-backed formatter is implemented"
|
||||
: activeFlavourLabel
|
||||
? `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",
|
||||
],
|
||||
[
|
||||
@@ -172,11 +201,11 @@ export function CapabilityPanel({
|
||||
))}
|
||||
</dl>
|
||||
<p className="capability-roadmap">
|
||||
PCRE2 10.47 matching and substitution run in the bundled WebAssembly
|
||||
worker. Its separate trace worker exposes bounded reported automatic
|
||||
callouts; movement classifications remain explicitly derived. Python,
|
||||
Go, Rust, .NET and Java remain unavailable until their named runtimes
|
||||
pass the same worker, offset, conformance and licensing gates.
|
||||
Availability is never inferred by substituting one flavour for another.
|
||||
Static analysis, generated cases and formatting are ECMAScript-only;
|
||||
automatic-callout tracing and reviewed C17 generation are PCRE2-only.
|
||||
Python and Java retain the engine identity and compatibility boundaries
|
||||
reported by their own workers.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -47,11 +47,19 @@ export function HelpDialog({
|
||||
<section>
|
||||
<h3>What runs where</h3>
|
||||
<p>
|
||||
Syntax parsing and actual ECMAScript or PCRE2 execution run in
|
||||
separate, killable browser workers. PCRE2 is the bundled,
|
||||
self-hosted 10.47 WebAssembly engine. Patterns, subjects and
|
||||
projects are never uploaded. There is no backend, account,
|
||||
telemetry, CDN dependency, or executable replacement function.
|
||||
Syntax parsing and active-engine execution run in separate, killable
|
||||
browser workers. ECMAScript uses the browser RegExp implementation;
|
||||
PCRE2 uses the bundled 10.47 WebAssembly engine; Python uses CPython
|
||||
3.14 through Pyodide; and Java uses TeaVM's java.util.regex
|
||||
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>
|
||||
</section>
|
||||
<section>
|
||||
@@ -87,9 +95,9 @@ export function HelpDialog({
|
||||
and negative subject runs through the selected actual engine before
|
||||
it can become a test; candidates with the wrong outcome are
|
||||
discarded. The coverage report names unsupported constructs.
|
||||
Sampling is not proof of complete language coverage. PCRE2 remains
|
||||
unavailable until its structural provider exposes the required AST;
|
||||
its syntax is never reinterpreted as ECMAScript.
|
||||
Sampling is not proof of complete language coverage. Other flavours
|
||||
remain unavailable until their structural providers expose the
|
||||
required AST; their syntax is never reinterpreted as ECMAScript.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
@@ -97,11 +105,11 @@ export function HelpDialog({
|
||||
<p>
|
||||
The ECMAScript formatter makes only grammar-backed literal/control
|
||||
escape changes; it does not insert layout whitespace or reinterpret
|
||||
PCRE2 syntax. Applying a preview requires independent source and
|
||||
candidate reparsing, actual-engine match/replacement comparison,
|
||||
every applicable exact unit test, and an explicit confirmation.
|
||||
Passing bounded checks is evidence for those snapshots, not proof
|
||||
for every possible subject.
|
||||
another flavour's syntax. Applying a preview requires
|
||||
independent source and candidate reparsing, actual-engine
|
||||
match/replacement comparison, every applicable exact unit test, and
|
||||
an explicit confirmation. Passing bounded checks is evidence for
|
||||
those snapshots, not proof for every possible subject.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
|
||||
@@ -211,6 +211,8 @@ export function MinimizePanel({
|
||||
() => optionsFor(activeFlavour, activeOptions, "pcre2"),
|
||||
[activeFlavour, activeOptions],
|
||||
);
|
||||
const activeEngineSupported =
|
||||
activeFlavour === "ecmascript" || activeFlavour === "pcre2";
|
||||
|
||||
const unitExpectation = (): RegexTestExpectation => {
|
||||
switch (unitKind) {
|
||||
@@ -244,10 +246,14 @@ export function MinimizePanel({
|
||||
const activeSide = {
|
||||
flavour:
|
||||
activeFlavour === "pcre2" ? ("pcre2" as const) : ("ecmascript" as const),
|
||||
flavourVersion: activeFlavourVersion,
|
||||
flavourVersion: activeEngineSupported
|
||||
? activeFlavourVersion
|
||||
: ECMASCRIPT.defaultVersion,
|
||||
pattern: initialPattern,
|
||||
flags: activeFlags,
|
||||
options: activeOptions,
|
||||
flags: activeEngineSupported ? activeFlags : ECMASCRIPT.defaultFlags,
|
||||
options: activeEngineSupported
|
||||
? activeOptions
|
||||
: defaultRegexOptions(ECMASCRIPT),
|
||||
...(unitKind === "replacement" ? { replacement: initialReplacement } : {}),
|
||||
};
|
||||
|
||||
@@ -324,6 +330,12 @@ export function MinimizePanel({
|
||||
};
|
||||
|
||||
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;
|
||||
setRunning(true);
|
||||
setResult(undefined);
|
||||
@@ -384,6 +396,13 @@ export function MinimizePanel({
|
||||
replacement. It does not claim a globally or semantically minimal
|
||||
example.
|
||||
</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">
|
||||
<header>
|
||||
@@ -716,7 +735,10 @@ export function MinimizePanel({
|
||||
<button
|
||||
type="button"
|
||||
className="primary-button"
|
||||
disabled={running}
|
||||
disabled={
|
||||
running ||
|
||||
(mode !== "comparison-mismatch" && !activeEngineSupported)
|
||||
}
|
||||
onClick={() => void run()}
|
||||
>
|
||||
{running ? "Minimizing…" : "Verify & minimize"}
|
||||
|
||||
@@ -53,4 +53,67 @@ describe("QuickReference", () => {
|
||||
screen.getByRole("link", { name: "PCRE2 pattern documentation" }),
|
||||
).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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import type { RegexFlavourId } from "../regex/model/flavour";
|
||||
import {
|
||||
ECMASCRIPT_REFERENCE,
|
||||
PCRE2_REFERENCE,
|
||||
} from "../regex/reference/token-reference";
|
||||
import { REFERENCE_BY_FLAVOUR } from "../regex/reference/token-reference";
|
||||
|
||||
export function QuickReference({
|
||||
onUsePattern,
|
||||
@@ -15,8 +12,8 @@ export function QuickReference({
|
||||
readonly flavour?: RegexFlavourId;
|
||||
}) {
|
||||
const [query, setQuery] = useState("");
|
||||
const reference =
|
||||
flavour === "pcre2" ? PCRE2_REFERENCE : ECMASCRIPT_REFERENCE;
|
||||
const referenceSet = REFERENCE_BY_FLAVOUR[flavour];
|
||||
const reference = referenceSet.entries;
|
||||
const entries = useMemo(() => {
|
||||
const normalizeSearchText = (value: string) =>
|
||||
value
|
||||
@@ -46,9 +43,7 @@ export function QuickReference({
|
||||
>
|
||||
<header className="panel-heading">
|
||||
<div>
|
||||
<p className="eyebrow">
|
||||
Original {flavour === "pcre2" ? "PCRE2" : "ECMAScript"} reference
|
||||
</p>
|
||||
<p className="eyebrow">Original {referenceSet.label} reference</p>
|
||||
<h2 id="reference-heading">Quick reference</h2>
|
||||
</div>
|
||||
<div className="panel-heading-actions">
|
||||
@@ -76,6 +71,9 @@ export function QuickReference({
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
{referenceSet.note ? (
|
||||
<p className="reference-note">{referenceSet.note}</p>
|
||||
) : null}
|
||||
{entries.length > 0 ? (
|
||||
<ul className="reference-list">
|
||||
{entries.map((entry) => (
|
||||
@@ -124,7 +122,9 @@ export function QuickReference({
|
||||
</ul>
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -51,6 +51,29 @@ function renderResult(result: RegexReplacementResult) {
|
||||
}
|
||||
|
||||
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", () => {
|
||||
renderResult({
|
||||
execution: { ...execution, truncated: true },
|
||||
|
||||
@@ -17,6 +17,17 @@ import {
|
||||
import { handleTreeNavigation } from "./tree-navigation";
|
||||
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({
|
||||
token,
|
||||
selected,
|
||||
@@ -79,7 +90,7 @@ export function ReplacementPanel({
|
||||
}) {
|
||||
const activeFlavour =
|
||||
flavour ?? result?.execution.engine.flavour ?? "ecmascript";
|
||||
const engineLabel = activeFlavour === "pcre2" ? "PCRE2" : "ECMAScript";
|
||||
const engineLabel = TEMPLATE_FLAVOUR_LABELS[activeFlavour];
|
||||
const retainedTokens = syntax?.tokens ?? [];
|
||||
const editorTokens = retainedTokens.slice(
|
||||
0,
|
||||
|
||||
@@ -225,7 +225,7 @@ describe("project import and export", () => {
|
||||
).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(
|
||||
importProjectDocument(
|
||||
JSON.stringify({
|
||||
@@ -258,14 +258,30 @@ describe("project import and export", () => {
|
||||
JSON.stringify({ ...project, flavourVersion: "ECMAScript 2026" }),
|
||||
),
|
||||
).toThrow(/flavour version.*unsupported/iu);
|
||||
expect(() =>
|
||||
expect(
|
||||
importProjectDocument(
|
||||
JSON.stringify({
|
||||
...project,
|
||||
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(() =>
|
||||
importProjectDocument(
|
||||
JSON.stringify({
|
||||
|
||||
@@ -71,26 +71,36 @@ class ResponsiveWorker implements WorkerLike {
|
||||
onmessageerror: ((event: MessageEvent<unknown>) => void) | null = null;
|
||||
readonly operations: EngineWorkerOperation[] = [];
|
||||
terminateCalls = 0;
|
||||
private readonly flavour: RegexFlavourId;
|
||||
|
||||
constructor(flavour: RegexFlavourId) {
|
||||
this.flavour = flavour;
|
||||
}
|
||||
|
||||
postMessage(message: unknown): void {
|
||||
const request = message as WorkerRequest<EngineWorkerOperation>;
|
||||
this.operations.push(request.payload);
|
||||
const payload: EngineWorkerResult =
|
||||
request.payload.kind === "execute"
|
||||
request.payload.kind === "load"
|
||||
? {
|
||||
kind: "execute",
|
||||
result: result(request.payload.request.flavour),
|
||||
kind: "load",
|
||||
info: result(this.flavour).engine,
|
||||
}
|
||||
: {
|
||||
kind: "replace",
|
||||
result: {
|
||||
execution: result(request.payload.request.flavour),
|
||||
output: "",
|
||||
outputBytes: 0,
|
||||
outputTruncated: false,
|
||||
truncated: false,
|
||||
},
|
||||
};
|
||||
: request.payload.kind === "execute"
|
||||
? {
|
||||
kind: "execute",
|
||||
result: result(request.payload.request.flavour),
|
||||
}
|
||||
: {
|
||||
kind: "replace",
|
||||
result: {
|
||||
execution: result(request.payload.request.flavour),
|
||||
output: "",
|
||||
outputBytes: 0,
|
||||
outputTruncated: false,
|
||||
truncated: false,
|
||||
},
|
||||
};
|
||||
const response: WorkerResponse<EngineWorkerResult> = {
|
||||
protocolVersion: WORKER_PROTOCOL_VERSION,
|
||||
requestId: request.requestId,
|
||||
@@ -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", () => {
|
||||
it("registers the shipped ECMAScript and PCRE2 execution workers", () => {
|
||||
it("registers every shipped execution worker with a startup budget", () => {
|
||||
expect(
|
||||
ENGINE_WORKER_REGISTRY.registrations.map(
|
||||
(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("python").startupTimeoutMs).toBe(
|
||||
30_000,
|
||||
);
|
||||
});
|
||||
|
||||
it("selects and reuses a dedicated supervisor per registered flavour", async () => {
|
||||
@@ -121,8 +151,9 @@ describe("engine worker selection", () => {
|
||||
const registration = (flavour: RegexFlavourId) => ({
|
||||
flavour,
|
||||
label: `${flavour} fixture`,
|
||||
startupTimeoutMs: 100,
|
||||
createWorker: () => {
|
||||
const worker = new ResponsiveWorker();
|
||||
const worker = new ResponsiveWorker(flavour);
|
||||
workers.set(flavour, [...(workers.get(flavour) ?? []), worker]);
|
||||
return worker;
|
||||
},
|
||||
@@ -150,7 +181,14 @@ describe("engine worker selection", () => {
|
||||
|
||||
expect(workers.get("ecmascript")).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();
|
||||
expect(workers.get("ecmascript")?.[0]?.terminateCalls).toBe(1);
|
||||
@@ -165,4 +203,72 @@ describe("engine worker selection", () => {
|
||||
);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
RegexReplacementRequest,
|
||||
RegexReplacementResult,
|
||||
} from "../model/match";
|
||||
import { WorkerSupervisor } from "./WorkerSupervisor";
|
||||
import { WorkerRequestError, WorkerSupervisor } from "./WorkerSupervisor";
|
||||
import type {
|
||||
EngineWorkerOperation,
|
||||
EngineWorkerResult,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ENGINE_WORKER_REGISTRY,
|
||||
type EngineWorkerRegistry,
|
||||
} from "./engine-registry";
|
||||
import type { RegexFlavourId } from "../model/flavour";
|
||||
import type { RegexEngineInfo, RegexFlavourId } from "../model/flavour";
|
||||
|
||||
export type EngineRuntimeState =
|
||||
| { readonly status: "unloaded" }
|
||||
@@ -28,6 +28,18 @@ export class EngineSupervisor {
|
||||
RegexFlavourId,
|
||||
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;
|
||||
|
||||
constructor(registry: EngineWorkerRegistry = ENGINE_WORKER_REGISTRY) {
|
||||
@@ -51,14 +63,76 @@ export class EngineSupervisor {
|
||||
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(
|
||||
request: RegexExecutionRequest,
|
||||
timeoutMs: number,
|
||||
): 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 },
|
||||
timeoutMs,
|
||||
{ supersede: true },
|
||||
);
|
||||
if (response.kind !== "execute") {
|
||||
throw new Error("Engine worker returned the wrong response kind");
|
||||
@@ -75,10 +149,10 @@ export class EngineSupervisor {
|
||||
request: RegexReplacementRequest,
|
||||
timeoutMs: number,
|
||||
): 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 },
|
||||
timeoutMs,
|
||||
{ supersede: true },
|
||||
);
|
||||
if (response.kind !== "replace") {
|
||||
throw new Error("Engine worker returned the wrong response kind");
|
||||
@@ -92,7 +166,13 @@ export class EngineSupervisor {
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -100,5 +180,8 @@ export class EngineSupervisor {
|
||||
this.disposed = true;
|
||||
for (const supervisor of this.supervisors.values()) supervisor.dispose();
|
||||
this.supervisors.clear();
|
||||
this.loaded.clear();
|
||||
this.loading.clear();
|
||||
this.operationRevisions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@ export class WorkerSupervisor<TOperation, TResult> {
|
||||
return this.active !== null;
|
||||
}
|
||||
|
||||
get hasWorker(): boolean {
|
||||
return this.worker !== null;
|
||||
}
|
||||
|
||||
run(
|
||||
operation: TOperation,
|
||||
timeoutMs: number,
|
||||
|
||||
263
src/regex/execution/adapters/java/JavaEngineAdapter.test.ts
Normal file
263
src/regex/execution/adapters/java/JavaEngineAdapter.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
657
src/regex/execution/adapters/java/JavaEngineAdapter.ts
Normal file
657
src/regex/execution/adapters/java/JavaEngineAdapter.ts
Normal 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.
|
||||
}
|
||||
}
|
||||
48
src/regex/execution/adapters/java/java-module.ts
Normal file
48
src/regex/execution/adapters/java/java-module.ts
Normal 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;
|
||||
}
|
||||
215
src/regex/execution/adapters/python/PythonEngineAdapter.test.ts
Normal file
215
src/regex/execution/adapters/python/PythonEngineAdapter.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
734
src/regex/execution/adapters/python/PythonEngineAdapter.ts
Normal file
734
src/regex/execution/adapters/python/PythonEngineAdapter.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
46
src/regex/execution/adapters/python/python-module.ts
Normal file
46
src/regex/execution/adapters/python/python-module.ts
Normal 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;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import type { WorkerFactory } from "./WorkerSupervisor";
|
||||
export interface EngineWorkerRegistration {
|
||||
readonly flavour: RegexFlavourId;
|
||||
readonly label: string;
|
||||
readonly startupTimeoutMs: number;
|
||||
readonly createWorker: WorkerFactory;
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ export const ENGINE_WORKER_REGISTRY = new EngineWorkerRegistry([
|
||||
{
|
||||
flavour: "ecmascript",
|
||||
label: "ECMAScript engine",
|
||||
startupTimeoutMs: 1_000,
|
||||
createWorker: () =>
|
||||
new Worker(
|
||||
new URL("../../workers/ecmascript.worker.ts", import.meta.url),
|
||||
@@ -57,10 +59,31 @@ export const ENGINE_WORKER_REGISTRY = new EngineWorkerRegistry([
|
||||
{
|
||||
flavour: "pcre2",
|
||||
label: "PCRE2 engine",
|
||||
startupTimeoutMs: 10_000,
|
||||
createWorker: () =>
|
||||
new Worker(new URL("../../workers/pcre2.worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
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",
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -9,10 +9,11 @@ import type {
|
||||
RegexSyntaxResult,
|
||||
ReplacementSyntaxResult,
|
||||
} from "../model/syntax";
|
||||
import type { RegexEngineInfo } from "../model/flavour";
|
||||
import type { ReplacementSyntaxRequest } from "../syntax/SyntaxProvider";
|
||||
import type { Pcre2TraceRequest, Pcre2TraceResult } from "../model/trace";
|
||||
|
||||
export const WORKER_PROTOCOL_VERSION = 1;
|
||||
export const WORKER_PROTOCOL_VERSION = 2;
|
||||
|
||||
export type SyntaxWorkerOperation =
|
||||
| {
|
||||
@@ -35,6 +36,9 @@ export type SyntaxWorkerResult =
|
||||
};
|
||||
|
||||
export type EngineWorkerOperation =
|
||||
| {
|
||||
readonly kind: "load";
|
||||
}
|
||||
| {
|
||||
readonly kind: "execute";
|
||||
readonly request: RegexExecutionRequest;
|
||||
@@ -45,6 +49,10 @@ export type EngineWorkerOperation =
|
||||
};
|
||||
|
||||
export type EngineWorkerResult =
|
||||
| {
|
||||
readonly kind: "load";
|
||||
readonly info: RegexEngineInfo;
|
||||
}
|
||||
| {
|
||||
readonly kind: "execute";
|
||||
readonly result: RegexExecutionResult;
|
||||
|
||||
@@ -44,18 +44,18 @@ const fixturePcre2: RegexFlavourDefinition = {
|
||||
};
|
||||
|
||||
describe("available regex flavour contracts", () => {
|
||||
it("publishes the two implemented production flavour boundaries", () => {
|
||||
it("publishes the four implemented production flavour boundaries", () => {
|
||||
expect(
|
||||
AVAILABLE_REGEX_FLAVOURS.definitions.map((definition) => definition.id),
|
||||
).toEqual(["ecmascript", "pcre2"]);
|
||||
).toEqual(["ecmascript", "pcre2", "python", "java"]);
|
||||
expect(
|
||||
AVAILABLE_REGEX_FLAVOURS.require("pcre2").options.map(
|
||||
(option) => option.name,
|
||||
),
|
||||
).toEqual(["matchLimit", "depthLimit", "heapLimitKib"]);
|
||||
expect(() =>
|
||||
AVAILABLE_REGEX_FLAVOURS.parse("python", "Project flavour"),
|
||||
).toThrow(/only supports ECMAScript and PCRE2/u);
|
||||
AVAILABLE_REGEX_FLAVOURS.parse("go", "Project flavour"),
|
||||
).toThrow(/ECMAScript, PCRE2, Python re and Java/u);
|
||||
});
|
||||
|
||||
it("validates ECMAScript flags through the flavour contract", () => {
|
||||
@@ -150,6 +150,29 @@ describe("available regex flavour contracts", () => {
|
||||
).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", () => {
|
||||
expect(
|
||||
() => new RegexFlavourRegistry([fixturePcre2, fixturePcre2]),
|
||||
|
||||
@@ -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([
|
||||
ECMASCRIPT_FLAVOUR,
|
||||
PCRE2_FLAVOUR,
|
||||
PYTHON_FLAVOUR,
|
||||
JAVA_FLAVOUR,
|
||||
]);
|
||||
|
||||
@@ -242,3 +242,265 @@ export const PCRE2_REFERENCE: readonly ReferenceEntry[] = [
|
||||
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: [],
|
||||
},
|
||||
};
|
||||
|
||||
84
src/regex/syntax/providers/java/JavaSyntaxProvider.test.ts
Normal file
84
src/regex/syntax/providers/java/JavaSyntaxProvider.test.ts
Normal 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" }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
61
src/regex/syntax/providers/java/JavaSyntaxProvider.ts
Normal file
61
src/regex/syntax/providers/java/JavaSyntaxProvider.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
169
src/regex/syntax/providers/java/replacement.ts
Normal file
169
src/regex/syntax/providers/java/replacement.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
285
src/regex/syntax/providers/lexical-captures.ts
Normal file
285
src/regex/syntax/providers/lexical-captures.ts
Normal 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"],
|
||||
};
|
||||
}
|
||||
@@ -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",
|
||||
]);
|
||||
});
|
||||
});
|
||||
63
src/regex/syntax/providers/python/PythonSyntaxProvider.ts
Normal file
63
src/regex/syntax/providers/python/PythonSyntaxProvider.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
166
src/regex/syntax/providers/python/replacement.ts
Normal file
166
src/regex/syntax/providers/python/replacement.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "de.add-ideas.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.",
|
||||
"entry": "./",
|
||||
"icon": "./favicon.svg",
|
||||
@@ -36,5 +36,15 @@
|
||||
"label": "Source",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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 REGEXPP_VERSION = "4.12.2";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference lib="webworker" />
|
||||
import {
|
||||
EcmaScriptEngineAdapter,
|
||||
executeEcmaScript,
|
||||
replaceEcmaScript,
|
||||
} from "../regex/execution/adapters/ecmascript/EcmaScriptEngineAdapter";
|
||||
@@ -13,8 +14,9 @@ import {
|
||||
} from "../regex/execution/worker-protocol";
|
||||
|
||||
const scope = self as DedicatedWorkerGlobalScope;
|
||||
const adapter = new EcmaScriptEngineAdapter();
|
||||
|
||||
scope.onmessage = (
|
||||
scope.onmessage = async (
|
||||
event: MessageEvent<WorkerRequest<EngineWorkerOperation>>,
|
||||
) => {
|
||||
const request = event.data;
|
||||
@@ -22,15 +24,20 @@ scope.onmessage = (
|
||||
let response: WorkerResponse<EngineWorkerResult>;
|
||||
try {
|
||||
const payload: EngineWorkerResult =
|
||||
request.payload.kind === "execute"
|
||||
request.payload.kind === "load"
|
||||
? {
|
||||
kind: "execute",
|
||||
result: executeEcmaScript(request.payload.request),
|
||||
kind: "load",
|
||||
info: await adapter.load(),
|
||||
}
|
||||
: {
|
||||
kind: "replace",
|
||||
result: replaceEcmaScript(request.payload.request),
|
||||
};
|
||||
: request.payload.kind === "execute"
|
||||
? {
|
||||
kind: "execute",
|
||||
result: executeEcmaScript(request.payload.request),
|
||||
}
|
||||
: {
|
||||
kind: "replace",
|
||||
result: replaceEcmaScript(request.payload.request),
|
||||
};
|
||||
response = {
|
||||
protocolVersion: WORKER_PROTOCOL_VERSION,
|
||||
requestId: request.requestId,
|
||||
|
||||
88
src/workers/java.worker.ts
Normal file
88
src/workers/java.worker.ts
Normal 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);
|
||||
};
|
||||
@@ -62,15 +62,20 @@ scope.onmessage = async (
|
||||
adapterPromise ??= loadAdapter();
|
||||
const adapter = await adapterPromise;
|
||||
const payload: EngineWorkerResult =
|
||||
request.payload.kind === "execute"
|
||||
request.payload.kind === "load"
|
||||
? {
|
||||
kind: "execute",
|
||||
result: await adapter.execute(request.payload.request),
|
||||
kind: "load",
|
||||
info: await adapter.load(),
|
||||
}
|
||||
: {
|
||||
kind: "replace",
|
||||
result: await adapter.replace(request.payload.request),
|
||||
};
|
||||
: 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,
|
||||
|
||||
99
src/workers/python.worker.ts
Normal file
99
src/workers/python.worker.ts
Normal 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);
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
/// <reference lib="webworker" />
|
||||
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 { PythonSyntaxProvider } from "../regex/syntax/providers/python/PythonSyntaxProvider";
|
||||
import {
|
||||
WORKER_PROTOCOL_VERSION,
|
||||
workerError,
|
||||
@@ -13,6 +15,8 @@ import {
|
||||
const providers = {
|
||||
ecmascript: new EcmaScriptSyntaxProvider(),
|
||||
pcre2: new Pcre2SyntaxProvider(),
|
||||
python: new PythonSyntaxProvider(),
|
||||
java: new JavaSyntaxProvider(),
|
||||
} as const;
|
||||
const scope = self as DedicatedWorkerGlobalScope;
|
||||
|
||||
@@ -29,7 +33,11 @@ scope.onmessage = async (
|
||||
? providers.ecmascript
|
||||
: flavour === "pcre2"
|
||||
? providers.pcre2
|
||||
: undefined;
|
||||
: flavour === "python"
|
||||
? providers.python
|
||||
: flavour === "java"
|
||||
? providers.java
|
||||
: undefined;
|
||||
if (!provider) {
|
||||
throw new Error(`No syntax provider is registered for ${flavour}.`);
|
||||
}
|
||||
|
||||
123
tests/browser/flavours.spec.ts
Normal file
123
tests/browser/flavours.spec.ts
Normal 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");
|
||||
});
|
||||
294
tests/conformance/java.conformance.test.ts
Normal file
294
tests/conformance/java.conformance.test.ts
Normal 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",
|
||||
);
|
||||
});
|
||||
});
|
||||
240
tests/conformance/python.conformance.test.ts
Normal file
240
tests/conformance/python.conformance.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user