diff --git a/.changeset/plucky-tigers-roar.md b/.changeset/plucky-tigers-roar.md new file mode 100644 index 000000000..4dc394885 --- /dev/null +++ b/.changeset/plucky-tigers-roar.md @@ -0,0 +1,5 @@ +--- +type: Security +pr: 3496 +--- +**`verify key-links` can no longer be hung by a plan's `key_links` pattern** — the pattern was compiled straight from plan frontmatter with a backtracking engine and tested against whole file contents, so a nested-quantifier pattern such as `(a+)+$` pinned a CPU core indefinitely and stalled any `verify-phase` run that reached it. Untrusted patterns now execute on the RE2 engine, whose match time is linear in the input length, and a pattern that cannot be compiled is refused outright rather than guessed at — a refused pattern can never report a match. (#3477) diff --git a/.changeset/witty-wasps-bark.md b/.changeset/witty-wasps-bark.md new file mode 100644 index 000000000..cd5bcc73f --- /dev/null +++ b/.changeset/witty-wasps-bark.md @@ -0,0 +1,5 @@ +--- +type: Changed +pr: 3496 +--- +**`must_haves.key_links[].pattern` now uses RE2 syntax** — backreferences and look-around are no longer supported in a key-links pattern, because they are the constructs that require a backtracking engine and cannot be evaluated in guaranteed linear time. A pattern using them is reported as `pattern_neutralized: "unsupported"` with the link marked unverified, rather than being silently matched as literal text. Ordinary patterns, including every example shipped in the docs, are unaffected. (#3477) diff --git a/.gitignore b/.gitignore index df786ab7d..c35e537e0 100644 --- a/.gitignore +++ b/.gitignore @@ -288,6 +288,15 @@ __pycache__/ venv/ target/ vendor/ +# Vendored third-party artifacts under version control on purpose — see +# gsd-core/bin/lib/vendor/README.md. bin/** must have zero external requires +# (installed trees ship with no node_modules), so this one directory is a +# deliberate, tracked exception to the blanket `vendor/` ignore above. +!/gsd-core/bin/lib/vendor/ +# Source-side twin (tsc resolves a .cts module's relative imports against +# src/, not the output dir) of the same vendored artifact — see +# scripts/lint-vendored-deps.cjs, which keeps both copies in sync. +!/src/vendor/ *.log .cache/ tmp/ diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index 45dbaea76..953f8ef5a 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -510,6 +510,7 @@ "update-context.cjs", "validate-command-router.cjs", "validate.cjs", + "vendor/re2js.cjs", "verification-command-router.cjs", "verification.cjs", "verify-command-router.cjs", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index 703ee2ab7..1252b18da 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -622,6 +622,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`. | `worktree-base-ref.cjs` | Worktree base-ref drift detection and degrade decision (`evaluateWorktreeBaseDegrade`) plus no-clobber `worktree.baseRef` settings management for the `base-check`/`set-baseref` subcommands (#683) | | `health-diagnostic-rules/worktree-health.cjs` | Health-diagnostic rules: worktree health checks (W020, W017, W027 — the split-off stale-worktree subject), ported behavior-preserving from `cmdValidateHealth` (ADR-3180 §8.2/§8.3/§8.5, Phase 11, #3309) | | `worktree-safety.cjs` | Worktree-root resolution and non-destructive prune policy decisions; owns W017 health-check logic | +| `vendor/re2js.cjs` | **Vendored third-party artifact, not a GSD module.** Verbatim copy of `re2js`' CJS build — the RE2 linear-time regex engine used by `pattern.cjs` to evaluate untrusted `key_links` patterns without catastrophic backtracking (#3477). Vendored because `gsd-core/bin/**` is copied into installed trees that have no `node_modules`, so it may contain no external requires (enforced by `local/no-external-require-in-bin`). Never hand-edit; `scripts/lint-vendored-deps.cjs` byte-compares it against the pinned `re2js` devDependency in `lint:ci`. See `gsd-core/bin/lib/vendor/README.md` | | `write-set.cjs` | Shared fail-loud `Result` (`{ok:true,value}\|{ok:false,reason}`) and per-surface write-set contracts (ADR-2143, epic #2143) — `WriteOutcome` (`{surface,applied}`), `WriteSet` (`WriteOutcome[]`), and `writeSetComplete(ws)` (true only when the set is non-empty AND every surface applied, never an OR-into-one-flag); `markdown-table.cjs` re-exports `Result` from here so existing importers are unaffected; consumed by `milestone.cts`'s `requirements mark-complete` handler to report a structured per-surface (`checkbox`/`traceability`) write-set alongside its existing fields (fixes the structural half of #2140) | [`docs/CLI-TOOLS.md`](CLI-TOOLS.md) may describe a subset of these modules; when it disagrees with the filesystem, this table and the directory listing are authoritative. diff --git a/eslint-rules/no-external-require-in-bin.cjs b/eslint-rules/no-external-require-in-bin.cjs new file mode 100644 index 000000000..fdee67656 --- /dev/null +++ b/eslint-rules/no-external-require-in-bin.cjs @@ -0,0 +1,129 @@ +'use strict'; + +/** + * no-external-require-in-bin + * + * Flag any `require(...)` / `import ... from '...'` under `gsd-core/bin/**` + * whose specifier is neither relative (`./`, `../`) nor a Node builtin + * (including the `node:` prefix form). + * + * ## Why + * + * `gsd-core/bin/**` is copied by the installer into trees that have NO + * `node_modules` (e.g. `~/.claude/gsd-core/`). An external (npm-package) + * `require()`/`import` under this tree resolves fine in THIS repo (where + * `node_modules/` exists) but throws `Cannot find module ''` for every + * installed user, because the package is never shipped there. #3477 + * follow-up: `src/pattern.cts` (compiled to `gsd-core/bin/lib/pattern.cjs`) + * shipped `import { RE2JS } from 're2js'` and broke `verify` for every + * installed user until the dependency was vendored under + * `gsd-core/bin/lib/vendor/`. + * + * The fix for a genuine external-package dependency is never "add it back to + * `dependencies`" — vendor the compiled artifact under + * `gsd-core/bin/lib/vendor/` (a verbatim, third-party copy; see + * `gsd-core/bin/lib/vendor/README.md`) and import it via a relative path + * instead. + * + * ## Why this is ALSO registered on src/**\/*.cts + * + * Every `src/**\/*.cts` module compiles 1:1 into `gsd-core/bin/lib/*.cjs` + * (ADR-457; `tsconfig.build.json` `rootDir: "src"`, `outDir: + * "gsd-core/bin/lib"`), and the emitted `.cjs` mirrors are almost entirely + * `eslint.config.mjs` global-`ignores`d as generated artifacts (lint the + * source, not the tsc output) — so a rule registered ONLY on + * `gsd-core/bin/**\/*.cjs` would never see a bad import re-introduced into an + * already-migrated module. `src/pattern.cts`'s `import { RE2JS } from + * 're2js'` is exactly this case: the compiled `gsd-core/bin/lib/pattern.cjs` + * is on the ignore list, so only catching it at the `.cts` source closes the + * gap. `TSImportEqualsDeclaration` (the `import x = require('./y.cjs')` form + * used throughout `src/**\/*.cts` for CommonJS interop) is handled alongside + * plain `ImportDeclaration` for this reason. + */ + +const { builtinModules } = require('node:module'); + +const BUILTIN_MODULES = new Set(builtinModules); + +/** + * Is `specifier` a Node builtin module (with or without the `node:` prefix)? + * @param {string} specifier + * @returns {boolean} + */ +function isBuiltinModule(specifier) { + const bare = specifier.startsWith('node:') ? specifier.slice('node:'.length) : specifier; + return BUILTIN_MODULES.has(bare) || BUILTIN_MODULES.has(specifier); +} + +/** + * Is `specifier` a relative import (`./` or `../`)? + * @param {string} specifier + * @returns {boolean} + */ +function isRelativeSpecifier(specifier) { + return specifier.startsWith('./') || specifier.startsWith('../'); +} + +/** @type {import('eslint').Rule.RuleModule} */ +const rule = { + meta: { + type: 'problem', + docs: { + description: + 'Disallow require()/import of an external (non-relative, non-builtin) module under gsd-core/bin/** (installed trees have no node_modules)', + category: 'Portability', + }, + schema: [], + messages: { + externalRequireInBin: + 'External module "{{specifier}}" required/imported under gsd-core/bin/**: installed ' + + 'trees have no node_modules (gsd-core/bin/** is copied verbatim into e.g. ' + + '~/.claude/gsd-core/), so this resolves here but throws "Cannot find module' + + '" for every installed user. Vendor the artifact under gsd-core/bin/lib/vendor/ ' + + '(see gsd-core/bin/lib/vendor/README.md) and import it via a relative path instead.', + }, + }, + + create(context) { + /** + * @param {import('eslint').Rule.Node} node — reported node + * @param {string} specifier + */ + function check(node, specifier) { + if (typeof specifier !== 'string') return; + if (isRelativeSpecifier(specifier)) return; + if (isBuiltinModule(specifier)) return; + context.report({ node, messageId: 'externalRequireInBin', data: { specifier } }); + } + + return { + CallExpression(node) { + if (node.callee.type !== 'Identifier' || node.callee.name !== 'require') return; + const arg = node.arguments[0]; + if (!arg || arg.type !== 'Literal' || typeof arg.value !== 'string') return; + check(node, arg.value); + }, + ImportDeclaration(node) { + check(node, node.source.value); + }, + ImportExpression(node) { + const arg = node.source; + if (!arg || arg.type !== 'Literal' || typeof arg.value !== 'string') return; + check(node, arg.value); + }, + // `import foo = require('...')` — the CommonJS-interop form used + // throughout src/**/*.cts (every src/*.cts compiles 1:1 into + // gsd-core/bin/lib/*.cjs, so it is exactly as much "gsd-core/bin/**" + // content as a hand-written .cjs file is). + TSImportEqualsDeclaration(node) { + const ref = node.moduleReference; + if (!ref || ref.type !== 'TSExternalModuleReference') return; + const arg = ref.expression; + if (!arg || arg.type !== 'Literal' || typeof arg.value !== 'string') return; + check(node, arg.value); + }, + }; + }, +}; + +module.exports = rule; diff --git a/eslint.config.mjs b/eslint.config.mjs index 3b42b72fa..bf346c853 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -29,6 +29,7 @@ import requireFsOpFallback from './eslint-rules/require-fs-op-fallback.cjs'; import noUnboundedSpawn from './eslint-rules/no-unbounded-spawn.cjs'; import noDuplicateFoldMarker from './eslint-rules/no-duplicate-fold-marker.cjs'; import requireSubprocessTimeout from './eslint-rules/require-subprocess-timeout.cjs'; +import noExternalRequireInBin from './eslint-rules/no-external-require-in-bin.cjs'; const localPlugin = { rules: { @@ -52,6 +53,7 @@ const localPlugin = { 'no-unbounded-spawn': noUnboundedSpawn, 'no-duplicate-fold-marker': noDuplicateFoldMarker, 'require-subprocess-timeout': requireSubprocessTimeout, + 'no-external-require-in-bin': noExternalRequireInBin, }, }; @@ -293,6 +295,15 @@ export default tseslint.config( 'gsd-core/bin/lib/workflow-fragments.cjs', // ADR-1671 Phase 5 (#2932): tsc-generated runtime artifact — lint the src/section-manifest.cts source. 'gsd-core/bin/lib/section-manifest.cjs', + // #3477 follow-up: verbatim third-party artifact vendored so gsd-core/bin/** + // carries zero external requires (installed trees have no node_modules). + // See gsd-core/bin/lib/vendor/README.md; never lint/edit these by hand. + 'gsd-core/bin/lib/vendor/**', + // Source-side twin of the same vendored .d.cts (needed so tsc resolves + // types for the relative './vendor/re2js.cjs' import from + // src/pattern.cts — module resolution for a .cts source is relative to + // src/, not the output dir). Same verbatim-third-party exemption. + 'src/vendor/**', ], }, @@ -342,6 +353,14 @@ export default tseslint.config( // repo/missing network (DEFECT.UNBOUNDED-SUBPROCESS in CONTEXT.md). // The 8 pre-existing call sites this surfaced were migrated in #2896. 'local/require-subprocess-timeout': 'error', + // #3477 follow-up: every src/**/*.cts module compiles 1:1 into + // gsd-core/bin/lib/*.cjs, which ships into installed trees with no + // node_modules — and the emitted mirror is almost always + // eslint-ignored as a generated artifact (see the src/pattern.cts note + // in eslint-rules/no-external-require-in-bin.cjs), so this is the ONLY + // place a bad external import in an already-migrated module is still + // visible to lint. + 'local/no-external-require-in-bin': 'error', }, }, @@ -428,6 +447,34 @@ export default tseslint.config( }, }, + // ── gsd-core/bin/**/*.cjs only — no-external-require-in-bin ──────────────── + // A NARROWER block than the combined glob above on purpose: gsd-core/bin/** + // is the ONLY surface in that shared glob that is copied verbatim into + // installed trees with no node_modules (scripts/**, eslint-rules/**, + // bin/lib/**, pi/**, examples/**, vscode/*.js, .kilo/plugins/*.js, and + // .opencode/plugins/*.js all run inside THIS repo checkout, where + // node_modules exists, and legitimately require npm packages). Registering + // this rule on the shared block above would falsely flag every one of + // those. #3477 follow-up: re2js was the live instance of this defect — + // src/pattern.cts (compiled to gsd-core/bin/lib/pattern.cjs) shipped + // `import { RE2JS } from 're2js'` and broke `verify` for every installed + // user until the dependency was vendored under gsd-core/bin/lib/vendor/. + { + files: ['gsd-core/bin/**/*.cjs'], + plugins: { + local: localPlugin, + }, + languageOptions: { + sourceType: 'commonjs', + globals: { + ...globals.node, + }, + }, + rules: { + 'local/no-external-require-in-bin': 'error', + }, + }, + // ── hooks/**/*.js — enforcement hooks (#3059) ────────────────────────────── { files: ['hooks/**/*.js', 'hooks/**/*.cjs'], diff --git a/gsd-core/bin/lib/vendor/README.md b/gsd-core/bin/lib/vendor/README.md new file mode 100644 index 000000000..58860c1f0 --- /dev/null +++ b/gsd-core/bin/lib/vendor/README.md @@ -0,0 +1,37 @@ +# vendor/ + +This directory holds **verbatim, unmodified** copies of third-party build +artifacts that `gsd-core/bin/**` needs at runtime. + +## Why + +`gsd-core/bin/**` is copied by the installer into trees that have **no +`node_modules`** (e.g. `~/.claude/gsd-core/`). Any external (non-relative, +non-builtin) `require()`/`import` under `gsd-core/bin/**` breaks `verify` +(and everything else) for every installed user, because the module simply +cannot be resolved there. The fix is to vendor the compiled artifact +in-tree instead of depending on it being installed as an npm package. +`eslint-rules/no-external-require-in-bin.cjs` enforces this at lint time. + +## Contents + +- `re2js.cjs` — verbatim copy of `node_modules/re2js/build/index.cjs` + (upstream package `re2js`, pinned version see `package.json` + `devDependencies.re2js`). Used by `src/pattern.cts` (compiled to + `gsd-core/bin/lib/pattern.cjs`) for linear-time RE2 pattern compilation. +- `re2js.d.cts` — verbatim copy of `node_modules/re2js/build/index.d.cts`, + so TypeScript resolves types for the relative import from `src/pattern.cts`. + +## Do not hand-edit + +These files are **verbatim** copies of the upstream build output. Never +edit them directly — refresh them from `node_modules` instead: + +``` +cp node_modules/re2js/build/index.cjs gsd-core/bin/lib/vendor/re2js.cjs +cp node_modules/re2js/build/index.d.cts gsd-core/bin/lib/vendor/re2js.d.cts +``` + +`node scripts/lint-vendored-deps.cjs` fails CI if the vendored copy drifts +byte-for-byte from `node_modules/re2js/build/` or from the `re2js` version +pinned in `package.json` `devDependencies`. diff --git a/gsd-core/bin/lib/vendor/re2js.cjs b/gsd-core/bin/lib/vendor/re2js.cjs new file mode 100644 index 000000000..ccfda0835 --- /dev/null +++ b/gsd-core/bin/lib/vendor/re2js.cjs @@ -0,0 +1,6480 @@ +/*! +* re2js +* RE2JS is the JavaScript port of RE2, a regular expression engine that provides linear time matching +* +* @version v2.8.6 +* @author Oleksii Vasyliev +* @homepage https://github.com/le0pard/re2js#readme +* @repository github:le0pard/re2js +* @license MIT +*/ +Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); +//#region src/RE2Flags.js +var RE2Flags = class RE2Flags { + static FOLD_CASE = 1; + static LITERAL = 2; + static CLASS_NL = 4; + static DOT_NL = 8; + static ONE_LINE = 16; + static NON_GREEDY = 32; + static PERL_X = 64; + static UNICODE_GROUPS = 128; + static WAS_DOLLAR = 256; + static LOOKBEHIND = 512; + static MATCH_NL = RE2Flags.CLASS_NL | RE2Flags.DOT_NL; + static PERL = RE2Flags.CLASS_NL | RE2Flags.ONE_LINE | RE2Flags.PERL_X | RE2Flags.UNICODE_GROUPS; + static POSIX = 0; + static UNANCHORED = 0; + static ANCHOR_START = 1; + static ANCHOR_BOTH = 2; +}; +//#endregion +//#region src/PublicFlags.js +/** +* Public flags for RE2JS and RE2Set compilation. +*/ +const PublicFlags = { + CASE_INSENSITIVE: 1, + DOTALL: 2, + MULTILINE: 4, + DISABLE_UNICODE_GROUPS: 8, + LONGEST_MATCH: 16, + LOOKBEHINDS: 512 +}; +//#endregion +//#region src/Codepoint.js +/** +* Various constants and helper for unicode codepoints. +*/ +const ASCII_SIZE = 128; +const ASCII_TO_UPPER = new Int32Array(ASCII_SIZE); +const ASCII_TO_LOWER = new Int32Array(ASCII_SIZE); +const MAX_BMP = 65535; +for (let i = 0; i < ASCII_SIZE; i++) { + if (i >= 97 && i <= 122) ASCII_TO_UPPER[i] = i - 32; + else ASCII_TO_UPPER[i] = i; + if (i >= 65 && i <= 90) ASCII_TO_LOWER[i] = i + 32; + else ASCII_TO_LOWER[i] = i; +} +var Codepoint = class { + static CODES = /* @__PURE__ */ new Map([ + ["\x07", 7], + ["\b", 8], + [" ", 9], + ["\n", 10], + ["\v", 11], + ["\f", 12], + ["\r", 13], + [" ", 32], + ["\"", 34], + ["$", 36], + ["&", 38], + ["'", 39], + ["(", 40], + [")", 41], + ["*", 42], + ["+", 43], + ["-", 45], + [".", 46], + ["0", 48], + ["1", 49], + ["2", 50], + ["3", 51], + ["4", 52], + ["5", 53], + ["6", 54], + ["7", 55], + ["8", 56], + ["9", 57], + [":", 58], + ["<", 60], + [">", 62], + ["?", 63], + ["A", 65], + ["B", 66], + ["C", 67], + ["F", 70], + ["P", 80], + ["Q", 81], + ["U", 85], + ["Z", 90], + ["[", 91], + ["\\", 92], + ["]", 93], + ["^", 94], + ["_", 95], + ["`", 96], + ["a", 97], + ["b", 98], + ["f", 102], + ["i", 105], + ["m", 109], + ["n", 110], + ["r", 114], + ["s", 115], + ["t", 116], + ["v", 118], + ["x", 120], + ["z", 122], + ["{", 123], + ["|", 124], + ["}", 125] + ]); + static toUpperCase(codepoint) { + if (codepoint < ASCII_SIZE) return ASCII_TO_UPPER[codepoint]; + const s = String.fromCodePoint(codepoint).toUpperCase(); + const expectedLen = s.codePointAt(0) > MAX_BMP ? 2 : 1; + if (s.length > expectedLen) return codepoint; + const sOrigin = String.fromCodePoint(s.codePointAt(0)).toLowerCase(); + const originExpectedLen = sOrigin.codePointAt(0) > MAX_BMP ? 2 : 1; + if (sOrigin.length > originExpectedLen || sOrigin.codePointAt(0) !== codepoint) return codepoint; + return s.codePointAt(0); + } + static toLowerCase(codepoint) { + if (codepoint < ASCII_SIZE) return ASCII_TO_LOWER[codepoint]; + const s = String.fromCodePoint(codepoint).toLowerCase(); + const expectedLen = s.codePointAt(0) > MAX_BMP ? 2 : 1; + if (s.length > expectedLen) return codepoint; + const sOrigin = String.fromCodePoint(s.codePointAt(0)).toUpperCase(); + const originExpectedLen = sOrigin.codePointAt(0) > MAX_BMP ? 2 : 1; + if (sOrigin.length > originExpectedLen || sOrigin.codePointAt(0) !== codepoint) return codepoint; + return s.codePointAt(0); + } +}; +//#endregion +//#region src/UnicodeRangeTable.js +var UnicodeRangeTable = class { + /** + * @param {Uint32Array | number[]} data + * @param {boolean} isStride1 + */ + constructor(data, isStride1 = false) { + this.data = data; + this.isStride1 = isStride1; + this.SIZE = isStride1 ? 2 : 3; + } + getLo(index) { + return this.data[index * this.SIZE]; + } + getHi(index) { + return this.data[index * this.SIZE + 1]; + } + getStride(index) { + return this.isStride1 ? 1 : this.data[index * this.SIZE + 2]; + } + get length() { + return this.data.length / this.SIZE; + } +}; +//#endregion +//#region src/UnicodeTables.js +const B64_MAP = /* @__PURE__ */ new Uint8Array(256); +for (let i = 0, b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"; i < 64; i++) B64_MAP[b.charCodeAt(i)] = i; +const decodeVLQ = (str) => { + const res = []; + let value = 0, shift = 0; + for (let i = 0; i < str.length; i++) { + let digit = B64_MAP[str.charCodeAt(i)]; + value |= (digit & 31) << shift; + if ((digit & 32) === 0) { + res.push(value); + value = 0; + shift = 0; + } else shift += 5; + } + return res; +}; +const decodeRanges = (str, isStride1) => { + const res = decodeVLQ(str); + const numRanges = isStride1 ? res.length / 2 : res.length / 3; + const out = new Uint32Array(numRanges * 3); + let current = 0, resIdx = 0; + for (let i = 0; i < numRanges; i++) { + current += res[resIdx++]; + out[i * 3] = current; + current += res[resIdx++]; + out[i * 3 + 1] = current; + out[i * 3 + 2] = isStride1 ? 1 : res[resIdx++]; + } + return out; +}; +const decodeOrbit = (str) => { + const res = decodeVLQ(str); + const map = /* @__PURE__ */ new Map(); + let currentKey = 0; + for (let i = 0; i < res.length; i += 2) { + currentKey += res[i]; + const zz = res[i + 1]; + const delta = zz >>> 1 ^ -(zz & 1); + map.set(currentKey, currentKey + delta); + } + return map; +}; +var LazyMap = class { + constructor(initializer) { + this.initializer = initializer; + this.cache = /* @__PURE__ */ new Map(); + } + has(key) { + return key in this.initializer; + } + get(key) { + if (this.cache.has(key)) return this.cache.get(key); + const fn = this.initializer[key]; + const val = fn ? fn() : null; + this.cache.set(key, val); + return val; + } +}; +var UnicodeTables = class { + static _CASE_ORBIT = null; + static get CASE_ORBIT() { + if (!this._CASE_ORBIT) this._CASE_ORBIT = decodeOrbit("rCgCIgCY+rQI4QiCuuBLgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCCgCBgCBgCBgCBgCBgCBgCB+7OB-BB-BB-BB-BB-BBskQB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BC-BB-BB-BB-BB-BB-BB-BByHBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBDCBBBCBBBCBBCCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBCCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBxHBCBBBCBBBCBBB3SBmMBkNBCBBBCBBB8MBCBBB6MB6MBCBBC+EB0MB2MBCBBB6MB+MBiGBmNBiNBCBBBmKBikzCBmNBqNBkIBsNBCBBBCBBBCBBB0NBCBBB0NDCBBB0NBCBBByNByNBCBBBCBBB2NBCBBDCBBCwDFCBCBDBCBCBDBCBCBDBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBB9EBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBCCBCBDBCBBBhGBvDBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBjICCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBH2iVBCBBBlKBwiVB+jVB+jVBCBBBlMBqEBuEBCBBBCBBBCBBBCBBBCBBB+hVB4hVB8hVBjNB7MC5MB5MCzMC1MB+0yCE5MB20yCC9MBu2yCBwyyCBo0yCChNBlNBo0yCBu-UBi0yCDlNC6-UBpNDrNIu+UDzNCm0yCBzNE0yyCBzNBpEBxNBxNBtEG1NLqxyCBkxyCnFoFrBCBBBCBBDCBBEkIBkIBkICoHHsCCqCBqCBqCCgEC+DB+DBmkOBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCC+BBgCBgCBgCBgCBgCBgCBgCBgCBrCBpCBpCBpCBmjOB-BB8BB-BB-BBgEB-BB-BByBBqgOBsDB-BBtwBB-BB-BB-BBsBBgDBCB-BB-BB-BBeB-BB-BB61OB-BB-BB-DB9DB9DBQB7DBmCE9CBrDBPBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBrFB-EBOBnHB3FB-FCCBBBNBCBBCjIBjIBjIBgFBgFBgFBgFBgFBgFBgFBgFBgFBgFBgFBgFBgFBgFBgFBgFBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCB-BB-BB8kMB-BB6kMB-BB-BB-BB-BB-BB-BB-BB-BB-BBokMB-BB-BBkkMBkkMB-BB-BB-BB-BB-BB-BB-BB4jMB-BB-BB-BB-BB-BB-EB-EB-EB-EB-EB-EB-EB-EB-EB-EB-EB-EB-EB-EB-EB-EBCBBBCBoiMBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBJCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBeBCBBBCBBBCBBBCBBBCBBBCBBBCBBBdBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBCgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDL-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-C64CgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOBgmOCgmOGgmODg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FBg8FDg8FBg8FBg8FhVg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBg9rCBQBQBQBQBQBQDPBPBPBPBPBPjkC7mMB5mMBnmMBjmMBCBlmMB3lMBpiMBk8kCBCBBG-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FB-7FD-7FB-7FB-7F6FoglCEsuHRwjlCyDCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCB0DBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBG1DD97OCCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBQBQBQBQBQBQBQBQBPBPBPBPBPBPBPBPBQBQBQBQBQBQDPBPBPBPBPBPDQBQBQBQBQBQBQBQBPBPBPBPBPBPBPBPBQBQBQBQBQBQBQBQBPBPBPBPBPBPBPBPBQBQBQBQBQBQDPBPBPBPBPBPEQCQCQCQCPCPCPCPBQBQBQBQBQBQBQBQBPBPBPBPBPBPBPBPB0EB0EBsFBsFBsFBsFBoGBoGBgIBgIBgHBgHB8HB8HDQBQBQBQBQBQBQBQBPBPBPBPBPBPBPBPBQBQBQBQBQBQBQBQBPBPBPBPBPBPBPBPBQBQBQBQBQBQBQBQBPBPBPBPBPBPBPBPBQBQCSFPBPBzEBzEBRCxnOFSFrFBrFBrFBrFBREQBQClkOFPBPBnGBnGFQBQCljOCODPBPB-GB-GBNHSF-HB-HB7HB7HBRqJ53OE9tQBrmQH4Bc3BSgBBgBBgBBgBBgBBgBBgBBgBBgBBgBBgBBgBBgBBgBBgBBgBBfBfBfBfBfBfBfBfBfBfBfBfBfBfBfBfECBByZ0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BB0BBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzBBzB34BgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDBgDB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CB-CBCBBBt-UBruHBt+UB1iVBviVBCBBBCBBBCBBB3hVB5-UB9hVB7hVCCBBCCBBI9jVB9jVBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBICBBBCBBECBBN-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOB-lOC-lOG-lOzoeCBBBCBBBCBBBCBBBCBBBCBl8kCBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBTCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBnECBBBCBBBCBBBCBBBCBBBCBBBCBBDCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBKCBBBCBBBnglCBCBBBCBBBCBBBCBBBCBBECBBBvyyCDCBBBCBBBgDCCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBn0yCB90yCB10yCBh0yCBn0yCCjxyCBzyyCBpxyCBg6BBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBB-CBl0yCBvjlCBCBBBCBBBt2yCBCBBBCBBBCBBBCBBBCBBBCBBBCBBBCBBBhkzCZCBB9a-5Bd-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCB-8rCm6TCBB7gBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCH-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BmlBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvChDwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCBwCFvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvCBvC1DuCBuCBuCBuCBuCBuCBuCBuCBuCBuCBuCCuCBuCBuCBuCBuCBuCBuCBuCBuCBuCBuCBuCBuCBuCBuCCuCBuCBuCBuCBuCBuCBuCCuCBuCCtCBtCBtCBtCBtCBtCBtCBtCBtCBtCBtCCtCBtCBtCBtCBtCBtCBtCBtCBtCBtCBtCBtCBtCBtCBtCCtCBtCBtCBtCBtCBtCBtCCtCBtCk2BgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEBgEO-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-DB-D+CgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCL-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-B74CgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BhrVgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCBgCB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BB-BhB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BB2BD1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BB1BtxekCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBkCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjCBjC"); + return this._CASE_ORBIT; + } + static _Print = null; + static get Print() { + if (!this._Print) this._Print = new UnicodeRangeTable(decodeRanges("hB9CBjBLBCpWBDFBFGBCCCBSBCsMBClBBDxBBDCBC2BBJaBFFBSVBC-FBCvBBD6BBDkDBP6BBDwBBDOBCbBDCCBJBGfBIqCBCgFBCHBDBBDVBCGBCEEBCBDIBDBBDDBJFFBCCBDBDYBDCBCFBFBBDVBCGBCBBCBBCBBDCCBDBFBBDCBEIIBCBCIIBPBLCBCIBCCBCVBCGBCBBCEBDJBCCBCCBDQQBCBDLBIGBCCBCHBDBBDVBCGBCBBCEBDIBDBBDCBICBFBBCEBDRBLBBCFBECBCDBEBBCCCBEEBEEBBBELBFEBECBCDBDHHPUBGMBCCBCWBCPBDIBCCBCDBIBBCCBCBBDDBDJBIVBCCBCWBCJBCEBDIBCCBCDBIBBGCBCDBDJBCCBNMBCCBCyBBCCBCFBFPBDZBCCBCRBEXBCIBCDDBFBEFFBEBCCCBGBHJBDCBN5BBFcBmBBBCCCBDBCXBCCCBVBDEBCCCBFBCJBDDBhBnCBCjBBFmBBCjBBCOBCMBmBlGBCGGD4LBCDBDGBCCCBCBDoBBCDBDgBBCDBDGBCCCBCBDOBC4BBCDBDiCBDfBEZBH1CBDFBD-TBCbBE4CBIVBKXBKTBNMBCCBCBBN9CBDJBHJBHNBCKBH4CBIqBBGlCBLeBCLBFLBFEEBoBBDEBMrBBFZBHKBE9BBDgCBCcBDKBHJBHNBDtBBDLBVsCBClFBJ7BBEOBE9BBGqBBDKBJqBBG1QBDFBDlBBDFBDHBCGCBdBD0BBCOBCNBDFBCSBDCBCIBSXBJuBBSBBDaBCMBEhBBPgBBQrEBF5UBXKBWz4BBD9LBGsBBCGGD3BBIBBPXBKGBCGBCGBCGBCGBCGBCGBCGBC9DBjBZBC4CBN1GBbPBC+BBC1CBDmDBGqBBC9CBC1CBKvBBCszcBE2BBK7KBV3FBJ8GBV7BBEJBH3BBJlCBJLBHzDBMdBEtCBCKBFgBBC2BBKNBDJBDmDBZbBLFBDFBDFBKGBCGBC7BBF9DBDJBHj9KBNWBFwBBloItLBDpDBnBGBNEBGZBCEBCCCBCCBCCBoUBhBpBBHyBBCSBCDBFEBCmEBF9FBEFBDFBDFBDCBEGBCGBOBBDLBCZBCSBCBBCOBDNBjB6DBGCBFsBBE3CBCMBEwBwBBsBBjEcBEwBBQbBFjBBKdBGqBBGdBCkBBFNBrB9EBDJBHjBBFjBBFnBBJzBBMLBCOBCGBCBBCKBCOBCGBCBBEzBBN2JBKVBLHBZFBCpBBCIBmCFBDCCBqBBCBBEDDBVBCnCBJIBxBSBCBBGgBBEaBGaBnB3BBFTBDxBBCBBGHBCCBCcBDCBFJBIIBI-BBhBmBBFLBK1BBEcBDaBGZBIDBNGBxCoCB4ByBBOyBBItBBJJBHlBBEcBJBBxGeBCpBBCCBDBBRFBJIBiBtBBJpBBXZBnBbBVWBKtCBFjBBK9BBCEBOYBIJBH0BBCRBJmBBK-CBCTBMRBCuBB-BGBCCCBCBCOBCKBH6BBGJBHDBCHBDBBDVBCGBCBBCEBCJBDBBDCBDHHGGBDGBEEBMJBCDDClBBCJBCDDCDBCJBCBBJBBe7CBCEBfnCBJJBnF1BBDlBBjBkCBMJBHMBU5BBHJBHTBdaBDOBFWB6F7BBlDyCBNHBDDDBGBCBBCdBCBBDLBKJBnCHBDtBBDKBcnCBJyCBOoCBIJB3CHB5ChBBPJBHIBCsBBCNBLcBEfBDVBCNBqCGBCBBCrBBECCBCCBHBJJBHFBCBBCkBBCBBCFBIJBHrBBFJB3HYBIQBCoBBEcB2CQQBwBBO6cBnDuDBCEBMjGBtyCiDBOvhBBRVBL68DBGmSB61G5BBn2B4RBIeBCJBFwCBCJBHdBDFBLlCBLJBCGBCUBGSBxN5BBnG6CBGYBDYBtBqCBF4BBIQBhCEBMGBK1mHBqBfBiDyDB+vIDBCGBCBBCiJBQeeBBBDPPBCBJrMBloCqDBGMBEIBIJBDDBh7D8HBEzNBHWBQQBQtBBDWBKzDB9B1HBLmBBDpCBJvDBWlCB7DTBNTBN2CBKYBoE0CBCmCBCBBDDDBDDBCBCLBCCCBFBCgCBCDBDHBCGBCbBCDBCEBCEEBFBCzKBDjJBD9VBQEBCOBxiBeBHFB2GGBCQBDGBCBBCEBG9BBiBxDxDBrBBENBDJBFBBhKeBS5BBGxOxOBoBB3GqBBFhGhGBdBCVBJBBhHGBCDBCBBCOBCkGBDPBqBrCBFJBFBByYjCBtC8BBjGDBCaBCBBCDDCJBCDBCCCHFFCECBBBCBBCDDCICBCCDDBCGBCDBCDBCCCBIBCQBGCBCEBCQB1BBBvIrBBFjDBNOBDOBCOBCkBBLtFB5BcBOrBBFIBIBBPFB7E4eBEQBEMBE5GBHLBFQQBKBF3BBJJBHnBBJdBDLBFBBPIBoB3KBJNBDMBEKBE4BBCFFBOBDLBFJBIyEBCmDBmgB-2pBBhB9oEBDt0FBDwpHBQtTBjtC9QBjvBq6EBGppIBnkzVvHB", false)); + return this._Print; + } + static CATEGORIES = new LazyMap({ + C: () => new UnicodeRangeTable(decodeRanges("AfBgDgBBOrWrWBHHBCBICCVuMuMnBBBzBBBE4B4BBGBcDBHQBXhGhGxBBB8BBBmDNB8BBByBBBQddBCCMEBhBGBsCiFiFJBBDBBXIICCBFBBKBBDBBFHBCDBDGGBaaBEEHDBDBBXIIDGDBCCGDBDBBECBCGBFCCBFBSJBEKKEXXIDDGBBLIEBCCBNBFBBNGBIEEJBBDBBXIIDGGBKKBDDBEEBFBEDBDGGBTTBIBDHHBBBEFFBBBDCCDCBDCBECBNDBGCBEFFBCCBEBCNBWEBOEEYRRBKKEFFBFBDEEDBBFBBLGBXEEYLLGBBKEEFGBDEBEFFBLLELBOEE0BEEHDBRBBbEETCBZKKCBBICBCDBHCCJFBLBBELB7BDBekBBDCCGZZCYYBGGCIILBBFfBpClBlBBCBoBlBlBQOOBjBBnGCCBDBCBB6LFFBIICFFBqBqBFBBiBFFBIICFFBQQ6BFFBkCkCBhBhBBBBbFB3CBBHBB+UCB6CGBXIBZIBVLBOEEDLB-CBBLFBLFBPMMBEB6CGBsBEBnCJBgBNNBCBNDBCCBrBBBGKBtBDBbFBMCB-BBBiCeeBMMBEBLFBPBBvBBBNTBuCnFnFBGB9BCBQCB-BEBsBBBMHBsBEB3QBBHBBnBBBHBBJGCgBBB2BQQPBBHUUBEEKMMBDBbEByBPBDBBcOOBBBjBNBiBOBtEDB7UVBMUB14BBB-LEBuBCCBDBCBB5BGBDNBZIBI4BI-DhBBb6C6CBKB3GZBxC3C3CBoDoDBDBsB-C-C3CIBxBuzcuzcBBB4BIB9KTB5FHB+GTB9BCBLFB5BHBnCHBNFB1DKBfCBvCMMBCBiB4B4BBHBPBBLBBoDXBdJBHBBHBBHIBIII9BDB-DBBLFBl9KLBYDByBjoIBvLBBrDlBBILBGEBbGGCGDrUfBrBFB0BUUFDBGoEoEBCB-FCBHBBHBBHBBECBIIIBLBDBBNbbUDDQBBPhBB8DEBEDBuBCB5COOBBBCuBBvBhEBeCByBOBdDBlBIBfEBsBEBfmBmBBCBPpBB-EBBLFBlBDBlBDBpBHB1BKBNQQIDDMQQIDDBBB1BLB4JIBXJBJXBHrBrBKkCBHBBCtBtBDCBCBBYpCpCBGBKvBBUDDBDBiBCBcEBclBB5BDBVBBzBDDBDBJEEeBBEDBLGBKGBhCfBoBDBNIB3BCBeBBcEBbGBFLBIvCBqC2BB0BMB0BGBvBHBLFBnBCBeHBDvGBgBrBrBEBBDPBHHBKgBBvBHBrBVBblBBdTBYIBvCDBlBIB-BGGBLBaGBLFB2BTTBGBoBIBhDVVBJBTwBwBB8BBICCFQQMFB8BEBLFBFJJBDDBXXIDDGLLBDDBEEBCCBEBCEBIBBICBGKBLCCBCCnBLLCBBCFFLDDBGBDcB9CGGBcBpCHBLlFB3BBBnBhBBmCKBLFBOSB7BFBLFBVbBcBBQDBY4FB9BjDB0CLBJBBCBBJDDfDDBNNBHBLlCBJBBvBBBMaBpCHB0CMBqCGBL1CBJ3CBjBNBLFBKuBuBPJBeCBhBBBXPPBnCBIDDtBCBCDDKHBLFBHDDmBDDHGBLFBtBDBL1HBaGBSqBqBBBBe0CBCOBzBMB8clDBwDGGBJBlGryCBkDMBxhBPBXJB88DEBoS41GB7Bl2BB6RGBgBLLBCByCLLBEBfBBHJBnCJBLIIWEBUvNB7BlGB8CEBaBBarBBsCDB6BGBS-BBGKBIIB3mHoBBhBgDB0D8vIBFIIDkJkJBNBCcBEBBCNBFHBtMjoCBsDEBOCBKGBLBBF-6DB+HCB1NFBYOBSOBvBBBYIB1D7BB3HJBoBBBrCHBxDUBnC5DBVLBVLB4CIBamEB2CoCoCDBBCBBDBBFNNCIIiCFFBJJIddFGGCCBI1K1KBlJlJB-V-VBNBGQQBuiBBgBFBH0GBISSBIIDGGBDB-BgBBCvDBuBCBPBBLDBD-JBgBQB7BEBCvOBrB1GBsBDBC-FBgBXXBGBD-GBIFFDQQmGBBRoBBtCDBLDBDwYBlCrCB+BhGBFccDCCBCCLFFCCCBEBCDBCECEDDCBBCICDCCBFFIKFCLLSEBEGGSzBBDtIBtBDBlDLBQBBQQQmBJBvF3BBeMBtBDBKGBDNBH5EB6eCBSCBOCB7GFBNDBCOBNDB5BHBLFBpBHBfBBNDBDNBKmBB5KHBPBBOCBMCB6BCCBCBRBBNDBLGB0EoDoDBjgBBh3pBfB-oEBBv0FBBypHOBvThtCB-QhvBBs6EEBrpIlkzVBxHvw-FB", false)), + Cc: () => new UnicodeRangeTable(decodeRanges("AfgDgB", true)), + Cf: () => new UnicodeRangeTable(decodeRanges("tFzqBzqBBEBXhGhGyBhMhMBxCxCs5D9-B9-BBDBbEByBEBCJBw03B6H6HBBBimEQQj7IPBhjiBDBwmFHBn0rYffB+CB", false)), + Cn: () => new UnicodeRangeTable(decodeRanges("4bBBHDBICCVuMuMnBBBzBBBE4B4BBGBcDBHKBvI9B9BBmDmDBMB8BBByBBBQddBCCMEBjBEBuHJJBDDBXXICCBBBFBBKBBDBBFHBCDBDGGBaaBEEHDBDBBXIIDGDBCCGDBDBBECBCGBFCCBFBSJBEKKEXXIDDGBBLIEBCCBNBFBBNGBIEEJBBDBBXIIDGGBKKBDDBEEBFBEDBDGGBTTBIBDHHBBBEFFBBBDCCDCBDCBECBNDBGCBEFFBCCBEBCNBWEBOEEYRRBKKEFFBFBDEEDBBFBBLGBXEEYLLGBBKEEFGBDEBEFFBLLELBOEE0BEEHDBRBBbEETCBZKKCBBICBCDBHCCJFBLBBELB7BDBekBBDCCGZZCYYBGGCIILBBFfBpClBlBBCBoBlBlBQOOBjBBnGCCBDBCBB6LFFBIICFFBqBqBFBBiBFFBIICFFBQQ6BFFBkCkCBhBhBBBBbFB3CBBHBB+UCB6CGBXIBZIBVLBOEEDLB-CBBLFBLFBbFB6CGBsBEBnCJBgBNNBCBNDBCCBrBBBGKBtBDBbFBMCB-BBBiCeeBMMBEBLFBPBBvBBBNTBuCnFnFBGB9BCBQCB-BEBsBBBMHBsBEB3QBBHBBnBBBHBBJGCgBBB2BQQPBBHUUBEEKmDmDNBBcOOBBBjBNBiBOBtEDB7UVBMUB14BBB-LEBuBCCBDBCBB5BGBDNBZIBI4BI-DhBBb6C6CBKB3GZBxC3C3CBoDoDBDBsB-C-C3CIBxBuzcuzcBBB4BIB9KTB5FHB+GTB9BCBLFB5BHBnCHBNFB1DKBfCBvCMMBCBiB4B4BBHBPBBLBBoDXBdJBHBBHBBHIBIII9BDB-DBBLFBl9KLBYDByBDBvzIBBrDlBBILBGEBbGGCGDrUfBrBFB0BUUFDBGoEoEBCC-FCBHBBHBBHBBECBIIIBIBGBBNbbUDDQBBPhBB8DEBEDBuBCB5COOBBBCuBBvBhEBeCByBOBdDBlBIBfEBsBEBfmBmBBCBPpBB-EBBLFBlBDBlBDBpBHB1BKBNQQIDDMQQIDDBBB1BLB4JIBXJBJXBHrBrBKkCBHBBCtBtBDCBCBBYpCpCBGBKvBBUDDBDBiBCBcEBclBB5BDBVBBzBDDBDBJEEeBBEDBLGBKGBhCfBoBDBNIB3BCBeBBcEBbGBFLBIvCBqC2BB0BMB0BGBvBHBLFBnBCBeHBDvGBgBrBrBEBBDPBHHBKgBBvBHBrBVBblBBdTBYIBvCDBlBIBlCJBCBBaGBLFB2BTTBGBoBIBhDVVBJBTwBwBB8BBICCFQQMFB8BEBLFBFJJBDDBXXIDDGLLBDDBEEBCCBEBCEBIBBICBGKBLCCBCCnBLLCBBCFFLDDBGBDcB9CGGBcBpCHBLlFB3BBBnBhBBmCKBLFBOSB7BFBLFBVbBcBBQDBY4FB9BjDB0CLBJBBCBBJDDfDDBNNBHBLlCBJBBvBBBMaBpCHB0CMBqCGBL1CBJ3CBjBNBLFBKuBuBPJBeCBhBBBXPPBnCBIDDtBCBCDDKHBLFBHDDmBDDHGBLFBtBDBL1HBaGBSqBqBBBBe0CBCOBzBMB8clDBwDGGBJBlGryCBkDMB3iBJB88DEBoS41GB7Bl2BB6RGBgBLLBCByCLLBEBfBBHJBnCJBLIIWEBUvNB7BlGB8CEBaBBarBBsCDB6BGBS-BBGKBIIB3mHoBBhBgDB0D8vIBFIIDkJkJBNBCcBEBBCNBFHBtMjoCBsDEBOCBKGBLBBJ76DB+HCB1NFBYOBSOBvBBBYIB1D7BB3HJBoBBBjGUBnC5DBVLBVLB4CIBamEB2CoCoCDBBCBBDBBFNNCIIiCFFBJJIddFGGCCBI1K1KBlJlJB-V-VBNBGQQBuiBBgBFBH0GBISSBIIDGGBDB-BgBBCvDBuBCBPBBLDBD-JBgBQB7BEBCvOBrB1GBsBDBC-FBgBXXBGBD-GBIFFDQQmGBBRoBBtCDBLDBDwYBlCrCB+BhGBFccDCCBCCLFFCCCBEBCDBCECEDDCBBCICDCCBFFIKFCLLSEBEGGSzBBDtIBtBDBlDLBQBBQQQmBJBvF3BBeMBtBDBKGBDNBH5EB6eCBSCBOCB7GFBNDBCOBNDB5BHBLFBpBHBfBBNDBDNBKmBB5KHBPBBOCBMCB6BCCBCBRBBNDBLGB0EoDoDBjgBBh3pBfB-oEBBv0FBBypHOBvThtCB-QhvBBs6EEBrpIm8yVBCdBhD-DBxHvw-BB---BBB---BBB", false)), + Co: () => new UnicodeRangeTable(decodeRanges("gg4B-nGh4hc9--BD9--B", true)), + Cs: () => new UnicodeRangeTable(decodeRanges("gg2B--B", true)), + L: () => new UnicodeRangeTable(decodeRanges("hCZBHZBwBLLFGGBVBCeBCpOBFLBPEBICCiEEBCBBDDBCHHCCBCCCBSBCyCBCqEBJlFBClBBDHHBnBBoCaBFDBuBqBBkBBBCiDBCQQBIIBLLBBBDRRCdBe4CBMZZBfBKBBFGGBUBFKKEYYBXBIKBGXBCGBRpBB7B1BBETTIJBQPBFHBDBBDVBCGBCEEBCBERROBBCCBPBBLJJBEBFBBDVBCGBCBBCBBCBBgBDBCUUBBBRIBCCBCVBCGBCBBCEBETTQBBYMMBGBDBBDVBCGBCBBCEBEffBCCBBBQSSCFBECBCDBEBBCCCBEEBEEBBBELBX1B1BBGBCCBCWBCPBEbbBBBCBBDBBfFFBGBCCBCWBCJBCEBEffBBBCBBQBBSIBCCBCoBBDRRGCBJCBZFBGRBEXBCIBCDDBFB7BvBBCBBNGB7BBBCCCBDBCXBCCCBIBCBBKDDBDBCWWBCBhBgCgCBGBCjBBcEB0DqBBVRRBEBFDBEEEBIIBBBFMBNSSBkBBCGGDqBBCsKBCDBDGBCCCBCBDoBBCDBDgBBCDBDGBCCCBCBDOBC4BBCDBDiCBmBPBR1CBDFBErTBDQBCZBGqCBHHBIRBOSBPRBPMBCCBQzBBkBFFkC4CBIEBDhBBCGGBkCBLeByBdBDEBMrBBFZB3BWBK0BBzC+C+CBtBBSHB3BdBOBBLrBBbjBBqBCBLjBBDKBGqBBDCBqBDBCFBCBBEGGB+FBhC1IBDFBDlBBDFBDHBCGCBdBD0BBCGBCEEBBBCGBEDBDFBFMBGCBCGB1DOORMBmDFFDJBCEEBDBHGCBCBCKBDDBGEBF1B1BB8zC8zCBjHBHDBEBBNlBBCGGD3BBIRRBVBKGBCGBCGBCGBCGBCGBCGBCGBxC2O2OBrBrBBDBGBBF1CBHCBC5CBCDBGqBBC9CBSfBxBPBhQ-tGBhCs0VBkCtBBDsIBEPBLBBVuBBReBDlCByBIBDmDBDxCBVQBCCBCDBCWBezBBPxBB-BFBECCBMMBaBLWBacBIuBBdRRBDBCJBLEBCoBBYCBCHBVWBEEEBwBBCEEBDDBDBDCCZCBDKBICBNFBDFBDFBKGBCGBCqBBCNBHyDBej9KBNWBFwBBloItLBDpDBnBGBNEBGCCBIBCMBCEBCCCBCCBCCBqDBiBqLBT-BBD1BBpBLB1DEBCmEBlBZBHZBM4CBEFBDFBDFBDCBkBLBCZBCSBCBBCOBDNBjB6DBmMcBEwBBwBfBOTBCHBHlBBLdBDjBBFHBxB9EBTjBBFjBBFnBBJzBBNKBCOBCGBCBBCKBCOBCGBCBBEzBBN2JBKVBLHBZFBCpBBCIBmCFBDCCBqBBCBBEDDBVBLWBKeBiCSBCBBLVBLZBHZBnB3BBHBBhCQQBCBCCBCcBrBcBEcBkBHBCbBc1BBLVBLSBORBvDoCB4ByBBOyBBOjBBnBbBKWB7HpBBHBBRFB5BcBLJJBUBrBRBvBUBcWBN0BB6BBBDOOBrBBhBYBbjBBeDDJiBBENNBuBBPDBWCCkBRBCYBUBBgCGBCCCBCBCOBCJBIuBBnBHBDBBDVBCGBCBBCEBETTNEBfJBCDDClBBCaaCtBtBBzBBTDBVCBfvBBVBBC5F5FBtBBqBDBlBvBBV8B8BBpBBOoCoCBZBmBGB6FrBB1D-BBgBHBDDDBGBCBBCXBQCC-CHBDmBBRCCdLLBmBBIWWMtBBUTTBnCBoGgBBgBIBCkBBSyByBBcBxDGBCBBClBBWaaBEBCBBCfBPYYBqBBlISBQCCBLBChBB9DwCwCB4cBnHjGBtyCgDBQvhBBSFBa68DBGmSB61GdBj3B4RBIeBSuCBSdBTvBBRDBgBUBGSBxNsBB0G-BBhBYBDYBtBqCBGjCjCBLBhCBBCPPBNNB0mHBqBfBiDyDB+vIDBCGBCBBCiJBQeeBBBDPPBCBJrMBloCqDBGMBEIBIJBn7F0CBCmCBCBBDDDBDDBCBCLBCCCBFBCgCBCDBDHBCGBCbBCDBCEBCEEBFBCzKBDYBCYBCeBCYBCeBCYBCeBCYBCeBCYBCHB15BeBHFBmI9BBzEsBBLGBRiKiKBcBTrBBlPbBlHdBDwGwGBdBCCBCBBCGBDEBKBBhHGBCDBCBBCOBCkGB8BjCBI1lB1lBBCBCaBCBBCDDCJBCDBCCCHFFCECBBBCBBCDDCICBCCDDBCGBCDBCDBCCCBIBCQBGCBCEBCQBlqE-2pBBhB9oEBDt0FBDwpHBQtTBjtC9QBjvBq6EBGppIB", false)), + LC: () => new UnicodeRangeTable(decodeRanges("hCZBHZB7BLLBVBCeBCiGBCDBFvGBDZBhGDBDBBECBCHHCCBCCCBSBCyCBCqEBJlFBClBBKoBB44ClBBCGGDqBBDCBhV1CBDFBjkCKBGqBBDCBhCrBBgCMBChBBmD1IBDFBDlBBDFBDHBCGCBdBD0BBCGBCEEBBBCGBEDBDFBFMBGCBCGBmIFFDJBCEEBDBHGCBCBCFBFDDBCBGEBF1B1BB8zC8zCB6DBDmDBHDBEBBNlBBCGGzoetBBTbBnEtCBCWBEDBCsCBZBBE2Z2ZBpBBGIBIvCBh6TGBNEBqgBZBHZBmlBvCBhDjBBFjBB1DKBCOBCGBCBBCKBCOBCGBCBBk2ByBBOyBB+CVBLVB74C-BBhrV-BBhBYBDYBtpZ0CBCmCBCBBDDDBDDBCBCLBCCCBFBCgCBCDBDHBCGBCbBCDBCEBCEEBFBCzKBDYBCYBCeBCYBCeBCYBCeBCYBCeBCYBCHB15BJBCTBHFB2uCjCB", false)), + Ll: () => new UnicodeRangeTable(decodeRanges("hDZB7BqBqBBWBCHBC2BCBQCBuBCDECBBBDCCDEEBFFDEEBBBDDDCCCDCCBCCDEECDDBDDBBBHGDCOCBSCBDDCEEC4BCBFBDDDBCCFICBjCBDZBiGCCEEEBBBTccBhBBCBBECBCWCBDBCGDB0B0BBuBBCgBCK0BCDMCBgDCxBoBBo6CqBBDCB5XFBjkCIBC2D2DBqBBgCMBChBBnD0ECBHBCgDCBHBJFBLHBJHBJFBLHBJHBJNBDHBJHBJHBJEBCBBHEEBBBCBBJDBDBBJHBLCBCBBzIEEBEEcKFDBBJDBF2B2Bs1CvBBCEEBGCFCCBCCBEBGiDCBIICFFNlBBCGG0oesBCUaCoEMCBBBC+BCBGBCCCDICFCCDCCBBBCSCGGGCMCFCCDOCbEE2ZqBBGIBIvCBh6TGBNEBqhBZBumBnBBpEjBB8EKBCOBCGBCBBk4ByBB+DVB75CfBhsVfB8BYBnqZZBbGBCRBbZBbDBCCCBFBCKBbZBbZBbZBbZBbZBbZBbZBbZBbbBdYBCFBbYBCFBbYBCFBbYBCFBbYBCFBC15B15BBIBCTBHFB4vChBB", false)), + Lm: () => new UnicodeRangeTable(decodeRanges("wVRBFLBPEBICCmEGG-OnHnHlFBBuIBBFgBgBKEEhFoFoF1mBgEgE2R72B72BsDkTkTxOFBvF+BBOjBjBBjBByVOORMBg-CBByHgGgG2OsBsBBDBGiDiDB+C+CBBB34bjnBjnBBEBvIzDzDdBB6DIBxCYYpDDBEBB2OXXqEtDtDWBBoDDBKngVngVuBBBh-BFBCpBBCIB0sBhBhB2K04D04DnrTDB9PCBpBBBnRMBhCBBCPPB9-P9-PBCBCGBCBByhM9BBqGGBud0Q0QsSAB", false)), + Lo: () => new UnicodeRangeTable(decodeRanges("qFQQhIFFBCBxGBB7ZaBFDBuBfBCJBkBBBCiDBCZZBLLBBBDRRCdBe4CBMZZBfBWVBrBYBIKBGXBCGBRoBB8B1BBETTIJBROBFHBDBBDVBCGBCEEBCBERROBBCCBPBBLJJBEBFBBDVBCGBCBBCBBCBBgBDBCUUBBBRIBCCBCVBCGBCBBCEBETTQBBYMMBGBDBBDVBCGBCBBCEBEffBCCBBBQSSCFBECBCDBEBBCCCBEEBEEBBBELBX1B1BBGBCCBCWBCPBEbbBBBCBBDBBfFFBGBCCBCWBCJBCEBEffBBBCBBQBBSIBCCBCoBBDRRGCBJCBZFBGRBEXBCIBCDDBFB7BvBBCBBNFB8BBBCCCBDBCXBCCCBIBCBBKDDBDBYDBhBgCgCBGBCjBBcEB0DqBBVRRBEBFDBEEEBIIBBBFMBNyDyDBnKBCDBDGBCCCBCBDoBBCDBDgBBCDBDGBCCCBCBDOBC4BBCDBDiCBmBPByDrTBDQBCZBGqCBHHBIRBOSBPRBPMBCCBQzBBpBkCkCBhBBC0BBIEBDhBBCGGBkCBLeByBdBDEBMrBBFZB3BWBK0BBxFuBBSHB3BdBOBBLrBBbjBBqBCBLdByDDBCFBCBBE7hB7hBBCB4-C3BBZWBKGBCGBCGBCGBCGBCGBCGBCGBoR2B2BF1CBJCCB4CBFGGBpBBC9CBSfBxBPBhQ-tGBhC0wUBC2jBBkCnBBJrIBFPBLBBjCyByBBkCBqFoDoDEGBCCBCDBCWBezBBPxBB-BFBECCBMMBaBLWBacBIuBBuBEBDIBLEBCoBBYCBCHBVPBCFBEEEBwBBCEEBDDBDBDCCZBBEKBIPPBEBDFBDFBKGBCGByEiBBej9KBNWBFwBBloItLBDpDBkCCCBIBCMBCEBCCCBCCBCCBqDBiBqLBT-BBD1BBpBLB1DEBCmEBqDJBCsBBDeBEFBDFBDFBDCBkBLBCZBCSBCBBCOBDNBjB6DBmMcBEwBBwBfBOTBCHBHlBBLdBDjBBFHBhEtCBjDnBBJzBB9CzBBN2JBKVBLHB5EFBDCCBqBBCBBEDDBVBLWBKeBiCSBCBBLVBLZBHZBnB3BBHBBhCQQBCBCCBCcBrBcBEcBkBHBCbBc1BBLVBLSBORBvDoCB4FjBBnBDBCxJxJBoBBHBBRCBCBB5BcBLJJBUBrBRBvBUBcWBN0BB6BBBDOOBrBBhBYBbjBBeDDJiBBENNBuBBPDBWCCkBRBCYBUBBgCGBCCCBCBCOBCJBIuBBnBHBDBBDVBCGBCBBCEBETTNEBfJBCDDClBBCaaCtBtBBzBBTDBVCBfvBBVBBC5F5FBtBBqBDBlBvBBV8B8BBpBBOoCoCBZBmBGB6FrBB0GHBDDDBGBCBBCXBQCC-CHBDmBBRCCdLLBmBBIWWMtBBUTTBnCBoGgBBgBIBCkBBSyByBBcBxDGBCBBClBBWaaBEBCBBCfBPYYBnBBCBBlISBQCCBLBChBB9DwCwCB4cBnHjGBtyCgDBQvhBBSFBa68DBGmSB61GdBj3B4RBIeBSuCBSdBTvBB0BUBGSB0NnBB2MqCBGwFwFB0mHBqBfBiDyDBuwIiJBQeeBBBDPPBCBJrMBloCqDBGMBEIBIJBxzI2P2PBrBBiBiKiKBcBTrBBlPaBmHdBDwGwGBdBCCBCBBCGBDEBKiHiHBFBCDBCBBCOBCkGB8pBDBCaBCBBCDDCJBCDBCCCHFFCECBBBCBBCDDCICBCCDDBCGBCDBCDBCCCBIBCQBGCBCEBCQBlqE-2pBBhB9oEBDt0FBDwpHBQtTBjtC9QBjvBq6EBGppIB", false)), + Lt: () => new UnicodeRangeTable(decodeRanges("lOGDnB2sH2sHBGBJHBJHBNQQwBAB", false)), + Lu: () => new UnicodeRangeTable(decodeRanges("hCZBmDWBCGBiB2BCDOCDuBCBECEBBCCCBCCBBBDDBCBBCCBEBBCBBCECBCCDCCBCCBBBCCCBEEIJDCMCDQCDDDCCBC4BCIBBCBBDCCBCBCGCiJCCEJJHCCBBBCCCBCCBPBCIBkBDDBBBEWCGDDCBBDyBBxBgBCK2BCBMCD+CCDlBBq6ClBBCGGzW1CB0kCHHBpBBDCBhK0ECKgDCKHBJFBLHBJHBJFBMGCJHBpCDBNDBNDBNEBMDBnIFFECBDCBDEEBDBHGCBCBDDBLBBG+B+B9zCvBBxBCCBBBDGCBCBCDDJCBCgDCJCCFuqeuqeCqBCUaCoEMCE8BCLECBICFCCDCCEUCBDBCEBCOCBCBCCCBQCZs5Vs5VBYBmmBnBBpEjBB9EKBCOBCGBCBBr3ByBB+EVB75CfBhsVfBhCYBoqZZBbZBbZBbCCBGDBDDBCBCHBbZBbBBCDBDHBCGBcBBCDBCEBCEEBFBcZBbZBbZBbZBbZBbZBfYBiBYBiBYBiBYBiBYBiB2pE2pEBgBB", false)), + M: () => new UnicodeRangeTable(decodeRanges("gYvDB0IGBoIsBBCCCBCCBCCpCKBxBUBRmDmDBFBDFBDBBCDBkBffBZB8CKB7BIBKZZBCBCIBCCBCEBsBCB8BIBrBXBCgBB3BCBCRBCGBLBBeCB5BCCBFBDBBDCBKLLBbbDCB5BCCBDBFBBDCBEffBEEMCB5BCCBGBCCBCCBVBBXFBCCB5BCCBFBDBBDCBICBLBBf8B8BBDBECBCDBKpBpBBDB4BCCBFBCCBCDBIBBMBBeCB5BCCBFBCCBCDBIBBMBBQNNBCB4BBBCGBCCBCDBKLLBeeBBBnCFFBEBCCCBGBTBB+BDDBFBNHBjDDDBHBMGBqCBBcECFBByBTBCBBGKBCjBBKlDlDBSBYDBFCBCCBDGBEDBOLBCLLBCBgWCBzdDBdCBeBBfBBhCfBKuBuBBBBC2D2DBjBjB3DLBFLB8GEB6BJBCcBDxBxBBsBBDLBVEBwBQBnBIBNCBfMB5BNBxBTB5ECBCUBFHHDCBnG-BBxWgBB--CCBuEhDhDBeBrRFBqDBB1udDBCJBhBBBxCBBxIEEFYYBDBF0C0CBzBzBBQBbRBOnBnBBGBaMBtBDBwBNBlBkCkCBMBNJJBuBuBBBBzBCCBBBDBBGBBCqBqBBDBGBBtHHBCBBx5TiXiXBOBRPBuejHjH2EEBn0BCBCBBGDBpBCBFmFmFB+R+RBCBiCEB+JBBuCFBnCKByBDB7DCB2BOBqBDDBLLBCBuBKBI+B+BBBBlBNBRBBtBNNBBBxBNBJDBCBB9CLBHDD+ELBWDB4BBBCGBDBBDCBKLLBDDBFBEEBkCIBCDDCDBCEBCPPBzCzCBQBYyCyCBSBsHGBDIBcBBzCQBrDMBmDOBhIOB2HFBCBBDDBCCCBuEuEBFBDGBEddBIBpBGBCDBJKKBJBvBPBnGHBoGHBCHBzCVBCNB7DFBECCBCCBFBCjCjCBDBCBBCEB8KDBKBBCxBxBBFBEEBYmnFmnFHOBpmLRBhuCEB8BGB5gBCCB1BBIDByCMMBslTslTBizEizEBsBBDWB-QEBEFBJHBDGBfDB1ECB89B2BBFxBBJPPXEBCOBxqBGBCQBDGBCBBCEBlDhFhFBFB4L+B+BBCB9PDB-HBB0HDDIBBG7O7OBFBuDGB29lYvHB", false)), + Mc: () => new UnicodeRangeTable(decodeRanges("joC4B4BDCBJDBCBBzBBB7BCBHBBDBBLsBsB7BCBjC7B7BBBBJCCB2B2BB7B7BCHHBDDBLLnDBBCBBECBCCBLqBqBBBB+BDB+BBB7BCCBDBDBBCBBKBBdPPB7B7BBBBGCBCCBLrBrBBsCsCBBBHHBTBBrKBBgCsFsFBFFHDDBaaBLLBBBDGBWBBDFBDLLBBB5zBffiEIIBGBCBB7KDBDCBFBBCFBhHBB7BCCKCCBJJBEByExBxBGCCBDBCBB+BffFBBD9B9BDCBCEEBxBxBBGBJBBsFWW35EBB0-dBBD5C5CBzBzBBOBvEBBwBxBxBBFFBDDBBBvDBBDBBZuBuBCuDuDDBBGuHuHBCCBCCBCC0gZCCgEuBuBBBBFBB0DZZB8B8BxBCBKBBO+C+CBBBEBBCrFrFBBBgBBB7BBBCDBDBBDCBKLLB1C1CBBBIDDCDBCBBCmDmDBBBJBBErDrDBBBHCCBCBDuHuHBBBHDBDyDyDBBBJBBCuDuDCBBHoDoDCBBFmImIBBBK4H4HBEBCBBFDDCvEvEBBBJDBF1C1CeBB-BqGqGECCoGPPrDIID2G2GBDBFBBC-K-KBNNxBBBJBBCpvQpvQBBBlxD2BBpDBB0rYBBHFB", false)), + Me: () => new UnicodeRangeTable(decodeRanges("okBBB1xF-wB-wBBCBCCBsshBCB", false)), + Mn: () => new UnicodeRangeTable(decodeRanges("gYvDB0IEBqIsBBCCCBCCBCCpCKBxBUBRmDmDBFBDFBDBBCDBkBffBZB8CKB7BIBKZZBCBCIBCCBCEBsBCB8BIBrBXBCfB4BCCFHBFEEBFBLBBe7B7BFDBJVVBbbDBB6BFFBFFBDDBBBEffBEEMBB6BFFBDBCBBFVVBXXBEBC7B7BDCCBCBJIIBMMBff+BNNzBEE4BCCBBBGCBCDBIBBMBBe7B7BDHHGBBVBBdBB6BBBFDBJVVBeepCIIBBBC7C7CDGBNHBjDDDBHBMGBqCBBcEC4BNBCEBCBBGKBCjBBKnDnDBCBCFBCBBDBBaBBFCBRDBODDBHHQgWgWBBBzdCBeBBfBBfBBhCBBCGBJDDBJBKuBuBBBBC2D2DBjBjB3DCBFBBKHHBBB8GBBD7B7BCGBCCCDHBHJBDxBxBBMBCeBDLBVDBxBCCBDBCGGpBIBNBBhBDBDBBCCB5BCCBEECCB7BHBDBB5ECBCMBCGBFHHEBBnG-BBxWMBFEEBKB--CCBuEhDhDBeBrRDBsDBB1udFFBIBhBBBxCBBxIEEFaaBGG4EBBbRBOnBnBBGBaKBvBCBxBDDBCBDBBoBkCkCBEBDBBDBBNJJwB0B0BCCBDBBGBBCrBrBBJJvHDDFx5Tx5TiXPBRPBuejHjH2EEBn0BCBCBBGDBpBCBFmFmFB+R+RBCBiCEB+JBBuCFBnCKByBDB8D3B3BBNBqBDDBLLBBByBDBDBBI+B+BBBBlBEBCHB-BNNB1B1BBHBLDBDgDgDBBBDCCBHHD+E+EEHBWBB6BBBEmBmBBFBEEBnCFBOECPBB2CHBDCBCYY1CFBCFFBCCBvHvHBCBHBBCBBcBB2CHBDCCBrDrDCDDBEBCmDmDCDDBCBCEBkIIBCBBhIBBCFFxEDBDBBFhBhBBIBpBFBDDBJKKBEBDCBvBMBCBBnGCCBBBCqGqGBFBCFBCzCzCBUBDGBCBBCBB7DFBECCBCCBFBCpCpCBEEC8K8KBMMB1B1BBDBGCCYmnFmnFHOBpmLLBECBhuCEB8BGB5gBgCgCBCByC5lT5lTBizEizEBsBBDWBhRCBSHBDGBfDB1ECB89B2BBFxBBJPPXEBCOBxqBGBCQBDGBCBBCEBlDhFhFBFB4L+B+BBCB9PDB-HBB0HDDIBBG7O7OBFBuDGB29lYvHB", false)), + N: () => new UnicodeRangeTable(decodeRanges("wBJB5DBBGDDBBBitBJBnEJBnGJB9MJB3DJBFFBtDJB3DJB3DJBDFBvDMB0DJBJGBoDJBpDGBISBuDJBhDJB3DJBnCTBtIJBnCJBwWTBybCBwHJBHJBXJBtJJBhEKBmFJBHJB3FJB3CJBnEJBHJB3gBEEBEBHJBnGyBBDEB3W7BBvCVB3TdBqrBqYqYaIBPCB4KDBrEJBfHBCOBhBJBoBOBh7cJB9FJBhKFB7EJBnBJBnGJBXJB3CJB3MJB34UJBuPsBBN4BBSBB2KaBlBDBeJJnEEBrGJBvdHBaGBoBIBsCEBXFBhFBBDPBDtBBhCIB1BBBfCBsCEBpDHBZHBqBGBrKFBxBJBHJB3IeB-EJBrBDBxDGBnEdBhEJB9BJBxEJBITB8HJB3KJB3DJB3LJBnDJBHTBtCLBlNSB+CJB3UJB3CcBkHJBnCJB3BJBnLJBnDUBshBuDBimPJBnpCJB3CJBnEJBCGBvQJBnIWB+KCB6nXJBnuBTBNTBtDYB2iBxBBhqCJBnNJB3PJB4HJBtWIBhEJB4Y6BBCCBCDBtCsBBCOBjeMBk3CJB", false)), + Nd: () => new UnicodeRangeTable(decodeRanges("wBJnxBJnEJnGJ9MJ3DJ3DJ3DJ3DJ3DJ3DJ3DJ3DJ3DJhDJ3DJnCJ3IJnCJn6BJnBJtJJhEJnFJHJ3FJ3CJnEJHJnuiBJnVJnBJnGJXJ3CJ3MJ34UJnsBJnkCJHJ9YJhEJ9BJxEJ3IJ3KJ3DJ3LJnDJHTtCJnNJnDJ3UJ3CJ3HJnCJ3BJnLJ3uQJnpCJ3CJnEJ3QJ37XJ12CxBhqCJnNJ3PJ4HJ2aJ30EJ", true)), + Nl: () => new UnicodeRangeTable(decodeRanges("u3FCBwzCiBBDDB-zDaaBHBPCBs1dJBxyW0BBtOJJnEEBrhIuDBm8SCB", false)), + No: () => new UnicodeRangeTable(decodeRanges("yFBBGDDBBB2pCFB5LFB5DCBmEGB6GGBSIByNJB2hBTB0jBJBhP20B20BEFBHJBnGPBqB3W3WB6BBvCVB3TdBqrB1kB1kBBCBrEJBfHBCOBhBJBoBOBxrdFBymWsBBiCDBSBB2KaBlBDB1pBHBaGBoBIBsCEBXFBhFBBDPBDtBBhCIB1BBBfCBsCEBpDHBZHBqBGBrKFBhLeB-EJBrBDBxDGBnETB8LTBmqBBBvNIBobSB0aUBn8SGB-YWBqhZTBNTBtDYBvqFIBid6BBCCBCDBtCsBBCOBjeMB", false)), + P: () => new UnicodeRangeTable(decodeRanges("hBCBCFBCDBLBBEBBbCBCccCkBkBGEELBBEEE-VJJzOFBqBBB0BCCDDDtBBBVBBCBBOCCBBBrCDBnDsBsBBMBqHCB3BOBgBmImIBLLtE5D5D6DnMnMNwLwL7CLLBpFpFBNBCmBmBBCBoCrCrCBDBFBBwDFBsFlTlTBHB4EuTuTtBBBvCCBoCBB+ECBCCBmBKB6JBB5GBBhEGBCFBhFBBLGBdCB9DDB8BEB-BBBhCHBM9Z9ZBWBJTBCMBCLBfBBPBB6TDBeBB+hBNBwCBBgBJB0MVBgCDBhBBB8XDBCBBxDwEwEBtBBCfBDLBkNCBFJBDLBRNNjD7C7CjgdBBuICBkDLL0DFB9LDB3CBBpBCBCyByBBwBwBiDMBRBB9DDB-DBBRBB6HzqUzqUBxGxGBIBXiBBCNBCFFCBB2ECBCFBCDBLBBEBBbCBCccCCCBFB7MCB9UxBxB-MoXoXoGgBgBxIIBnBxDxDBFBjCGB6CDByO-J-JjBlElEBDBtBDB+FGBuDBBCDB-DDBxBBBwCDBFOOCCB5CFBsDrJrJBCCBzDzDBDBLBBCpDpD7HWBqDCBdMBtCjEjEBBB9HpIpIBBB8E9C9CBGB0CCBCEB+CJB4GgDgDBDBrBBBmUBBrCMBwFxjBxjBBDB97CBB8zOBBmEiCiCBDBJpRpRBBBoJDBoK9lT9lTovHEB07C-a-aBAB", false)), + Pc: () => new UnicodeRangeTable(decodeRanges("-Cg-Hg-HBUU-u3BBBZCBwHAB", false)), + Pd: () => new UnicodeRangeTable(decodeRanges("tB9qB9qB0BiyDiyDmgBqgCqgCBEBiwDDDgBBBFdd-NUUwDxszBxszBBmBmBLqFqFhzD-J-J", false)), + Pe: () => new UnicodeRangeTable(decodeRanges("pB0B0BgB+1D+1DC-6B-6BqtC4B4BQ7T7TCff-hBMCxChBhBCGC1MUChCCCiBmhBmhBCECtBGCtNICEGCDBB-ozB6G6GeOCESSCCCrF0B0BgBGD", false)), + Pf: () => new UnicodeRangeTable(decodeRanges("7F+6H+6HEddpuDCCFDDQEE", false)), + Pi: () => new UnicodeRangeTable(decodeRanges("rFt7Ht7HDBBDaapuDCCFDDQEE", false)), + Po: () => new UnicodeRangeTable(decodeRanges("hBCBCCBDECBLLBEEBcclCGGPBBI-V-VJzOzOBEBqB3B3BDDDtBBBVBBCBBOCCBBBrCDBnDsBsBBMBqHCB3BOBgBmImIBLLtE5D5D6DnMnMNwLwL7CLLBpFpFBNBCxDxDrCEBFBBwDFBsFlTlTBHBmY9D9DBBBoCBB+ECBCCBmBFBCDB6JBB5GBBhEGBCFBhFBBLGBdCB9DDB8BEB-BBBhCHBMjajaBJJBGBJIBDDBDCBEKBCCCBIB7kDDBCBBxDwEwEBFFBBBDDDBHBCBBCDDBLLBDBCJBDDBCCCBLBDCBtNCB6B+F+FjgdBBuICBkDLL0DFB9LDB3CBBpBCBCyByBBwBwBiDMBRBB9DDB-DBBRBB6HlxUlxUBFBDXXVBBDDBECBCDBICBHCCB2E2EBBBCCBDECBLLBEEBcclBDDB7M7MBBB9UxBxB-MoXoXoGgBgBxIIBnBxDxDBFBjCGB6CDB0ZlElEBDBtBDB+FGBuDBBCDB-DDBxBBBwCDBFOOCCB5CFBsDrJrJBCCBzDzDBDBLBBCpDpD7HWBqDCBdMBtCjEjEBBB9HpIpIBBB8E9C9CBGB0CCBCEB+CJB4GgDgDBDBrBBBmUBBrCMBwFxjBxjBBDB97CBB8zOBBmEiCiCBDBJpRpRBBBoJDBoK9lT9lTovHEB07C-a-aBAB", false)), + Ps: () => new UnicodeRangeTable(decodeRanges("oBzBzBgB-1D-1DC-6B-6B-rCEEnB4B4BQ7T7TCff-hBMCxChBhBCGC1MUChCCCiBmhBmhBCECaTTCECtNICEGCDipzBipzB4GeeCMCESSCCCrFzBzBgBEEDAB", false)), + S: () => new UnicodeRangeTable(decodeRanges("kBHHRCBgBCCcCCkBEBCBBDCCBCBDEEfgBgBrODBNNBGGBCCCBPB2DPPBxDxDsErIrIBBB3DCBDDDBvGvGLUUB4H4HIBBpEqLqLBHHB2H2H-DjEjEBGBlEwGwGqBmGmGiGCBQCCBBBDFBVECmEHBCFBCBBGDBmGBBxXJB0WuLuLlL+E+EBgBBiLJBKIBhiBCCBBBMCBOCBOCBOBBmCOOoBCBOCBUhBB-BBBCDBCBBLCCBBBGFBCECFMMBFFBDBGDBC7B7BBFFB2LBFcBD+HBXKByCtCBXnTBtBwBBDeBLyMBX+BBFfBD1LBDpEBmHFBmLBBvBZBC4CBN1GBbPBFOOBNNWBBHBB8CBB0HBBFJBhBlBBKRRBdBMdBJQQBeBLmBBQ-JBhuG-BBx0V2BB6RWBKBBoDBB+EDBLDB+RCBiHPPB+9T+9TpEgBBuLPBhCBB3BHBtBDBjDCCBBBD7E7EHRRBBBgBCCcCCiEGBCGBOBB6JIB6BQBDCBCMBEwBwBBrBB7zBBBwSmWmWBiKiKBGBnjC2kC2kCBbBr6SDBG3qU3qUk7DvHBLCBEzNBHWBQQBgDzDB9B1HBLmBBD7BBGCBXBBIdBF8BBWhCBE7F7FB1CBrbaagBaagBaagBaagBaa9B-PB4BDBzBHBCNBCBBp2BwNwNttCEE+DiOiOBvIvIBqBBFjDBNOBDOBCOBCkBBYgFB5BcBOrBBFIBIBBPFB7E4eBEQBEMBE5GBHLBFQQBKBF3BBJJBHnBBJdBDLBFBBPIBoB3KBJNBDMBEKBE4BBCFFBOBDLBFJBIyEBC7CBLAB", false)), + Sc: () => new UnicodeRangeTable(decodeRanges("kB+D+DBCBqnB8D8DzPBBzPBBI2H2HoImSmS8sClmClmCBgBB37hBkuVkuVtD7E7E8GBBEBB3-HDB-4wBxtCxtC", false)), + Sk: () => new UnicodeRangeTable(decodeRanges("+CCCoCHHFEEqQDBNNBGGBCCCBPB2DPPBjoBjoB15FCCBBBMCBOCBOCBOBB9kEBBkzdWBKBBoDBBxePPBniUniUBPB8bCCjF4g9B4g9BBDB", false)), + Sm: () => new UnicodeRangeTable(decodeRanges("rBRRBBB+BCCuBFFmBgBgB-XwQwQBBB8xGOOoBCBOCBsEoBoBBDBHlClCBDBGBBFGDIgBgBBDDCgBgBBqIBhBBB7CffBXBpBFB2OKK3BHBwDxKxKBDBDeBLPBhIiEBX+BBFfBDhIBxBUBDFB9+zB5Z5ZCCBlFRRBBB+BCCkEHHBCBitDBBhrwBx+Bx+BagBgBagBgBagBgBagBgBat5Ft5FB-uC-uCBHB", false)), + So: () => new UnicodeRangeTable(decodeRanges("mFDDFCCyerIrIBgEgEBvGvGLUUB4H4HkQ2L2LjEFBClElEwGqBqBoMCBQCCBBBDFBVECmEHBCFBCBBGDBmGBBxXJB0WzWzW+EhBBiLJBKIBksBBBCDBCBBLCCBHHBEBCECFMMBPPCBBC7B7BBKKBDBDDBCBBCBBCGBCeBDBBCCCBdBtIHBFTBDGBDwCBCdBanBBHnCBXKByCtCBX2FBCIBC1BBJuDBC3HBtBrBBhC-HBhQvBBWBBHmBBDpEBmHFBmLBBvBZBC4CBN1GBbPBFOOBNNWBBHBBxKBBFJBhBlBBKRRBdBMdBJQQBeBLmBBQ-JBhuG-BBx0V2BBibDBLBBC+R+RBBBqqUPBuLPBhCBB3BHBuBCBlPEEFBBOBB6JIB6BQBDCBCMBEwBwBBrBB7zBBBwSpgBpgBBGBnjC2kC2kCBGBFQBr6SDBG3qU3qUk7DvHBLCBEzNBHWBQPBhDzDB9B1HBLmBBD7BBGCBXBBIdBF8BBWhCBE7F7FB1CBqlB-PB4BDBzBHBCNBCBBp2B96C96CiEyWyWBqBBFjDBNOBDOBCOBCkBBYgFB5BcBOrBBFIBIBBPFB7E6HBG4WBEQBEMBE5GBHLBFQQBKBF3BBJJBHnBBJdBDLBFBB-B3KBJNBDMBEKBE4BBCFFBOBDLBFJBIyEBC7CBLAB", false)), + Z: () => new UnicodeRangeTable(decodeRanges("gBgEgEgvFgsCgsCBJBeBBGwBwBh9DAB", false)), + Zl: () => new UnicodeRangeTable(decodeRanges("ohIA", true)), + Zp: () => new UnicodeRangeTable(decodeRanges("phIA", true)), + Zs: () => new UnicodeRangeTable(decodeRanges("gBgEgEgvFgsCgsCBJBlBwBwBh9DAB", false)), + ASCII_Hex_Digit: () => new UnicodeRangeTable(decodeRanges("wBJIFbF", true)), + Alphabetic: () => new UnicodeRangeTable(decodeRanges("hCZBHZBwBLLFGGBVBCeBCpOBFLBPEBICC3CeeBQBCBBDDBCHHCCBCCCBSBCyCBCqEBJlFBClBBDHHBnBBoBNBCCCBCCBCCJaBFDBeKBG3BBCGBPlDBCHBFHBFCBLCBDRRBuBBOkDBZgBBKBBFGGBWBDSBUYBIKBGXBCGBIJJBoBBLLBEGBHrCBCPBCCBFOBOSBCHBDBBDVBCGBCEEBCBEHBDBBDBBCJJFBBCEBNBBLFFBBBCFBFBBDVBCGBCBBCBBCBBFEBFBBDBBFIIBCBCSSBEBMCBCIBCCBCVBCGBCBBCEBEIBCCBCBBEQQBCBWDBFCBCHBDBBDVBCGBCBBCEBEHBDBBDBBKBBFBBCEBORRBCCBEBECBCDBEBBCCCBEEBEEBBBELBFEBECBCCBEHHpBMBCCBCWBCPBEHBCCBCCBJBBCCBCBBDDBdDBCHBCCBCWBCJBCEBEHBCCBCCBJBBGCBCDBOCBNMBCCBCoBBDHBCCBCCBCGGBCBIEBXFBCCBCRBEXBCIBCDDBFBJFBCCCBGBTBBO5BBGGBH0B0BBECBDBCXBCCCBRBCCBDEBCHHPDBhBgCgCBGBCjBBFSBFPBCjBBkC2BBCDDBDBR-BBLDBDlBBCGGDqBBCsKBCDBDGBCCCBCBDoBBCDBDgBBCDBDGBCCCBCBDOBC4BBCDBDiCBmBPBR1CBDFBErTBDQBCZBGqCBEKBITBMUBNTBNMBCCBCBBNzBBDSBPFFkC4CBIqBBGlCBLeBCLBFIBYdBDEBMrBBFZB3BbBF+BBDTBzBYYBMMBBByBzBBCOBCHB0BpBBDDBLrBBCKBP2BBXCBLjBBDKBGqBBDCBqBDBCFBCBBEGGB+FBUhBBM1IBDFBDlBBDFBDHBCGCBdBD0BBCGBCEEBBBCGBEDBDFBFMBGCBCGB1DOORMBmDFFDJBCEEBDBHGCBCBCKBDDBGEBFSSBnBBuZzBB34BkHBHDBEBBNlBBCGGD3BBIRRBVBKGBCGBCGBCGBCGBCGBCGBCGBCfBwB2O2OBBBaIBIEBDEBF1CBHCBC5CBCDBGqBBC9CBSfBxBPBhQ-tGBhCs0VBkCtBBDsIBEPBLBBVuBBGHBEwDBoBIBDmDBDxCBVUBCgBBZzBBNjCBCtBtBBEBECCBBBLgBBGiBBOcBEyBBCLBQRRBOBLEBC2BBKNBTWBEkCBCCCZCBDPBDDBMFBDFBDFBKGBCGBCqBBCNBH6DBWj9KBNWBFwBBloItLBDpDBnBGBNEBGLBCMBCEBCCCBCCBCCBqDBiBqLBT-BBD1BBpBLB1DEBCmEBlBZBHZBM4CBEFBDFBDFBDCBkBLBCZBCSBCBBCOBDNBjB6DBmC0BBsIcBEwBBwBfBOdBGqBBGdBDjBBFHBCEBrB9EBTjBBFjBBFnBBJzBBNKBCOBCGBCBBCKBCOBCGBCBBEzBBN2JBKVBLHBZFBCpBBCIBmCFBDCCBqBBCBBEDDBVBLWBKeBiCSBCBBLVBLZBHZBnB3BBHBBhCDBCBBGHBCCBCcBrBcBEcBkBHBCbBc1BBLVBLSBORBvDoCB4ByBBOyBBOnBBjBbBEGGBVB7HpBBCBBEBBRFBzBCBEcBLJJBUBrBRBvBUBcWBKlCBsBEBL4BBKOOBXBYyBBSDBJiBBEKKB+BBCDBKBBLCCkBRBChBBDHHBCB-BGBCCCBCBCOBCJBI4BBYDBCHBDBBDVBCGBCBBCEBEHBDBBDBBEHHGGBdJBCDDClBBCJBCDDCDBCBBECCtBhCBCCBCDBVCBfhCBDBBC5F5FB0BBDGBaFBjB+BBCEE8B1BBDoCoCBZBDNBWGB6F4BBoD-BBgBHBDDDBGBCBBCdBCBBDBBDDB+CHBDtBBDFBCCCBccBxBBDJBSnCBGTTBnCBoDHB5CgBBgBIBCsBBCGBCyByBBcBDVBCNBqCGBCBBCrBBECCBCCBBBCDDBZZBEBCBBCkBBCBBCDBCYYBqBBlIWBKQBCoBBECBwDwCwCB4cBnDuDBSjGBtyCgDBQvhBBSFBa68DBGmSB61GuBBy2B4RBIeBSuCBSdBTvBBRDBgBUBGSBxNsBB0G-BBhBYBDYBtBqCBF4BBIQBhCBBCNNBFBK1mHBqBfBiDyDB+vIDBCGBCBBCiJBQeeBBBDPPBCBJrMBloCqDBGMBEIBIJBFi7Fi7FBzCBCmCBCBBDDDBDDBCBCLBCCCBFBCgCBCDBDHBCGBCbBCDBCEBCEEBFBCzKBDYBCYBCeBCYBCeBCYBCeBCYBCeBCYBCHB15BeBHFB2GGBCQBDGBCBBCEBG9BBiBxDxDBrBBLGBRiKiKBcBTrBBlPbBlHdBDwGwGBdBCVBJBBhHGBCDBCBBCOBCkGB8BjCBEEE1lBDBCaBCBBCDDCJBCDBCCCHFFCECBBBCBBCDDCICBCCDDBCGBCDBCDBCCCBIBCQBGCBCEBCQB1TZBHZBHZB3zD-2pBBhB9oEBDt0FBDwpHBQtTBjtC9QBjvBq6EBGppIB", false)), + Dash: () => new UnicodeRangeTable(decodeRanges("tB9qB9qB0BiyDiyDmgBqgCqgCBEB+BoBoBQnMnMlgDDDgBBBFdd-NUUwDxszBxszBBmBmBLqFqFhzD-J-J", false)), + Emoji: () => new UnicodeRangeTable(decodeRanges("jBHHGJBwDFFu8HNN5GXX7CFBQBBwLBBNnFnFaKBFCBoGoHoHBLLK7B7BBCBCEBKGDBDDFDDCBBDIEBJJBBBGCCGLBMBBDCCBCCTDDBTTBEBCCCBEEBGGDBBFBBMBBGBBDGGBECBVVBGGBEBCDBDFFDDDBEBCDDCCCHEEHLLBQQDFFCFFBBBCMMBxBxBBBBKeP1LBBwOCBUBB0BFF7mBNN6SCCrrvDrGrGhFBBNBBPDDBIBsCZBCBBYVVDIBWBBvFhBBDvDBDBBCCBDyCBDCBCmIBC+BBMFBCXBIBBDHBNDDBCBDFFBOOBDDJBBKGGBBBNCBJCBDCCFHHEHHB0CBxBlCBGHBDDBEJBECCBEEDJBkHLBF8I8IBtBBCJBC4FBxDMBEKBE4BBCFFBOBDLBFJB", false)), + Emoji_Component: () => new UnicodeRangeTable(decodeRanges("jBHHGJB0+H2G2Gsp3B3+8B3+8BBYB8PEBxtBDBtzhY-CB", false)), + Emoji_Modifier: () => new UnicodeRangeTable(decodeRanges("7-8DE", true)), + Emoji_Modifier_Base: () => new UnicodeRangeTable(decodeRanges("9wJ8G8GRDB4jzD9B9BBBBDDDBBB2DBBDKBWSBEFFBBBCCBICCZqGqGBFFWFFBvFvFBBBEEB0CRRBBBKMMgSDDJHBHKKBIBDCB5B+B+BBCCBCCSCBCMBmHCBrBIB", false)), + Emoji_Presentation: () => new UnicodeRangeTable(decodeRanges("64IBBuGDBEDDqQBBWBBzBLBsBUUOJJBSSBGGBJJGWWIBBCFFDIIFBBdkBkBCFFBBBC+B+BBBBZPP8aBB0BFFvlxDrGrG-FDDBIBsCZBCZZVDDBDBCCBWBBvFgBBNIBClCBCVBNqBBFEBNQBEEEBlCBCCCB5FBD+BBODBCXBTbbBOO3C0CBxBlCBHEEBBBDDBEDBMBBIIBkHLBF8I8IBtBBCJBC4FBxDMBEKBE4BBCFFBOBDLBFJB", false)), + Extended_Pictographic: () => new UnicodeRangeTable(decodeRanges("pFFFu8HNN5GXX7CFBQBBwLBBNnFnFaKBFCBoGoHoHBLLK7B7BBCBCEBKGDBDDFDDCBBDIEBJJBBBGCCGLBMBBDCCBCCTDDBTTBEBCCCBEEBGGDBBFBBMBBGBBDGGBECBVVBGGBEBCDBDFFDDDBEBCDDCCCHEEHLLBQQDFFCFFBBBCMMBxBxBBBBKeP1LBBwOCBUBB0BFF7mBNN6SCCrrvDoBoBBCBlDLBQBBQPPBmBmBBIBxDBBNBBPDDBIBU3BBcOBLVVDIBCDBKWBH7FBDvDBDBBCCBDyCBDCBCDBG9HBC+BBMFBCXBIBBDHBNDDBCBDFFBOOBDDJBBKGGBBBNCBJCBDCCFHHEHHB0CBxBlCBGHBDQBECCBEBDMB7GlBBNDB5BHBLFBpBHBfBBNDBDNBKmBBNuBBCJBC4FB5CHBPxEBhI9fB", false)), + Hex_Digit: () => new UnicodeRangeTable(decodeRanges("wBJIFbFq1-BJIFbF", true)), + Lowercase: () => new UnicodeRangeTable(decodeRanges("hDZBwBLLFlBlBBWBCHBC2BCBQCBuBCDECBBBDCCDEEBFFDEEBBBDDDCCCDCCBCCDEECDDBDDBBBHGDCOCBSCBDDCEEC4BCBFBDDDBCCFICBjCBDiBBIBBfEBhDsBsBCEEDDBTccBhBBCBBECBCWCBDBCGDB0B0BBuBBCgBCK0BCDMCBgDCxBoBBo6CqBBCDB5XFBjkCIBC2D2DB+FBiC0ECBHBCgDCBHBJFBLHBJHBJFBLHBJHBJNBDHBJHBJHBJEBCBBHEEBBBCBBJDBDBBJHBLCBCBB6DOORMBuDEEBEEcKFDBBJDBFiBiBBOBFsasaBYBn6BvBBCEEBGCFCCBCCBGBEiDCBIICFFNlBBCGG0oesBCUaCBBBmEMCBBBC8BCBIBCCCDICFCCDCCBBBCSCGGGCMCFCCDOCWDBCCCBBB2ZqBBCNBHvCBh6TGBNEBqhBZBumBnBBpEjBB8EKBCOBCGBCBBkODDBBBCpBBCIBmoByBB+DVB75CfBhsVfB8BYBnqZZBbGBCRBbZBbDBCCCBFBCKBbZBbZBbZBbZBbZBbZBbZBbZBbbBdYBCFBbYBCFBbYBCFBbYBCFBbYBCFBC15B15BBIBCTBHFBmI9BB1lChBB", false)), + Math: () => new UnicodeRangeTable(decodeRanges("rBRRBBBgBeeCuBuBFmBmBgB5W5WBBBDbbBDDBBBwQCBuwGccBBBMEEOPPBCBWEBMEBiCMBFEEBFFBDBTFFDJBCDDBEBHEEBDDBCCBBBCFBENBClClCBWBCFBCBBFBBFfBCHHBPPBqIBJDBVBB7CffBZBCZZMGB+NBBNJBFFBFBBDBBEEBPCCDFBMHBGBB6BCCeDBKCBxK-BBhI-PBxBUBDFB9+zB4Z4ZBEBCjFjFRCBeCCeCCkEHHBCBitDBBhrwBwoBwoBBzCBCmCBCBBDDDBDDBCBCLBCCCBFBCgCBCDBDHBCGBCbBCDBCEBCEEBFBCzKBDjJBDxBBhwFDBCaBCBBCDDCJBCDBCCCHFFCECBBBCBBCDDCICBCCDDBCGBCDBCDBCCCBIBCQBGCBCEBCQB1BBB-uCIB", false)), + Quotation_Mark: () => new UnicodeRangeTable(decodeRanges("iBFFkEQQ96HHBaBBowDqOqOBCBOCBixzBDB+FFF7CBB", false)), + Terminal_Punctuation: () => new UnicodeRangeTable(decodeRanges("hBLLCMMBEE-ZJJiQ6B6BpCPPCCB1FsBsBBJBCsHsHB3B3BBEBCHBgBmImIB1nB1nBBtFtFFFB4JBB2YHBmY9D9DBBBoCBB+ECBEoBoBBCBDBB7JBBjLDBjFBBLBBCCBeCB8FEB-BBBldYYBKKBBBwlDCBzJOOFLLCBBEBBtNBB8ndBBuICBkHEB-LBB3CBBgD4E4EBBB0ECBgERRB6H6HnxUDDB6B6BBBBCDBqFLLCMMBEEiCDD7hBxBxBnkBoGoG3JBB5EFBlCFB6CDB5dEBtBDB+FGBxDDBgECBiEBBHRRB5C5CBDBtDrJrJB2D2DBBBNBBnLDBEOBqDBB6HCBmQCC8HBB4CBBFBB-MCBuBmUmUBrCrCBspBspBBDB6vRBBmEiCiCBBBLqRqRBoJoJBnwTnwTovHDB", false)), + Uppercase: () => new UnicodeRangeTable(decodeRanges("hCZBmDWBCGBiB2BCDOCDuBCBECEBBCCCBCCBBBDDBCBBCCBEBBCBBCECBCCDCCBCCBBBCCCBEEIJDCMCDQCDDDCCBC4BCIBBCBBDCCBCBCGCiJCCEJJHCCBBBCCCBCCBPBCIBkBDDBBBEWCGDDCBBDyBBxBgBCK2BCBMCD+CCDlBBq6ClBBCGGzW1CB0kCHHBpBBDCBhK0ECKgDCKHBJFBLHBJHBJFBMGCJHBpCDBNDBNDBNEBMDBnIFFECBDCBDEEBDBHGCBCBDDBLBBGbbBOBUzZzZBYBx5BvBBxBCCBBBDGCBCBCDDJCBCgDCJCCFuqeuqeCqBCUaCoEMCE8BCLECBICFCCDCCEUCBDBCEBCOCBCBCCCBQCZs5Vs5VBYBmmBnBBpEjBB9EKBCOBCGBCBBr3ByBB+EVB75CfBhsVfBhCYBoqZZBbZBbZBbCCBGDBDDBCBCHBbZBbBBCDBDHBCGBcBBCDBCEBCEEBFBcZBbZBbZBbZBbZBbZBfYBiBYBiBYBiBYBiBYBiB2pE2pEBgBBvgCZBHZBHZB", false)), + White_Space: () => new UnicodeRangeTable(decodeRanges("JEBTlDlDbgvFgvFgsCKBeBBGwBwBh9DAB", false)) + }); + static get Upper() { + return this.CATEGORIES.get("Lu"); + } + static SCRIPTS = new LazyMap({ + Adlam: () => new UnicodeRangeTable(decodeRanges("go6DrCFJFB", true)), + Ahom: () => new UnicodeRangeTable(decodeRanges("g4lCaDOFW", true)), + Anatolian_Hieroglyphs: () => new UnicodeRangeTable(decodeRanges("ggxCmS", true)), + Arabic: () => new UnicodeRangeTable(decodeRanges("gwBEBCFBCNBCCBCfBCJBMZBCrDBChBBxCvBBxHhBBGqCBCcBxy8BtPBDvEBhBPBxDEBCmEBk7DeBkCFBJIBiBFBh43BDBCaBCBBCDDCJBCDBCCCHFFCECBBBCBBCDDCICBCCDDBCGBCDBCDBCCCBIBCQBGCBCEBCQB1BBB", false)), + Armenian: () => new UnicodeRangeTable(decodeRanges("xpBlBDxBDCks9BE", true)), + Avestan: () => new UnicodeRangeTable(decodeRanges("g4iC1BEG", true)), + Balinese: () => new UnicodeRangeTable(decodeRanges("g4GsCCxB", true)), + Bamum: () => new UnicodeRangeTable(decodeRanges("g1pB3CpowB4R", true)), + Bassa_Vah: () => new UnicodeRangeTable(decodeRanges("w26CdDF", true)), + Batak: () => new UnicodeRangeTable(decodeRanges("g+GzBJD", true)), + Bengali: () => new UnicodeRangeTable(decodeRanges("gsCDBCHBDBBDVBCGBCEEBCBDIBDBBDDBJFFBCCBDBDYB", false)), + Beria_Erfe: () => new UnicodeRangeTable(decodeRanges("g17CYDY", true)), + Bhaiksuki: () => new UnicodeRangeTable(decodeRanges("ggnCICsBCNLc", true)), + Bopomofo: () => new UnicodeRangeTable(decodeRanges("qXB6wLqBxDf", true)), + Brahmi: () => new UnicodeRangeTable(decodeRanges("ggkCtCFjBKA", true)), + Braille: () => new UnicodeRangeTable(decodeRanges("ggK-H", true)), + Buginese: () => new UnicodeRangeTable(decodeRanges("gwGbDB", true)), + Buhid: () => new UnicodeRangeTable(decodeRanges("g6FT", true)), + Canadian_Aboriginal: () => new UnicodeRangeTable(decodeRanges("ggF-TxRlC7tgCP", true)), + Carian: () => new UnicodeRangeTable(decodeRanges("g1gCwB", true)), + Caucasian_Albanian: () => new UnicodeRangeTable(decodeRanges("wphCzBMA", true)), + Chakma: () => new UnicodeRangeTable(decodeRanges("gokC0BCR", true)), + Cham: () => new UnicodeRangeTable(decodeRanges("gwqB2BKNDJDD", true)), + Cherokee: () => new UnicodeRangeTable(decodeRanges("g9E1CDFz7lBvC", true)), + Chorasmian: () => new UnicodeRangeTable(decodeRanges("w9jCb", true)), + Common: () => new UnicodeRangeTable(decodeRanges("AgCBbFBbuBBCOBCEBYgBgBiOmBBGEBDTB1DKKHCC+THHPEEhB9E9ElQiEiEB6mB6mB2MDBjJwvBwvBBBBoCBBsGBBCumBumBOIIBCBCFBCCBDmYmYBKBD2CBCKBEKBCOBShBB-BlBBCCBDFBCaBCQBqBCBF5UBXKBW-cBhIzTBDpEBhQ9CBzMUBCCCBXBQHBFDB8CBBE7C7CB0E0EBOBhBlBBKxBxBB+BBgBwCBwB5C5CBmFBhuG-BBhoWhBBnDCBmFJB1HhFhFsMPPBzuUzuUBxGxGBIBXiBBCSBCDB0ECCBeBbFBbKBLuBuBBhChCBFBCGBLEBjICBFsBBEIBxCMB0BsBBlHaBltuBDB96D8HBEzNBHWBQQBgDzDB9B1HBLmBBD9BBEQBJBBIdBF8BB2GTBNTBN2CBKYBoE0CBCmCBCBBDDDBDDBCBCLBCCCBFBCgCBCDBDHBCGBCbBCDBCEBCEEBFBCzKBDjJBDxBByjFjCBtC8BBjWrBBFjDBNOBDOBCOBCkBBLtFB5BZBCBBOrBBFIBIBBPFB7E4eBEQBEMBE5GBHLBFQQBKBF3BBJJBHnBBJdBDLBFBBPIBoB3KBJNBDMBEKBE4BBCFFBOBDLBFJBIyEBCmDBnghYffB+CB", false)), + Coptic: () => new UnicodeRangeTable(decodeRanges("ifNxkKzDGG", true)), + Cuneiform: () => new UnicodeRangeTable(decodeRanges("ggoC5cnDuDCEMjG", true)), + Cypriot: () => new UnicodeRangeTable(decodeRanges("ggiCFBDCCBqBBCBBEDD", false)), + Cypro_Minoan: () => new UnicodeRangeTable(decodeRanges("w8rCiD", true)), + Cyrillic: () => new UnicodeRangeTable(decodeRanges("ggBkEBDoFBx6FKBhFtCtCojEfBhie-CBv8VBBhw4B9BBiBAB", false)), + Deseret: () => new UnicodeRangeTable(decodeRanges("gghCvC", true)), + Devanagari: () => new UnicodeRangeTable(decodeRanges("goCwCFODZh7nBfhwcJ", true)), + Dives_Akuru: () => new UnicodeRangeTable(decodeRanges("gomCGBDDDBGBCBBCdBCBBDLBKJB", false)), + Dogra: () => new UnicodeRangeTable(decodeRanges("ggmC7B", true)), + Duployan: () => new UnicodeRangeTable(decodeRanges("ggvDqDGMEIIJDD", true)), + Egyptian_Hieroglyphs: () => new UnicodeRangeTable(decodeRanges("ggsC1iBL68D", true)), + Elbasan: () => new UnicodeRangeTable(decodeRanges("gohCnB", true)), + Elymaic: () => new UnicodeRangeTable(decodeRanges("g-jCW", true)), + Ethiopic: () => new UnicodeRangeTable(decodeRanges("gwEoCBCDBDGBCCCBCBDoBBCDBDgBBCDBDGBCCCBCBDOBC4BBCDBDiCBDfBEZBnvGWBKGBCGBCGBCGBCGBCGBCGBCGBjpfFBDFBDFBKGBCGBylvCGBCDBCBBCOB", false)), + Garay: () => new UnicodeRangeTable(decodeRanges("gqjClBEcJB", true)), + Georgian: () => new UnicodeRangeTable(decodeRanges("glElBBCGGDqBBCDBx8CqBBDCBhiElBBCGG", false)), + Glagolitic: () => new UnicodeRangeTable(decodeRanges("ggL-Ch9sDGCQDGCBCE", true)), + Gothic: () => new UnicodeRangeTable(decodeRanges("w5gCa", true)), + Grantha: () => new UnicodeRangeTable(decodeRanges("g4kCDBCHBDBBDVBCGBCBBCEBDIBDBBDCBDHHGGBDGBEEB", false)), + Greek: () => new UnicodeRangeTable(decodeRanges("wbDBCCBDDBCFFCCCBBBCCCBSBC+BBPPBnpGEBzBEBFEB1ChKhKBUBDFBDlBBDFBDHBCGCBdBD0BBCOBCNBDFBCSBDCBCIBoJ-xiB-xiB7uVuCBSgj0Bgj0BBkCB", false)), + Gujarati: () => new UnicodeRangeTable(decodeRanges("h0CCBCIBCCBCVBCGBCBBCEBDJBCCBCCBDQQBCBDLBIGB", false)), + Gunjala_Gondi: () => new UnicodeRangeTable(decodeRanges("grnCFCBCkBCBCFIJ", true)), + Gurmukhi: () => new UnicodeRangeTable(decodeRanges("hwCCBCFBFBBDVBCGBCBBCBBCBBDCCBDBFBBDCBEIIBCBCIIBPB", false)), + Gurung_Khema: () => new UnicodeRangeTable(decodeRanges("go4C5B", true)), + Han: () => new UnicodeRangeTable(decodeRanges("g0LZBC4CBN1GBwBCCaIBPDBle-tGBhC-vUBhoWtLBDpDBpodBBNGBqgkB-2pBBhB9oEBDt0FBDwpHBQtTBjtC9QBjvBq6EBGppIB", false)), + Hangul: () => new UnicodeRangeTable(decodeRanges("goE-HvxHBiI9CyDeiCei3dckUj9KNWFwBl9JeEFDFDFDC", true)), + Hanifi_Rohingya: () => new UnicodeRangeTable(decodeRanges("gojCnBJJ", true)), + Hanunoo: () => new UnicodeRangeTable(decodeRanges("g5FU", true)), + Hatran: () => new UnicodeRangeTable(decodeRanges("gniCSCBGE", true)), + Hebrew: () => new UnicodeRangeTable(decodeRanges("xsB2BBJaBFFBpp9BZBCEBCCCBCCBCCBIB", false)), + Hiragana: () => new UnicodeRangeTable(decodeRanges("hiM1CBHCBi7-C+IBTeeBBBulQAB", false)), + Imperial_Aramaic: () => new UnicodeRangeTable(decodeRanges("giiCVCI", true)), + Inherited: () => new UnicodeRangeTable(decodeRanges("gYvDB2IBBlOKBbhXhXBCB8qEtBBDLBlPCBCMBCGBFHHEBBnG-BBtQBBjGgBB65DDBsDBBmrzBPBRNBwejHjH7iEl+uBl+uBBsBBDWBhRCBSHBDGBfDBz6rYvHB", false)), + Inscriptional_Pahlavi: () => new UnicodeRangeTable(decodeRanges("g7iCSGH", true)), + Inscriptional_Parthian: () => new UnicodeRangeTable(decodeRanges("g6iCVDH", true)), + Javanese: () => new UnicodeRangeTable(decodeRanges("gsqBtCDJFB", true)), + Kaithi: () => new UnicodeRangeTable(decodeRanges("gkkCiCLA", true)), + Kannada: () => new UnicodeRangeTable(decodeRanges("gkDMCCCWCJCEDICCCDIBGCCDDJCC", true)), + Katakana: () => new UnicodeRangeTable(decodeRanges("hlM5CBDCBxHPBxGuBBC3CBvgzBJBCsBBzisBDBCGBCBBCgJgJBBBzBPPBCB", false)), + Kawi: () => new UnicodeRangeTable(decodeRanges("g4nCQCoBEc", true)), + Kayah_Li: () => new UnicodeRangeTable(decodeRanges("goqBtBCA", true)), + Kharoshthi: () => new UnicodeRangeTable(decodeRanges("gwiCDCBGHCCCcDCFJII", true)), + Khitan_Small_Script: () => new UnicodeRangeTable(decodeRanges("k-7C84G84GB0OBqBAB", false)), + Khmer: () => new UnicodeRangeTable(decodeRanges("g8F9CDJHJnPf", true)), + Khojki: () => new UnicodeRangeTable(decodeRanges("gwkCRCuB", true)), + Khudawadi: () => new UnicodeRangeTable(decodeRanges("w1kC6BGJ", true)), + Kirat_Rai: () => new UnicodeRangeTable(decodeRanges("gq7C5B", true)), + Lao: () => new UnicodeRangeTable(decodeRanges("h0DBBCCCBDBCXBCCCBVBDEBCCCBFBCJBDDB", false)), + Latin: () => new UnicodeRangeTable(decodeRanges("hCZBHZBwBQQGWBCeBCgOBoBEB8wGlBBHwBBGDBGMBClCBiC-HByLOORMBuEBBHccSoBB42CfBj1elDBExCBVOBxZqBBCIBCDB38TGB7gBZBHZBmhCFBCpBBCIBm61BeBHFB", false)), + Lepcha: () => new UnicodeRangeTable(decodeRanges("ggH3BEOEC", true)), + Limbu: () => new UnicodeRangeTable(decodeRanges("goGeBCLBFLBFEEBKB", false)), + Linear_A: () => new UnicodeRangeTable(decodeRanges("gwhC2JKVLH", true)), + Linear_B: () => new UnicodeRangeTable(decodeRanges("gggCLCZCSCBCODNjB6D", true)), + Lisu: () => new UnicodeRangeTable(decodeRanges("wmpBvBx1eA", true)), + Lycian: () => new UnicodeRangeTable(decodeRanges("g0gCc", true)), + Lydian: () => new UnicodeRangeTable(decodeRanges("gpiCZGA", true)), + Mahajani: () => new UnicodeRangeTable(decodeRanges("wqkCmB", true)), + Makasar: () => new UnicodeRangeTable(decodeRanges("g3nCY", true)), + Malayalam: () => new UnicodeRangeTable(decodeRanges("goDMCCCyBCCCFFPDZ", true)), + Mandaic: () => new UnicodeRangeTable(decodeRanges("giCbDA", true)), + Manichaean: () => new UnicodeRangeTable(decodeRanges("g2iCmBFL", true)), + Marchen: () => new UnicodeRangeTable(decodeRanges("wjnCfDVCN", true)), + Masaram_Gondi: () => new UnicodeRangeTable(decodeRanges("gonCGBCBBCrBBECCBCCBHBJJB", false)), + Medefaidrin: () => new UnicodeRangeTable(decodeRanges("gy7C6C", true)), + Meetei_Mayek: () => new UnicodeRangeTable(decodeRanges("g3qBWqGtBDJ", true)), + Mende_Kikakui: () => new UnicodeRangeTable(decodeRanges("gg6DkGDP", true)), + Meroitic_Cursive: () => new UnicodeRangeTable(decodeRanges("gtiCXFTDtB", true)), + Meroitic_Hieroglyphs: () => new UnicodeRangeTable(decodeRanges("gsiCf", true)), + Miao: () => new UnicodeRangeTable(decodeRanges("g47CqCF4BIQ", true)), + Modi: () => new UnicodeRangeTable(decodeRanges("gwlCkCMJ", true)), + Mongolian: () => new UnicodeRangeTable(decodeRanges("ggGBBDCCBSBH4CBIqBB2t-BMB", false)), + Mro: () => new UnicodeRangeTable(decodeRanges("gy6CeCJFB", true)), + Multani: () => new UnicodeRangeTable(decodeRanges("g0kCGBCCCBCBCOBCKB", false)), + Myanmar: () => new UnicodeRangeTable(decodeRanges("ggE-EhqmBeiDfxibT", true)), + Nabataean: () => new UnicodeRangeTable(decodeRanges("gkiCeJI", true)), + Nag_Mundari: () => new UnicodeRangeTable(decodeRanges("wm5DpB", true)), + Nandinagari: () => new UnicodeRangeTable(decodeRanges("gtmCHDtBDK", true)), + New_Tai_Lue: () => new UnicodeRangeTable(decodeRanges("gsGrBFZHKEB", true)), + Newa: () => new UnicodeRangeTable(decodeRanges("gglC7CCE", true)), + Nko: () => new UnicodeRangeTable(decodeRanges("g+B6BDC", true)), + Nushu: () => new UnicodeRangeTable(decodeRanges("h-7CvsQvsQBqMB", false)), + Nyiakeng_Puachue_Hmong: () => new UnicodeRangeTable(decodeRanges("go4DsBENDJFB", true)), + Ogham: () => new UnicodeRangeTable(decodeRanges("g0Fc", true)), + Ol_Chiki: () => new UnicodeRangeTable(decodeRanges("wiHvB", true)), + Ol_Onal: () => new UnicodeRangeTable(decodeRanges("wu5DqBFA", true)), + Old_Hungarian: () => new UnicodeRangeTable(decodeRanges("gkjCyBOyBIF", true)), + Old_Italic: () => new UnicodeRangeTable(decodeRanges("g4gCjBKC", true)), + Old_North_Arabian: () => new UnicodeRangeTable(decodeRanges("g0iCf", true)), + Old_Permic: () => new UnicodeRangeTable(decodeRanges("w6gCqB", true)), + Old_Persian: () => new UnicodeRangeTable(decodeRanges("g9gCjBFN", true)), + Old_Sogdian: () => new UnicodeRangeTable(decodeRanges("g4jCnB", true)), + Old_South_Arabian: () => new UnicodeRangeTable(decodeRanges("gziCf", true)), + Old_Turkic: () => new UnicodeRangeTable(decodeRanges("ggjCoC", true)), + Old_Uyghur: () => new UnicodeRangeTable(decodeRanges("w7jCZ", true)), + Oriya: () => new UnicodeRangeTable(decodeRanges("h4CCCHDBDVCGCBCEDIDBDCICFBCEDR", true)), + Osage: () => new UnicodeRangeTable(decodeRanges("wlhCjBFjB", true)), + Osmanya: () => new UnicodeRangeTable(decodeRanges("gkhCdDJ", true)), + Pahawh_Hmong: () => new UnicodeRangeTable(decodeRanges("g46ClCLJCGCUGS", true)), + Palmyrene: () => new UnicodeRangeTable(decodeRanges("gjiCf", true)), + Pau_Cin_Hau: () => new UnicodeRangeTable(decodeRanges("g2mC4B", true)), + Phags_Pa: () => new UnicodeRangeTable(decodeRanges("giqB3B", true)), + Phoenician: () => new UnicodeRangeTable(decodeRanges("goiCbEA", true)), + Psalter_Pahlavi: () => new UnicodeRangeTable(decodeRanges("g8iCRIDNG", true)), + Rejang: () => new UnicodeRangeTable(decodeRanges("wpqBjBMA", true)), + Runic: () => new UnicodeRangeTable(decodeRanges("g1FqCEK", true)), + Samaritan: () => new UnicodeRangeTable(decodeRanges("ggCtBDO", true)), + Saurashtra: () => new UnicodeRangeTable(decodeRanges("gkqBlCJL", true)), + Sharada: () => new UnicodeRangeTable(decodeRanges("gskC-ChsCH", true)), + Shavian: () => new UnicodeRangeTable(decodeRanges("wihCvB", true)), + Siddham: () => new UnicodeRangeTable(decodeRanges("gslC1BDlB", true)), + Sidetic: () => new UnicodeRangeTable(decodeRanges("gqiCZ", true)), + SignWriting: () => new UnicodeRangeTable(decodeRanges("gg2DrUQECO", true)), + Sinhala: () => new UnicodeRangeTable(decodeRanges("hsDCBCRBEXBCIBCDDBFBEFFBEBCCCBGBHJBDCBt-gCTB", false)), + Sogdian: () => new UnicodeRangeTable(decodeRanges("w5jCpB", true)), + Sora_Sompeng: () => new UnicodeRangeTable(decodeRanges("wmkCYIJ", true)), + Soyombo: () => new UnicodeRangeTable(decodeRanges("wymCyC", true)), + Sundanese: () => new UnicodeRangeTable(decodeRanges("g8G-BhIH", true)), + Sunuwar: () => new UnicodeRangeTable(decodeRanges("g+mChBPJ", true)), + Syloti_Nagri: () => new UnicodeRangeTable(decodeRanges("ggqBsB", true)), + Syriac: () => new UnicodeRangeTable(decodeRanges("g4BNC7BDCxIK", true)), + Tagalog: () => new UnicodeRangeTable(decodeRanges("g4FVKA", true)), + Tagbanwa: () => new UnicodeRangeTable(decodeRanges("g7FMCCCB", true)), + Tai_Le: () => new UnicodeRangeTable(decodeRanges("wqGdDE", true)), + Tai_Tham: () => new UnicodeRangeTable(decodeRanges("gxG+BCcDKHJHN", true)), + Tai_Viet: () => new UnicodeRangeTable(decodeRanges("g0qBiCZE", true)), + Tai_Yo: () => new UnicodeRangeTable(decodeRanges("g25DeCVJB", true)), + Takri: () => new UnicodeRangeTable(decodeRanges("g0lC5BHJ", true)), + Tamil: () => new UnicodeRangeTable(decodeRanges("i8CBBCFBECBCDBEBBCCCBEEBEEBBBELBFEBECBCDBDHHPUBm+kCxBBOAB", false)), + Tangsa: () => new UnicodeRangeTable(decodeRanges("wz6CuCCJ", true)), + Tangut: () => new UnicodeRangeTable(decodeRanges("g-7CgBgBB+3GBhQeBiDyDB", false)), + Telugu: () => new UnicodeRangeTable(decodeRanges("ggDMCCCWCPDICCCDIBCCCBDDDJII", true)), + Thaana: () => new UnicodeRangeTable(decodeRanges("g8BxB", true)), + Thai: () => new UnicodeRangeTable(decodeRanges("hwD5BGb", true)), + Tibetan: () => new UnicodeRangeTable(decodeRanges("g4DnCCjBFmBCjBCOCGFB", true)), + Tifinagh: () => new UnicodeRangeTable(decodeRanges("wpL3BIBPA", true)), + Tirhuta: () => new UnicodeRangeTable(decodeRanges("gklCnCJJ", true)), + Todhri: () => new UnicodeRangeTable(decodeRanges("guhCzB", true)), + Tolong_Siki: () => new UnicodeRangeTable(decodeRanges("wtnCrBFJ", true)), + Toto: () => new UnicodeRangeTable(decodeRanges("w04De", true)), + Tulu_Tigalari: () => new UnicodeRangeTable(decodeRanges("g8kCJBCDDClBBCJBCDDCDBCJBCBBJBB", false)), + Ugaritic: () => new UnicodeRangeTable(decodeRanges("g8gCdCA", true)), + Unknown: () => new UnicodeRangeTable(decodeRanges("4bBBHDBICCVuMuMnBBBzBBBE4B4BBGBcDBHKBvI9B9BBmDmDBMB8BBByBBBQddBCCMEBjBEBuHJJBDDBXXICCBBBFBBKBBDBBFHBCDBDGGBaaBEEHDBDBBXIIDGDBCCGDBDBBECBCGBFCCBFBSJBEKKEXXIDDGBBLIEBCCBNBFBBNGBIEEJBBDBBXIIDGGBKKBDDBEEBFBEDBDGGBTTBIBDHHBBBEFFBBBDCCDCBDCBECBNDBGCBEFFBCCBEBCNBWEBOEEYRRBKKEFFBFBDEEDBBFBBLGBXEEYLLGBBKEEFGBDEBEFFBLLELBOEE0BEEHDBRBBbEETCBZKKCBBICBCDBHCCJFBLBBELB7BDBekBBDCCGZZCYYBGGCIILBBFfBpClBlBBCBoBlBlBQOOBjBBnGCCBDBCBB6LFFBIICFFBqBqBFBBiBFFBIICFFBQQ6BFFBkCkCBhBhBBBBbFB3CBBHBB+UCB6CGBXIBZIBVLBOEEDLB-CBBLFBLFBbFB6CGBsBEBnCJBgBNNBCBNDBCCBrBBBGKBtBDBbFBMCB-BBBiCeeBMMBEBLFBPBBvBBBNTBuCnFnFBGB9BCBQCB-BEBsBBBMHBsBEB3QBBHBBnBBBHBBJGCgBBB2BQQPBBHUUBEEKmDmDNBBcOOBBBjBNBiBOBtEDB7UVBMUB14BBB-LEBuBCCBDBCBB5BGBDNBZIBI4BI-DhBBb6C6CBKB3GZBxC3C3CBoDoDBDBsB-C-C3CIBxBuzcuzcBBB4BIB9KTB5FHB+GTB9BCBLFB5BHBnCHBNFB1DKBfCBvCMMBCBiB4B4BBHBPBBLBBoDXBdJBHBBHBBHIBIII9BDB-DBBLFBl9KLBYDByBjoIBvLBBrDlBBILBGEBbGGCGDrUfBrBFB0BUUFDBGoEoEBCC-FCBHBBHBBHBBECBIIIBIBGBBNbbUDDQBBPhBB8DEBEDBuBCB5COOBBBCuBBvBhEBeCByBOBdDBlBIBfEBsBEBfmBmBBCBPpBB-EBBLFBlBDBlBDBpBHB1BKBNQQIDDMQQIDDBBB1BLB4JIBXJBJXBHrBrBKkCBHBBCtBtBDCBCBBYpCpCBGBKvBBUDDBDBiBCBcEBclBB5BDBVBBzBDDBDBJEEeBBEDBLGBKGBhCfBoBDBNIB3BCBeBBcEBbGBFLBIvCBqC2BB0BMB0BGBvBHBLFBnBCBeHBDvGBgBrBrBEBBDPBHHBKgBBvBHBrBVBblBBdTBYIBvCDBlBIBlCJBCBBaGBLFB2BTTBGBoBIBhDVVBJBTwBwBB8BBICCFQQMFB8BEBLFBFJJBDDBXXIDDGLLBDDBEEBCCBEBCEBIBBICBGKBLCCBCCnBLLCBBCFFLDDBGBDcB9CGGBcBpCHBLlFB3BBBnBhBBmCKBLFBOSB7BFBLFBVbBcBBQDBY4FB9BjDB0CLBJBBCBBJDDfDDBNNBHBLlCBJBBvBBBMaBpCHB0CMBqCGBL1CBJ3CBjBNBLFBKuBuBPJBeCBhBBBXPPBnCBIDDtBCBCDDKHBLFBHDDmBDDHGBLFBtBDBL1HBaGBSqBqBBBBe0CBCOBzBMB8clDBwDGGBJBlGryCBkDMB3iBJB88DEBoS41GB7Bl2BB6RGBgBLLBCByCLLBEBfBBHJBnCJBLIIWEBUvNB7BlGB8CEBaBBarBBsCDB6BGBS-BBGKBIIB3mHoBBhBgDB0D8vIBFIIDkJkJBNBCcBEBBCNBFHBtMjoCBsDEBOCBKGBLBBJ76DB+HCB1NFBYOBSOBvBBBYIB1D7BB3HJBoBBBjGUBnC5DBVLBVLB4CIBamEB2CoCoCDBBCBBDBBFNNCIIiCFFBJJIddFGGCCBI1K1KBlJlJB-V-VBNBGQQBuiBBgBFBH0GBISSBIIDGGBDB-BgBBCvDBuBCBPBBLDBD-JBgBQB7BEBCvOBrB1GBsBDBC-FBgBXXBGBD-GBIFFDQQmGBBRoBBtCDBLDBDwYBlCrCB+BhGBFccDCCBCCLFFCCCBEBCDBCECEDDCBBCICDCCBFFIKFCLLSEBEGGSzBBDtIBtBDBlDLBQBBQQQmBJBvF3BBeMBtBDBKGBDNBH5EB6eCBSCBOCB7GFBNDBCOBNDB5BHBLFBpBHBfBBNDBDNBKmBB5KHBPBBOCBMCB6BCCBCBRBBNDBLGB0EoDoDBjgBBh3pBfB-oEBBv0FBBypHOBvThtCB-QhvBBs6EEBrpIm8yVBCdBhD-DBxHvw-FB", false)), + Vai: () => new UnicodeRangeTable(decodeRanges("gopBrJ", true)), + Vithkuqi: () => new UnicodeRangeTable(decodeRanges("wrhCKCOCGCBCKCOCGCB", true)), + Wancho: () => new UnicodeRangeTable(decodeRanges("g24D5BGA", true)), + Warang_Citi: () => new UnicodeRangeTable(decodeRanges("glmCyCNA", true)), + Yezidi: () => new UnicodeRangeTable(decodeRanges("g0jCpBCCDB", true)), + Yi: () => new UnicodeRangeTable(decodeRanges("ggoBskBE2B", true)), + Zanabazar_Square: () => new UnicodeRangeTable(decodeRanges("gwmCnC", true)) + }); + static FOLD_CATEGORIES = new LazyMap({ + L: () => new UnicodeRangeTable(decodeRanges("laA", true)), + LC: () => new UnicodeRangeTable(decodeRanges("laA", true)), + Ll: () => new UnicodeRangeTable(decodeRanges("hCZBmDWBCGBiBuBCEECDOCDuBCBECEBBCCCBCCBBBDDBCBBCCBEBBCBBCECBCCDCCBCCBBBCCCBEEIBBCBBCBBCOCDQCDBBCCCBBBC4BCIBBCBBDCCBCBCGC3HrBrBCEEJHHCCBCCCBCCBPBCIBkBJJCUCGDDCBBDyBBxBgBCK2BCBMCD+CCDlBBq6ClBBCGGzW1CB0kCHHBpBBDCBhK0ECKgDCKHBJFBLHBJHBJFBMGCJHBZHBJHBJHBJEBMEBMDBNEBMEBqJEEBHHxC9zC9zCBuBBxBCCBBBDGCBCBCDDJCBCgDCJCCFuqeuqeCqBCUaCoEMCE8BCLECBICFCCDCCEUCBDBCEBCOCBCBCCCBQCZs5Vs5VBYBmmBnBBpEjBB9EKBCOBCGBCBBr3ByBB+EVB75CfBhsVfBhCYBoyehBB", false)), + Lt: () => new UnicodeRangeTable(decodeRanges("kOCCBCCBCClBCCtsHHBJHBJHBMQQwBAB", false)), + Lu: () => new UnicodeRangeTable(decodeRanges("hDZB7BqBqBBWBCHBCuBCEECDOCDsBCDECBBBDCCDEEGDDECBDDDCCCDFFDEECDDECCGBBCBBCBBCOCBSCDBBCEECkBCEQCJDDBCCFICBEBCBBCCCBEEBCCBCBCEBDCCBDDIDDCBBEFBGLLBnFnFsBCCEEEBBBvBDBCdBCBBECBCWCBDBCGD1BvBBCgBCK0BCDMCBgDCyBlBBq6CqBBDCB5XFBjkCIBCvHvHERRzD0ECGGGC8CCBHBJFBLHBJHBJFBMGCJHBJNBzBBBNSSBPPBEEpL2B2Bs1CvBBCEEBGCHDDLiDCJCCFNNBkBBCGG0oesBCUaCoEMCE8BCLCCDICFFFCBBDSCMOCFCCDOCb9a9advCBi8UZBumBnBBpEjBB8EKBCOBCGBCBBk4ByBB+DVB75CfBhsVfB8BYBvyehBB", false)), + M: () => new UnicodeRangeTable(decodeRanges("5cgBgBlgHAB", false)), + Mn: () => new UnicodeRangeTable(decodeRanges("5cgBgBlgHAB", false)), + Emoji: () => new UnicodeRangeTable(decodeRanges("8mJA", true)), + Extended_Pictographic: () => new UnicodeRangeTable(decodeRanges("8mJA", true)), + Lowercase: () => new UnicodeRangeTable(decodeRanges("hCZBmDWBCGBiBuBCEECDOCDuBCBECEBBCCCBCCBBBDDBCBBCCBEBBCBBCECBCCDCCBCCBBBCCCBEEIBBCBBCBBCOCDQCDBBCCCBBBC4BCIBBCBBDCCBCBCGCiJCCEJJHCCBBBCCCBCCBPBCIBkBJJCUCGDDCBBDyBBxBgBCK2BCBMCD+CCDlBBq6ClBBCGGzW1CB0kCHHBpBBDCBhK0ECKgDCKHBJFBLHBJHBJFBMGCJHBZHBJHBJHBJEBMEBMDBNEBMEBqJEEBHHuBPBUzZzZBYBx5BvBBxBCCBBBDGCBCBCDDJCBCgDCJCCFuqeuqeCqBCUaCoEMCE8BCLECBICFCCDCCEUCBDBCEBCOCBCBCCCBQCZs5Vs5VBYBmmBnBBpEjBB9EKBCOBCGBCBBr3ByBB+EVB75CfBhsVfBhCYBoyehBB", false)), + Math: () => new UnicodeRangeTable(decodeRanges("ycGDCHHFMMDDDCHHFAB", false)), + Uppercase: () => new UnicodeRangeTable(decodeRanges("hDZB7BqBqBBWBCHBCuBCEECDOCDsBCDECBBBDCCDEEGDDECBDDDCCCDFFDEECDDECCGBBCBBCBBCOCBSCDBBCEECkBCEQCJDDBCCFICBEBCBBCCCBEEBCCBCBCEBDCCBDDIDDCBBEFBGLLBnFnFsBCCEEEBBBvBDBCdBCBBECBCWCBDBCGD1BvBBCgBCK0BCDMCBgDCyBlBBq6CqBBDCB5XFBjkCIBCvHvHERRzD0ECGGGC8CCBHBJFBLHBJHBJFBMGCJHBJNBzBBBNSSBPPBEEpLiBiBBOBFsasaBYBn6BvBBCEEBGCHDDLiDCJCCFNNBkBBCGG0oesBCUaCoEMCE8BCLCCDICFFFCBBDSCMOCFCCDOCb9a9advCBi8UZBumBnBBpEjBB8EKBCOBCGBCBBk4ByBB+DVB75CfBhsVfB8BYBvyehBB", false)) + }); + static FOLD_SCRIPT = new LazyMap({ + Common: () => new UnicodeRangeTable(decodeRanges("8cgBgB", false)), + Greek: () => new UnicodeRangeTable(decodeRanges("1FwUwU", false)), + Inherited: () => new UnicodeRangeTable(decodeRanges("5cgBgBlgHAB", false)) + }); +}; +//#endregion +//#region src/Unicode.js +/** +* Utilities for dealing with Unicode better than JS does. +*/ +var Unicode = class Unicode { + static MAX_RUNE = 1114111; + static MAX_ASCII = 127; + static MAX_LATIN1 = 255; + static MAX_BMP = 65535; + static MIN_FOLD = 65; + static MAX_FOLD = 125251; + static MIN_HIGH_SURROGATE = 55296; + static MAX_HIGH_SURROGATE = 56319; + static MIN_LOW_SURROGATE = 56320; + static MAX_LOW_SURROGATE = 57343; + static MIN_SUPPLEMENTARY_CODE_POINT = 65536; + static is32(ranges, r) { + let lo = 0; + let hi = ranges.length; + while (lo < hi) { + const m = lo + Math.floor((hi - lo) / 2); + const rlo = ranges.getLo(m); + const rhi = ranges.getHi(m); + if (rlo <= r && r <= rhi) { + const stride = ranges.getStride(m); + return (r - rlo) % stride === 0; + } + if (r < rlo) hi = m; + else lo = m + 1; + } + return false; + } + static is(ranges, r) { + if (r <= Unicode.MAX_LATIN1) { + for (let i = 0; i < ranges.length; i++) { + if (r > ranges.getHi(i)) continue; + const rlo = ranges.getLo(i); + if (r < rlo) return false; + const stride = ranges.getStride(i); + return (r - rlo) % stride === 0; + } + return false; + } + return ranges.length > 0 && r >= ranges.getLo(0) && Unicode.is32(ranges, r); + } + static isUpper(r) { + if (r <= Unicode.MAX_LATIN1) { + const s = String.fromCodePoint(r); + return s.toUpperCase() === s && s.toLowerCase() !== s; + } + return Unicode.is(UnicodeTables.Upper, r); + } + static isPrint(r) { + if (r <= Unicode.MAX_LATIN1) return r >= 32 && r < Unicode.MAX_ASCII || r >= 161 && r !== 173; + return Unicode.is(UnicodeTables.Print, r); + } + static simpleFold(r) { + if (UnicodeTables.CASE_ORBIT.has(r)) return UnicodeTables.CASE_ORBIT.get(r); + const l = Codepoint.toLowerCase(r); + if (l !== r) return l; + return Codepoint.toUpperCase(r); + } + static equalsIgnoreCase(r1, r2) { + if (r1 === r2) return true; + if (r1 < 0 || r2 < 0) return false; + if (r1 <= Unicode.MAX_ASCII && r2 <= Unicode.MAX_ASCII) { + if (65 <= r1 && r1 <= 90) r1 |= 32; + if (65 <= r2 && r2 <= 90) r2 |= 32; + return r1 === r2; + } + for (let r = Unicode.simpleFold(r1); r !== r1; r = Unicode.simpleFold(r)) if (r === r2) return true; + return false; + } +}; +//#endregion +//#region src/Utils.js +/** +* Size of the precomputed single-byte lookup table. +* Covers standard ASCII and Latin-1 characters for fast-path execution. +*/ +const FAST_PATH_TABLE_SIZE = 256; +/** +* Precomputed lookup table for Word Boundary (\b, \B) assertions. +* * By precomputing the boolean results for standard ASCII word ranges +* ('a'-'z', 'A'-'Z', '0'-'9', '_'), we completely eliminate 4 logical +* branches from the NFA's hot execution loop. This prevents costly +* CPU branch mispredictions when scanning large strings. +*/ +const WORD_RUNE_TABLE = new Uint8Array(FAST_PATH_TABLE_SIZE); +for (let i = 0; i < FAST_PATH_TABLE_SIZE; i++) WORD_RUNE_TABLE[i] = 97 <= i && i <= 122 || 65 <= i && i <= 90 || 48 <= i && i <= 57 || i === 95 ? 1 : 0; +let cachedNativeEncoder = null; +let cachedNativeDecoder = null; +/** +* Various constants and helper utilities. +*/ +var Utils = class Utils { + static METACHARACTERS = "\\.+*?()|[]{}^$"; + static EMPTY_BEGIN_LINE = 1; + static EMPTY_END_LINE = 2; + static EMPTY_BEGIN_TEXT = 4; + static EMPTY_END_TEXT = 8; + static EMPTY_WORD_BOUNDARY = 16; + static EMPTY_NO_WORD_BOUNDARY = 32; + static EMPTY_ALL = -1; + static emptyInts() { + return []; + } + static isByteArray(input) { + return Array.isArray(input) || input instanceof Uint8Array; + } + static isalnum(c) { + return Codepoint.CODES.get("0") <= c && c <= Codepoint.CODES.get("9") || Codepoint.CODES.get("a") <= c && c <= Codepoint.CODES.get("z") || Codepoint.CODES.get("A") <= c && c <= Codepoint.CODES.get("Z"); + } + static unhex(c) { + if (Codepoint.CODES.get("0") <= c && c <= Codepoint.CODES.get("9")) return c - Codepoint.CODES.get("0"); + if (Codepoint.CODES.get("a") <= c && c <= Codepoint.CODES.get("f")) return c - Codepoint.CODES.get("a") + 10; + if (Codepoint.CODES.get("A") <= c && c <= Codepoint.CODES.get("F")) return c - Codepoint.CODES.get("A") + 10; + return -1; + } + static escapeRune(rune) { + let out = ""; + if (Unicode.isPrint(rune)) { + if (Utils.METACHARACTERS.indexOf(String.fromCodePoint(rune)) >= 0) out += "\\"; + out += String.fromCodePoint(rune); + } else switch (rune) { + case Codepoint.CODES.get("\""): + out += "\\\""; + break; + case Codepoint.CODES.get("\\"): + out += "\\\\"; + break; + case Codepoint.CODES.get(" "): + out += "\\t"; + break; + case Codepoint.CODES.get("\n"): + out += "\\n"; + break; + case Codepoint.CODES.get("\r"): + out += "\\r"; + break; + case Codepoint.CODES.get("\b"): + out += "\\b"; + break; + case Codepoint.CODES.get("\f"): + out += "\\f"; + break; + default: { + let s = rune.toString(16); + if (rune < 256) { + out += "\\x"; + if (s.length === 1) out += "0"; + out += s; + } else out += `\\x{${s}}`; + break; + } + } + return out; + } + static stringToRunes(str) { + const string = String(str); + const runes = []; + let i = 0; + while (i < string.length) { + const cp = string.codePointAt(i); + runes.push(cp); + i += cp > Unicode.MAX_BMP ? 2 : 1; + } + return runes; + } + static runeToString(r) { + return String.fromCodePoint(r); + } + static isWordRune(r) { + return r < FAST_PATH_TABLE_SIZE ? WORD_RUNE_TABLE[r] === 1 : false; + } + static emptyOpContext(r1, r2) { + let op = 0; + if (r1 < 0) op |= Utils.EMPTY_BEGIN_TEXT | Utils.EMPTY_BEGIN_LINE; + if (r1 === 10) op |= Utils.EMPTY_BEGIN_LINE; + if (r2 < 0) op |= Utils.EMPTY_END_TEXT | Utils.EMPTY_END_LINE; + if (r2 === 10) op |= Utils.EMPTY_END_LINE; + if (Utils.isWordRune(r1) !== Utils.isWordRune(r2)) op |= Utils.EMPTY_WORD_BOUNDARY; + else op |= Utils.EMPTY_NO_WORD_BOUNDARY; + return op; + } + /** + * Returns a string that quotes all regular expression metacharacters inside the argument text; + * the returned string is a regular expression matching the literal text. For example, + * {@code quoteMeta("[foo]").equals("\\[foo\\]")}. + * @param {string} str + * @returns {string} + */ + static quoteMeta(str) { + return str.split("").map((s) => { + if (Utils.METACHARACTERS.indexOf(s) >= 0) return `\\${s}`; + return s; + }).join(""); + } + static charCount(codePoint) { + return codePoint > Unicode.MAX_BMP ? 2 : 1; + } + /** + * High-speed conversion from TypedArrays to standard JS Arrays. + * Bypasses the expensive Symbol.iterator overhead of Array.from() + */ + static toArray(typedArray) { + const len = typedArray.length; + const res = new Array(len); + for (let i = 0; i < len; i++) res[i] = typedArray[i]; + return res; + } + static stringToUtf8ByteArray(str) { + if (globalThis.TextEncoder) { + if (!cachedNativeEncoder) cachedNativeEncoder = new TextEncoder(); + return cachedNativeEncoder.encode(str); + } else { + let out = [], p = 0; + for (let i = 0; i < str.length; i++) { + let c = str.charCodeAt(i); + if (c < 128) out[p++] = c; + else if (c < 2048) { + out[p++] = c >> 6 | 192; + out[p++] = c & 63 | 128; + } else if ((c & 64512) === Unicode.MIN_HIGH_SURROGATE && i + 1 < str.length && (str.charCodeAt(i + 1) & 64512) === Unicode.MIN_LOW_SURROGATE) { + c = Unicode.MIN_SUPPLEMENTARY_CODE_POINT + ((c & 1023) << 10) + (str.charCodeAt(++i) & 1023); + out[p++] = c >> 18 | 240; + out[p++] = c >> 12 & 63 | 128; + out[p++] = c >> 6 & 63 | 128; + out[p++] = c & 63 | 128; + } else { + out[p++] = c >> 12 | 224; + out[p++] = c >> 6 & 63 | 128; + out[p++] = c & 63 | 128; + } + } + return out; + } + } + static utf8ByteArrayToString(bytes) { + if (globalThis.TextDecoder) { + if (!cachedNativeDecoder) cachedNativeDecoder = new TextDecoder("utf-8"); + const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes); + return cachedNativeDecoder.decode(view); + } else { + let out = [], pos = 0, c = 0; + while (pos < bytes.length) { + let c1 = bytes[pos++]; + if (c1 < 128) out[c++] = String.fromCharCode(c1); + else if (c1 > 191 && c1 < 224) { + let c2 = bytes[pos++]; + out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63); + } else if (c1 > 239 && c1 < 365) { + let c2 = bytes[pos++]; + let c3 = bytes[pos++]; + let c4 = bytes[pos++]; + let u = ((c1 & 7) << 18 | (c2 & 63) << 12 | (c3 & 63) << 6 | c4 & 63) - Unicode.MIN_SUPPLEMENTARY_CODE_POINT; + out[c++] = String.fromCharCode(Unicode.MIN_HIGH_SURROGATE + (u >> 10)); + out[c++] = String.fromCharCode(Unicode.MIN_LOW_SURROGATE + (u & 1023)); + } else { + let c2 = bytes[pos++]; + let c3 = bytes[pos++]; + out[c++] = String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63); + } + } + return out.join(""); + } + } +}; +//#endregion +//#region src/helpers.js +const createEnum = (values = [], initNum = 0) => { + const enumObject = Object.create(null); + for (let i = 0; i < values.length; i++) { + const val = values[i]; + const keyVal = initNum + i; + enumObject[val] = keyVal; + enumObject[keyVal] = val; + } + return Object.freeze(enumObject); +}; +//#endregion +//#region src/MatcherInput.js +/** +* Abstract the representations of input text supplied to Matcher. +*/ +var MatcherInputBase = class MatcherInputBase { + static Encoding = createEnum(["UTF_16", "UTF_8"]); + getEncoding() { + throw Error("not implemented"); + } + /** @returns {string} */ + asCharSequence() { + throw Error("not implemented"); + } + /** @returns {Uint8Array|number[]} */ + asBytes() { + throw Error("not implemented"); + } + /** @returns {number} */ + length() { + throw Error("not implemented"); + } + /** + * + * @returns {boolean} + */ + isUTF8Encoding() { + return this.getEncoding() === MatcherInputBase.Encoding.UTF_8; + } + /** + * + * @returns {boolean} + */ + isUTF16Encoding() { + return this.getEncoding() === MatcherInputBase.Encoding.UTF_16; + } +}; +var Utf8MatcherInput = class extends MatcherInputBase { + /** @param {Uint8Array|number[]|null} bytes */ + constructor(bytes = null) { + super(); + this.bytes = bytes; + } + getEncoding() { + return MatcherInputBase.Encoding.UTF_8; + } + /** + * + * @returns {string} + */ + asCharSequence() { + return Utils.utf8ByteArrayToString(this.bytes); + } + /** + * + * @returns {Uint8Array|number[]|null} + */ + asBytes() { + return this.bytes; + } + /** + * + * @returns {number} + */ + length() { + return this.bytes.length; + } +}; +var Utf16MatcherInput = class extends MatcherInputBase { + /** @param {string|null} charSequence */ + constructor(charSequence = null) { + super(); + this.charSequence = charSequence; + } + getEncoding() { + return MatcherInputBase.Encoding.UTF_16; + } + /** + * + * @returns {string} + */ + asCharSequence() { + return this.charSequence; + } + /** + * + * @returns {number[]} + */ + asBytes() { + return Utils.stringToUtf8ByteArray(this.charSequence.toString()); + } + /** + * + * @returns {number} + */ + length() { + return this.charSequence.length; + } +}; +var MatcherInput = class { + /** + * Return the MatcherInput for UTF_16 encoding. + * @returns {Utf16MatcherInput} + */ + static utf16(charSequence) { + return new Utf16MatcherInput(charSequence); + } + /** + * Return the MatcherInput for UTF_8 encoding. + * @returns {Utf8MatcherInput} + */ + static utf8(input) { + if (Utils.isByteArray(input)) return new Utf8MatcherInput(input); + return new Utf8MatcherInput(Utils.stringToUtf8ByteArray(input)); + } +}; +//#endregion +//#region src/MachineInput.js +/** +* MachineInput abstracts different representations of the input text supplied to the Machine. It +* provides one-character lookahead. +*/ +var MachineInputBase = class { + static EOF() { + return -8; + } + constructor() { + this.end = 0; + } + canCheckPrefix() { + return true; + } + endPos() { + return this.end; + } + hasString() { + return false; + } + hasAnyString() { + return false; + } + prefixLength() { + return 0; + } +}; +var MachineUTF8Input = class extends MachineInputBase { + constructor(bytes, start = 0, end = bytes.length) { + super(); + this.bytes = bytes; + this.start = start; + this.end = end; + } + hasString(prefilter, pos) { + const target = prefilter.bytes; + if (target.length === 0) return true; + const idx = this.indexOf(this.bytes, target, this.start + pos); + return idx !== -1 && idx <= this.end - target.length; + } + hasAnyString(prefilter, pos) { + if (!prefilter.ac8) return false; + return prefilter.ac8.searchUTF8(this.bytes, this.start + pos, this.end); + } + step(pos) { + pos += this.start; + if (pos >= this.end) return MachineInputBase.EOF(); + const c = this.bytes[pos] & 255; + if (c < 128) return c << 3 | 1; + else if (c >= 194 && c <= 223 && pos + 1 < this.end) { + const c1 = this.bytes[pos + 1] & 255; + if ((c1 & 192) !== 128) return c << 3 | 1; + return ((c & 31) << 6 | c1 & 63) << 3 | 2; + } else if (c >= 224 && c <= 239 && pos + 2 < this.end) { + const c1 = this.bytes[pos + 1] & 255; + if ((c1 & 192) !== 128) return c << 3 | 1; + const c2 = this.bytes[pos + 2] & 255; + if ((c2 & 192) !== 128) return c << 3 | 1; + return ((c & 15) << 12 | (c1 & 63) << 6 | c2 & 63) << 3 | 3; + } else if (c >= 240 && c <= 244 && pos + 3 < this.end) { + const c1 = this.bytes[pos + 1] & 255; + if ((c1 & 192) !== 128) return c << 3 | 1; + const c2 = this.bytes[pos + 2] & 255; + if ((c2 & 192) !== 128) return c << 3 | 1; + const c3 = this.bytes[pos + 3] & 255; + if ((c3 & 192) !== 128) return c << 3 | 1; + return ((c & 7) << 18 | (c1 & 63) << 12 | (c2 & 63) << 6 | c3 & 63) << 3 | 4; + } else return c << 3 | 1; + } + index(re2, pos) { + pos += this.start; + const i = this.indexOf(this.bytes, re2.prefixUTF8, pos); + return i < 0 ? i : i - pos; + } + context(pos) { + pos += this.start; + let r1 = -1; + if (pos > this.start && pos <= this.end) { + let start = pos - 1; + r1 = this.bytes[start--]; + if (r1 >= 128) { + let lim = pos - 4; + if (lim < this.start) lim = this.start; + while (start >= lim && (this.bytes[start] & 192) === 128) start--; + if (start < this.start) start = this.start; + r1 = this.step(start - this.start) >> 3; + } + } + const r2 = pos < this.end ? this.step(pos - this.start) >> 3 : -1; + return Utils.emptyOpContext(r1, r2); + } + indexOf(source, target, fromIndex = 0) { + let targetLength = target.length; + if (targetLength === 0) return fromIndex <= this.end ? fromIndex : -1; + const firstByte = target[0]; + let limit = this.end - targetLength; + const hasNativeIndexOf = typeof source.indexOf === "function"; + let i = fromIndex; + while (i <= limit) { + if (hasNativeIndexOf) { + i = source.indexOf(firstByte, i); + if (i === -1 || i > limit) return -1; + } else { + while (i <= limit && source[i] !== firstByte) i++; + if (i > limit) return -1; + } + let match = true; + for (let j = 1; j < targetLength; j++) if (source[i + j] !== target[j]) { + match = false; + break; + } + if (match) return i; + i++; + } + return -1; + } + prefixLength(re2) { + return re2.prefixUTF8.length; + } +}; +var MachineUTF16Input = class extends MachineInputBase { + constructor(charSequence, start = 0, end = charSequence.length) { + super(); + this.charSequence = charSequence; + this.start = start; + this.end = end; + } + hasString(prefilter, pos) { + const idx = this.charSequence.indexOf(prefilter.str, this.start + pos); + return idx !== -1 && idx <= this.end - prefilter.str.length; + } + hasAnyString(prefilter, pos) { + if (!prefilter.ac16) return false; + return prefilter.ac16.searchUTF16(this.charSequence, this.start + pos, this.end); + } + step(pos) { + pos += this.start; + if (pos >= this.end) return MachineInputBase.EOF(); + const c1 = this.charSequence.charCodeAt(pos); + if (c1 < Unicode.MIN_HIGH_SURROGATE || c1 > Unicode.MAX_HIGH_SURROGATE || pos + 1 >= this.end) return c1 << 3 | 1; + const c2 = this.charSequence.charCodeAt(pos + 1); + if (c2 >= Unicode.MIN_LOW_SURROGATE && c2 <= Unicode.MAX_LOW_SURROGATE) return (c1 - Unicode.MIN_HIGH_SURROGATE) * 1024 + (c2 - Unicode.MIN_LOW_SURROGATE) + Unicode.MIN_SUPPLEMENTARY_CODE_POINT << 3 | 2; + return c1 << 3 | 1; + } + index(re2, pos) { + pos += this.start; + const i = this.charSequence.indexOf(re2.prefix, pos); + if (i < 0 || i > this.end - re2.prefix.length) return -1; + return i - pos; + } + context(pos) { + pos += this.start; + const r1 = pos > this.start && pos <= this.end ? this.charSequence.charCodeAt(pos - 1) : -1; + const r2 = pos < this.end ? this.charSequence.charCodeAt(pos) : -1; + return Utils.emptyOpContext(r1, r2); + } + prefixLength(re2) { + return re2.prefix.length; + } +}; +var MachineInput = class { + static fromUTF8(bytes, start = 0, end = bytes.length) { + return new MachineUTF8Input(bytes, start, end); + } + static fromUTF16(charSequence, start = 0, end = charSequence.length) { + return new MachineUTF16Input(charSequence, start, end); + } +}; +//#endregion +//#region src/exceptions.js +var RE2JSException = class extends Error { + /** @param {string} message */ + constructor(message) { + super(message); + this.name = "RE2JSException"; + } +}; +/** +* An exception thrown by the parser if the pattern was invalid. +*/ +var RE2JSSyntaxException = class extends RE2JSException { + /** + * @param {string} error + * @param {string|null} [input=null] + */ + constructor(error, input = null) { + let message = `error parsing regexp: ${error}`; + if (input) message += `: \`${input}\``; + super(message); + this.name = "RE2JSSyntaxException"; + this.message = message; + /** @type {string} */ + this.error = error; + /** @type {string|null} */ + this.input = input; + } + /** + * Retrieves the description of the error. + * @returns {string} + */ + getDescription() { + return this.error; + } + /** + * Retrieves the erroneous regular-expression pattern. + * @returns {string|null} + */ + getPattern() { + return this.input; + } +}; +/** +* An exception thrown by the compiler +*/ +var RE2JSCompileException = class extends RE2JSException { + /** @param {string} message */ + constructor(message) { + super(message); + this.name = "RE2JSCompileException"; + } +}; +/** +* An exception thrown by using groups +*/ +var RE2JSGroupException = class extends RE2JSException { + /** @param {string} message */ + constructor(message) { + super(message); + this.name = "RE2JSGroupException"; + } +}; +/** +* An exception thrown by flags +*/ +var RE2JSFlagsException = class extends RE2JSException { + /** @param {string} message */ + constructor(message) { + super(message); + this.name = "RE2JSFlagsException"; + } +}; +/** +* An exception thrown for internal engine errors, such as corrupted bytecodes. +*/ +var RE2JSInternalException = class extends RE2JSException { + /** @param {string} message */ + constructor(message) { + super(message); + this.name = "RE2JSInternalException"; + } +}; +//#endregion +//#region src/Matcher.js +/** +* A stateful iterator that interprets a regex {@code RE2JS} on a specific input. +* +* Conceptually, a Matcher consists of four parts: +*
    +*
  1. A compiled regular expression {@code RE2JS}, set at construction and fixed for the lifetime +* of the matcher.
  2. +* +*
  3. The remainder of the input string, set at construction or {@link #reset()} and advanced by +* each match operation such as {@link #find}, {@link #matches} or {@link #lookingAt}.
  4. +* +*
  5. The current match information, accessible via {@link #start}, {@link #end}, and +* {@link #group}, and updated by each match operation.
  6. +* +*
  7. The append position, used and advanced by {@link #appendReplacement} and {@link #appendTail} +* if performing a search and replace from the input to an external {@code StringBuffer}. +* +*
+* +* +* @author rsc@google.com (Russ Cox) +*/ +var Matcher = class Matcher { + /** + * V8 and WebKit have historical hard limits on the number of arguments + * that can be passed to a function. We cap replacer arguments to prevent + * Call Stack Overflow (DoS) vulnerabilities on massive ASTs. + */ + static MAX_REPLACER_ARGS = 65535; + /** + * Quotes '\' and '$' in {@code s}, so that the returned string could be used in + * {@link #appendReplacement} as a literal replacement of {@code s}. + * + * @param {string} str the string to be quoted + * @param {boolean} [javaMode=false] whether the replacement will be used in javaMode + * @returns {string} the quoted string + */ + static quoteReplacement(str, javaMode = false) { + if (javaMode) { + if (str.indexOf("\\") < 0 && str.indexOf("$") < 0) return str; + return str.split("").map((s) => { + const c = s.codePointAt(0); + if (c === Codepoint.CODES.get("\\") || c === Codepoint.CODES.get("$")) return `\\${s}`; + return s; + }).join(""); + } + if (str.indexOf("$") < 0) return str; + return str.split("").map((s) => { + if (s.codePointAt(0) === Codepoint.CODES.get("$")) return "$$"; + return s; + }).join(""); + } + /** + * + * @param {import('./index.js').RE2JS} pattern + * @param {string|number[]|Uint8Array|MatcherInputBase} input + */ + constructor(pattern, input) { + if (pattern === null) throw new Error("pattern is null"); + /** + * The pattern being matched. + * @type {import('./index.js').RE2JS} + */ + this.patternInput = pattern; + const re2 = this.patternInput.re2(); + /** @type {number} */ + this.patternGroupCount = re2.numberOfCapturingGroups(); + /** @type {number[]} */ + this.groups = []; + /** @type {Record} */ + this.namedGroups = re2.namedGroups; + /** @type {number} */ + this.numberOfInstructions = re2.numberOfInstructions(); + if (input instanceof MatcherInputBase) this.resetMatcherInput(input); + else if (Utils.isByteArray(input)) this.resetMatcherInput(MatcherInput.utf8(input)); + else this.resetMatcherInput(MatcherInput.utf16(input)); + } + /** + * Returns the {@code RE2JS} associated with this {@code Matcher}. + * @returns {import('./index.js').RE2JS} + */ + pattern() { + return this.patternInput; + } + /** + * Resets the {@code Matcher}, rewinding input and discarding any match information. + * + * @returns {Matcher} the {@code Matcher} itself, for chained method calls + */ + reset() { + /** @type {number} */ + this.matcherInputLength = this.matcherInput.length(); + /** @type {number} */ + this.appendPos = 0; + this.hasMatch = false; + this.hasGroups = false; + this.anchorFlag = 0; + return this; + } + /** + * Resets the {@code Matcher} and changes the input. + * @param {string|number[]|Uint8Array|MatcherInputBase} input + * @returns {Matcher} the {@code Matcher} itself, for chained method calls + */ + resetMatcherInput(input) { + if (input === null) throw new Error("input is null"); + if (!(input instanceof MatcherInputBase)) if (Utils.isByteArray(input)) input = MatcherInput.utf8(input); + else input = MatcherInput.utf16(input); + this.matcherInput = input; + this.reset(); + return this; + } + /** + * Returns the start of the named group of the most recent match, or -1 if the group was not + * matched. + * @param {string|number} [group=0] + * @returns {number} + */ + start(group = 0) { + if (typeof group === "string") { + const groupInt = this.namedGroups[group]; + if (!Number.isFinite(groupInt)) throw new RE2JSGroupException(`group '${group}' not found`); + group = groupInt; + } + this.loadGroup(group); + return this.groups[2 * group]; + } + /** + * Returns the end of the named group of the most recent match, or -1 if the group was not + * matched. + * @param {string|number} [group=0] + * @returns {number} + */ + end(group = 0) { + if (typeof group === "string") { + const groupInt = this.namedGroups[group]; + if (!Number.isFinite(groupInt)) throw new RE2JSGroupException(`group '${group}' not found`); + group = groupInt; + } + this.loadGroup(group); + return this.groups[2 * group + 1]; + } + /** + * Returns the program size of this pattern. + * + *

+ * Similar to the C++ implementation, the program size is a very approximate measure of a regexp's + * "cost". Larger numbers are more expensive than smaller numbers. + *

+ * + * @returns {number} the program size of this pattern + */ + programSize() { + return this.numberOfInstructions; + } + /** + * Returns the named group of the most recent match, or {@code null} if the group was not matched. + * @param {string|number} [group=0] + * @returns {string|null} + */ + group(group = 0) { + if (typeof group === "string") { + const groupInt = this.namedGroups[group]; + if (!Number.isFinite(groupInt)) throw new RE2JSGroupException(`group '${group}' not found`); + group = groupInt; + } + const start = this.start(group); + const end = this.end(group); + if (start < 0 && end < 0) return null; + return this.substring(start, end); + } + /** + * Returns a dictionary map of all named capturing groups and their matched values. + * If a group was not matched, its value will be `null`. + * @returns {Record} + */ + getNamedGroups() { + if (!this.hasMatch) throw new RE2JSGroupException("perhaps no match attempted"); + const result = Object.create(null); + for (const name of Object.keys(this.namedGroups)) result[name] = this.group(name); + return result; + } + /** + * Returns the number of subgroups in this pattern. + * + * @returns {number} the number of subgroups; the overall match (group 0) does not count + */ + groupCount() { + return this.patternGroupCount; + } + /** + * Helper: finds subgroup information if needed for group. + * @param {number} group + * @private + */ + loadGroup(group) { + if (group < 0 || group > this.patternGroupCount) throw new RE2JSGroupException(`Group index out of bounds: ${group}`); + if (!this.hasMatch) throw new RE2JSGroupException("perhaps no match attempted"); + if (group === 0 || this.hasGroups) return; + const end = this.matcherInputLength; + const res = this.patternInput.re2().matchMachineInput(this.matcherInput, this.groups[0], end, this.anchorFlag, 1 + this.patternGroupCount); + if (!res[0]) throw new RE2JSGroupException("inconsistency in matching group data"); + this.groups = res[1]; + this.hasGroups = true; + } + /** + * Matches the entire input against the pattern (anchored start and end). If there is a match, + * {@code matches} sets the match state to describe it. + * + * @returns {boolean} true if the entire input matches the pattern + */ + matches() { + return this.genMatch(0, RE2Flags.ANCHOR_BOTH); + } + /** + * Matches the beginning of input against the pattern (anchored start). If there is a match, + * {@code lookingAt} sets the match state to describe it. + * + * @returns {boolean} true if the beginning of the input matches the pattern + */ + lookingAt() { + return this.genMatch(0, RE2Flags.ANCHOR_START); + } + /** + * Matches the input against the pattern (unanchored), starting at a specified position. If there + * is a match, {@code find} sets the match state to describe it. + * + * @param {number|null} [start=null] the input position where the search begins + * @returns {boolean} if it finds a match + * @throws IndexOutOfBoundsException if start is not a valid input position + */ + find(start = null) { + if (start !== null) { + if (start < 0 || start > this.matcherInputLength) throw new RE2JSGroupException(`start index out of bounds: ${start}`); + this.reset(); + return this.genMatch(start, 0); + } + start = 0; + if (this.hasMatch) { + start = this.groups[1]; + if (this.groups[0] === this.groups[1]) { + const r = (this.matcherInput.isUTF16Encoding() ? MachineInput.fromUTF16(this.matcherInput.asCharSequence(), 0, this.matcherInputLength) : MachineInput.fromUTF8(this.matcherInput.asBytes(), 0, this.matcherInputLength)).step(start); + if (r < 0) start++; + else start += r & 7; + } + } + return this.genMatch(start, RE2Flags.UNANCHORED); + } + /** + * Helper: does match starting at start, with RE2 anchor flag. + * @param {number} startByte + * @param {number} anchor + * @returns {boolean} + * @private + */ + genMatch(startByte, anchor) { + const res = this.patternInput.re2().matchMachineInput(this.matcherInput, startByte, this.matcherInputLength, anchor, 1); + if (!res[0]) { + this.hasMatch = false; + return false; + } + this.groups = res[1]; + this.hasMatch = true; + this.hasGroups = this.patternGroupCount === 0; + this.anchorFlag = anchor; + return true; + } + /** + * Helper: return substring for [start, end). + * @param {number} start + * @param {number} end + * @returns {string} + */ + substring(start, end) { + if (this.matcherInput.isUTF8Encoding()) return Utils.utf8ByteArrayToString(this.matcherInput.asBytes().slice(start, end)); + return this.matcherInput.asCharSequence().substring(start, end).toString(); + } + /** + * Helper for Pattern: return input length. + * @returns {number} + */ + inputLength() { + return this.matcherInputLength; + } + /** + * Appends to result two strings: the text from the append position up to the beginning of the + * most recent match, and then the replacement with submatch groups substituted for references of + * the form {@code $n}, where {@code n} is the group number in decimal. It advances the append + * position to where the most recent match ended. + * + * To embed a literal {@code $}, use \$ (actually {@code "\\$"} with string escapes). The escape + * is only necessary when {@code $} is followed by a digit, but it is always allowed. Only + * {@code $} and {@code \} need escaping, but any character can be escaped. + * + * The group number {@code n} in {@code $n} is always at least one digit and expands to use more + * digits as long as the resulting number is a valid group number for this pattern. To cut it off + * earlier, escape the first digit that should not be used. + * + * @param {string} replacement the replacement string + * @param {boolean} [javaMode=false] activate java mode (different behaviour for capture groups and special characters) + * @returns {string} + * @throws IllegalStateException if there was no most recent match + * @throws IndexOutOfBoundsException if replacement refers to an invalid group + * @private + */ + appendReplacement(replacement, javaMode = false) { + let res = ""; + const s = this.start(); + const e = this.end(); + if (this.appendPos < s) res += this.substring(this.appendPos, s); + this.appendPos = e; + res += javaMode ? this.appendReplacementInternalJava(replacement) : this.appendReplacementInternalJs(replacement); + return res; + } + /** + * @param {string} replacement - the replacement string + * @returns {string} + * @private + */ + appendReplacementInternalJava(replacement) { + let res = ""; + let last = 0; + const m = replacement.length; + let i = 0; + while (i < m) { + const cCode = replacement.codePointAt(i); + if (cCode === Codepoint.CODES.get("\\")) { + if (last < i) res += replacement.substring(last, i); + i++; + if (i >= m) throw new RE2JSGroupException("character to be escaped is missing"); + last = i; + i++; + continue; + } + if (cCode === Codepoint.CODES.get("$")) { + if (last < i) res += replacement.substring(last, i); + if (i + 1 >= m) throw new RE2JSGroupException("Illegal group reference: group index is missing"); + const nextCode = replacement.codePointAt(i + 1); + if (Codepoint.CODES.get("0") <= nextCode && nextCode <= Codepoint.CODES.get("9")) { + let n = nextCode - Codepoint.CODES.get("0"); + let j = i + 2; + for (; j < m; j++) { + const digit = replacement.codePointAt(j); + if (digit < Codepoint.CODES.get("0") || digit > Codepoint.CODES.get("9") || n * 10 + digit - Codepoint.CODES.get("0") > this.patternGroupCount) break; + n = n * 10 + digit - Codepoint.CODES.get("0"); + } + if (n > this.patternGroupCount) throw new RE2JSGroupException(`n > number of groups: ${n}`); + const group = this.group(n); + if (group !== null) res += group; + i = j; + last = i; + } else if (nextCode === Codepoint.CODES.get("{")) { + let j = i + 2; + while (j < m && replacement.codePointAt(j) !== Codepoint.CODES.get("}")) j++; + if (j >= m) throw new RE2JSGroupException("named capture group is missing trailing '}'"); + const groupName = replacement.substring(i + 2, j); + const groupVal = this.group(groupName); + if (groupVal !== null) res += groupVal; + i = j + 1; + last = i; + } else throw new RE2JSGroupException("Illegal group reference"); + continue; + } + i++; + } + if (last < m) res += replacement.substring(last, m); + return res; + } + /** + * @param {string} replacement - the replacement string + * @returns {string} + * @private + */ + appendReplacementInternalJs(replacement) { + let res = ""; + let last = 0; + const m = replacement.length; + for (let i = 0; i < m - 1; i++) if (replacement.codePointAt(i) === Codepoint.CODES.get("$")) { + let c = replacement.codePointAt(i + 1); + if (Codepoint.CODES.get("$") === c) { + if (last < i) res += replacement.substring(last, i); + res += "$"; + i++; + last = i + 1; + continue; + } else if (Codepoint.CODES.get("&") === c) { + if (last < i) res += replacement.substring(last, i); + const group = this.group(0); + if (group !== null) res += group; + else res += "$&"; + i++; + last = i + 1; + continue; + } else if (Codepoint.CODES.get("`") === c) { + if (last < i) res += replacement.substring(last, i); + res += this.substring(0, this.start(0)); + i++; + last = i + 1; + continue; + } else if (Codepoint.CODES.get("'") === c) { + if (last < i) res += replacement.substring(last, i); + res += this.substring(this.end(0), this.matcherInputLength); + i++; + last = i + 1; + continue; + } else if (Codepoint.CODES.get("1") <= c && c <= Codepoint.CODES.get("9")) { + let n = c - Codepoint.CODES.get("0"); + if (last < i) res += replacement.substring(last, i); + for (i += 2; i < m; i++) { + c = replacement.codePointAt(i); + if (c < Codepoint.CODES.get("0") || c > Codepoint.CODES.get("9") || n * 10 + c - Codepoint.CODES.get("0") > this.patternGroupCount) break; + n = n * 10 + c - Codepoint.CODES.get("0"); + } + if (n > this.patternGroupCount) { + res += `$${n}`; + last = i; + i--; + continue; + } + const group = this.group(n); + if (group !== null) res += group; + last = i; + i--; + continue; + } else if (c === Codepoint.CODES.get("<")) { + if (last < i) res += replacement.substring(last, i); + i++; + let j = i + 1; + while (j < replacement.length && replacement.codePointAt(j) !== Codepoint.CODES.get(">") && replacement.codePointAt(j) !== Codepoint.CODES.get(" ")) j++; + if (j === replacement.length || replacement.codePointAt(j) !== Codepoint.CODES.get(">")) { + res += replacement.substring(i - 1, j + 1); + last = j + 1; + i = j; + continue; + } + const groupName = replacement.substring(i + 1, j); + if (Object.prototype.hasOwnProperty.call(this.namedGroups, groupName)) { + const groupVal = this.group(groupName); + if (groupVal !== null) res += groupVal; + } else res += `$<${groupName}>`; + last = j + 1; + i = j; + continue; + } + } + if (last < m) res += replacement.substring(last, m); + return res; + } + /** + * Return the substring of the input from the append position to the end of the + * input. + * @returns {string} + */ + appendTail() { + return this.substring(this.appendPos, this.matcherInputLength); + } + /** + * Returns the input with all matches replaced by {@code replacement}, interpreted as for + * {@code appendReplacement}. + * + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} the input string with the matches replaced + * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true + */ + replaceAll(replacement, javaMode = false) { + return this.replace(replacement, true, javaMode); + } + /** + * Returns the input with the first match replaced by {@code replacement}, interpreted as for + * {@code appendReplacement}. + * + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} the input string with the first match replaced + * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true + */ + replaceFirst(replacement, javaMode = false) { + return this.replace(replacement, false, javaMode); + } + /** + * Helper: replaceAll/replaceFirst hybrid. + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [all=true] - replace all matches + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} + * @private + */ + replace(replacement, all = true, javaMode = false) { + let res = ""; + this.reset(); + const isFunc = typeof replacement === "function"; + const hasNamedGroups = Object.keys(this.namedGroups).length > 0; + let originalInput = null; + if (isFunc) { + if (this.groupCount() >= Matcher.MAX_REPLACER_ARGS) throw new RE2JSGroupException("Too many capture groups to safely invoke replacer function"); + originalInput = this.matcherInput.isUTF8Encoding() ? this.matcherInput.asBytes() : this.matcherInput.asCharSequence(); + } + while (this.find()) { + res += isFunc ? this.appendReplacementFunc(replacement, hasNamedGroups, originalInput) : this.appendReplacement(replacement, javaMode); + if (!all) break; + } + res += this.appendTail(); + return res; + } + /** + * Evaluates a replacer function for the current match and appends the result, + * along with any un-matched preceding text, advancing the append position. + * @param {Function} replacer - the replacer function + * @param {boolean} hasNamedGroups - cached flag if pattern has named groups + * @param {string|Uint8Array|number[]} originalInput - the cached original input reference + * @returns {string} the evaluated string to append + * @private + */ + appendReplacementFunc(replacer, hasNamedGroups, originalInput) { + let res = ""; + const s = this.start(); + const e = this.end(); + if (this.appendPos < s) res += this.substring(this.appendPos, s); + this.appendPos = e; + const args = this.buildReplacerArgs(s, hasNamedGroups, originalInput); + res += String(replacer(...args)); + return res; + } + /** + * Builds the argument array for the replacer function matching the standard + * JS String.prototype.replace(regex, replacer) signature. + * @param {number} matchStart - the start index of the match + * @param {boolean} hasNamedGroups - cached flag if pattern has named groups + * @param {string|Uint8Array|number[]} originalInput - the cached original input reference + * @returns {Array} array of arguments + * @private + */ + buildReplacerArgs(matchStart, hasNamedGroups, originalInput) { + const args = [this.group(0)]; + const numGroups = this.groupCount(); + for (let i = 1; i <= numGroups; i++) { + const start = this.start(i); + if (start < 0) args.push(void 0); + else args.push(this.substring(start, this.end(i))); + } + args.push(matchStart); + args.push(originalInput); + if (hasNamedGroups) { + const parsedGroups = this.getNamedGroups(); + for (const key in parsedGroups) if (parsedGroups[key] === null) parsedGroups[key] = void 0; + args.push(parsedGroups); + } + return args; + } +}; +//#endregion +//#region src/Inst.js +/** +* A single instruction in the regular expression virtual machine. +* +* @see http://swtch.com/~rsc/regexp/regexp2.html +*/ +var Inst = class Inst { + static ALT = 1; + static ALT_MATCH = 2; + static CAPTURE = 3; + static EMPTY_WIDTH = 4; + static FAIL = 5; + static MATCH = 6; + static NOP = 7; + static RUNE = 8; + static RUNE1 = 9; + static RUNE_ANY = 10; + static RUNE_ANY_NOT_NL = 11; + static LB_WRITE = 12; + static LB_CHECK = 13; + static isRuneOp(op) { + return Inst.RUNE <= op && op <= Inst.RUNE_ANY_NOT_NL; + } + static escapeRunes(runes) { + let out = "\""; + for (let rune of runes) out += Utils.escapeRune(rune); + out += "\""; + return out; + } + constructor(op) { + this.op = op; + this.out = 0; + this.arg = 0; + this.runes = []; + /** @type {Uint32Array | number[] | null} */ + this.next = null; + } + matchRune(r) { + if (this.runes.length === 1) { + const r0 = this.runes[0]; + if ((this.arg & RE2Flags.FOLD_CASE) !== 0) return Unicode.equalsIgnoreCase(r0, r); + return r === r0; + } + const len = this.runes.length; + if (len === 0) return false; + if (len === 2 || len === 4 || len === 6 || len === 8) { + for (let j = 0; j < len; j += 2) { + if (r < this.runes[j]) return false; + if (r <= this.runes[j + 1]) return true; + } + return false; + } + let base = 0; + let n = len >> 1; + while (n > 1) { + const half = n >> 1; + base += this.runes[base + half << 1] <= r ? half : 0; + n -= half; + } + base += this.runes[base << 1] <= r ? 1 : 0; + const m = base - 1; + return m >= 0 && r <= this.runes[m << 1 | 1]; + } + matchRunePos(r) { + if (this.runes.length === 1) { + const r0 = this.runes[0]; + if ((this.arg & RE2Flags.FOLD_CASE) !== 0) return Unicode.equalsIgnoreCase(r0, r) ? 0 : -1; + return r === r0 ? 0 : -1; + } + const len = this.runes.length; + if (len === 0) return -1; + if (len === 2 || len === 4 || len === 6 || len === 8) { + for (let j = 0; j < len; j += 2) { + if (r < this.runes[j]) return -1; + if (r <= this.runes[j + 1]) return Math.floor(j / 2); + } + return -1; + } + let base = 0; + let n = len >> 1; + while (n > 1) { + const half = n >> 1; + base += this.runes[base + half << 1] <= r ? half : 0; + n -= half; + } + base += this.runes[base << 1] <= r ? 1 : 0; + const m = base - 1; + return m >= 0 && r <= this.runes[m << 1 | 1] ? m : -1; + } + /** + * + * @returns {string} + */ + toString() { + switch (this.op) { + case Inst.ALT: return `alt -> ${this.out}, ${this.arg}`; + case Inst.ALT_MATCH: return `altmatch -> ${this.out}, ${this.arg}`; + case Inst.CAPTURE: return `cap ${this.arg} -> ${this.out}`; + case Inst.EMPTY_WIDTH: return `empty ${this.arg} -> ${this.out}`; + case Inst.MATCH: return `match${this.arg !== 0 ? ` ${this.arg}` : ""}`; + case Inst.FAIL: return "fail"; + case Inst.NOP: return `nop -> ${this.out}`; + case Inst.LB_WRITE: return `lbwrite ${this.arg} -> ${this.out}`; + case Inst.LB_CHECK: return `lbcheck ${this.arg} -> ${this.out}`; + case Inst.RUNE: + if (this.runes === null) return "rune "; + return [ + "rune ", + Inst.escapeRunes(this.runes), + (this.arg & RE2Flags.FOLD_CASE) !== 0 ? "/i" : "", + " -> ", + this.out + ].join(""); + case Inst.RUNE1: return `rune1 ${Inst.escapeRunes(this.runes)} -> ${this.out}`; + case Inst.RUNE_ANY: return `any -> ${this.out}`; + case Inst.RUNE_ANY_NOT_NL: return `anynotnl -> ${this.out}`; + default: throw new Error("unhandled case in Inst.toString"); + } + } +}; +//#endregion +//#region src/Machine.js +var Queue = class { + constructor(numInst) { + this.sparse = new Int32Array(numInst); + this.densePcs = new Int32Array(numInst); + this.denseCaps = null; + this.size = 0; + this.ncap = 0; + } + init(ncap) { + this.ncap = ncap; + const needed = this.densePcs.length * ncap; + if (!this.denseCaps || this.denseCaps.length < needed) this.denseCaps = new Int32Array(needed); + } + contains(pc) { + const j = this.sparse[pc]; + return j < this.size && this.densePcs[j] === pc; + } + isEmpty() { + return this.size === 0; + } + add(pc) { + const j = this.size++; + this.sparse[pc] = j; + this.densePcs[j] = pc; + return j; + } + clear() { + this.size = 0; + } + toString() { + let out = "{"; + for (let i = 0; i < this.size; i++) { + if (i !== 0) out += ", "; + out += this.densePcs[i]; + } + out += "}"; + return out; + } +}; +var Machine = class Machine { + static fromRE2(re2) { + const m = new Machine(); + m.prog = re2.prog; + m.re2 = re2; + m.q0 = new Queue(m.prog.numInst()); + m.q1 = new Queue(m.prog.numInst()); + m.matched = false; + m.matchcap = new Int32Array(m.prog.numCap < 2 ? 2 : m.prog.numCap); + m.ncap = 0; + return m; + } + static fromMachine(machine) { + return Machine.fromRE2(machine.re2); + } + constructor() { + this.prog = null; + this.re2 = null; + this.q0 = null; + this.q1 = null; + this.matched = false; + this.matchcap = null; + this.ncap = 0; + this.lbTable = null; + } + init(ncap) { + this.ncap = ncap; + if (ncap > this.matchcap.length) this.matchcap = new Int32Array(ncap).fill(-1); + else this.matchcap.fill(-1); + this.q0.init(ncap); + this.q1.init(ncap); + if (this.prog.numLb > 0) { + if (!this.lbTable || this.lbTable.length < this.prog.numLb + 1) this.lbTable = new Int32Array(this.prog.numLb + 1); + this.lbTable.fill(-1); + } + } + submatches() { + if (this.ncap === 0) return Utils.emptyInts(); + return Utils.toArray(this.matchcap.subarray(0, this.ncap)); + } + match(input, pos, anchor) { + const startCond = this.re2.cond; + if (startCond === Utils.EMPTY_ALL) return false; + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) return false; + this.matched = false; + this.matchcap.fill(-1); + let currentPos = this.prog.numLb > 0 ? 0 : pos; + let matchStartPos = pos; + let runq = this.q0; + let nextq = this.q1; + let r = input.step(currentPos); + let rune = r >> 3; + let width = r & 7; + let rune1 = -1; + let width1 = 0; + if (r !== MachineInputBase.EOF()) { + r = input.step(currentPos + width); + rune1 = r >> 3; + width1 = r & 7; + } + let flag; + if (currentPos === 0) flag = Utils.emptyOpContext(-1, rune); + else flag = input.context(currentPos); + while (true) { + if (runq.isEmpty()) { + if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 && currentPos !== 0) break; + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && currentPos !== 0) break; + if (this.matched) break; + if (this.prog.numLb === 0 && !(this.re2.prefix.length === 0) && rune1 !== this.re2.prefixRune && input.canCheckPrefix()) { + const advance = input.index(this.re2, currentPos); + if (advance < 0) break; + currentPos += advance; + r = input.step(currentPos); + rune = r >> 3; + width = r & 7; + r = input.step(currentPos + width); + rune1 = r >> 3; + width1 = r & 7; + flag = input.context(currentPos); + } + } + if (currentPos === 0 && this.prog.numLb > 0) for (let i = 0; i < this.prog.lbStarts.length; i++) this.add(runq, this.prog.lbStarts[i], currentPos, this.matchcap, 0, flag); + if (!this.matched && (currentPos === 0 || anchor === RE2Flags.UNANCHORED)) { + if (currentPos >= matchStartPos) { + if (this.ncap > 0) this.matchcap[0] = currentPos; + this.add(runq, this.prog.start, currentPos, this.matchcap, 0, flag); + } + } + const nextPos = currentPos + width; + flag = input.context(nextPos); + this.step(runq, nextq, currentPos, nextPos, rune, flag, anchor, currentPos === input.endPos()); + if (width === 0) break; + if (this.ncap === 0 && this.matched) break; + currentPos += width; + rune = rune1; + width = width1; + if (rune !== -1) { + r = input.step(currentPos + width); + rune1 = r >> 3; + width1 = r & 7; + } + const tmpq = runq; + runq = nextq; + nextq = tmpq; + } + nextq.clear(); + return this.matched; + } + matchSet(input, pos, anchor) { + const startCond = this.re2.cond; + if (startCond === Utils.EMPTY_ALL) return []; + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) return []; + let currentPos = this.prog.numLb > 0 ? 0 : pos; + let matchStartPos = pos; + let runq = this.q0; + let nextq = this.q1; + let r = input.step(currentPos); + let rune = r >> 3; + let width = r & 7; + let rune1 = -1; + let width1 = 0; + if (r !== MachineInputBase.EOF()) { + r = input.step(currentPos + width); + rune1 = r >> 3; + width1 = r & 7; + } + let flag = currentPos === 0 ? Utils.emptyOpContext(-1, rune) : input.context(currentPos); + const matches = /* @__PURE__ */ new Set(); + while (true) { + if (runq.isEmpty()) { + if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 && currentPos !== 0) break; + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && currentPos !== 0) break; + } + if (currentPos === 0 && this.prog.numLb > 0) for (let i = 0; i < this.prog.lbStarts.length; i++) this.add(runq, this.prog.lbStarts[i], currentPos, this.matchcap, 0, flag); + if (currentPos === 0 || anchor === RE2Flags.UNANCHORED) { + if (currentPos >= matchStartPos) this.add(runq, this.prog.start, currentPos, this.matchcap, 0, flag); + } + const nextPos = currentPos + width; + flag = input.context(nextPos); + for (let j = 0; j < runq.size; j++) { + const pc = runq.densePcs[j]; + const i = this.prog.inst[pc]; + const capOffset = j * this.ncap; + let add = false; + switch (i.op) { + case Inst.MATCH: + if (anchor === RE2Flags.ANCHOR_BOTH && currentPos !== input.endPos()) break; + matches.add(i.arg); + break; + case Inst.RUNE: + add = i.matchRune(rune); + break; + case Inst.RUNE1: + add = rune === i.runes[0]; + break; + case Inst.RUNE_ANY: + add = true; + break; + case Inst.RUNE_ANY_NOT_NL: + add = rune !== 10; + break; + default: continue; + } + if (add) this.add(nextq, i.out, nextPos, runq.denseCaps, capOffset, flag); + } + runq.clear(); + if (width === 0) break; + currentPos += width; + rune = rune1; + width = width1; + if (rune !== -1) { + r = input.step(currentPos + width); + rune1 = r >> 3; + width1 = r & 7; + } + const tmpq = runq; + runq = nextq; + nextq = tmpq; + } + nextq.clear(); + return Array.from(matches).sort((a, b) => a - b); + } + step(runq, nextq, pos, nextPos, c, nextCond, anchor, atEnd) { + const longest = this.re2.longest; + for (let j = 0; j < runq.size; j++) { + const pc = runq.densePcs[j]; + const capOffset = j * this.ncap; + if (longest && this.matched && this.ncap > 0 && this.matchcap[0] < runq.denseCaps[capOffset]) continue; + const i = this.prog.inst[pc]; + let add = false; + switch (i.op) { + case Inst.MATCH: + if (anchor === RE2Flags.ANCHOR_BOTH && !atEnd) break; + if (this.ncap > 0 && (!longest || !this.matched || this.matchcap[1] < pos)) { + runq.denseCaps[capOffset + 1] = pos; + for (let k = 0; k < this.ncap; k++) this.matchcap[k] = runq.denseCaps[capOffset + k]; + } + if (!longest) runq.size = 0; + this.matched = true; + break; + case Inst.RUNE: + add = i.matchRune(c); + break; + case Inst.RUNE1: + add = c === i.runes[0]; + break; + case Inst.RUNE_ANY: + add = true; + break; + case Inst.RUNE_ANY_NOT_NL: + add = c !== 10; + break; + default: continue; + } + if (add) this.add(nextq, i.out, nextPos, runq.denseCaps, capOffset, nextCond); + } + runq.clear(); + } + add(q, pc, pos, capArray, capOffset, cond) { + while (true) { + if (pc === 0) return; + if (q.contains(pc)) return; + const d = q.add(pc); + const inst = this.prog.inst[pc]; + switch (inst.op) { + case Inst.FAIL: return; + case Inst.ALT: + case Inst.ALT_MATCH: + this.add(q, inst.out, pos, capArray, capOffset, cond); + pc = inst.arg; + continue; + case Inst.EMPTY_WIDTH: + if ((inst.arg & ~cond) === 0) { + pc = inst.out; + continue; + } + return; + case Inst.NOP: + pc = inst.out; + continue; + case Inst.CAPTURE: if (inst.arg < this.ncap) { + const opos = capArray[capOffset + inst.arg]; + capArray[capOffset + inst.arg] = pos; + this.add(q, inst.out, pos, capArray, capOffset, cond); + capArray[capOffset + inst.arg] = opos; + return; + } else { + pc = inst.out; + continue; + } + case Inst.LB_WRITE: + this.lbTable[Math.abs(inst.arg)] = pos; + pc = inst.out; + continue; + case Inst.LB_CHECK: + if (inst.arg > 0) { + if (this.lbTable[inst.arg] === pos) { + pc = inst.out; + continue; + } + } else if (this.lbTable[-inst.arg] !== pos) { + pc = inst.out; + continue; + } + return; + case Inst.MATCH: + case Inst.RUNE: + case Inst.RUNE1: + case Inst.RUNE_ANY: + case Inst.RUNE_ANY_NOT_NL: + if (this.ncap > 0) { + const destOffset = d * this.ncap; + for (let c = 0; c < this.ncap; c++) q.denseCaps[destOffset + c] = capArray[capOffset + c]; + } + return; + default: throw new RE2JSInternalException("unhandled"); + } + } + } +}; +//#endregion +//#region src/DFA.js +const hashPCs = (pcs) => { + let h = -2128831035; + for (let i = 0; i < pcs.length; i++) { + h ^= pcs[i]; + h = Math.imul(h, 16777619); + } + return h; +}; +const arraysEqual = (a, b) => { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; + return true; +}; +var DFAState = class { + constructor(nfaStates, isMatch, matchIDs = []) { + this.nfaStates = nfaStates; + this.isMatch = isMatch; + this.matchIDs = matchIDs; + this.nextLatin1 = new Array(Unicode.MAX_LATIN1 + 1).fill(null); + this.nextLatin1Anchored = new Array(Unicode.MAX_LATIN1 + 1).fill(null); + this.transKeys = []; + this.transVals = []; + this.lastSeen = 0; + } +}; +var DFA = class DFA { + static MAX_CACHE_CLEARS = 5; + static STATE_MEMORY_ESTIMATE = 838; + constructor(prog, maxMem = 8388608) { + this.prog = prog; + this.stateCache = /* @__PURE__ */ new Map(); + this.stateCount = 0; + this.startState = null; + this.stateLimit = Math.max(1, Math.floor(maxMem / DFA.STATE_MEMORY_ESTIMATE)); + this.cacheClears = 0; + this.failed = false; + this.clock = 0; + } + computeClosure(pcs) { + const closure = /* @__PURE__ */ new Set(); + const stack = [...pcs]; + let isMatch = false; + const matchIDs = []; + while (stack.length > 0) { + const pc = stack.pop(); + if (closure.has(pc)) continue; + closure.add(pc); + const inst = this.prog.getInst(pc); + switch (inst.op) { + case Inst.MATCH: + isMatch = true; + if (!matchIDs.includes(inst.arg)) matchIDs.push(inst.arg); + break; + case Inst.ALT: + case Inst.ALT_MATCH: + stack.push(inst.out); + stack.push(inst.arg); + break; + case Inst.NOP: + case Inst.CAPTURE: + stack.push(inst.out); + break; + case Inst.EMPTY_WIDTH: + case Inst.LB_WRITE: + case Inst.LB_CHECK: return null; + } + } + const sortedPCs = Int32Array.from(closure).sort(); + matchIDs.sort((a, b) => a - b); + return { + pcs: sortedPCs, + isMatch, + matchIDs + }; + } + getState(pcs) { + const closureResult = this.computeClosure(pcs); + if (!closureResult) return null; + const sortedPCs = closureResult.pcs; + const hash = hashPCs(sortedPCs); + let bucket = this.stateCache.get(hash); + if (bucket) for (let i = 0; i < bucket.length; i++) { + const state = bucket[i]; + if (arraysEqual(state.nfaStates, sortedPCs)) { + state.lastSeen = ++this.clock; + return state; + } + } + else { + bucket = []; + this.stateCache.set(hash, bucket); + } + if (this.failed) return null; + if (this.stateCount >= this.stateLimit) { + this.cacheClears++; + if (this.cacheClears >= DFA.MAX_CACHE_CLEARS) { + this.failed = true; + this.stateCache.clear(); + this.stateCount = 0; + this.startState = null; + return null; + } + this.evictCache(); + bucket = this.stateCache.get(hash); + if (!bucket) { + bucket = []; + this.stateCache.set(hash, bucket); + } + } + const state = new DFAState(sortedPCs, closureResult.isMatch, closureResult.matchIDs); + state.lastSeen = ++this.clock; + bucket.push(state); + this.stateCount++; + return state; + } + evictCache() { + const allStates = []; + for (const bucket of this.stateCache.values()) for (let i = 0; i < bucket.length; i++) allStates.push(bucket[i]); + allStates.sort((a, b) => a.lastSeen - b.lastSeen); + const keepCount = Math.max(1, Math.floor(this.stateLimit / 2)); + const startIndex = allStates.length - keepCount; + const survivorsArray = allStates.slice(startIndex); + const survivors = new Set(survivorsArray); + this.stateCache.clear(); + this.stateCount = 0; + for (let i = 0; i < survivorsArray.length; i++) { + const state = survivorsArray[i]; + state.nextLatin1.fill(null); + state.nextLatin1Anchored.fill(null); + state.transKeys.length = 0; + state.transVals.length = 0; + const hash = hashPCs(state.nfaStates); + let bucket = this.stateCache.get(hash); + if (!bucket) { + bucket = []; + this.stateCache.set(hash, bucket); + } + bucket.push(state); + this.stateCount++; + } + if (this.startState && !survivors.has(this.startState)) this.startState = null; + } + step(state, charCode, anchor) { + if (charCode <= Unicode.MAX_LATIN1) if (anchor === RE2Flags.UNANCHORED) { + const next = state.nextLatin1[charCode]; + if (next !== null) return next; + } else { + const next = state.nextLatin1Anchored[charCode]; + if (next !== null) return next; + } + else { + const key = charCode + (anchor === RE2Flags.UNANCHORED ? 0 : Unicode.MAX_RUNE + 1); + const keys = state.transKeys; + const len = keys.length; + for (let i = 0; i < len; i++) if (keys[i] === key) return state.transVals[i]; + } + const nextPCs = []; + for (let i = 0; i < state.nfaStates.length; i++) { + const pc = state.nfaStates[i]; + const inst = this.prog.getInst(pc); + if (Inst.isRuneOp(inst.op) && inst.matchRune(charCode)) nextPCs.push(inst.out); + } + if (anchor === RE2Flags.UNANCHORED) nextPCs.push(this.prog.start); + const nextState = this.getState(nextPCs); + if (charCode <= Unicode.MAX_LATIN1) if (anchor === RE2Flags.UNANCHORED) state.nextLatin1[charCode] = nextState; + else state.nextLatin1Anchored[charCode] = nextState; + else { + const key = charCode + (anchor === RE2Flags.UNANCHORED ? 0 : Unicode.MAX_RUNE + 1); + state.transKeys.push(key); + state.transVals.push(nextState); + } + return nextState; + } + match(input, pos, anchor) { + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) return false; + if (!this.startState) { + this.startState = this.getState([this.prog.start]); + if (!this.startState) return null; + } + let endPos = input.endPos(); + let currentState = this.startState; + if (currentState.isMatch) if (anchor === RE2Flags.ANCHOR_BOTH) { + if (pos === endPos) return true; + } else return true; + let i = pos; + while (i < endPos) { + const r = input.step(i); + const rune = r >> 3; + const width = r & 7; + if (width === 0) break; + currentState = anchor === RE2Flags.UNANCHORED && rune <= Unicode.MAX_LATIN1 && currentState.nextLatin1[rune] || this.step(currentState, rune, anchor); + if (currentState === null) return null; + currentState.lastSeen = ++this.clock; + if (currentState.isMatch) if (anchor === RE2Flags.ANCHOR_BOTH) { + if (i + width === endPos) return true; + } else return true; + if (currentState.nfaStates.length === 0) { + if (anchor !== RE2Flags.UNANCHORED) return false; + } + i += width; + } + return false; + } + matchSet(input, pos, anchor) { + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) return []; + if (!this.startState) { + this.startState = this.getState([this.prog.start]); + if (!this.startState) return null; + } + let endPos = input.endPos(); + let currentState = this.startState; + const matches = /* @__PURE__ */ new Set(); + const checkMatch = (state, currentPos) => { + if (state.isMatch) if (anchor === RE2Flags.ANCHOR_BOTH) { + if (currentPos === endPos) state.matchIDs.forEach((id) => matches.add(id)); + } else state.matchIDs.forEach((id) => matches.add(id)); + }; + checkMatch(currentState, pos); + let i = pos; + while (i < endPos) { + const r = input.step(i); + const rune = r >> 3; + const width = r & 7; + if (width === 0) break; + currentState = anchor === RE2Flags.UNANCHORED && rune <= Unicode.MAX_LATIN1 && currentState.nextLatin1[rune] || this.step(currentState, rune, anchor); + if (currentState === null) return null; + currentState.lastSeen = ++this.clock; + i += width; + checkMatch(currentState, i); + if (currentState.nfaStates.length === 0) { + if (anchor !== RE2Flags.UNANCHORED) break; + } + } + return Array.from(matches).sort((a, b) => a - b); + } +}; +//#endregion +//#region src/Backtracker.js +const VISITED_BITS = 32; +const MAX_BACKTRACK_PROG = 500; +const INITIAL_JOB_CAPACITY = 256; +const MAX_BACKTRACK_VECTOR = 256 * 1024; +var BitState = class { + constructor() { + this.end = 0; + this.cap = /* @__PURE__ */ new Int32Array(0); + this.matchcap = /* @__PURE__ */ new Int32Array(0); + this.ncap = 0; + this.jobPc = new Int32Array(INITIAL_JOB_CAPACITY); + this.jobArg = new Uint8Array(INITIAL_JOB_CAPACITY); + this.jobPos = new Int32Array(INITIAL_JOB_CAPACITY); + this.jobLen = 0; + this.visited = /* @__PURE__ */ new Uint32Array(0); + } + reset(prog, end, ncap) { + this.end = end; + this.jobLen = 0; + this.ncap = ncap; + const visitedSize = prog.numInst() * (end + 1) + VISITED_BITS - 1 >>> 5; + if (this.visited.length < visitedSize) this.visited = new Uint32Array(visitedSize); + else this.visited.fill(0, 0, visitedSize); + if (this.cap.length < ncap) this.cap = new Int32Array(ncap).fill(-1); + else this.cap.fill(-1, 0, ncap); + if (this.matchcap.length < ncap) this.matchcap = new Int32Array(ncap).fill(-1); + else this.matchcap.fill(-1, 0, ncap); + } + shouldVisit(pc, pos) { + const n = pc * (this.end + 1) + pos; + const idx = n >>> 5; + const mask = 1 << (n & 31); + if ((this.visited[idx] & mask) !== 0) return false; + this.visited[idx] |= mask; + return true; + } + push(re2, pc, pos, arg) { + if (re2.prog.getInst(pc).op !== Inst.FAIL && (arg || this.shouldVisit(pc, pos))) { + if (this.jobLen >= this.jobPc.length) { + const newSize = this.jobPc.length * 2; + const newPc = new Int32Array(newSize); + newPc.set(this.jobPc); + this.jobPc = newPc; + const newArg = new Uint8Array(newSize); + newArg.set(this.jobArg); + this.jobArg = newArg; + const newPos = new Int32Array(newSize); + newPos.set(this.jobPos); + this.jobPos = newPos; + } + this.jobPc[this.jobLen] = pc; + this.jobArg[this.jobLen] = arg ? 1 : 0; + this.jobPos[this.jobLen] = pos; + this.jobLen++; + } + } + tryBacktrack(re2, input, pc, pos, anchor) { + const longest = re2.longest; + this.push(re2, pc, pos, false); + while (this.jobLen > 0) { + this.jobLen--; + let currentPc = this.jobPc[this.jobLen]; + let arg = this.jobArg[this.jobLen] === 1; + let currentPos = this.jobPos[this.jobLen]; + let skipShouldVisit = true; + while (true) { + if (!skipShouldVisit) { + if (!this.shouldVisit(currentPc, currentPos)) break; + } + skipShouldVisit = false; + const inst = re2.prog.getInst(currentPc); + switch (inst.op) { + case Inst.FAIL: throw new RE2JSInternalException("unexpected InstFail"); + case Inst.ALT: if (arg) { + arg = false; + currentPc = inst.arg; + continue; + } else { + this.push(re2, currentPc, currentPos, true); + currentPc = inst.out; + continue; + } + case Inst.ALT_MATCH: { + const outInst = re2.prog.getInst(inst.out); + if (Inst.isRuneOp(outInst.op)) { + this.push(re2, inst.arg, currentPos, false); + currentPc = inst.arg; + currentPos = this.end; + continue; + } + this.push(re2, inst.out, this.end, false); + currentPc = inst.out; + continue; + } + case Inst.RUNE: { + const r = input.step(currentPos); + if (r === MachineInputBase.EOF()) break; + if (!inst.matchRune(r >> 3)) break; + currentPos += r & 7; + currentPc = inst.out; + continue; + } + case Inst.RUNE1: { + const r = input.step(currentPos); + if (r === MachineInputBase.EOF()) break; + if (r >> 3 !== inst.runes[0]) break; + currentPos += r & 7; + currentPc = inst.out; + continue; + } + case Inst.RUNE_ANY_NOT_NL: { + const r = input.step(currentPos); + if (r === MachineInputBase.EOF()) break; + if (r >> 3 === 10) break; + currentPos += r & 7; + currentPc = inst.out; + continue; + } + case Inst.RUNE_ANY: { + const r = input.step(currentPos); + if (r === MachineInputBase.EOF()) break; + currentPos += r & 7; + currentPc = inst.out; + continue; + } + case Inst.CAPTURE: if (arg) { + this.cap[inst.arg] = currentPos; + break; + } else { + if (inst.arg < this.ncap) { + this.push(re2, currentPc, this.cap[inst.arg], true); + this.cap[inst.arg] = currentPos; + } + currentPc = inst.out; + continue; + } + case Inst.EMPTY_WIDTH: { + const flag = input.context(currentPos); + if ((inst.arg & ~flag) !== 0) break; + currentPc = inst.out; + continue; + } + case Inst.NOP: + currentPc = inst.out; + continue; + case Inst.MATCH: { + if (anchor === RE2Flags.ANCHOR_BOTH && currentPos !== this.end) break; + if (this.ncap === 0) return true; + if (this.ncap > 1) this.cap[1] = currentPos; + const old = this.matchcap[1]; + if (old === -1 || longest && currentPos > 0 && currentPos > old) this.matchcap.set(this.cap); + if (!longest) return true; + if (currentPos === this.end) return true; + break; + } + case Inst.LB_WRITE: + case Inst.LB_CHECK: throw new RE2JSInternalException("Backtracker cannot evaluate Lookbehind instructions"); + default: throw new RE2JSInternalException("bad inst"); + } + break; + } + } + return longest && this.matchcap.length > 1 && this.matchcap[1] >= 0; + } +}; +const bitStatePool = []; +var Backtracker = class Backtracker { + static shouldBacktrack(prog) { + return prog.numInst() <= MAX_BACKTRACK_PROG; + } + static maxBitStateLen(prog) { + if (!Backtracker.shouldBacktrack(prog)) return 0; + return Math.floor(MAX_BACKTRACK_VECTOR / prog.numInst()); + } + static execute(re2, input, pos, anchor, ncap) { + const startCond = re2.cond; + if (startCond === Utils.EMPTY_ALL) return null; + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) return null; + if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 && pos !== 0) return null; + const b = bitStatePool.length > 0 ? bitStatePool.pop() : new BitState(); + const end = input.endPos(); + b.reset(re2.prog, end, ncap); + let matched = false; + if ((startCond & Utils.EMPTY_BEGIN_TEXT) !== 0 || anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) { + if (b.ncap > 0) b.cap[0] = pos; + if (b.tryBacktrack(re2, input, re2.prog.start, pos, anchor)) matched = true; + } else { + let width = -1; + for (; pos <= end && width !== 0; pos += width) { + if (re2.prefix.length > 0) { + const advance = input.index(re2, pos); + if (advance < 0) break; + pos += advance; + } + if (b.ncap > 0) b.cap[0] = pos; + if (b.tryBacktrack(re2, input, re2.prog.start, pos, anchor)) { + matched = true; + break; + } + const r = input.step(pos); + width = r === MachineInputBase.EOF() ? 0 : r & 7; + } + } + if (!matched) { + bitStatePool.push(b); + return null; + } + const result = ncap === 0 ? [] : Utils.toArray(b.matchcap.subarray(0, ncap)); + bitStatePool.push(b); + return result; + } +}; +//#endregion +//#region src/OnePass.js +var QueueOnePass = class { + constructor(size) { + this.sparse = new Uint32Array(size); + this.dense = new Uint32Array(size); + this.size = 0; + this.nextIndex = 0; + } + empty() { + return this.nextIndex >= this.size; + } + next() { + return this.dense[this.nextIndex++]; + } + clear() { + this.size = 0; + this.nextIndex = 0; + } + contains(u) { + return u < this.sparse.length && this.sparse[u] < this.size && this.dense[this.sparse[u]] === u; + } + insert(u) { + if (!this.contains(u)) this.insertNew(u); + } + insertNew(u) { + if (u >= this.sparse.length) return; + this.sparse[u] = this.size; + this.dense[this.size] = u; + this.size++; + } +}; +const mergeRuneSets = (leftRunes, rightRunes, leftPC, rightPC) => { + const leftLen = leftRunes.length; + const rightLen = rightRunes.length; + let lx = 0, rx = 0; + const merged = []; + const next = []; + let ok = true; + let ix = -1; + const extend = (isLeft) => { + const newArray = isLeft ? leftRunes : rightRunes; + const low = isLeft ? lx : rx; + const pc = isLeft ? leftPC : rightPC; + if (ix > 0 && newArray[low] <= merged[ix]) return false; + merged.push(newArray[low], newArray[low + 1]); + if (isLeft) lx += 2; + else rx += 2; + ix += 2; + next.push(pc); + return true; + }; + while (lx < leftLen || rx < rightLen) { + if (rx >= rightLen) ok = extend(true); + else if (lx >= leftLen) ok = extend(false); + else if (rightRunes[rx] < leftRunes[lx]) ok = extend(false); + else ok = extend(true); + if (!ok) return null; + } + return { + merged, + next + }; +}; +var OnePassProg = class { + constructor(prog) { + this.start = prog.start; + this.numCap = prog.numCap; + this.inst = new Array(prog.inst.length); + for (let i = 0; i < prog.inst.length; i++) { + const orig = prog.inst[i]; + const inst = new Inst(orig.op); + inst.out = orig.out; + inst.arg = orig.arg; + inst.runes = orig.runes ? orig.runes.slice() : []; + inst.next = null; + this.inst[i] = inst; + } + } +}; +const onePassCopy = (prog) => { + const p = new OnePassProg(prog); + for (let pc = 0; pc < p.inst.length; pc++) { + const inst = p.inst[pc]; + if (inst.op !== Inst.ALT && inst.op !== Inst.ALT_MATCH) continue; + let pAOther = "out"; + let pAAlt = "arg"; + let instAlt = p.inst[inst[pAAlt]]; + if (instAlt.op !== Inst.ALT && instAlt.op !== Inst.ALT_MATCH) { + pAOther = "arg"; + pAAlt = "out"; + instAlt = p.inst[inst[pAAlt]]; + if (instAlt.op !== Inst.ALT && instAlt.op !== Inst.ALT_MATCH) continue; + } + const instOther = p.inst[inst[pAOther]]; + if (instOther.op === Inst.ALT || instOther.op === Inst.ALT_MATCH) continue; + let pBAlt = "out"; + let pBOther = "arg"; + let patch = false; + if (instAlt.out === pc) patch = true; + else if (instAlt.arg === pc) { + patch = true; + pBAlt = "arg"; + pBOther = "out"; + } + if (patch) instAlt[pBAlt] = inst[pAOther]; + if (inst[pAOther] === instAlt[pBAlt]) inst[pAAlt] = instAlt[pBOther]; + } + return p; +}; +const makeOnePass = (p) => { + if (p.inst.length >= 1e3) return null; + const instQueue = new QueueOnePass(p.inst.length); + const visitQueue = new QueueOnePass(p.inst.length); + const onePassRunes = new Array(p.inst.length); + const m = new Array(p.inst.length).fill(false); + const check = (pc) => { + let ok = true; + const inst = p.inst[pc]; + if (visitQueue.contains(pc)) return true; + visitQueue.insert(pc); + switch (inst.op) { + case Inst.ALT: + case Inst.ALT_MATCH: { + ok = check(inst.out) && check(inst.arg); + let matchOut = m[inst.out]; + let matchArg = m[inst.arg]; + if (matchOut && matchArg) return false; + if (matchArg) { + const tempOut = inst.out; + inst.out = inst.arg; + inst.arg = tempOut; + const tempMatch = matchOut; + matchOut = matchArg; + matchArg = tempMatch; + } + if (matchOut) { + m[pc] = true; + inst.op = Inst.ALT_MATCH; + } + const leftRunes = onePassRunes[inst.out] || []; + const rightRunes = onePassRunes[inst.arg] || []; + const mergeRes = mergeRuneSets(leftRunes, rightRunes, inst.out, inst.arg); + if (!mergeRes) return false; + onePassRunes[pc] = mergeRes.merged; + inst.next = new Uint32Array(mergeRes.next); + break; + } + case Inst.CAPTURE: + case Inst.EMPTY_WIDTH: + case Inst.NOP: + ok = check(inst.out); + m[pc] = m[inst.out]; + onePassRunes[pc] = onePassRunes[inst.out] ? onePassRunes[inst.out].slice() : []; + inst.next = new Uint32Array(Math.floor(onePassRunes[pc].length / 2) + 1).fill(inst.out); + break; + case Inst.MATCH: + case Inst.FAIL: + m[pc] = inst.op === Inst.MATCH; + break; + case Inst.RUNE: { + m[pc] = false; + if (inst.next && inst.next.length > 0) break; + instQueue.insert(inst.out); + if (!inst.runes || inst.runes.length === 0) { + onePassRunes[pc] = []; + inst.next = new Uint32Array([inst.out]); + break; + } + let runes = []; + if (inst.runes.length === 1 && (inst.arg & RE2Flags.FOLD_CASE) !== 0) { + const r0 = inst.runes[0]; + runes.push(r0, r0); + for (let r1 = Unicode.simpleFold(r0); r1 !== r0; r1 = Unicode.simpleFold(r1)) runes.push(r1, r1); + runes.sort((a, b) => a - b); + } else for (let j = 0; j < inst.runes.length; j++) runes.push(inst.runes[j]); + onePassRunes[pc] = runes; + inst.next = new Uint32Array(Math.floor(runes.length / 2) + 1).fill(inst.out); + inst.op = Inst.RUNE; + break; + } + case Inst.RUNE1: { + m[pc] = false; + if (inst.next && inst.next.length > 0) break; + instQueue.insert(inst.out); + let runes = []; + if ((inst.arg & RE2Flags.FOLD_CASE) !== 0) { + const r0 = inst.runes[0]; + runes.push(r0, r0); + for (let r1 = Unicode.simpleFold(r0); r1 !== r0; r1 = Unicode.simpleFold(r1)) runes.push(r1, r1); + runes.sort((a, b) => a - b); + } else runes.push(inst.runes[0], inst.runes[0]); + onePassRunes[pc] = runes; + inst.next = new Uint32Array(Math.floor(runes.length / 2) + 1).fill(inst.out); + inst.op = Inst.RUNE; + break; + } + case Inst.RUNE_ANY: + m[pc] = false; + if (inst.next && inst.next.length > 0) break; + instQueue.insert(inst.out); + onePassRunes[pc] = [0, Unicode.MAX_RUNE]; + inst.next = new Uint32Array([inst.out]); + break; + case Inst.RUNE_ANY_NOT_NL: + m[pc] = false; + if (inst.next && inst.next.length > 0) break; + instQueue.insert(inst.out); + onePassRunes[pc] = [ + 0, + 9, + 11, + Unicode.MAX_RUNE + ]; + inst.next = new Uint32Array(Math.floor(onePassRunes[pc].length / 2) + 1).fill(inst.out); + break; + } + return ok; + }; + instQueue.clear(); + instQueue.insert(p.start); + while (!instQueue.empty()) { + visitQueue.clear(); + if (!check(instQueue.next())) return null; + } + for (let i = 0; i < p.inst.length; i++) if (onePassRunes[i]) p.inst[i].runes = onePassRunes[i]; + return p; +}; +const cleanupOnePass = (p, original) => { + for (let ix = 0; ix < original.inst.length; ix++) { + const instOriginal = original.inst[ix]; + switch (instOriginal.op) { + case Inst.ALT: + case Inst.ALT_MATCH: + case Inst.RUNE: break; + case Inst.CAPTURE: + case Inst.EMPTY_WIDTH: + case Inst.NOP: + case Inst.MATCH: + case Inst.FAIL: + p.inst[ix].next = null; + break; + case Inst.RUNE1: + case Inst.RUNE_ANY: + case Inst.RUNE_ANY_NOT_NL: + p.inst[ix].next = null; + p.inst[ix].op = instOriginal.op; + p.inst[ix].runes = instOriginal.runes ? instOriginal.runes.slice() : []; + break; + } + } +}; +var OnePass = class OnePass { + static compile(prog) { + if (prog.start === 0) return null; + if (prog.numLb > 0) return null; + const startInst = prog.inst[prog.start]; + if (startInst.op !== Inst.EMPTY_WIDTH || (startInst.arg & Utils.EMPTY_BEGIN_TEXT) === 0) return null; + let hasAlt = false; + for (let i = 0; i < prog.inst.length; i++) if (prog.inst[i].op === Inst.ALT || prog.inst[i].op === Inst.ALT_MATCH) { + hasAlt = true; + break; + } + for (let i = 0; i < prog.inst.length; i++) { + const inst = prog.inst[i]; + const opOut = prog.inst[inst.out].op; + switch (inst.op) { + case Inst.ALT: + case Inst.ALT_MATCH: + if (opOut === Inst.MATCH || prog.inst[inst.arg].op === Inst.MATCH) return null; + break; + case Inst.EMPTY_WIDTH: + if (opOut === Inst.MATCH) { + if ((inst.arg & Utils.EMPTY_END_TEXT) === Utils.EMPTY_END_TEXT) continue; + return null; + } + break; + default: + if (opOut === Inst.MATCH && hasAlt) return null; + break; + } + } + let p = onePassCopy(prog); + p = makeOnePass(p); + if (p !== null) cleanupOnePass(p, prog); + return p; + } + static next(inst, r) { + const nextIdx = inst.matchRunePos(r); + if (nextIdx >= 0) return inst.next[nextIdx]; + if (inst.op === Inst.ALT_MATCH) return inst.out; + return 0; + } + static execute(re2, input, pos, anchor, ncap) { + const onepass = re2.onepass; + if (!onepass) return null; + const matchcap = new Int32Array(ncap).fill(-1); + let matched = false; + let r = input.step(pos); + let rune = r >> 3; + let width = r & 7; + let r1 = MachineInputBase.EOF(); + let rune1 = -1; + let width1 = 0; + if (r !== MachineInputBase.EOF()) { + r1 = input.step(pos + width); + if (r1 !== MachineInputBase.EOF()) { + rune1 = r1 >> 3; + width1 = r1 & 7; + } + } + let flag = pos === 0 ? Utils.emptyOpContext(-1, rune) : input.context(pos); + let pc = onepass.start; + let inst; + while (true) { + inst = onepass.inst[pc]; + pc = inst.out; + switch (inst.op) { + case Inst.MATCH: + if (anchor === RE2Flags.ANCHOR_BOTH && pos !== input.endPos()) return null; + matched = true; + if (matchcap.length > 0) { + matchcap[0] = 0; + matchcap[1] = pos; + } + return ncap === 0 ? [] : Utils.toArray(matchcap); + case Inst.RUNE: + if (!inst.matchRune(rune)) return null; + break; + case Inst.RUNE1: + if (rune !== inst.runes[0]) return null; + break; + case Inst.RUNE_ANY: break; + case Inst.RUNE_ANY_NOT_NL: + if (rune === 10) return null; + break; + case Inst.ALT: + case Inst.ALT_MATCH: + pc = OnePass.next(inst, rune); + continue; + case Inst.FAIL: return null; + case Inst.NOP: continue; + case Inst.EMPTY_WIDTH: + if ((inst.arg & ~flag) !== 0) return null; + continue; + case Inst.CAPTURE: + if (inst.arg < matchcap.length) matchcap[inst.arg] = pos; + continue; + default: throw new RE2JSInternalException("bad inst"); + } + if (width === 0) break; + flag = Utils.emptyOpContext(rune, rune1); + pos += width; + rune = rune1; + width = width1; + if (rune !== -1) { + r1 = input.step(pos + width); + if (r1 !== MachineInputBase.EOF()) { + rune1 = r1 >> 3; + width1 = r1 & 7; + } else { + rune1 = -1; + width1 = 0; + } + } + } + if (!matched) return null; + return ncap === 0 ? [] : Utils.toArray(matchcap); + } +}; +//#endregion +//#region src/Regexp.js +/** +* Regular expression abstract syntax tree. Produced by parser, used by compiler. NB, this +* corresponds to {@code syntax.regexp} in the Go implementation; Go's {@code regexp} is called +* {@code RE2} in Java. +*/ +var Regexp = class Regexp { + static Op = createEnum([ + "NO_MATCH", + "EMPTY_MATCH", + "LITERAL", + "CHAR_CLASS", + "ANY_CHAR_NOT_NL", + "ANY_CHAR", + "BEGIN_LINE", + "END_LINE", + "BEGIN_TEXT", + "END_TEXT", + "WORD_BOUNDARY", + "NO_WORD_BOUNDARY", + "CAPTURE", + "STAR", + "PLUS", + "QUEST", + "REPEAT", + "CONCAT", + "ALTERNATE", + "PLB", + "NLB", + "LEFT_PAREN", + "VERTICAL_BAR" + ]); + static isPseudoOp(op) { + return op >= Regexp.Op.LEFT_PAREN; + } + static emptySubs() { + return []; + } + static quoteIfHyphen(rune) { + if (rune === Codepoint.CODES.get("-")) return "\\"; + return ""; + } + static fromRegexp(re) { + const regex = new Regexp(re.op); + regex.flags = re.flags; + regex.subs = re.subs; + regex.runes = re.runes; + regex.cap = re.cap; + regex.min = re.min; + regex.max = re.max; + regex.name = re.name; + regex.namedGroups = re.namedGroups; + regex.lb = re.lb; + return regex; + } + constructor(op) { + this.op = op; + this.flags = 0; + this.subs = Regexp.emptySubs(); + this.runes = []; + this.min = 0; + this.max = 0; + this.cap = 0; + this.name = null; + this.namedGroups = Object.create(null); + this.lb = 0; + } + reinit() { + this.flags = 0; + this.subs = Regexp.emptySubs(); + this.runes = []; + this.cap = 0; + this.min = 0; + this.max = 0; + this.name = null; + this.namedGroups = Object.create(null); + this.lb = 0; + } + toString() { + return this.appendTo(); + } + appendTo() { + let out = ""; + switch (this.op) { + case Regexp.Op.NO_MATCH: + out += "[^\\x00-\\x{10FFFF}]"; + break; + case Regexp.Op.EMPTY_MATCH: + out += "(?:)"; + break; + case Regexp.Op.STAR: + case Regexp.Op.PLUS: + case Regexp.Op.QUEST: + case Regexp.Op.REPEAT: { + const sub = this.subs[0]; + if (sub.op > Regexp.Op.CAPTURE || sub.op === Regexp.Op.LITERAL && sub.runes.length > 1) out += `(?:${sub.appendTo()})`; + else out += sub.appendTo(); + switch (this.op) { + case Regexp.Op.STAR: + out += "*"; + break; + case Regexp.Op.PLUS: + out += "+"; + break; + case Regexp.Op.QUEST: + out += "?"; + break; + case Regexp.Op.REPEAT: + out += `{${this.min}`; + if (this.min !== this.max) { + out += ","; + if (this.max >= 0) out += this.max; + } + out += "}"; + break; + } + if ((this.flags & RE2Flags.NON_GREEDY) !== 0) out += "?"; + break; + } + case Regexp.Op.CONCAT: + for (let sub of this.subs) if (sub.op === Regexp.Op.ALTERNATE) out += `(?:${sub.appendTo()})`; + else out += sub.appendTo(); + break; + case Regexp.Op.ALTERNATE: { + let sep = ""; + for (let sub of this.subs) { + out += sep; + sep = "|"; + out += sub.appendTo(); + } + break; + } + case Regexp.Op.LITERAL: + if ((this.flags & RE2Flags.FOLD_CASE) !== 0) out += "(?i:"; + for (let rune of this.runes) out += Utils.escapeRune(rune); + if ((this.flags & RE2Flags.FOLD_CASE) !== 0) out += ")"; + break; + case Regexp.Op.ANY_CHAR_NOT_NL: + out += "(?-s:.)"; + break; + case Regexp.Op.ANY_CHAR: + out += "(?s:.)"; + break; + case Regexp.Op.PLB: + out += `(?<=${this.subs[0].appendTo()})`; + break; + case Regexp.Op.NLB: + out += `(?`; + if (this.subs[0].op !== Regexp.Op.EMPTY_MATCH) out += this.subs[0].appendTo(); + out += ")"; + break; + case Regexp.Op.BEGIN_TEXT: + out += "\\A"; + break; + case Regexp.Op.END_TEXT: + if ((this.flags & RE2Flags.WAS_DOLLAR) !== 0) out += "(?-m:$)"; + else out += "\\z"; + break; + case Regexp.Op.BEGIN_LINE: + out += "^"; + break; + case Regexp.Op.END_LINE: + out += "$"; + break; + case Regexp.Op.WORD_BOUNDARY: + out += "\\b"; + break; + case Regexp.Op.NO_WORD_BOUNDARY: + out += "\\B"; + break; + case Regexp.Op.CHAR_CLASS: + if (this.runes.length % 2 !== 0) { + out += "[invalid char class]"; + break; + } + out += "["; + if (this.runes.length === 0) out += "^\\x00-\\x{10FFFF}"; + else if (this.runes[0] === 0 && this.runes[this.runes.length - 1] === Unicode.MAX_RUNE) { + out += "^"; + for (let i = 1; i < this.runes.length - 1; i += 2) { + const lo = this.runes[i] + 1; + const hi = this.runes[i + 1] - 1; + out += Regexp.quoteIfHyphen(lo); + out += Utils.escapeRune(lo); + if (lo !== hi) { + out += "-"; + out += Regexp.quoteIfHyphen(hi); + out += Utils.escapeRune(hi); + } + } + } else for (let i = 0; i < this.runes.length; i += 2) { + const lo = this.runes[i]; + const hi = this.runes[i + 1]; + out += Regexp.quoteIfHyphen(lo); + out += Utils.escapeRune(lo); + if (lo !== hi) { + out += "-"; + out += Regexp.quoteIfHyphen(hi); + out += Utils.escapeRune(hi); + } + } + out += "]"; + break; + default: + out += this.op; + break; + } + return out; + } + maxCap() { + let m = 0; + if (this.op === Regexp.Op.CAPTURE) m = this.cap; + if (this.subs !== null) for (let sub of this.subs) { + const n = sub.maxCap(); + if (m < n) m = n; + } + return m; + } + equals(that) { + if (!(that !== null && that instanceof Regexp)) return false; + if (this.op !== that.op) return false; + switch (this.op) { + case Regexp.Op.END_TEXT: + if ((this.flags & RE2Flags.WAS_DOLLAR) !== (that.flags & RE2Flags.WAS_DOLLAR)) return false; + break; + case Regexp.Op.LITERAL: + case Regexp.Op.CHAR_CLASS: + if (this.runes === null && that.runes === null) break; + if (this.runes === null || that.runes === null) return false; + if (this.runes.length !== that.runes.length) return false; + for (let i = 0; i < this.runes.length; i++) if (this.runes[i] !== that.runes[i]) return false; + break; + case Regexp.Op.ALTERNATE: + case Regexp.Op.CONCAT: + if (this.subs.length !== that.subs.length) return false; + for (let i = 0; i < this.subs.length; ++i) if (!this.subs[i].equals(that.subs[i])) return false; + break; + case Regexp.Op.STAR: + case Regexp.Op.PLUS: + case Regexp.Op.QUEST: + if ((this.flags & RE2Flags.NON_GREEDY) !== (that.flags & RE2Flags.NON_GREEDY) || !this.subs[0].equals(that.subs[0])) return false; + break; + case Regexp.Op.REPEAT: + if ((this.flags & RE2Flags.NON_GREEDY) !== (that.flags & RE2Flags.NON_GREEDY) || this.min !== that.min || this.max !== that.max || !this.subs[0].equals(that.subs[0])) return false; + break; + case Regexp.Op.CAPTURE: + if (this.cap !== that.cap || (this.name === null ? that.name !== null : this.name !== that.name) || !this.subs[0].equals(that.subs[0])) return false; + break; + case Regexp.Op.PLB: + case Regexp.Op.NLB: + if (this.lb !== that.lb || !this.subs[0].equals(that.subs[0])) return false; + break; + } + return true; + } +}; +//#endregion +//#region src/Prefilter.js +var AhoCorasick = class { + constructor(wordArrays) { + this.next = [Object.create(null)]; + this.fail = [0]; + this.match = [false]; + for (const word of wordArrays) { + let node = 0; + for (let i = 0; i < word.length; i++) { + const val = word[i]; + if (!(val in this.next[node])) { + this.next.push(Object.create(null)); + this.fail.push(0); + this.match.push(false); + this.next[node][val] = this.next.length - 1; + } + node = this.next[node][val]; + } + this.match[node] = true; + } + const queue = []; + for (const val in this.next[0]) if (Object.prototype.hasOwnProperty.call(this.next[0], val)) { + const child = this.next[0][val]; + this.fail[child] = 0; + queue.push(child); + } + while (queue.length > 0) { + const curr = queue.shift(); + for (const val in this.next[curr]) if (Object.prototype.hasOwnProperty.call(this.next[curr], val)) { + const child = this.next[curr][val]; + let failNode = this.fail[curr]; + while (failNode !== 0 && !(val in this.next[failNode])) failNode = this.fail[failNode]; + if (val in this.next[failNode]) this.fail[child] = this.next[failNode][val]; + else this.fail[child] = 0; + this.match[child] = this.match[child] || this.match[this.fail[child]]; + queue.push(child); + } + } + } + searchUTF16(charSeq, start, end) { + let node = 0; + for (let i = start; i < end; i++) { + const val = charSeq.charCodeAt(i); + while (node !== 0 && !(val in this.next[node])) node = this.fail[node]; + if (val in this.next[node]) node = this.next[node][val]; + if (this.match[node]) return true; + } + return false; + } + searchUTF8(bytes, start, end) { + let node = 0; + for (let i = start; i < end; i++) { + const val = bytes[i]; + while (node !== 0 && !(val in this.next[node])) node = this.fail[node]; + if (val in this.next[node]) node = this.next[node][val]; + if (this.match[node]) return true; + } + return false; + } +}; +var Prefilter = class Prefilter { + static Type = { + NONE: 0, + EXACT: 1, + AND: 2, + OR: 3 + }; + constructor(type) { + this.type = type; + this.subs = []; + this.str = ""; + this.bytes = null; + this.ac16 = null; + this.ac8 = null; + } + eval(input, pos) { + switch (this.type) { + case Prefilter.Type.NONE: return true; + case Prefilter.Type.EXACT: return input.hasString(this, pos); + case Prefilter.Type.AND: + for (let i = 0; i < this.subs.length; i++) if (!this.subs[i].eval(input, pos)) return false; + return true; + case Prefilter.Type.OR: + if (this.ac16 && this.ac8) return input.hasAnyString(this, pos); + for (let i = 0; i < this.subs.length; i++) if (this.subs[i].eval(input, pos)) return true; + return false; + default: return true; + } + } +}; +var PrefilterTree = class PrefilterTree { + static build(re) { + const pf = PrefilterTree.fromRegexp(re); + return PrefilterTree.simplify(pf); + } + static fromRegexp(re) { + if (!re) return new Prefilter(Prefilter.Type.NONE); + switch (re.op) { + case Regexp.Op.PLB: + case Regexp.Op.NLB: + case Regexp.Op.NO_MATCH: + case Regexp.Op.EMPTY_MATCH: + case Regexp.Op.BEGIN_LINE: + case Regexp.Op.END_LINE: + case Regexp.Op.BEGIN_TEXT: + case Regexp.Op.END_TEXT: + case Regexp.Op.WORD_BOUNDARY: + case Regexp.Op.NO_WORD_BOUNDARY: + case Regexp.Op.CHAR_CLASS: + case Regexp.Op.ANY_CHAR_NOT_NL: + case Regexp.Op.ANY_CHAR: return new Prefilter(Prefilter.Type.NONE); + case Regexp.Op.LITERAL: { + if (re.runes.length === 0 || (re.flags & RE2Flags.FOLD_CASE) !== 0) return new Prefilter(Prefilter.Type.NONE); + const pf = new Prefilter(Prefilter.Type.EXACT); + let str = ""; + for (let i = 0; i < re.runes.length; i++) str += String.fromCodePoint(re.runes[i]); + pf.str = str; + pf.bytes = Utils.stringToUtf8ByteArray(pf.str); + return pf; + } + case Regexp.Op.CAPTURE: + case Regexp.Op.PLUS: return PrefilterTree.fromRegexp(re.subs[0]); + case Regexp.Op.REPEAT: + if (re.min >= 1) return PrefilterTree.fromRegexp(re.subs[0]); + return new Prefilter(Prefilter.Type.NONE); + case Regexp.Op.CONCAT: { + const pf = new Prefilter(Prefilter.Type.AND); + for (const sub of re.subs) pf.subs.push(PrefilterTree.fromRegexp(sub)); + return pf; + } + case Regexp.Op.ALTERNATE: { + const pf = new Prefilter(Prefilter.Type.OR); + for (const sub of re.subs) pf.subs.push(PrefilterTree.fromRegexp(sub)); + return pf; + } + default: return new Prefilter(Prefilter.Type.NONE); + } + } + static simplify(pf) { + if (pf.type === Prefilter.Type.EXACT || pf.type === Prefilter.Type.NONE) return pf; + if (pf.type === Prefilter.Type.AND) { + const newSubs = []; + for (const sub of pf.subs) { + const s = PrefilterTree.simplify(sub); + if (s.type !== Prefilter.Type.NONE) if (s.type === Prefilter.Type.AND) for (let j = 0; j < s.subs.length; j++) newSubs.push(s.subs[j]); + else newSubs.push(s); + } + if (newSubs.length === 0) return new Prefilter(Prefilter.Type.NONE); + if (newSubs.length === 1) return newSubs[0]; + pf.subs = newSubs; + return pf; + } + if (pf.type === Prefilter.Type.OR) { + const newSubs = []; + for (const sub of pf.subs) { + const s = PrefilterTree.simplify(sub); + if (s.type === Prefilter.Type.NONE) return new Prefilter(Prefilter.Type.NONE); + if (s.type === Prefilter.Type.OR) for (let j = 0; j < s.subs.length; j++) newSubs.push(s.subs[j]); + else newSubs.push(s); + } + if (newSubs.length === 0) return new Prefilter(Prefilter.Type.NONE); + if (newSubs.length === 1) return newSubs[0]; + const seen = /* @__PURE__ */ new Set(); + const uniqueSubs = []; + for (const sub of newSubs) if (sub.type === Prefilter.Type.EXACT) { + if (!seen.has(sub.str)) { + seen.add(sub.str); + uniqueSubs.push(sub); + } + } else uniqueSubs.push(sub); + pf.subs = uniqueSubs; + let allExact = true; + for (const sub of uniqueSubs) if (sub.type !== Prefilter.Type.EXACT) { + allExact = false; + break; + } + if (allExact && uniqueSubs.length > 1) { + pf.ac16 = new AhoCorasick(uniqueSubs.map((s) => { + const arr = []; + for (let i = 0; i < s.str.length; i++) arr.push(s.str.charCodeAt(i)); + return arr; + })); + pf.ac8 = new AhoCorasick(uniqueSubs.map((s) => s.bytes)); + } + return pf; + } + return pf; + } +}; +//#endregion +//#region src/Prog.js +/** +* A list of instruction pointers waiting to be patched. +* Tracks both `head` and `tail` to allow O(1) appending during compilation. +* * Values are encoded integers, not standard memory pointers: +* - Program instruction index: `l >> 1` +* - Patch `.out` field if: `(l & 1) === 0` +* - Patch `.arg` field if: `(l & 1) === 1` +* - `0` denotes an empty list. +* * @see https://swtch.com/~rsc/regexp/regexp1.html +*/ +var PatchList = class { + /** + * @param {number} head - Encoded pointer to the start of the patch list. + * @param {number} tail - Encoded pointer to the end of the patch list. + */ + constructor(head = 0, tail = 0) { + this.head = head; + this.tail = tail; + } +}; +/** +* A Prog is a compiled regular expression program. +*/ +var Prog = class { + constructor() { + this.inst = []; + this.start = 0; + this.numCap = 2; + this.lbStarts = []; + this.numLb = 0; + } + getInst(pc) { + return this.inst[pc]; + } + numInst() { + return this.inst.length; + } + addInst(op) { + this.inst.push(new Inst(op)); + } + skipNop(pc) { + let i = this.inst[pc]; + while (i.op === Inst.NOP || i.op === Inst.CAPTURE) { + i = this.inst[pc]; + pc = i.out; + } + return i; + } + prefix() { + let prefix = ""; + let i = this.skipNop(this.start); + if (!Inst.isRuneOp(i.op) || i.runes.length !== 1) return [i.op === Inst.MATCH, prefix]; + while (Inst.isRuneOp(i.op) && i.runes.length === 1 && (i.arg & RE2Flags.FOLD_CASE) === 0) { + prefix += String.fromCodePoint(i.runes[0]); + i = this.skipNop(i.out); + } + return [i.op === Inst.MATCH, prefix]; + } + startCond() { + let flag = 0; + let pc = this.start; + loop: for (;;) { + const i = this.inst[pc]; + switch (i.op) { + case Inst.EMPTY_WIDTH: + flag |= i.arg; + break; + case Inst.FAIL: return -1; + case Inst.CAPTURE: + case Inst.NOP: break; + default: break loop; + } + pc = i.out; + } + return flag; + } + patch(l, val) { + let head = l.head; + while (head !== 0) { + const i = this.inst[head >> 1]; + if ((head & 1) === 0) { + head = i.out; + i.out = val; + } else { + head = i.arg; + i.arg = val; + } + } + } + append(l1, l2) { + if (l1.head === 0) return l2; + if (l2.head === 0) return l1; + const i = this.inst[l1.tail >> 1]; + if ((l1.tail & 1) === 0) i.out = l2.head; + else i.arg = l2.head; + return new PatchList(l1.head, l2.tail); + } + /** + * + * @returns {string} + */ + toString() { + let out = ""; + for (let pc = 0; pc < this.inst.length; pc++) { + const len = out.length; + out += pc; + if (pc === this.start) out += "*"; + out += " ".substring(out.length - len); + out += this.inst[pc]; + out += "\n"; + } + return out; + } +}; +//#endregion +//#region src/Compiler.js +/** +* A fragment of a compiled regular expression program. +* +* @see http://swtch.com/~rsc/regexp/regexp1.html +* @class +*/ +var Frag = class { + constructor(i = 0, out = new PatchList(), nullable = false) { + this.i = i; + this.out = out; + this.nullable = nullable; + } +}; +/** +* Compiler from {@code Regexp} (RE2 abstract syntax) to {@code RE2} (compiled regular expression). +* +* The only entry point is {@link #compileRegexp}. +*/ +var Compiler = class Compiler { + static ANY_RUNE_NOT_NL() { + return [ + 0, + Codepoint.CODES.get("\n") - 1, + Codepoint.CODES.get("\n") + 1, + Unicode.MAX_RUNE + ]; + } + static ANY_RUNE() { + return [0, Unicode.MAX_RUNE]; + } + static compileRegexp(re) { + const c = new Compiler(); + const f = c.compile(re); + c.prog.patch(f.out, c.newInst(Inst.MATCH).i); + c.prog.start = f.i; + return c.prog; + } + static compileSet(regexps) { + const c = new Compiler(); + if (regexps.length === 0) { + c.prog.start = c.newInst(Inst.FAIL).i; + return c.prog; + } + let starts = []; + for (let i = 0; i < regexps.length; i++) { + const f = c.compile(regexps[i]); + const m = c.newInst(Inst.MATCH); + c.prog.getInst(m.i).arg = i; + c.prog.patch(f.out, m.i); + starts.push(f.i); + } + let start = starts[0]; + for (let i = 1; i < starts.length; i++) { + const f = c.newInst(Inst.ALT); + const inst = c.prog.getInst(f.i); + inst.out = start; + inst.arg = starts[i]; + start = f.i; + } + c.prog.start = start; + return c.prog; + } + constructor() { + this.prog = new Prog(); + this.newInst(Inst.FAIL); + } + newInst(op) { + this.prog.addInst(op); + return new Frag(this.prog.numInst() - 1, new PatchList(), true); + } + nop() { + const f = this.newInst(Inst.NOP); + f.out = new PatchList(f.i << 1, f.i << 1); + return f; + } + fail() { + return new Frag(); + } + cap(arg) { + const f = this.newInst(Inst.CAPTURE); + f.out = new PatchList(f.i << 1, f.i << 1); + this.prog.getInst(f.i).arg = arg; + if (this.prog.numCap < arg + 1) this.prog.numCap = arg + 1; + return f; + } + cat(f1, f2) { + if (f1.i === 0 || f2.i === 0) return this.fail(); + this.prog.patch(f1.out, f2.i); + return new Frag(f1.i, f2.out, f1.nullable && f2.nullable); + } + alt(f1, f2) { + if (f1.i === 0) return f2; + if (f2.i === 0) return f1; + const f = this.newInst(Inst.ALT); + const i = this.prog.getInst(f.i); + i.out = f1.i; + i.arg = f2.i; + f.out = this.prog.append(f1.out, f2.out); + f.nullable = f1.nullable || f2.nullable; + return f; + } + loop(f1, nongreedy) { + const f = this.newInst(Inst.ALT); + const i = this.prog.getInst(f.i); + if (nongreedy) { + i.arg = f1.i; + f.out = new PatchList(f.i << 1, f.i << 1); + } else { + i.out = f1.i; + f.out = new PatchList(f.i << 1 | 1, f.i << 1 | 1); + } + this.prog.patch(f1.out, f.i); + return f; + } + quest(f1, nongreedy) { + const f = this.newInst(Inst.ALT); + const i = this.prog.getInst(f.i); + if (nongreedy) { + i.arg = f1.i; + f.out = new PatchList(f.i << 1, f.i << 1); + } else { + i.out = f1.i; + f.out = new PatchList(f.i << 1 | 1, f.i << 1 | 1); + } + f.out = this.prog.append(f.out, f1.out); + return f; + } + star(f1, nongreedy) { + if (f1.nullable) return this.quest(this.plus(f1, nongreedy), nongreedy); + return this.loop(f1, nongreedy); + } + plus(f1, nongreedy) { + return new Frag(f1.i, this.loop(f1, nongreedy).out, f1.nullable); + } + empty(op) { + const f = this.newInst(Inst.EMPTY_WIDTH); + this.prog.getInst(f.i).arg = op; + f.out = new PatchList(f.i << 1, f.i << 1); + return f; + } + rune(runes, flags) { + const f = this.newInst(Inst.RUNE); + f.nullable = false; + const i = this.prog.getInst(f.i); + i.runes = runes; + flags &= RE2Flags.FOLD_CASE; + if (runes.length !== 1 || Unicode.simpleFold(runes[0]) === runes[0]) flags &= ~RE2Flags.FOLD_CASE; + i.arg = flags; + f.out = new PatchList(f.i << 1, f.i << 1); + if ((flags & RE2Flags.FOLD_CASE) === 0 && runes.length === 1 || runes.length === 2 && runes[0] === runes[1]) i.op = Inst.RUNE1; + else if (runes.length === 2 && runes[0] === 0 && runes[1] === Unicode.MAX_RUNE) i.op = Inst.RUNE_ANY; + else if (runes.length === 4 && runes[0] === 0 && runes[1] === Codepoint.CODES.get("\n") - 1 && runes[2] === Codepoint.CODES.get("\n") + 1 && runes[3] === Unicode.MAX_RUNE) i.op = Inst.RUNE_ANY_NOT_NL; + return f; + } + lookBehind(a, lb) { + const id = this.newInst(Inst.LB_WRITE); + this.prog.getInst(id.i).arg = lb; + const any = this.rune(Compiler.ANY_RUNE(), 0); + const dotStar = this.star(any, true); + const lbAutomaton = this.cat(dotStar, a); + this.prog.patch(lbAutomaton.out, id.i); + const checkId = this.newInst(Inst.LB_CHECK); + this.prog.getInst(checkId.i).arg = lb; + this.prog.lbStarts.push(lbAutomaton.i); + if (Math.abs(lb) > this.prog.numLb) this.prog.numLb = Math.abs(lb); + checkId.out = new PatchList(checkId.i << 1, checkId.i << 1); + return checkId; + } + compile(re) { + switch (re.op) { + case Regexp.Op.NO_MATCH: return this.fail(); + case Regexp.Op.EMPTY_MATCH: return this.nop(); + case Regexp.Op.LITERAL: if (re.runes.length === 0) return this.nop(); + else { + let f = null; + for (let r of re.runes) { + const f1 = this.rune([r], re.flags); + f = f === null ? f1 : this.cat(f, f1); + } + return f; + } + case Regexp.Op.CHAR_CLASS: return this.rune(re.runes, re.flags); + case Regexp.Op.ANY_CHAR_NOT_NL: return this.rune(Compiler.ANY_RUNE_NOT_NL(), 0); + case Regexp.Op.ANY_CHAR: return this.rune(Compiler.ANY_RUNE(), 0); + case Regexp.Op.BEGIN_LINE: return this.empty(Utils.EMPTY_BEGIN_LINE); + case Regexp.Op.END_LINE: return this.empty(Utils.EMPTY_END_LINE); + case Regexp.Op.BEGIN_TEXT: return this.empty(Utils.EMPTY_BEGIN_TEXT); + case Regexp.Op.END_TEXT: return this.empty(Utils.EMPTY_END_TEXT); + case Regexp.Op.WORD_BOUNDARY: return this.empty(Utils.EMPTY_WORD_BOUNDARY); + case Regexp.Op.NO_WORD_BOUNDARY: return this.empty(Utils.EMPTY_NO_WORD_BOUNDARY); + case Regexp.Op.PLB: + case Regexp.Op.NLB: return this.lookBehind(this.compile(re.subs[0]), re.lb); + case Regexp.Op.CAPTURE: { + const bra = this.cap(re.cap << 1); + const sub = this.compile(re.subs[0]); + const ket = this.cap(re.cap << 1 | 1); + return this.cat(this.cat(bra, sub), ket); + } + case Regexp.Op.STAR: return this.star(this.compile(re.subs[0]), (re.flags & RE2Flags.NON_GREEDY) !== 0); + case Regexp.Op.PLUS: return this.plus(this.compile(re.subs[0]), (re.flags & RE2Flags.NON_GREEDY) !== 0); + case Regexp.Op.QUEST: return this.quest(this.compile(re.subs[0]), (re.flags & RE2Flags.NON_GREEDY) !== 0); + case Regexp.Op.CONCAT: if (re.subs.length === 0) return this.nop(); + else { + let f = null; + for (let sub of re.subs) { + const f1 = this.compile(sub); + f = f === null ? f1 : this.cat(f, f1); + } + return f; + } + case Regexp.Op.ALTERNATE: if (re.subs.length === 0) return this.nop(); + else { + let f = null; + for (let sub of re.subs) { + const f1 = this.compile(sub); + f = f === null ? f1 : this.alt(f, f1); + } + return f; + } + default: throw new RE2JSCompileException("regexp: unhandled case in compile"); + } + } +}; +//#endregion +//#region src/Simplify.js +var Simplify = class Simplify { + static simplify(re) { + if (re === null) return null; + switch (re.op) { + case Regexp.Op.PLB: + case Regexp.Op.NLB: + case Regexp.Op.CAPTURE: { + const sub = Simplify.simplify(re.subs[0]); + if (sub !== re.subs[0]) { + const nre = Regexp.fromRegexp(re); + nre.runes = []; + nre.subs = [sub]; + return nre; + } + return re; + } + case Regexp.Op.CONCAT: + case Regexp.Op.ALTERNATE: { + const newSubs = []; + let changed = false; + for (let i = 0; i < re.subs.length; i++) { + const sub = re.subs[i]; + const nsub = Simplify.simplify(sub); + if (nsub !== sub) changed = true; + if (re.op === Regexp.Op.CONCAT) { + if (nsub.op === Regexp.Op.NO_MATCH) return new Regexp(Regexp.Op.NO_MATCH); + if (nsub.op === Regexp.Op.EMPTY_MATCH) { + changed = true; + continue; + } + if (nsub.op === Regexp.Op.CONCAT) { + changed = true; + for (let j = 0; j < nsub.subs.length; j++) newSubs.push(nsub.subs[j]); + continue; + } + } else if (re.op === Regexp.Op.ALTERNATE) { + if (nsub.op === Regexp.Op.NO_MATCH) { + changed = true; + continue; + } + if (nsub.op === Regexp.Op.ALTERNATE) { + changed = true; + for (let j = 0; j < nsub.subs.length; j++) newSubs.push(nsub.subs[j]); + continue; + } + } + newSubs.push(nsub); + } + if (changed) { + if (newSubs.length === 0) return new Regexp(re.op === Regexp.Op.CONCAT ? Regexp.Op.EMPTY_MATCH : Regexp.Op.NO_MATCH); + if (newSubs.length === 1) return newSubs[0]; + const nre = Regexp.fromRegexp(re); + nre.runes = []; + nre.subs = newSubs; + return nre; + } + return re; + } + case Regexp.Op.CHAR_CLASS: + if (re.runes === null) return re; + if (re.runes.length === 0) return new Regexp(Regexp.Op.NO_MATCH); + if (re.runes.length === 2 && re.runes[0] === 0 && re.runes[1] === Unicode.MAX_RUNE) return new Regexp(Regexp.Op.ANY_CHAR); + if (re.runes.length === 4 && re.runes[0] === 0 && re.runes[1] === Codepoint.CODES.get("\n") - 1 && re.runes[2] === Codepoint.CODES.get("\n") + 1 && re.runes[3] === Unicode.MAX_RUNE) return new Regexp(Regexp.Op.ANY_CHAR_NOT_NL); + return re; + case Regexp.Op.STAR: + case Regexp.Op.PLUS: + case Regexp.Op.QUEST: { + const sub = Simplify.simplify(re.subs[0]); + return Simplify.simplify1(re.op, re.flags, sub, re); + } + case Regexp.Op.REPEAT: { + if (re.min === 0 && re.max === 0) return new Regexp(Regexp.Op.EMPTY_MATCH); + const sub = Simplify.simplify(re.subs[0]); + if (re.max === -1) { + if (re.min === 0) return Simplify.simplify1(Regexp.Op.STAR, re.flags, sub, null); + if (re.min === 1) return Simplify.simplify1(Regexp.Op.PLUS, re.flags, sub, null); + const nre = new Regexp(Regexp.Op.CONCAT); + const subs = []; + for (let i = 0; i < re.min - 1; i++) subs.push(sub); + subs.push(Simplify.simplify1(Regexp.Op.PLUS, re.flags, sub, null)); + nre.subs = subs.slice(0); + return Simplify.simplify(nre); + } + if (re.min === 1 && re.max === 1) return sub; + let prefixSubs = null; + if (re.min > 0) { + prefixSubs = []; + for (let i = 0; i < re.min; i++) prefixSubs.push(sub); + } + if (re.max > re.min) { + let suffix = Simplify.simplify1(Regexp.Op.QUEST, re.flags, sub, null); + for (let i = re.min + 1; i < re.max; i++) { + const nre2 = new Regexp(Regexp.Op.CONCAT); + nre2.subs = [sub, suffix]; + suffix = Simplify.simplify1(Regexp.Op.QUEST, re.flags, nre2, null); + } + if (prefixSubs === null) return suffix; + prefixSubs.push(suffix); + } + if (prefixSubs !== null) { + const prefix = new Regexp(Regexp.Op.CONCAT); + prefix.subs = prefixSubs.slice(0); + return Simplify.simplify(prefix); + } + return new Regexp(Regexp.Op.NO_MATCH); + } + } + return re; + } + static simplify1(op, flags, sub, re) { + if (sub.op === Regexp.Op.EMPTY_MATCH) return sub; + if (sub.op === Regexp.Op.NO_MATCH) { + if (op === Regexp.Op.PLUS) return sub; + return new Regexp(Regexp.Op.EMPTY_MATCH); + } + if (op === sub.op && (flags & RE2Flags.NON_GREEDY) === (sub.flags & RE2Flags.NON_GREEDY)) return sub; + if (re !== null && re.op === op && (re.flags & RE2Flags.NON_GREEDY) === (flags & RE2Flags.NON_GREEDY) && sub === re.subs[0]) return re; + const nre = new Regexp(op); + nre.flags = flags; + nre.subs = [sub]; + return nre; + } +}; +//#endregion +//#region src/CharGroup.js +var CharGroup = class { + constructor(sign, cls) { + this.sign = sign; + this.cls = cls; + } +}; +const code1 = [48, 57]; +const code2 = [ + 9, + 10, + 12, + 13, + 32, + 32 +]; +const code3 = [ + 48, + 57, + 65, + 90, + 95, + 95, + 97, + 122 +]; +const PERL_GROUPS = /* @__PURE__ */ new Map([ + ["\\d", new CharGroup(1, code1)], + ["\\D", new CharGroup(-1, code1)], + ["\\s", new CharGroup(1, code2)], + ["\\S", new CharGroup(-1, code2)], + ["\\w", new CharGroup(1, code3)], + ["\\W", new CharGroup(-1, code3)] +]); +const code4 = [ + 48, + 57, + 65, + 90, + 97, + 122 +]; +const code5 = [ + 65, + 90, + 97, + 122 +]; +const code6 = [0, 127]; +const code7 = [ + 9, + 9, + 32, + 32 +]; +const code8 = [ + 0, + 31, + 127, + 127 +]; +const code9 = [48, 57]; +const code10 = [33, 126]; +const code11 = [97, 122]; +const code12 = [32, 126]; +const code13 = [ + 33, + 47, + 58, + 64, + 91, + 96, + 123, + 126 +]; +const code14 = [ + 9, + 13, + 32, + 32 +]; +const code15 = [65, 90]; +const code16 = [ + 48, + 57, + 65, + 90, + 95, + 95, + 97, + 122 +]; +const code17 = [ + 48, + 57, + 65, + 70, + 97, + 102 +]; +const POSIX_GROUPS = /* @__PURE__ */ new Map([ + ["[:alnum:]", new CharGroup(1, code4)], + ["[:^alnum:]", new CharGroup(-1, code4)], + ["[:alpha:]", new CharGroup(1, code5)], + ["[:^alpha:]", new CharGroup(-1, code5)], + ["[:ascii:]", new CharGroup(1, code6)], + ["[:^ascii:]", new CharGroup(-1, code6)], + ["[:blank:]", new CharGroup(1, code7)], + ["[:^blank:]", new CharGroup(-1, code7)], + ["[:cntrl:]", new CharGroup(1, code8)], + ["[:^cntrl:]", new CharGroup(-1, code8)], + ["[:digit:]", new CharGroup(1, code9)], + ["[:^digit:]", new CharGroup(-1, code9)], + ["[:graph:]", new CharGroup(1, code10)], + ["[:^graph:]", new CharGroup(-1, code10)], + ["[:lower:]", new CharGroup(1, code11)], + ["[:^lower:]", new CharGroup(-1, code11)], + ["[:print:]", new CharGroup(1, code12)], + ["[:^print:]", new CharGroup(-1, code12)], + ["[:punct:]", new CharGroup(1, code13)], + ["[:^punct:]", new CharGroup(-1, code13)], + ["[:space:]", new CharGroup(1, code14)], + ["[:^space:]", new CharGroup(-1, code14)], + ["[:upper:]", new CharGroup(1, code15)], + ["[:^upper:]", new CharGroup(-1, code15)], + ["[:word:]", new CharGroup(1, code16)], + ["[:^word:]", new CharGroup(-1, code16)], + ["[:xdigit:]", new CharGroup(1, code17)], + ["[:^xdigit:]", new CharGroup(-1, code17)] +]); +//#endregion +//#region src/CharClass.js +/** +* A "builder"-style helper class for manipulating character classes represented as an array of +* pairs of runes [lo, hi], each denoting an inclusive interval. +* +* All methods mutate the internal state and return {@code this}, allowing operations to be chained. +*/ +var CharClass = class CharClass { + static charClassToString(r, len) { + let result = "["; + for (let i = 0; i < len; i += 2) { + if (i > 0) result += " "; + const lo = r[i]; + const hi = r[i + 1]; + if (lo === hi) result += `0x${lo.toString(16)}`; + else result += `0x${lo.toString(16)}-0x${hi.toString(16)}`; + } + result += "]"; + return result; + } + static cmp(array, i, pivotFrom, pivotTo) { + const cmp = array[i] - pivotFrom; + return cmp !== 0 ? cmp : pivotTo - array[i + 1]; + } + static qsortIntPair(array, left, right) { + const pivotIndex = ((left + right) / 2 | 0) & -2; + const pivotFrom = array[pivotIndex]; + const pivotTo = array[pivotIndex + 1]; + let i = left; + let j = right; + while (i <= j) { + while (i < right && CharClass.cmp(array, i, pivotFrom, pivotTo) < 0) i += 2; + while (j > left && CharClass.cmp(array, j, pivotFrom, pivotTo) > 0) j -= 2; + if (i <= j) { + if (i !== j) { + let temp = array[i]; + array[i] = array[j]; + array[j] = temp; + temp = array[i + 1]; + array[i + 1] = array[j + 1]; + array[j + 1] = temp; + } + i += 2; + j -= 2; + } + } + if (left < j) CharClass.qsortIntPair(array, left, j); + if (i < right) CharClass.qsortIntPair(array, i, right); + } + constructor(r = Utils.emptyInts()) { + this.r = r; + this.len = r.length; + } + toArray() { + if (this.len === this.r.length) return this.r; + else return this.r.slice(0, this.len); + } + cleanClass() { + if (this.len < 4) return this; + CharClass.qsortIntPair(this.r, 0, this.len - 2); + let w = 2; + for (let i = 2; i < this.len; i += 2) { + const lo = this.r[i]; + const hi = this.r[i + 1]; + if (lo <= this.r[w - 1] + 1) { + if (hi > this.r[w - 1]) this.r[w - 1] = hi; + continue; + } + this.r[w] = lo; + this.r[w + 1] = hi; + w += 2; + } + this.len = w; + return this; + } + appendLiteral(x, flags) { + return (flags & RE2Flags.FOLD_CASE) !== 0 ? this.appendFoldedRange(x, x) : this.appendRange(x, x); + } + appendRange(lo, hi) { + if (this.len > 0) { + for (let i = 2; i <= 4; i += 2) if (this.len >= i) { + const rlo = this.r[this.len - i]; + const rhi = this.r[this.len - i + 1]; + if (lo <= rhi + 1 && rlo <= hi + 1) { + if (lo < rlo) this.r[this.len - i] = lo; + if (hi > rhi) this.r[this.len - i + 1] = hi; + return this; + } + } + } + this.r[this.len++] = lo; + this.r[this.len++] = hi; + return this; + } + appendFoldedRange(lo, hi) { + if (lo <= Unicode.MIN_FOLD && hi >= Unicode.MAX_FOLD) return this.appendRange(lo, hi); + if (hi < Unicode.MIN_FOLD || lo > Unicode.MAX_FOLD) return this.appendRange(lo, hi); + if (lo < Unicode.MIN_FOLD) { + this.appendRange(lo, Unicode.MIN_FOLD - 1); + lo = Unicode.MIN_FOLD; + } + if (hi > Unicode.MAX_FOLD) { + this.appendRange(Unicode.MAX_FOLD + 1, hi); + hi = Unicode.MAX_FOLD; + } + for (let c = lo; c <= hi; c++) { + this.appendRange(c, c); + for (let f = Unicode.simpleFold(c); f !== c; f = Unicode.simpleFold(f)) this.appendRange(f, f); + } + return this; + } + appendClass(x) { + for (let i = 0; i < x.length; i += 2) this.appendRange(x[i], x[i + 1]); + return this; + } + appendFoldedClass(x) { + for (let i = 0; i < x.length; i += 2) this.appendFoldedRange(x[i], x[i + 1]); + return this; + } + appendNegatedClass(x) { + let nextLo = 0; + for (let i = 0; i < x.length; i += 2) { + const lo = x[i]; + const hi = x[i + 1]; + if (nextLo <= lo - 1) this.appendRange(nextLo, lo - 1); + nextLo = hi + 1; + } + if (nextLo <= Unicode.MAX_RUNE) this.appendRange(nextLo, Unicode.MAX_RUNE); + return this; + } + appendTable(table) { + for (let i = 0; i < table.length; ++i) { + const lo = table.getLo(i); + const hi = table.getHi(i); + const stride = table.getStride(i); + if (stride === 1) { + this.appendRange(lo, hi); + continue; + } + for (let c = lo; c <= hi; c += stride) this.appendRange(c, c); + } + return this; + } + appendNegatedTable(table) { + let nextLo = 0; + for (let i = 0; i < table.length; ++i) { + const lo = table.getLo(i); + const hi = table.getHi(i); + const stride = table.getStride(i); + if (stride === 1) { + if (nextLo <= lo - 1) this.appendRange(nextLo, lo - 1); + nextLo = hi + 1; + continue; + } + for (let c = lo; c <= hi; c += stride) { + if (nextLo <= c - 1) this.appendRange(nextLo, c - 1); + nextLo = c + 1; + } + } + if (nextLo <= Unicode.MAX_RUNE) this.appendRange(nextLo, Unicode.MAX_RUNE); + return this; + } + appendTableWithSign(table, sign) { + return sign < 0 ? this.appendNegatedTable(table) : this.appendTable(table); + } + negateClass() { + let nextLo = 0; + let w = 0; + for (let i = 0; i < this.len; i += 2) { + const lo = this.r[i]; + const hi = this.r[i + 1]; + if (nextLo <= lo - 1) { + this.r[w] = nextLo; + this.r[w + 1] = lo - 1; + w += 2; + } + nextLo = hi + 1; + } + this.len = w; + if (nextLo <= Unicode.MAX_RUNE) { + this.r[this.len++] = nextLo; + this.r[this.len++] = Unicode.MAX_RUNE; + } + return this; + } + appendClassWithSign(x, sign) { + return sign < 0 ? this.appendNegatedClass(x) : this.appendClass(x); + } + appendGroup(g, foldCase) { + let cls = g.cls; + if (foldCase) cls = new CharClass().appendFoldedClass(cls).cleanClass().toArray(); + return this.appendClassWithSign(cls, g.sign); + } + toString() { + return CharClass.charClassToString(this.r, this.len); + } +}; +//#endregion +//#region src/Parser.js +var StringIterator = class { + constructor(str) { + this.str = str; + this.position = 0; + } + pos() { + return this.position; + } + rewindTo(pos) { + this.position = pos; + } + more() { + return this.position < this.str.length; + } + peek() { + return this.str.codePointAt(this.position); + } + skip(n) { + this.position += n; + } + skipString(s) { + this.position += s.length; + } + pop() { + const r = this.str.codePointAt(this.position); + this.position += Utils.charCount(r); + return r; + } + lookingAt(s) { + return this.str.startsWith(s, this.position); + } + rest() { + return this.str.substring(this.position); + } + from(beforePos) { + return this.str.substring(beforePos, this.position); + } + toString() { + return this.rest(); + } +}; +/** +* A parser of regular expression patterns. +* +* The only public entry point is {@link #parse(String pattern, int flags)}. +*/ +var Parser = class Parser { + static ERR_INTERNAL_ERROR = "regexp/syntax: internal error"; + static ERR_INVALID_CHAR_RANGE = "invalid character class range"; + static ERR_INVALID_ESCAPE = "invalid escape sequence"; + static ERR_INVALID_NAMED_CAPTURE = "invalid named capture"; + static ERR_INVALID_PERL_OP = "invalid or unsupported Perl syntax"; + static ERR_INVALID_REPEAT_OP = "invalid nested repetition operator"; + static ERR_INVALID_REPEAT_SIZE = "invalid repeat count"; + static ERR_MISSING_BRACKET = "missing closing ]"; + static ERR_MISSING_PAREN = "missing closing )"; + static ERR_MISSING_REPEAT_ARGUMENT = "missing argument to repetition operator"; + static ERR_TRAILING_BACKSLASH = "trailing backslash at end of expression"; + static ERR_DUPLICATE_NAMED_CAPTURE = "duplicate capture group name"; + static ERR_UNEXPECTED_PAREN = "unexpected )"; + static ERR_NESTING_DEPTH = "expression nests too deeply"; + static ERR_LARGE = "expression too large"; + static ERR_INVALID_CAPTURE_IN_LOOKBEHIND = "invalid capture in lookbehind"; + static MAX_HEIGHT = 1e3; + static MAX_SIZE = 3355443; + static MAX_RUNES = 33554432; + static ANY_TABLE = new UnicodeRangeTable(new Uint32Array([ + 0, + Unicode.MAX_RUNE, + 1 + ])); + static ASCII_TABLE = new UnicodeRangeTable(new Uint32Array([ + 0, + 127, + 1 + ])); + static ASCII_FOLD_TABLE = new UnicodeRangeTable(new Uint32Array([ + 0, + 127, + 1, + 383, + 383, + 1, + 8490, + 8490, + 1 + ])); + static unicodeTable(name) { + if (name === "Any") return { + tab: Parser.ANY_TABLE, + fold: Parser.ANY_TABLE, + sign: 1 + }; + if (name === "Ascii") return { + tab: Parser.ASCII_TABLE, + fold: Parser.ASCII_FOLD_TABLE, + sign: 1 + }; + if (name === "Assigned") return { + tab: UnicodeTables.CATEGORIES.get("Cn"), + fold: UnicodeTables.CATEGORIES.get("Cn"), + sign: -1 + }; + if (name === "Lc") return { + tab: UnicodeTables.CATEGORIES.get("LC"), + fold: UnicodeTables.FOLD_CATEGORIES.get("LC"), + sign: 1 + }; + if (UnicodeTables.CATEGORIES.has(name)) return { + tab: UnicodeTables.CATEGORIES.get(name), + fold: UnicodeTables.FOLD_CATEGORIES.get(name), + sign: 1 + }; + if (UnicodeTables.SCRIPTS.has(name)) return { + tab: UnicodeTables.SCRIPTS.get(name), + fold: UnicodeTables.FOLD_SCRIPT.get(name), + sign: 1 + }; + return null; + } + static minFoldRune(r) { + if (r < Unicode.MIN_FOLD || r > Unicode.MAX_FOLD) return r; + let min = r; + const r0 = r; + for (r = Unicode.simpleFold(r); r !== r0; r = Unicode.simpleFold(r)) if (min > r) min = r; + return min; + } + static leadingRegexp(re) { + if (re.op === Regexp.Op.EMPTY_MATCH) return null; + if (re.op === Regexp.Op.CONCAT && re.subs.length > 0) { + const sub = re.subs[0]; + if (sub.op === Regexp.Op.EMPTY_MATCH) return null; + return sub; + } + return re; + } + static literalRegexp(s, flags) { + const re = new Regexp(Regexp.Op.LITERAL); + re.flags = flags; + re.runes = Utils.stringToRunes(s); + return re; + } + /** + * Parse regular expression pattern {@code pattern} with mode flags {@code flags}. + * @param {string} pattern + * @param {number} flags + */ + static parse(pattern, flags) { + return new Parser(pattern, flags).parseInternal(); + } + static parseRepeat(t) { + const start = t.pos(); + if (!t.more() || !t.lookingAt("{")) return -1; + t.skip(1); + const min = Parser.parseInt(t); + if (min === -1) return -1; + if (!t.more()) return -1; + let max; + if (!t.lookingAt(",")) max = min; + else { + t.skip(1); + if (!t.more()) return -1; + if (t.lookingAt("}")) max = -1; + else if ((max = Parser.parseInt(t)) === -1) return -1; + } + if (!t.more() || !t.lookingAt("}")) return -1; + t.skip(1); + if (min < 0 || min > 1e3 || max === -2 || max > 1e3 || max >= 0 && min > max) throw new RE2JSSyntaxException(Parser.ERR_INVALID_REPEAT_SIZE, t.from(start)); + return min << 16 | max & Unicode.MAX_BMP; + } + static isValidCaptureName(name) { + if (name.length === 0) return false; + for (let i = 0; i < name.length; i++) { + const c = name.codePointAt(i); + if (c !== Codepoint.CODES.get("_") && !Utils.isalnum(c)) return false; + } + return true; + } + static parseInt(t) { + const start = t.pos(); + while (t.more() && t.peek() >= Codepoint.CODES.get("0") && t.peek() <= Codepoint.CODES.get("9")) t.skip(1); + const n = t.from(start); + if (n.length === 0 || n.length > 1 && n.codePointAt(0) === Codepoint.CODES.get("0")) return -1; + if (n.length > 8) return -2; + return parseInt(n, 10); + } + static isCharClass(re) { + return re.op === Regexp.Op.LITERAL && re.runes.length === 1 || re.op === Regexp.Op.CHAR_CLASS || re.op === Regexp.Op.ANY_CHAR_NOT_NL || re.op === Regexp.Op.ANY_CHAR; + } + static matchRune(re, r) { + switch (re.op) { + case Regexp.Op.LITERAL: return re.runes.length === 1 && re.runes[0] === r; + case Regexp.Op.CHAR_CLASS: + for (let i = 0; i < re.runes.length; i += 2) if (re.runes[i] <= r && r <= re.runes[i + 1]) return true; + return false; + case Regexp.Op.ANY_CHAR_NOT_NL: return r !== Codepoint.CODES.get("\n"); + case Regexp.Op.ANY_CHAR: return true; + } + return false; + } + static mergeCharClass(dst, src) { + switch (dst.op) { + case Regexp.Op.ANY_CHAR: break; + case Regexp.Op.ANY_CHAR_NOT_NL: + if (Parser.matchRune(src, Codepoint.CODES.get("\n"))) dst.op = Regexp.Op.ANY_CHAR; + break; + case Regexp.Op.CHAR_CLASS: + if (src.op === Regexp.Op.LITERAL) dst.runes = new CharClass(dst.runes).appendLiteral(src.runes[0], src.flags).toArray(); + else dst.runes = new CharClass(dst.runes).appendClass(src.runes).toArray(); + break; + case Regexp.Op.LITERAL: + if (src.runes[0] === dst.runes[0] && src.flags === dst.flags) break; + dst.op = Regexp.Op.CHAR_CLASS; + dst.runes = new CharClass().appendLiteral(dst.runes[0], dst.flags).appendLiteral(src.runes[0], src.flags).toArray(); + break; + } + } + static parseEscape(t) { + const startPos = t.pos(); + t.skip(1); + if (!t.more()) throw new RE2JSSyntaxException(Parser.ERR_TRAILING_BACKSLASH); + let c = t.pop(); + bigswitch: switch (c) { + case Codepoint.CODES.get("1"): + case Codepoint.CODES.get("2"): + case Codepoint.CODES.get("3"): + case Codepoint.CODES.get("4"): + case Codepoint.CODES.get("5"): + case Codepoint.CODES.get("6"): + case Codepoint.CODES.get("7"): if (!t.more() || t.peek() < Codepoint.CODES.get("0") || t.peek() > Codepoint.CODES.get("7")) break; + case Codepoint.CODES.get("0"): { + let r = c - Codepoint.CODES.get("0"); + for (let i = 1; i < 3; i++) { + if (!t.more() || t.peek() < Codepoint.CODES.get("0") || t.peek() > Codepoint.CODES.get("7")) break; + r = r * 8 + t.peek() - Codepoint.CODES.get("0"); + t.skip(1); + } + return r; + } + case Codepoint.CODES.get("x"): { + if (!t.more()) break; + c = t.pop(); + if (c === Codepoint.CODES.get("{")) { + let nhex = 0; + let r = 0; + while (true) { + if (!t.more()) break bigswitch; + c = t.pop(); + if (c === Codepoint.CODES.get("}")) break; + const v = Utils.unhex(c); + if (v < 0) break bigswitch; + r = r * 16 + v; + if (r > Unicode.MAX_RUNE) break bigswitch; + nhex++; + } + if (nhex === 0) break bigswitch; + return r; + } + const x = Utils.unhex(c); + if (!t.more()) break; + c = t.pop(); + const y = Utils.unhex(c); + if (x < 0 || y < 0) break; + return x * 16 + y; + } + case Codepoint.CODES.get("a"): return Codepoint.CODES.get("\x07"); + case Codepoint.CODES.get("f"): return Codepoint.CODES.get("\f"); + case Codepoint.CODES.get("n"): return Codepoint.CODES.get("\n"); + case Codepoint.CODES.get("r"): return Codepoint.CODES.get("\r"); + case Codepoint.CODES.get("t"): return Codepoint.CODES.get(" "); + case Codepoint.CODES.get("v"): return Codepoint.CODES.get("\v"); + default: + if (c <= Unicode.MAX_ASCII && !Utils.isalnum(c)) return c; + break; + } + throw new RE2JSSyntaxException(Parser.ERR_INVALID_ESCAPE, t.from(startPos)); + } + static parseClassChar(t, wholeClassPos) { + if (!t.more()) throw new RE2JSSyntaxException(Parser.ERR_MISSING_BRACKET, t.from(wholeClassPos)); + if (t.lookingAt("\\")) return Parser.parseEscape(t); + return t.pop(); + } + static concatRunes(x, y) { + for (let i = 0; i < y.length; i++) x.push(y[i]); + return x; + } + static hasCapture(re) { + if (re === null) return false; + if (re.op === Regexp.Op.CAPTURE) return true; + if (re.subs) { + for (let sub of re.subs) if (Parser.hasCapture(sub)) return true; + } + return false; + } + constructor(wholeRegexp, flags = 0) { + this.wholeRegexp = wholeRegexp; + this.flags = flags; + this.numCap = 0; + this.namedGroups = Object.create(null); + this.stack = []; + this.free = null; + this.numRegexp = 0; + this.numRunes = 0; + this.repeats = 0; + this.height = null; + this.size = null; + this.nlb = 0; + } + newRegexp(op) { + let re = this.free; + if (re !== null && re.subs !== null && re.subs.length > 0) { + this.free = re.subs[0]; + re.reinit(); + re.op = op; + } else { + re = new Regexp(op); + this.numRegexp += 1; + } + return re; + } + reuse(re) { + if (this.height !== null && this.height.has(re)) this.height.delete(re); + if (re.subs !== null && re.subs.length > 0) re.subs[0] = this.free; + this.free = re; + } + checkLimits(re) { + if (this.numRunes > Parser.MAX_RUNES) throw new RE2JSSyntaxException(Parser.ERR_LARGE); + this.checkSize(re); + this.checkHeight(re); + } + checkSize(re) { + if (this.size === null) { + if (this.repeats === 0) this.repeats = 1; + if (re.op === Regexp.Op.REPEAT) { + let n = re.max; + if (n === -1) n = re.min; + if (n <= 0) n = 1; + if (n > Math.floor(Parser.MAX_SIZE / this.repeats)) this.repeats = Parser.MAX_SIZE; + else this.repeats *= n; + } + if (this.numRegexp < Math.floor(Parser.MAX_SIZE / this.repeats)) return; + this.size = /* @__PURE__ */ new Map(); + for (let reEx of this.stack) this.checkSize(reEx); + } + if (this.calcSize(re, true) > Parser.MAX_SIZE) throw new RE2JSSyntaxException(Parser.ERR_LARGE); + } + calcSize(re, force = false) { + if (!force && this.size !== null) { + if (this.size.has(re)) return this.size.get(re); + } + let size = 0; + switch (re.op) { + case Regexp.Op.LITERAL: + size = re.runes.length; + break; + case Regexp.Op.PLB: + case Regexp.Op.NLB: + case Regexp.Op.CAPTURE: + case Regexp.Op.STAR: + size = 2 + this.calcSize(re.subs[0]); + break; + case Regexp.Op.PLUS: + case Regexp.Op.QUEST: + size = 1 + this.calcSize(re.subs[0]); + break; + case Regexp.Op.CONCAT: + for (let sub of re.subs) size = size + this.calcSize(sub); + break; + case Regexp.Op.ALTERNATE: + for (let sub of re.subs) size = size + this.calcSize(sub); + if (re.subs.length > 1) size = size + re.subs.length - 1; + break; + case Regexp.Op.REPEAT: { + let sub = this.calcSize(re.subs[0]); + if (re.max === -1) { + if (re.min === 0) size = 2 + sub; + else size = 1 + re.min * sub; + break; + } + size = re.max * sub + (re.max - re.min); + break; + } + } + size = Math.max(1, size); + if (this.size === null) this.size = /* @__PURE__ */ new Map(); + this.size.set(re, size); + return size; + } + checkHeight(re) { + if (this.numRegexp < Parser.MAX_HEIGHT) return; + if (this.height === null) { + this.height = /* @__PURE__ */ new Map(); + for (let reEx of this.stack) this.checkHeight(reEx); + } + if (this.calcHeight(re, true) > Parser.MAX_HEIGHT) throw new RE2JSSyntaxException(Parser.ERR_NESTING_DEPTH); + } + calcHeight(re, force = false) { + if (!force && this.height !== null) { + if (this.height.has(re)) return this.height.get(re); + } + let h = 1; + for (let sub of re.subs) { + const hsub = this.calcHeight(sub); + if (h < 1 + hsub) h = 1 + hsub; + } + if (this.height === null) this.height = /* @__PURE__ */ new Map(); + this.height.set(re, h); + return h; + } + pop() { + return this.stack.pop(); + } + popToPseudo() { + const n = this.stack.length; + let i = n; + while (i > 0 && !Regexp.isPseudoOp(this.stack[i - 1].op)) i--; + const r = this.stack.slice(i, n); + this.stack = this.stack.slice(0, i); + return r; + } + push(re) { + this.numRunes += re.runes.length; + if (re.op === Regexp.Op.CHAR_CLASS && re.runes.length === 2 && re.runes[0] === re.runes[1]) { + if (this.maybeConcat(re.runes[0], this.flags & ~RE2Flags.FOLD_CASE)) return null; + re.op = Regexp.Op.LITERAL; + re.runes = [re.runes[0]]; + re.flags = this.flags & ~RE2Flags.FOLD_CASE; + } else if (re.op === Regexp.Op.CHAR_CLASS && re.runes.length === 4 && re.runes[0] === re.runes[1] && re.runes[2] === re.runes[3] && Unicode.simpleFold(re.runes[0]) === re.runes[2] && Unicode.simpleFold(re.runes[2]) === re.runes[0] || re.op === Regexp.Op.CHAR_CLASS && re.runes.length === 2 && re.runes[0] + 1 === re.runes[1] && Unicode.simpleFold(re.runes[0]) === re.runes[1] && Unicode.simpleFold(re.runes[1]) === re.runes[0]) { + if (this.maybeConcat(re.runes[0], this.flags | RE2Flags.FOLD_CASE)) return null; + re.op = Regexp.Op.LITERAL; + re.runes = [re.runes[0]]; + re.flags = this.flags | RE2Flags.FOLD_CASE; + } else this.maybeConcat(-1, 0); + this.stack.push(re); + this.checkLimits(re); + return re; + } + maybeConcat(r, flags) { + const n = this.stack.length; + if (n < 2) return false; + const re1 = this.stack[n - 1]; + const re2 = this.stack[n - 2]; + if (re1.op !== Regexp.Op.LITERAL || re2.op !== Regexp.Op.LITERAL || (re1.flags & RE2Flags.FOLD_CASE) !== (re2.flags & RE2Flags.FOLD_CASE)) return false; + re2.runes = Parser.concatRunes(re2.runes, re1.runes); + if (r >= 0) { + re1.runes = [r]; + re1.flags = flags; + return true; + } + this.pop(); + this.reuse(re1); + return false; + } + newLiteral(r, flags) { + const re = this.newRegexp(Regexp.Op.LITERAL); + re.flags = flags; + if ((flags & RE2Flags.FOLD_CASE) !== 0) r = Parser.minFoldRune(r); + re.runes = [r]; + return re; + } + literal(r) { + this.push(this.newLiteral(r, this.flags)); + } + op(op) { + const re = this.newRegexp(op); + re.flags = this.flags; + return this.push(re); + } + repeat(op, min, max, beforePos, t, lastRepeatPos) { + let flags = this.flags; + if ((flags & RE2Flags.PERL_X) !== 0) { + if (t.more() && t.lookingAt("?")) { + t.skip(1); + flags ^= RE2Flags.NON_GREEDY; + } + if (lastRepeatPos !== -1) throw new RE2JSSyntaxException(Parser.ERR_INVALID_REPEAT_OP, t.from(lastRepeatPos)); + } + const n = this.stack.length; + if (n === 0) throw new RE2JSSyntaxException(Parser.ERR_MISSING_REPEAT_ARGUMENT, t.from(beforePos)); + const sub = this.stack[n - 1]; + if (Regexp.isPseudoOp(sub.op)) throw new RE2JSSyntaxException(Parser.ERR_MISSING_REPEAT_ARGUMENT, t.from(beforePos)); + const re = this.newRegexp(op); + re.min = min; + re.max = max; + re.flags = flags; + re.subs = [sub]; + this.stack[n - 1] = re; + this.checkLimits(re); + if (op === Regexp.Op.REPEAT && (min >= 2 || max >= 2) && !this.repeatIsValid(re, 1e3)) throw new RE2JSSyntaxException(Parser.ERR_INVALID_REPEAT_SIZE, t.from(beforePos)); + } + repeatIsValid(re, n) { + if (re.op === Regexp.Op.REPEAT) { + let m = re.max; + if (m === 0) return true; + if (m < 0) m = re.min; + if (m > n) return false; + if (m > 0) n = Math.trunc(n / m); + } + for (let sub of re.subs) if (!this.repeatIsValid(sub, n)) return false; + return true; + } + concat() { + this.maybeConcat(-1, 0); + const subs = this.popToPseudo(); + if (subs.length === 0) return this.push(this.newRegexp(Regexp.Op.EMPTY_MATCH)); + return this.push(this.collapse(subs, Regexp.Op.CONCAT)); + } + alternate() { + const subs = this.popToPseudo(); + if (subs.length > 0) this.cleanAlt(subs[subs.length - 1]); + if (subs.length === 0) return this.push(this.newRegexp(Regexp.Op.NO_MATCH)); + return this.push(this.collapse(subs, Regexp.Op.ALTERNATE)); + } + cleanAlt(re) { + if (re.op === Regexp.Op.CHAR_CLASS) { + re.runes = new CharClass(re.runes).cleanClass().toArray(); + if (re.runes.length === 2 && re.runes[0] === 0 && re.runes[1] === Unicode.MAX_RUNE) { + re.runes = []; + re.op = Regexp.Op.ANY_CHAR; + } else if (re.runes.length === 4 && re.runes[0] === 0 && re.runes[1] === Codepoint.CODES.get("\n") - 1 && re.runes[2] === Codepoint.CODES.get("\n") + 1 && re.runes[3] === Unicode.MAX_RUNE) { + re.runes = []; + re.op = Regexp.Op.ANY_CHAR_NOT_NL; + } + } + } + collapse(subs, op) { + if (subs.length === 1) return subs[0]; + let len = 0; + for (let sub of subs) len += sub.op === op ? sub.subs.length : 1; + let newsubs = new Array(len).fill(null); + let i = 0; + for (let sub of subs) if (sub.op === op) { + for (let j = 0; j < sub.subs.length; j++) newsubs[i++] = sub.subs[j]; + this.reuse(sub); + } else newsubs[i++] = sub; + let re = this.newRegexp(op); + re.subs = newsubs; + if (op === Regexp.Op.ALTERNATE) { + re.subs = this.factor(re.subs); + if (re.subs.length === 1) { + const old = re; + re = re.subs[0]; + this.reuse(old); + } + } + return re; + } + factor(array) { + if (array.length < 2) return array; + let s = 0; + let lensub = array.length; + let lenout = 0; + let str = null; + let strlen = 0; + let strflags = 0; + let start = 0; + for (let i = 0; i <= lensub; i++) { + let istr = null; + let istrlen = 0; + let iflags = 0; + if (i < lensub) { + let re = array[s + i]; + if (re.op === Regexp.Op.CONCAT && re.subs.length > 0) re = re.subs[0]; + if (re.op === Regexp.Op.LITERAL) { + istr = re.runes; + istrlen = re.runes.length; + iflags = re.flags & RE2Flags.FOLD_CASE; + } + if (iflags === strflags) { + let same = 0; + while (same < strlen && same < istrlen && str[same] === istr[same]) same++; + if (same > 0) { + strlen = same; + continue; + } + } + } + if (i === start) {} else if (i === start + 1) array[lenout++] = array[s + start]; + else { + const prefix = this.newRegexp(Regexp.Op.LITERAL); + prefix.flags = strflags; + prefix.runes = str.slice(0, strlen); + for (let j = start; j < i; j++) { + array[s + j] = this.removeLeadingString(array[s + j], strlen); + this.checkLimits(array[s + j]); + } + const suffix = this.collapse(array.slice(s + start, s + i), Regexp.Op.ALTERNATE); + const re = this.newRegexp(Regexp.Op.CONCAT); + re.subs = [prefix, suffix]; + array[lenout++] = re; + } + start = i; + str = istr; + strlen = istrlen; + strflags = iflags; + } + lensub = lenout; + s = 0; + start = 0; + lenout = 0; + let first = null; + for (let i = 0; i <= lensub; i++) { + let ifirst = null; + if (i < lensub) { + ifirst = Parser.leadingRegexp(array[s + i]); + if (first !== null && first.equals(ifirst) && (Parser.isCharClass(first) || first.op === Regexp.Op.REPEAT && first.min === first.max && Parser.isCharClass(first.subs[0]))) continue; + } + if (i === start) {} else if (i === start + 1) array[lenout++] = array[s + start]; + else { + const prefix = first; + for (let j = start; j < i; j++) { + const reuse = j !== start; + array[s + j] = this.removeLeadingRegexp(array[s + j], reuse); + this.checkLimits(array[s + j]); + } + const suffix = this.collapse(array.slice(s + start, s + i), Regexp.Op.ALTERNATE); + const re = this.newRegexp(Regexp.Op.CONCAT); + re.subs = [prefix, suffix]; + array[lenout++] = re; + } + start = i; + first = ifirst; + } + lensub = lenout; + s = 0; + start = 0; + lenout = 0; + for (let i = 0; i <= lensub; i++) { + if (i < lensub && Parser.isCharClass(array[s + i])) continue; + if (i === start) {} else if (i === start + 1) array[lenout++] = array[s + start]; + else { + let max = start; + for (let j = start + 1; j < i; j++) { + const subMax = array[s + max]; + const subJ = array[s + j]; + if (subMax.op < subJ.op || subMax.op === subJ.op && (subMax.runes !== null ? subMax.runes.length : 0) < (subJ.runes !== null ? subJ.runes.length : 0)) max = j; + } + const tmp = array[s + start]; + array[s + start] = array[s + max]; + array[s + max] = tmp; + for (let j = start + 1; j < i; j++) { + Parser.mergeCharClass(array[s + start], array[s + j]); + this.reuse(array[s + j]); + } + this.cleanAlt(array[s + start]); + array[lenout++] = array[s + start]; + } + if (i < lensub) array[lenout++] = array[s + i]; + start = i + 1; + } + lensub = lenout; + s = 0; + start = 0; + lenout = 0; + for (let i = 0; i < lensub; ++i) { + if (i + 1 < lensub && array[s + i].op === Regexp.Op.EMPTY_MATCH && array[s + i + 1].op === Regexp.Op.EMPTY_MATCH) continue; + array[lenout++] = array[s + i]; + } + lensub = lenout; + s = 0; + return array.slice(s, lensub); + } + removeLeadingString(re, n) { + if (re.op === Regexp.Op.CONCAT && re.subs.length > 0) { + const sub = this.removeLeadingString(re.subs[0], n); + re.subs[0] = sub; + if (sub.op === Regexp.Op.EMPTY_MATCH) { + this.reuse(sub); + switch (re.subs.length) { + case 0: + case 1: + re.op = Regexp.Op.EMPTY_MATCH; + re.subs = Regexp.emptySubs(); + break; + case 2: { + const old = re; + re = re.subs[1]; + this.reuse(old); + break; + } + default: + re.subs = re.subs.slice(1, re.subs.length); + break; + } + } + return re; + } + if (re.op === Regexp.Op.LITERAL) { + re.runes = re.runes.slice(n, re.runes.length); + if (re.runes.length === 0) re.op = Regexp.Op.EMPTY_MATCH; + } + return re; + } + removeLeadingRegexp(re, reuse) { + if (re.op === Regexp.Op.CONCAT && re.subs.length > 0) { + if (reuse) this.reuse(re.subs[0]); + re.subs = re.subs.slice(1, re.subs.length); + switch (re.subs.length) { + case 0: + re.op = Regexp.Op.EMPTY_MATCH; + re.subs = Regexp.emptySubs(); + break; + case 1: { + const old = re; + re = re.subs[0]; + this.reuse(old); + break; + } + } + return re; + } + if (reuse) this.reuse(re); + return this.newRegexp(Regexp.Op.EMPTY_MATCH); + } + parseInternal() { + if ((this.flags & RE2Flags.LITERAL) !== 0) return Parser.literalRegexp(this.wholeRegexp, this.flags); + let lastRepeatPos = -1; + let min = -1; + let max = -1; + const t = new StringIterator(this.wholeRegexp); + while (t.more()) { + let repeatPos = -1; + bigswitch: switch (t.peek()) { + case Codepoint.CODES.get("("): + if ((this.flags & RE2Flags.LOOKBEHIND) !== 0) { + if (t.lookingAt("(?<=")) { + this.parsePosLookBehind(); + t.skip(4); + break; + } + if (t.lookingAt("(?> 16; + max = (minMax & Unicode.MAX_BMP) << 16 >> 16; + this.repeat(Regexp.Op.REPEAT, min, max, repeatPos, t, lastRepeatPos); + break; + } + case Codepoint.CODES.get("\\"): { + const savedPos = t.pos(); + t.skip(1); + if ((this.flags & RE2Flags.PERL_X) !== 0 && t.more()) switch (t.pop()) { + case Codepoint.CODES.get("A"): + this.op(Regexp.Op.BEGIN_TEXT); + break bigswitch; + case Codepoint.CODES.get("b"): + this.op(Regexp.Op.WORD_BOUNDARY); + break bigswitch; + case Codepoint.CODES.get("B"): + this.op(Regexp.Op.NO_WORD_BOUNDARY); + break bigswitch; + case Codepoint.CODES.get("C"): throw new RE2JSSyntaxException(Parser.ERR_INVALID_ESCAPE, "\\C"); + case Codepoint.CODES.get("Q"): { + let lit = t.rest(); + const i = lit.indexOf("\\E"); + if (i >= 0) { + lit = lit.substring(0, i); + t.skipString(lit); + t.skipString("\\E"); + } else t.skipString(lit); + let j = 0; + while (j < lit.length) { + const codepoint = lit.codePointAt(j); + this.literal(codepoint); + j += Utils.charCount(codepoint); + } + break bigswitch; + } + case Codepoint.CODES.get("z"): + this.op(Regexp.Op.END_TEXT); + break bigswitch; + default: + t.rewindTo(savedPos); + break; + } + else t.rewindTo(savedPos); + const re = this.newRegexp(Regexp.Op.CHAR_CLASS); + re.flags = this.flags; + if (t.lookingAt("\\p") || t.lookingAt("\\P")) { + const cc = new CharClass(); + if (this.parseUnicodeClass(t, cc)) { + re.runes = cc.toArray(); + this.push(re); + break bigswitch; + } + } + const cc = new CharClass(); + if (this.parsePerlClassEscape(t, cc)) { + re.runes = cc.toArray(); + this.push(re); + break bigswitch; + } + t.rewindTo(savedPos); + this.reuse(re); + this.literal(Parser.parseEscape(t)); + break; + } + default: + this.literal(t.pop()); + break; + } + lastRepeatPos = repeatPos; + } + this.concat(); + if (this.swapVerticalBar()) this.pop(); + this.alternate(); + if (this.stack.length !== 1) throw new RE2JSSyntaxException(Parser.ERR_MISSING_PAREN, this.wholeRegexp); + this.stack[0].namedGroups = this.namedGroups; + return this.stack[0]; + } + parsePerlFlags(t) { + const startPos = t.pos(); + const s = t.rest(); + if (s.startsWith("(?P<") || s.startsWith("(?<")) { + const begin = s.charAt(2) === "P" ? 4 : 3; + const end = s.indexOf(">"); + if (end < 0) throw new RE2JSSyntaxException(Parser.ERR_INVALID_NAMED_CAPTURE, s); + const name = s.substring(begin, end); + t.skipString(name); + t.skip(begin + 1); + if (!Parser.isValidCaptureName(name)) throw new RE2JSSyntaxException(Parser.ERR_INVALID_NAMED_CAPTURE, s.substring(0, end + 1)); + const re = this.op(Regexp.Op.LEFT_PAREN); + re.cap = ++this.numCap; + if (this.namedGroups[name]) throw new RE2JSSyntaxException(Parser.ERR_DUPLICATE_NAMED_CAPTURE, name); + this.namedGroups[name] = this.numCap; + re.name = name; + return; + } + t.skip(2); + let flags = this.flags; + let sign = 1; + let sawFlag = false; + loop: while (t.more()) { + const c = t.pop(); + switch (c) { + case Codepoint.CODES.get("i"): + flags |= RE2Flags.FOLD_CASE; + sawFlag = true; + break; + case Codepoint.CODES.get("m"): + flags &= ~RE2Flags.ONE_LINE; + sawFlag = true; + break; + case Codepoint.CODES.get("s"): + flags |= RE2Flags.DOT_NL; + sawFlag = true; + break; + case Codepoint.CODES.get("U"): + flags |= RE2Flags.NON_GREEDY; + sawFlag = true; + break; + case Codepoint.CODES.get("-"): + if (sign < 0) break loop; + sign = -1; + flags = ~flags; + sawFlag = false; + break; + case Codepoint.CODES.get(":"): + case Codepoint.CODES.get(")"): + if (sign < 0) { + if (!sawFlag) break loop; + flags = ~flags; + } + if (c === Codepoint.CODES.get(":")) this.op(Regexp.Op.LEFT_PAREN); + this.flags = flags; + return; + default: break loop; + } + } + throw new RE2JSSyntaxException(Parser.ERR_INVALID_PERL_OP, t.from(startPos)); + } + parsePosLookBehind() { + const re = this.newRegexp(Regexp.Op.LEFT_PAREN); + re.flags = this.flags; + re.lb = ++this.nlb; + return this.push(re); + } + parseNegLookBehind() { + const re = this.newRegexp(Regexp.Op.LEFT_PAREN); + re.flags = this.flags; + re.lb = -++this.nlb; + return this.push(re); + } + parseVerticalBar() { + this.concat(); + if (!this.swapVerticalBar()) this.op(Regexp.Op.VERTICAL_BAR); + } + swapVerticalBar() { + const n = this.stack.length; + if (n >= 3 && this.stack[n - 2].op === Regexp.Op.VERTICAL_BAR && Parser.isCharClass(this.stack[n - 1]) && Parser.isCharClass(this.stack[n - 3])) { + let re1 = this.stack[n - 1]; + let re3 = this.stack[n - 3]; + if (re1.op > re3.op) { + const tmp = re3; + re3 = re1; + re1 = tmp; + this.stack[n - 3] = re3; + } + Parser.mergeCharClass(re3, re1); + this.reuse(re1); + this.pop(); + return true; + } + if (n >= 2) { + const re1 = this.stack[n - 1]; + const re2 = this.stack[n - 2]; + if (re2.op === Regexp.Op.VERTICAL_BAR) { + if (n >= 3) this.cleanAlt(this.stack[n - 3]); + this.stack[n - 2] = re1; + this.stack[n - 1] = re2; + return true; + } + } + return false; + } + parseRightParen() { + this.concat(); + if (this.swapVerticalBar()) this.pop(); + this.alternate(); + if (this.stack.length < 2) throw new RE2JSSyntaxException(Parser.ERR_UNEXPECTED_PAREN, this.wholeRegexp); + const re1 = this.pop(); + const re2 = this.pop(); + if (re2.op !== Regexp.Op.LEFT_PAREN) throw new RE2JSSyntaxException(Parser.ERR_UNEXPECTED_PAREN, this.wholeRegexp); + this.flags = re2.flags; + if (re2.lb !== 0) { + if (Parser.hasCapture(re1)) throw new RE2JSSyntaxException(Parser.ERR_INVALID_CAPTURE_IN_LOOKBEHIND, this.wholeRegexp); + if (re2.lb > 0) re2.op = Regexp.Op.PLB; + else re2.op = Regexp.Op.NLB; + re2.subs = [re1]; + this.push(re2); + return; + } + if (re2.cap === 0) this.push(re1); + else { + re2.op = Regexp.Op.CAPTURE; + re2.subs = [re1]; + this.push(re2); + } + } + parsePerlClassEscape(t, cc) { + const beforePos = t.pos(); + if ((this.flags & RE2Flags.PERL_X) === 0 || !t.more() || t.pop() !== Codepoint.CODES.get("\\") || !t.more()) return false; + t.pop(); + const p = t.from(beforePos); + const g = PERL_GROUPS.has(p) ? PERL_GROUPS.get(p) : null; + if (g === null) return false; + cc.appendGroup(g, (this.flags & RE2Flags.FOLD_CASE) !== 0); + return true; + } + parseNamedClass(t, cc) { + const cls = t.rest(); + const i = cls.indexOf(":]"); + if (i < 0) return false; + const name = cls.substring(0, i + 2); + t.skipString(name); + const g = POSIX_GROUPS.has(name) ? POSIX_GROUPS.get(name) : null; + if (g === null) throw new RE2JSSyntaxException(Parser.ERR_INVALID_CHAR_RANGE, name); + cc.appendGroup(g, (this.flags & RE2Flags.FOLD_CASE) !== 0); + return true; + } + parseUnicodeClass(t, cc) { + const startPos = t.pos(); + if ((this.flags & RE2Flags.UNICODE_GROUPS) === 0 || !t.lookingAt("\\p") && !t.lookingAt("\\P")) return false; + t.skip(1); + let sign = 1; + let c = t.pop(); + if (c === Codepoint.CODES.get("P")) sign = -1; + if (!t.more()) { + t.rewindTo(startPos); + throw new RE2JSSyntaxException(Parser.ERR_INVALID_CHAR_RANGE, t.rest()); + } + c = t.pop(); + let name; + if (c !== Codepoint.CODES.get("{")) name = Utils.runeToString(c); + else { + const rest = t.rest(); + const end = rest.indexOf("}"); + if (end < 0) { + t.rewindTo(startPos); + throw new RE2JSSyntaxException(Parser.ERR_INVALID_CHAR_RANGE, t.rest()); + } + name = rest.substring(0, end); + t.skipString(name); + t.skip(1); + } + if (!(name.length === 0) && name.codePointAt(0) === Codepoint.CODES.get("^")) { + sign = 0 - sign; + name = name.substring(1); + } + const pair = Parser.unicodeTable(name); + if (pair === null) throw new RE2JSSyntaxException(Parser.ERR_INVALID_CHAR_RANGE, t.from(startPos)); + if (pair.sign < 0) sign = 0 - sign; + const tab = pair.tab; + const fold = pair.fold; + if ((this.flags & RE2Flags.FOLD_CASE) === 0 || fold === null) cc.appendTableWithSign(tab, sign); + else { + const tmp = new CharClass().appendTable(tab).appendTable(fold).cleanClass().toArray(); + cc.appendClassWithSign(tmp, sign); + } + return true; + } + parseClass(t) { + const startPos = t.pos(); + t.skip(1); + const re = this.newRegexp(Regexp.Op.CHAR_CLASS); + re.flags = this.flags; + const cc = new CharClass(); + let sign = 1; + if (t.more() && t.lookingAt("^")) { + sign = -1; + t.skip(1); + if ((this.flags & RE2Flags.CLASS_NL) === 0) cc.appendRange(Codepoint.CODES.get("\n"), Codepoint.CODES.get("\n")); + } + let first = true; + while (!t.more() || t.peek() !== Codepoint.CODES.get("]") || first) { + if (t.more() && t.lookingAt("-") && (this.flags & RE2Flags.PERL_X) === 0 && !first) { + const s = t.rest(); + if (s === "-" || !s.startsWith("-]")) { + t.rewindTo(startPos); + throw new RE2JSSyntaxException(Parser.ERR_INVALID_CHAR_RANGE, t.rest()); + } + } + first = false; + const beforePos = t.pos(); + if (t.lookingAt("[:")) { + if (this.parseNamedClass(t, cc)) continue; + t.rewindTo(beforePos); + } + if (this.parseUnicodeClass(t, cc)) continue; + if (this.parsePerlClassEscape(t, cc)) continue; + t.rewindTo(beforePos); + const lo = Parser.parseClassChar(t, startPos); + let hi = lo; + if (t.more() && t.lookingAt("-")) { + t.skip(1); + if (t.more() && t.lookingAt("]")) t.skip(-1); + else { + hi = Parser.parseClassChar(t, startPos); + if (hi < lo) throw new RE2JSSyntaxException(Parser.ERR_INVALID_CHAR_RANGE, t.from(beforePos)); + } + } + if ((this.flags & RE2Flags.FOLD_CASE) === 0) cc.appendRange(lo, hi); + else cc.appendFoldedRange(lo, hi); + } + t.skip(1); + cc.cleanClass(); + if (sign < 0) cc.negateClass(); + re.runes = cc.toArray(); + this.push(re); + } +}; +//#endregion +//#region src/RE2.js +/** +* An RE2 class instance is a compiled representation of an RE2 regular expression, independent of +* the public Java-like Pattern/Matcher API. +* +* This class also contains various implementation helpers for RE2 regular expressions. +* +* Use the {@link #quoteMeta(String)} utility function to quote all regular expression +* metacharacters in an arbitrary string. +* +*/ +var RE2 = class RE2 { + static initTest(expr) { + const re2 = RE2.compile(expr); + const res = new RE2(re2.expr, re2.prog, re2.numSubexp, re2.longest); + res.cond = re2.cond; + res.prefix = re2.prefix; + res.prefixUTF8 = re2.prefixUTF8; + res.prefixComplete = re2.prefixComplete; + res.prefixRune = re2.prefixRune; + res.prefilter = re2.prefilter; + return res; + } + /** + * Parses a regular expression and returns, if successful, an {@code RE2} instance that can be + * used to match against text. + * + * When matching against text, the regexp returns a match that begins as early as possible in the + * input (leftmost), and among those it chooses the one that a backtracking search would have + * found first. This so-called leftmost-first matching is the same semantics that Perl, Python, + * and other implementations use, although this package implements it without the expense of + * backtracking. For POSIX leftmost-longest matching, see {@link #compilePOSIX}. + */ + static compile(expr) { + return RE2.compileImpl(expr, RE2Flags.PERL, false); + } + /** + * {@code compilePOSIX} is like {@link #compile} but restricts the regular expression to POSIX ERE + * (egrep) syntax and changes the match semantics to leftmost-longest. + * + * That is, when matching against text, the regexp returns a match that begins as early as + * possible in the input (leftmost), and among those it chooses a match that is as long as + * possible. This so-called leftmost-longest matching is the same semantics that early regular + * expression implementations used and that POSIX specifies. + * + * However, there can be multiple leftmost-longest matches, with different submatch choices, and + * here this package diverges from POSIX. Among the possible leftmost-longest matches, this + * package chooses the one that a backtracking search would have found first, while POSIX + * specifies that the match be chosen to maximize the length of the first subexpression, then the + * second, and so on from left to right. The POSIX rule is computationally prohibitive and not + * even well-defined. See http://swtch.com/~rsc/regexp/regexp2.html#posix + */ + static compilePOSIX(expr) { + return RE2.compileImpl(expr, RE2Flags.POSIX, true); + } + static compileImpl(expr, mode, longest) { + let re = Parser.parse(expr, mode); + const maxCap = re.maxCap(); + re = Simplify.simplify(re); + const prefilter = PrefilterTree.build(re); + const prog = Compiler.compileRegexp(re); + const re2 = new RE2(expr, prog, maxCap, longest); + re2.prefilter = prefilter.type === Prefilter.Type.NONE ? null : prefilter; + const [prefixCompl, prefixStr] = prog.prefix(); + re2.prefixComplete = prefixCompl; + re2.prefix = prefixStr; + re2.prefixUTF8 = Utils.stringToUtf8ByteArray(re2.prefix); + if (re2.prefix.length > 0) re2.prefixRune = re2.prefix.codePointAt(0); + re2.namedGroups = re.namedGroups; + return re2; + } + /** + * Returns true iff textual regular expression {@code pattern} matches string {@code s}. + * + * More complicated queries need to use {@link #compile} and the full {@code RE2} interface. + */ + static match(pattern, s) { + return RE2.compile(pattern).match(s); + } + constructor(expr, prog, numSubexp = 0, longest = 0) { + this.expr = expr; + this.prog = prog; + this.numSubexp = numSubexp; + this.longest = longest; + this.cond = prog.startCond(); + this.prefix = null; + this.prefixUTF8 = null; + this.prefixComplete = false; + this.prefixRune = 0; + this.machinePool = []; + this.dfa = new DFA(this.prog); + this.onepass = OnePass.compile(this.prog); + this.prefilter = null; + } + matchPrefixComplete(input, pos, anchor, ncap) { + if ((anchor === RE2Flags.ANCHOR_START || anchor === RE2Flags.ANCHOR_BOTH) && pos !== 0) return null; + let matchStart = -1; + let matchEnd = -1; + const pLen = input.prefixLength(this); + if (anchor === RE2Flags.UNANCHORED) { + const idx = input.index(this, pos); + if (idx < 0) return null; + matchStart = pos + idx; + matchEnd = matchStart + pLen; + } else if (anchor === RE2Flags.ANCHOR_BOTH) { + if (input.endPos() !== pLen) return null; + if (input.index(this, 0) !== 0) return null; + matchStart = 0; + matchEnd = pLen; + } else if (anchor === RE2Flags.ANCHOR_START) { + if (input.index(this, 0) !== 0) return null; + matchStart = 0; + matchEnd = pLen; + } + if (matchStart < 0) return null; + if (ncap > 0) { + const matchcap = new Int32Array(ncap).fill(-1); + matchcap[0] = matchStart; + matchcap[1] = matchEnd; + return Array.from(matchcap); + } + return []; + } + executeEngine(input, pos, anchor, ncap) { + if (this.prefixComplete && (ncap === 0 || this.numSubexp === 0)) return this.matchPrefixComplete(input, pos, anchor, ncap); + if (this.prefilter !== null && anchor === RE2Flags.UNANCHORED) { + if (!this.prefilter.eval(input, pos)) return null; + } + if (this.onepass !== null) return OnePass.execute(this, input, pos, anchor, ncap); + if (ncap > 0) { + if (this.prog.numLb === 0 && input.endPos() <= Backtracker.maxBitStateLen(this.prog)) return Backtracker.execute(this, input, pos, anchor, ncap); + return this.doExecuteNFA(input, pos, anchor, ncap); + } + if (this.prog.numLb === 0) { + const dfaResult = this.dfa.match(input, pos, anchor); + if (dfaResult !== null) return dfaResult ? [] : null; + if (input.endPos() <= Backtracker.maxBitStateLen(this.prog)) return Backtracker.execute(this, input, pos, anchor, ncap); + } + return this.doExecuteNFA(input, pos, anchor, ncap); + } + /** + * Returns the number of parenthesized subexpressions in this regular expression. + */ + numberOfCapturingGroups() { + return this.numSubexp; + } + /** + * Returns the number of instructions in this compiled regular expression program. + */ + numberOfInstructions() { + return this.prog.numInst(); + } + get() { + return this.machinePool.length > 0 ? this.machinePool.pop() : null; + } + reset() { + this.machinePool.length = 0; + } + put(m) { + this.machinePool.push(m); + } + toString() { + return this.expr; + } + doExecuteNFA(input, pos, anchor, ncap) { + let m = this.get(); + if (!m) m = Machine.fromRE2(this); + m.init(ncap); + const cap = m.match(input, pos, anchor) ? m.submatches() : null; + this.put(m); + return cap; + } + match(s) { + return this.executeEngine(MachineInput.fromUTF16(s), 0, RE2Flags.UNANCHORED, 0) !== null; + } + /** + * Matches the regular expression against input starting at position start and ending at position + * end, with the given anchoring. Records the submatch boundaries in group, which is [start, end) + * pairs of byte offsets. The number of boundaries needed is inferred from the size of the group + * array. It is most efficient not to ask for submatch boundaries. + * + * @param input the input byte array + * @param start the beginning position in the input + * @param end the end position in the input + * @param anchor the anchoring flag (UNANCHORED, ANCHOR_START, ANCHOR_BOTH) + * @param group the array to fill with submatch positions + * @param ngroup the number of array pairs to fill in + * @returns true if a match was found + */ + matchWithGroup(input, start, end, anchor, ngroup) { + if (!(input instanceof MatcherInputBase)) if (Utils.isByteArray(input)) input = MatcherInput.utf8(input); + else input = MatcherInput.utf16(input); + return this.matchMachineInput(input, start, end, anchor, ngroup); + } + matchMachineInput(input, start, end, anchor, ngroup) { + if (start > end) return [false, null]; + const machineInput = input.isUTF16Encoding() ? MachineInput.fromUTF16(input.asCharSequence(), 0, end) : MachineInput.fromUTF8(input.asBytes(), 0, end); + const groupMatch = this.executeEngine(machineInput, start, anchor, 2 * ngroup); + if (groupMatch === null) return [false, null]; + return [true, groupMatch]; + } + /** + * Returns true iff this regexp matches the UTF-8 byte array {@code b}. + */ + matchUTF8(b) { + return this.executeEngine(MachineInput.fromUTF8(b), 0, RE2Flags.UNANCHORED, 0) !== null; + } + /** + * Returns a copy of {@code src} in which all matches for this regexp have been replaced by + * {@code repl}. No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceAll(src, repl) { + return this.replaceAllFunc(src, () => repl, 2 * src.length + 1); + } + /** + * Returns a copy of {@code src} in which only the first match for this regexp has been replaced + * by {@code repl}. No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceFirst(src, repl) { + return this.replaceAllFunc(src, () => repl, 1); + } + /** + * Returns a copy of {@code src} in which at most {@code maxReplaces} matches for this regexp have + * been replaced by the return value of of function {@code repl} (whose first argument is the + * matched string). No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceAllFunc(src, replFunc, maxReplaces) { + let lastMatchEnd = 0; + let searchPos = 0; + let out = ""; + const input = MachineInput.fromUTF16(src); + let numReplaces = 0; + while (searchPos <= src.length) { + const a = this.executeEngine(input, searchPos, RE2Flags.UNANCHORED, 2); + if (a === null || a.length === 0) break; + out += src.substring(lastMatchEnd, a[0]); + if (a[1] > lastMatchEnd || a[0] === 0) { + out += replFunc(src.substring(a[0], a[1])); + numReplaces++; + } + lastMatchEnd = a[1]; + const width = input.step(searchPos) & 7; + if (searchPos + width > a[1]) searchPos += width; + else if (searchPos + 1 > a[1]) searchPos++; + else searchPos = a[1]; + if (numReplaces >= maxReplaces) break; + } + out += src.substring(lastMatchEnd); + return out; + } + pad(a) { + if (a === null) return null; + let n = (1 + this.numSubexp) * 2; + if (a.length < n) { + let a2 = new Array(n).fill(-1); + for (let i = 0; i < a.length; i++) a2[i] = a[i]; + a = a2; + } + return a; + } + allMatches(input, n, deliverFun = (v) => v) { + let result = []; + const end = input.endPos(); + if (n < 0) n = end + 1; + let pos = 0; + let i = 0; + let prevMatchEnd = -1; + while (i < n && pos <= end) { + const matches = this.executeEngine(input, pos, RE2Flags.UNANCHORED, this.prog.numCap); + if (matches === null || matches.length === 0) break; + let accept = true; + if (matches[1] === pos) { + if (matches[0] === prevMatchEnd) accept = false; + const r = input.step(pos); + if (r < 0) pos = end + 1; + else pos += r & 7; + } else pos = matches[1]; + prevMatchEnd = matches[1]; + if (accept) { + result.push(deliverFun(this.pad(matches))); + i++; + } + } + return result; + } + /** + * Returns an array holding the text of the leftmost match in {@code b} of this regular + * expression. + * + * A return value of null indicates no match. + */ + findUTF8(b) { + const a = this.executeEngine(MachineInput.fromUTF8(b), 0, RE2Flags.UNANCHORED, 2); + if (a === null) return null; + return b.slice(a[0], a[1]); + } + /** + * Returns a two-element array of integers defining the location of the leftmost match in + * {@code b} of this regular expression. The match itself is at {@code b[loc[0]...loc[1]]}. + * + * A return value of null indicates no match. + */ + findUTF8Index(b) { + const a = this.executeEngine(MachineInput.fromUTF8(b), 0, RE2Flags.UNANCHORED, 2); + if (a === null) return null; + return a.slice(0, 2); + } + /** + * Returns a string holding the text of the leftmost match in {@code s} of this regular + * expression. + * + * If there is no match, the return value is an empty string, but it will also be empty if the + * regular expression successfully matches an empty string. Use {@link #findIndex} or + * {@link #findSubmatch} if it is necessary to distinguish these cases. + */ + find(s) { + const a = this.executeEngine(MachineInput.fromUTF16(s), 0, RE2Flags.UNANCHORED, 2); + if (a === null) return ""; + return s.substring(a[0], a[1]); + } + /** + * Returns a two-element array of integers defining the location of the leftmost match in + * {@code s} of this regular expression. The match itself is at + * {@code s.substring(loc[0], loc[1])}. + * + * A return value of null indicates no match. + */ + findIndex(s) { + return this.executeEngine(MachineInput.fromUTF16(s), 0, RE2Flags.UNANCHORED, 2); + } + /** + * Returns an array of arrays the text of the leftmost match of the regular expression in + * {@code b} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findUTF8Submatch(b) { + const a = this.executeEngine(MachineInput.fromUTF8(b), 0, RE2Flags.UNANCHORED, this.prog.numCap); + if (a === null) return null; + const ret = new Array(1 + this.numSubexp).fill(null); + for (let i = 0; i < ret.length; i++) if (2 * i < a.length && a[2 * i] >= 0) ret[i] = b.slice(a[2 * i], a[2 * i + 1]); + return ret; + } + /** + * Returns an array holding the index pairs identifying the leftmost match of this regular + * expression in {@code b} and the matches, if any, of its subexpressions, as defined by the the + * Submatch and Index descriptions above. + * + * A return value of null indicates no match. + */ + findUTF8SubmatchIndex(b) { + return this.pad(this.executeEngine(MachineInput.fromUTF8(b), 0, RE2Flags.UNANCHORED, this.prog.numCap)); + } + /** + * Returns an array of strings holding the text of the leftmost match of the regular expression in + * {@code s} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findSubmatch(s) { + const a = this.executeEngine(MachineInput.fromUTF16(s), 0, RE2Flags.UNANCHORED, this.prog.numCap); + if (a === null) return null; + const ret = new Array(1 + this.numSubexp).fill(null); + for (let i = 0; i < ret.length; i++) if (2 * i < a.length && a[2 * i] >= 0) ret[i] = s.substring(a[2 * i], a[2 * i + 1]); + return ret; + } + /** + * Returns an array holding the index pairs identifying the leftmost match of this regular + * expression in {@code s} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findSubmatchIndex(s) { + return this.pad(this.executeEngine(MachineInput.fromUTF16(s), 0, RE2Flags.UNANCHORED, this.prog.numCap)); + } + /** + * {@code findAllUTF8()} is the All version of {@link #findUTF8}; it returns a + * list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + * + * TODO(adonovan): think about defining a byte slice view class, like a read-only Go slice backed + * by |b|. + */ + findAllUTF8(b, n) { + const result = this.allMatches(MachineInput.fromUTF8(b), n, (match) => b.slice(match[0], match[1])); + if (result.length === 0) return null; + return result; + } + /** + * {@code findAllUTF8Index} is the All version of {@link #findUTF8Index}; it + * returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8Index(b, n) { + const result = this.allMatches(MachineInput.fromUTF8(b), n, (match) => match.slice(0, 2)); + if (result.length === 0) return null; + return result; + } + /** + * {@code findAll} is the All version of {@link #find}; it returns a list of up + * to {@code n} successive matches of the expression, as defined by the All + * description above. + * + * A return value of null indicates no match. + */ + findAll(s, n) { + const result = this.allMatches(MachineInput.fromUTF16(s), n, (match) => s.substring(match[0], match[1])); + if (result.length === 0) return null; + return result; + } + /** + * {@code findAllIndex} is the All version of {@link #findIndex}; it returns a + * list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllIndex(s, n) { + const result = this.allMatches(MachineInput.fromUTF16(s), n, (match) => match.slice(0, 2)); + if (result.length === 0) return null; + return result; + } + /** + * {@code findAllUTF8Submatch} is the All version of {@link #findUTF8Submatch}; + * it returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8Submatch(b, n) { + const result = this.allMatches(MachineInput.fromUTF8(b), n, (match) => { + let slice = new Array(match.length / 2 | 0).fill(null); + for (let j = 0; j < slice.length; j++) if (match[2 * j] >= 0) slice[j] = b.slice(match[2 * j], match[2 * j + 1]); + return slice; + }); + if (result.length === 0) return null; + return result; + } + /** + * {@code findAllUTF8SubmatchIndex} is the All version of + * {@link #findUTF8SubmatchIndex}; it returns a list of up to {@code n} successive matches of the + * expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8SubmatchIndex(b, n) { + const result = this.allMatches(MachineInput.fromUTF8(b), n); + if (result.length === 0) return null; + return result; + } + /** + * {@code findAllSubmatch} is the All version of {@link #findSubmatch}; it + * returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllSubmatch(s, n) { + const result = this.allMatches(MachineInput.fromUTF16(s), n, (match) => { + let slice = new Array(match.length / 2 | 0).fill(null); + for (let j = 0; j < slice.length; j++) if (match[2 * j] >= 0) slice[j] = s.substring(match[2 * j], match[2 * j + 1]); + return slice; + }); + if (result.length === 0) return null; + return result; + } + /** + * {@code findAllSubmatchIndex} is the All version of + * {@link #findSubmatchIndex}; it returns a list of up to {@code n} successive matches of the + * expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllSubmatchIndex(s, n) { + const result = this.allMatches(MachineInput.fromUTF16(s), n); + if (result.length === 0) return null; + return result; + } +}; +//#endregion +//#region src/Set.js +var RE2Set = class RE2Set { + /** @type {number} */ + static UNANCHORED = RE2Flags.UNANCHORED; + /** @type {number} */ + static ANCHOR_START = RE2Flags.ANCHOR_START; + /** @type {number} */ + static ANCHOR_BOTH = RE2Flags.ANCHOR_BOTH; + /** + * Constructs a new RE2Set with the specified anchor mode and flags. + * @param {number} [anchor=RE2Set.UNANCHORED] - The anchoring mode (e.g., RE2Set.UNANCHORED). + * @param {number} [flags=0] - The public flags to apply to all patterns in the set. + * @param {number} [maxMem=8388608] - The maximum memory in bytes to use for the DFA (default 8MB). + */ + constructor(anchor = RE2Set.UNANCHORED, flags = 0, maxMem = 8388608) { + this.anchor = anchor; + this.jsFlags = flags; + this.maxMem = maxMem; + let re2Flags = RE2Flags.PERL; + if ((flags & PublicFlags.DISABLE_UNICODE_GROUPS) !== 0) re2Flags &= ~RE2Flags.UNICODE_GROUPS; + if ((flags & PublicFlags.LOOKBEHINDS) !== 0) re2Flags |= RE2Flags.LOOKBEHIND; + this.re2Flags = re2Flags; + this.regexps = []; + this.prog = null; + this.dfa = null; + this.dummyRe2 = null; + } + /** + * Adds a new regular expression pattern to the set. + * Patterns cannot be added after the set has been compiled. + * @param {string} pattern - The regular expression pattern to add. + * @returns {number} The integer index assigned to the added pattern. + * @throws {RE2JSCompileException} If patterns are added after compilation. + */ + add(pattern) { + if (this.prog) throw new RE2JSCompileException("Cannot add patterns after compile"); + let fregex = pattern; + if ((this.jsFlags & PublicFlags.CASE_INSENSITIVE) !== 0) fregex = `(?i)${fregex}`; + if ((this.jsFlags & PublicFlags.DOTALL) !== 0) fregex = `(?s)${fregex}`; + if ((this.jsFlags & PublicFlags.MULTILINE) !== 0) fregex = `(?m)${fregex}`; + const re = Parser.parse(fregex, this.re2Flags); + this.regexps.push(Simplify.simplify(re)); + return this.regexps.length - 1; + } + /** + * Compiles the added patterns into a single state machine. + * This is automatically called on the first match if not called explicitly. + * @returns {void} + */ + compile() { + if (this.prog) return; + this.prog = Compiler.compileSet(this.regexps); + this.dfa = new DFA(this.prog, this.maxMem); + this.dummyRe2 = { + prog: this.prog, + cond: this.prog.startCond(), + prefix: "", + prefixRune: 0, + longest: false + }; + } + /** + * Matches the input against the compiled set of regular expressions. + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to match against. + * @returns {number[]} An array of indices representing the patterns that successfully matched the input. + */ + match(input) { + if (!this.prog) this.compile(); + const machineInput = Utils.isByteArray(input) ? MachineInput.fromUTF8(input) : MachineInput.fromUTF16(input); + let internalAnchor = RE2Flags.UNANCHORED; + if (this.anchor === RE2Set.ANCHOR_START) internalAnchor = RE2Flags.ANCHOR_START; + else if (this.anchor === RE2Set.ANCHOR_BOTH) internalAnchor = RE2Flags.ANCHOR_BOTH; + const dfaResult = this.dfa.matchSet(machineInput, 0, internalAnchor); + if (dfaResult !== null) return dfaResult; + const machine = Machine.fromRE2(this.dummyRe2); + machine.init(0); + return machine.matchSet(machineInput, 0, internalAnchor); + } +}; +//#endregion +//#region src/TranslateRegExpString.js +/** +* Transform JS regex string to RE2 regex string +*/ +var TranslateRegExpString = class TranslateRegExpString { + static isHexadecimal(ch) { + return "0" <= ch && ch <= "9" || "A" <= ch && ch <= "F" || "a" <= ch && ch <= "f"; + } + static translate(data) { + let prefixFlags = ""; + if (data instanceof RegExp) { + if (data.ignoreCase) prefixFlags += "i"; + if (data.multiline) prefixFlags += "m"; + if (data.dotAll) prefixFlags += "s"; + data = data.source; + } + if (typeof data !== "string") return data; + let result = ""; + let changed = false; + let size = data.length; + if (size === 0) { + result = "(?:)"; + changed = true; + } + let inCharClass = false; + let i = 0; + while (i < size) { + let ch = data[i]; + if (ch === "\\") { + if (i + 1 < size) { + ch = data[i + 1]; + switch (ch) { + case "\\": + result += "\\\\"; + i += 2; + continue; + case "c": + if (i + 2 < size) { + let code = data[i + 2].charCodeAt(0); + if (code >= 65 && code <= 90 || code >= 97 && code <= 122) { + let val = code % 32; + result += "\\x"; + result += (val >> 4).toString(16).toUpperCase(); + result += (val & 15).toString(16).toUpperCase(); + i += 3; + changed = true; + continue; + } + } + result += "c"; + i += 2; + changed = true; + continue; + case "u": + if (i + 2 < size) { + if (data[i + 2] === "{") { + let j = i + 3; + let hasHex = false; + let closed = false; + while (j < size) { + const hexChar = data[j]; + if (hexChar === "}") { + closed = true; + break; + } + if (!TranslateRegExpString.isHexadecimal(hexChar)) break; + hasHex = true; + j++; + } + if (closed && hasHex) { + result += "\\x"; + i += 2; + changed = true; + continue; + } + } else if (i + 5 < size) { + let isHex4 = true; + for (let j = 0; j < 4; j++) if (!TranslateRegExpString.isHexadecimal(data[i + 2 + j])) { + isHex4 = false; + break; + } + if (isHex4) { + result += "\\x{" + data.substring(i + 2, i + 6) + "}"; + i += 6; + changed = true; + continue; + } + } + } + result += "u"; + i += 2; + changed = true; + continue; + case "x": { + let isValidHex = false; + if (i + 2 < size && data[i + 2] === "{") { + let j = i + 3; + let hasHex = false; + let closed = false; + while (j < size) { + const hexChar = data[j]; + if (hexChar === "}") { + closed = true; + break; + } + if (!TranslateRegExpString.isHexadecimal(hexChar)) break; + hasHex = true; + j++; + } + if (closed && hasHex) isValidHex = true; + } else if (i + 3 < size && TranslateRegExpString.isHexadecimal(data[i + 2]) && TranslateRegExpString.isHexadecimal(data[i + 3])) isValidHex = true; + if (isValidHex) { + result += "\\x"; + i += 2; + } else { + result += "x"; + i += 2; + changed = true; + } + continue; + } + case "n": + case "r": + case "t": + case "a": + case "f": + case "v": + case "d": + case "D": + case "s": + case "S": + case "w": + case "W": + case "b": + case "B": + case "p": + case "P": + case "A": + case "z": + case "Q": + case "E": + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + result += "\\" + ch; + i += 2; + continue; + default: { + let cp = data.codePointAt(i + 1); + if (cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122) { + let symSize = Utils.charCount(cp); + result += data.substring(i + 1, i + 1 + symSize); + i += symSize + 1; + changed = true; + } else { + result += "\\"; + let symSize = Utils.charCount(cp); + result += data.substring(i + 1, i + 1 + symSize); + i += symSize + 1; + } + continue; + } + } + } + } else if (ch === "/") { + result += "\\/"; + i += 1; + changed = true; + continue; + } else if (ch === "[") inCharClass = true; + else if (ch === "]") inCharClass = false; + else if (!inCharClass && ch === "(" && i + 2 < size && data[i + 1] === "?" && data[i + 2] === "<") { + if (i + 3 < size && !"=!>)".includes(data[i + 3])) { + result += "(?P<"; + i += 3; + changed = true; + continue; + } + } + let cp = data.codePointAt(i); + let symSize = Utils.charCount(cp); + result += data.substring(i, i + symSize); + i += symSize; + } + const finalResult = changed ? result : data; + if (prefixFlags.length > 0) return `(?${prefixFlags})${finalResult}`; + return finalResult; + } +}; +//#endregion +//#region src/index.js +/** +* A compiled representation of an RE2 regular expression +* +* The matching functions take {@code String} arguments instead of the more general Java +* {@code CharSequence} since the latter doesn't provide UTF-16 decoding. +* +* +* @author rsc@google.com (Russ Cox) +* @class +*/ +var RE2JS = class RE2JS { + /** + * Flag: case insensitive matching. + */ + static CASE_INSENSITIVE = PublicFlags.CASE_INSENSITIVE; + /** + * Flag: dot ({@code .}) matches all characters, including newline. + */ + static DOTALL = PublicFlags.DOTALL; + /** + * Flag: multiline matching: {@code ^} and {@code $} match at beginning and end of line, not just + * beginning and end of input. + */ + static MULTILINE = PublicFlags.MULTILINE; + /** + * Flag: Unicode groups (e.g. {@code \p\ Greek\} ) will be syntax errors. + */ + static DISABLE_UNICODE_GROUPS = PublicFlags.DISABLE_UNICODE_GROUPS; + /** + * Flag: matches longest possible string. + */ + static LONGEST_MATCH = PublicFlags.LONGEST_MATCH; + /** + * Flag: enable linear-time captureless lookbehinds. + */ + static LOOKBEHINDS = PublicFlags.LOOKBEHINDS; + /** + * Returns a literal pattern string for the specified string. + * + * This method produces a string that can be used to create a RE2JS that would + * match the string s as if it were a literal pattern. + * + * Metacharacters or escape sequences in the input sequence will be given no special meaning. + * + * @param {string} str The string to be literalized + * @returns {string} A literal string replacement + */ + static quote(str) { + return Utils.quoteMeta(str); + } + /** + * Quotes '\' and '$' in {@code str}, so that the returned string could be used in + * replacement methods as a literal replacement of {@code str}. + * + * This is a convenience delegation to {@link Matcher.quoteReplacement}. + * + * @param {string} str the string to be quoted + * @param {boolean} [javaMode=false] whether the replacement will be used in javaMode + * @returns {string} the quoted string + */ + static quoteReplacement(str, javaMode = false) { + return Matcher.quoteReplacement(str, javaMode); + } + /** + * Translates a given regular expression string to ensure compatibility with RE2JS. + * + * This function preprocesses the input regex string by applying necessary transformations, + * such as escaping special characters (e.g., `/`), converting named capture groups to + * RE2JS-compatible syntax, and handling Unicode sequences properly. It ensures that the + * resulting regex is safe and properly formatted before compilation. + * + * @param {string|RegExp} expr - The regular expression string to be translated. + * @returns {string} - The transformed regular expression string, ready for compilation. + */ + static translateRegExp(expr) { + return TranslateRegExpString.translate(expr); + } + /** + * Helper: create new RE2JS with given regex and flags. Flregex is the regex with flags applied. + * @param {string} regex + * @param {number} [flags=0] + * @returns {RE2JS} + */ + static compile(regex, flags = 0) { + let fregex = regex; + if ((flags & RE2JS.CASE_INSENSITIVE) !== 0) fregex = `(?i)${fregex}`; + if ((flags & RE2JS.DOTALL) !== 0) fregex = `(?s)${fregex}`; + if ((flags & RE2JS.MULTILINE) !== 0) fregex = `(?m)${fregex}`; + if ((flags & ~(RE2JS.MULTILINE | RE2JS.DOTALL | RE2JS.CASE_INSENSITIVE | RE2JS.DISABLE_UNICODE_GROUPS | RE2JS.LONGEST_MATCH | RE2JS.LOOKBEHINDS)) !== 0) throw new RE2JSFlagsException("Flags should only be a combination of MULTILINE, DOTALL, CASE_INSENSITIVE, DISABLE_UNICODE_GROUPS, LONGEST_MATCH, LOOKBEHINDS"); + let re2Flags = RE2Flags.PERL; + if ((flags & RE2JS.DISABLE_UNICODE_GROUPS) !== 0) re2Flags &= ~RE2Flags.UNICODE_GROUPS; + if ((flags & RE2JS.LOOKBEHINDS) !== 0) re2Flags |= RE2Flags.LOOKBEHIND; + const p = new RE2JS(regex, flags); + p.re2Input = RE2.compileImpl(fregex, re2Flags, (flags & RE2JS.LONGEST_MATCH) !== 0); + return p; + } + /** + * Matches a string against a regular expression. + * + * @param {string} regex the regular expression + * @param {string|number[]|Uint8Array} input the input + * @returns {boolean} true if the regular expression matches the entire input + * @throws RE2JSSyntaxException if the regular expression is malformed + */ + static matches(regex, input) { + return RE2JS.compile(regex).testExact(input); + } + /** + * This is visible for testing. + * @private + */ + static initTest(pattern, flags, re2) { + if (pattern == null) throw new Error("pattern is null"); + if (re2 == null) throw new Error("re2 is null"); + const p = new RE2JS(pattern, flags); + p.re2Input = re2; + return p; + } + /** + * + * @param {string} pattern + * @param {number} flags + */ + constructor(pattern, flags) { + this.patternInput = pattern; + this.flagsInput = flags; + /** @type {import('./RE2.js').RE2} */ + this.re2Input = null; + } + /** + * Releases memory used by internal caches associated with this pattern. Does not change the + * observable behaviour. Useful for tests that detect memory leaks via allocation tracking. + */ + reset() { + this.re2Input.reset(); + } + /** + * Returns the flags used in the constructor. + * @returns {number} + */ + flags() { + return this.flagsInput; + } + /** + * Returns the pattern used in the constructor. + * @returns {string} + */ + pattern() { + return this.patternInput; + } + re2() { + return this.re2Input; + } + /** + * Matches a string against a regular expression. + * + * @param {string|number[]|Uint8Array} input the input + * @returns {boolean} true if the regular expression matches the entire input + */ + matches(input) { + return this.testExact(input); + } + /** + * Creates a new {@code Matcher} matching the pattern against the input. + * + * @param {string|number[]|Uint8Array|MatcherInputBase} input the input string + * @returns {Matcher} + */ + matcher(input) { + if (Utils.isByteArray(input)) input = MatcherInput.utf8(input); + return new Matcher(this, input); + } + /** + * Tests whether the regular expression matches any part of the input string. + * Performance Note: This method is highly optimized. Because it only returns + * a boolean and does not extract capture groups, it bypasses the `Matcher` overhead + * and guarantees execution on the high-speed DFA engine whenever possible. + * + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to test against. + * @returns {boolean} `true` if the pattern is found anywhere in the input, `false` otherwise. + */ + test(input) { + if (Utils.isByteArray(input)) return this.re2Input.matchUTF8(input); + return this.re2Input.match(input); + } + /** + * Tests whether the regular expression matches the ENTIRE input string. + * * **Performance Note:** This operates identically to `.matches()`, but is significantly + * faster because it does not request capture group data. By requesting 0 capture groups, + * it securely routes execution through the DFA fast-path. + * + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to test against. + * @returns {boolean} `true` if the exact input string fully matches the pattern, `false` otherwise. + */ + testExact(input) { + const machineInput = Utils.isByteArray(input) ? MachineInput.fromUTF8(input) : MachineInput.fromUTF16(input); + return this.re2Input.executeEngine(machineInput, 0, RE2Flags.ANCHOR_BOTH, 0) !== null; + } + /** + * Executes a search for a match in a specified string. + * Returns a result array, or null if no match is found. + * The returned array perfectly mirrors standard JavaScript `RegExpExecArray`, + * including `.index`, `.input`, and `.groups` properties. + * + * @param {string|number[]|Uint8Array} input the input string or byte array + * @returns {Array|null} the match array with index, input, and groups properties, or null + */ + exec(input) { + const m = this.matcher(input); + if (!m.find()) return null; + const result = [m.group(0)]; + for (let i = 1; i <= m.groupCount(); i++) { + const val = m.group(i); + result.push(val === null ? void 0 : val); + } + result.index = m.start(0); + result.input = input; + const namedGroups = this.namedGroups(); + if (Object.keys(namedGroups).length > 0) { + const parsedGroups = m.getNamedGroups(); + for (const key in parsedGroups) if (parsedGroups[key] === null) parsedGroups[key] = void 0; + result.groups = parsedGroups; + } else result.groups = void 0; + return result; + } + /** + * Splits input around instances of the regular expression. It returns an array giving the strings + * that occur before, between, and after instances of the regular expression. + * + * If {@code limit <= 0}, there is no limit on the size of the returned array. If + * {@code limit == 0}, empty strings that would occur at the end of the array are omitted. If + * {@code limit > 0}, at most limit strings are returned. The final string contains the remainder + * of the input, possibly including additional matches of the pattern. + * + * @param {string} input the input string to be split + * @param {number} [limit=0] the limit + * @returns {string[]} the split strings + */ + split(input, limit = 0) { + const m = this.matcher(input); + const result = []; + let emptiesSkipped = 0; + let last = 0; + while (m.find()) { + if (last === 0 && m.end() === 0) { + last = m.end(); + continue; + } + if (limit > 0 && result.length === limit - 1) break; + if (last === m.start()) { + if (limit === 0) { + emptiesSkipped += 1; + last = m.end(); + continue; + } + } else while (emptiesSkipped > 0) { + result.push(""); + emptiesSkipped -= 1; + } + result.push(m.substring(last, m.start())); + last = m.end(); + } + if (limit === 0 && last !== m.inputLength()) { + while (emptiesSkipped > 0) { + result.push(""); + emptiesSkipped -= 1; + } + result.push(m.substring(last, m.inputLength())); + } + if (limit !== 0 || result.length === 0 && !(last === m.inputLength() && last > 0)) result.push(m.substring(last, m.inputLength())); + return result; + } + /** + * Returns an iterator of all results matching a string against the regular expression, + * including capturing groups. + * + * @param {string|number[]|Uint8Array} input the input string or byte array + * @returns {IterableIterator} + */ + *matchAll(input) { + const m = this.matcher(input); + while (m.find()) { + const result = [m.group(0)]; + for (let i = 1; i <= m.groupCount(); i++) { + const groupVal = m.group(i); + result.push(groupVal === null ? void 0 : groupVal); + } + result.index = m.start(0); + result.input = input; + const namedGroups = this.namedGroups(); + if (Object.keys(namedGroups).length > 0) { + const parsedGroups = m.getNamedGroups(); + for (const key in parsedGroups) if (parsedGroups[key] === null) parsedGroups[key] = void 0; + result.groups = parsedGroups; + } else result.groups = void 0; + yield result; + } + } + /** + * + * @returns {string} + */ + toString() { + return this.patternInput; + } + /** + * Returns the program size of this pattern. + * + *

+ * Similar to the C++ implementation, the program size is a very approximate measure of a regexp's + * "cost". Larger numbers are more expensive than smaller numbers. + *

+ * + * @returns {number} the program size of this pattern + */ + programSize() { + return this.re2Input.numberOfInstructions(); + } + /** + * Returns the number of capturing groups in this matcher's pattern. Group zero denotes the entire + * pattern and is excluded from this count. + * + * @returns {number} the number of capturing groups in this pattern + */ + groupCount() { + return this.re2Input.numberOfCapturingGroups(); + } + /** + * Return a map of the capturing groups in this matcher's pattern, where key is the name and value + * is the index of the group in the pattern. + * @returns {Record} + */ + namedGroups() { + return this.re2Input.namedGroups; + } + /** + * + * @param {*} other + * @returns {boolean} + */ + equals(other) { + if (this === other) return true; + if (other === null || this.constructor !== other.constructor) return false; + return this.flagsInput === other.flagsInput && this.patternInput === other.patternInput; + } +}; +/** +* Creates an RE2JS regex directly from a template literal. +* @overload +* @param {TemplateStringsArray} stringsOrFlags - The raw string segments of the template literal. +* @param {...any} values - The interpolated values. +* @returns {RE2JS} +*/ +/** +* Creates a template literal tag function with specific RE2JS flags. +* @overload +* @param {number} stringsOrFlags - The RE2JS flags to apply (e.g., RE2JS.CASE_INSENSITIVE). +* @returns {(strings: TemplateStringsArray, ...tagValues: any[]) => RE2JS} +*/ +/** +* Implementation signature +* @param {TemplateStringsArray | number} stringsOrFlags +* @param {...any} values +* @returns {RE2JS | ((strings: TemplateStringsArray, ...tagValues: any[]) => RE2JS)} +*/ +const re = (stringsOrFlags, ...values) => { + if (Array.isArray(stringsOrFlags) && "raw" in stringsOrFlags) { + const pattern = String.raw(stringsOrFlags, ...values); + return RE2JS.compile(pattern); + } + const flags = typeof stringsOrFlags === "number" ? stringsOrFlags : 0; + return (strings, ...tagValues) => { + const pattern = String.raw(strings, ...tagValues); + return RE2JS.compile(pattern, flags); + }; +}; +//#endregion +exports.Matcher = Matcher; +exports.MatcherInput = MatcherInput; +exports.MatcherInputBase = MatcherInputBase; +exports.RE2JS = RE2JS; +exports.RE2JSCompileException = RE2JSCompileException; +exports.RE2JSException = RE2JSException; +exports.RE2JSFlagsException = RE2JSFlagsException; +exports.RE2JSGroupException = RE2JSGroupException; +exports.RE2JSInternalException = RE2JSInternalException; +exports.RE2JSSyntaxException = RE2JSSyntaxException; +exports.RE2Set = RE2Set; +exports.re = re; diff --git a/gsd-core/bin/lib/vendor/re2js.d.cts b/gsd-core/bin/lib/vendor/re2js.d.cts new file mode 100644 index 000000000..645999e8c --- /dev/null +++ b/gsd-core/bin/lib/vendor/re2js.d.cts @@ -0,0 +1,938 @@ +// Generated by dts-bundle-generator v9.5.1 + +declare class DFA { + static MAX_CACHE_CLEARS: number; + static STATE_MEMORY_ESTIMATE: number; + constructor(prog: any, maxMem?: number); + prog: any; + stateCache: Map; + stateCount: number; + startState: any; + stateLimit: number; + cacheClears: number; + failed: boolean; + clock: number; + computeClosure(pcs: any): { + pcs: Int32Array; + isMatch: boolean; + matchIDs: any[]; + }; + getState(pcs: any): any; + evictCache(): void; + step(state: any, charCode: any, anchor: any): any; + match(input: any, pos: any, anchor: any): boolean; + matchSet(input: any, pos: any, anchor: any): any[]; +} +declare class Prog { + inst: any[]; + start: number; + numCap: number; + lbStarts: any[]; + numLb: number; + getInst(pc: any): any; + numInst(): number; + addInst(op: any): void; + skipNop(pc: any): any; + prefix(): (string | boolean)[]; + startCond(): number; + patch(l: any, val: any): void; + append(l1: any, l2: any): any; + /** + * + * @returns {string} + */ + toString(): string; +} +export class RE2Set { + /** @type {number} */ + static UNANCHORED: number; + /** @type {number} */ + static ANCHOR_START: number; + /** @type {number} */ + static ANCHOR_BOTH: number; + /** + * Constructs a new RE2Set with the specified anchor mode and flags. + * @param {number} [anchor=RE2Set.UNANCHORED] - The anchoring mode (e.g., RE2Set.UNANCHORED). + * @param {number} [flags=0] - The public flags to apply to all patterns in the set. + * @param {number} [maxMem=8388608] - The maximum memory in bytes to use for the DFA (default 8MB). + */ + constructor(anchor?: number, flags?: number, maxMem?: number); + anchor: number; + jsFlags: number; + maxMem: number; + re2Flags: number; + regexps: any[]; + prog: Prog; + dfa: DFA; + dummyRe2: { + prog: Prog; + cond: number; + prefix: string; + prefixRune: number; + longest: boolean; + }; + /** + * Adds a new regular expression pattern to the set. + * Patterns cannot be added after the set has been compiled. + * @param {string} pattern - The regular expression pattern to add. + * @returns {number} The integer index assigned to the added pattern. + * @throws {RE2JSCompileException} If patterns are added after compilation. + */ + add(pattern: string): number; + /** + * Compiles the added patterns into a single state machine. + * This is automatically called on the first match if not called explicitly. + * @returns {void} + */ + compile(): void; + /** + * Matches the input against the compiled set of regular expressions. + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to match against. + * @returns {number[]} An array of indices representing the patterns that successfully matched the input. + */ + match(input: string | number[] | Uint8Array): number[]; +} +export class MatcherInput { + /** + * Return the MatcherInput for UTF_16 encoding. + * @returns {Utf16MatcherInput} + */ + static utf16(charSequence: any): Utf16MatcherInput; + /** + * Return the MatcherInput for UTF_8 encoding. + * @returns {Utf8MatcherInput} + */ + static utf8(input: any): Utf8MatcherInput; +} +/** + * Abstract the representations of input text supplied to Matcher. + */ +export class MatcherInputBase { + static Encoding: any; + getEncoding(): void; + /** @returns {string} */ + asCharSequence(): string; + /** @returns {Uint8Array|number[]} */ + asBytes(): Uint8Array | number[]; + /** @returns {number} */ + length(): number; + /** + * + * @returns {boolean} + */ + isUTF8Encoding(): boolean; + /** + * + * @returns {boolean} + */ + isUTF16Encoding(): boolean; +} +declare class Utf16MatcherInput extends MatcherInputBase { + /** @param {string|null} charSequence */ + constructor(charSequence?: string | null); + charSequence: string; + getEncoding(): any; + /** + * + * @returns {number[]} + */ + asBytes(): number[]; +} +declare class Utf8MatcherInput extends MatcherInputBase { + /** @param {Uint8Array|number[]|null} bytes */ + constructor(bytes?: Uint8Array | number[] | null); + bytes: number[] | Uint8Array; + getEncoding(): any; +} +/** + * A stateful iterator that interprets a regex {@code RE2JS} on a specific input. + * + * Conceptually, a Matcher consists of four parts: + *
    + *
  1. A compiled regular expression {@code RE2JS}, set at construction and fixed for the lifetime + * of the matcher.
  2. + * + *
  3. The remainder of the input string, set at construction or {@link #reset()} and advanced by + * each match operation such as {@link #find}, {@link #matches} or {@link #lookingAt}.
  4. + * + *
  5. The current match information, accessible via {@link #start}, {@link #end}, and + * {@link #group}, and updated by each match operation.
  6. + * + *
  7. The append position, used and advanced by {@link #appendReplacement} and {@link #appendTail} + * if performing a search and replace from the input to an external {@code StringBuffer}. + * + *
+ * + * + * @author rsc@google.com (Russ Cox) + */ +export class Matcher { + /** + * V8 and WebKit have historical hard limits on the number of arguments + * that can be passed to a function. We cap replacer arguments to prevent + * Call Stack Overflow (DoS) vulnerabilities on massive ASTs. + */ + static MAX_REPLACER_ARGS: number; + /** + * Quotes '\' and '$' in {@code s}, so that the returned string could be used in + * {@link #appendReplacement} as a literal replacement of {@code s}. + * + * @param {string} str the string to be quoted + * @param {boolean} [javaMode=false] whether the replacement will be used in javaMode + * @returns {string} the quoted string + */ + static quoteReplacement(str: string, javaMode?: boolean): string; + /** + * + * @param {import('./index.js').RE2JS} pattern + * @param {string|number[]|Uint8Array|MatcherInputBase} input + */ + constructor(pattern: RE2JS, input: string | number[] | Uint8Array | MatcherInputBase); + /** + * The pattern being matched. + * @type {import('./index.js').RE2JS} + */ + patternInput: RE2JS; + /** @type {number} */ + patternGroupCount: number; + /** @type {number[]} */ + groups: number[]; + /** @type {Record} */ + namedGroups: Record; + /** @type {number} */ + numberOfInstructions: number; + /** + * Returns the {@code RE2JS} associated with this {@code Matcher}. + * @returns {import('./index.js').RE2JS} + */ + pattern(): RE2JS; + /** + * Resets the {@code Matcher}, rewinding input and discarding any match information. + * + * @returns {Matcher} the {@code Matcher} itself, for chained method calls + */ + reset(): Matcher; + /** @type {number} */ + matcherInputLength: number; + /** @type {number} */ + appendPos: number; + hasMatch: boolean; + hasGroups: boolean; + anchorFlag: number; + /** + * Resets the {@code Matcher} and changes the input. + * @param {string|number[]|Uint8Array|MatcherInputBase} input + * @returns {Matcher} the {@code Matcher} itself, for chained method calls + */ + resetMatcherInput(input: string | number[] | Uint8Array | MatcherInputBase): Matcher; + matcherInput: MatcherInputBase; + /** + * Returns the start of the named group of the most recent match, or -1 if the group was not + * matched. + * @param {string|number} [group=0] + * @returns {number} + */ + start(group?: string | number): number; + /** + * Returns the end of the named group of the most recent match, or -1 if the group was not + * matched. + * @param {string|number} [group=0] + * @returns {number} + */ + end(group?: string | number): number; + /** + * Returns the program size of this pattern. + * + *

+ * Similar to the C++ implementation, the program size is a very approximate measure of a regexp's + * "cost". Larger numbers are more expensive than smaller numbers. + *

+ * + * @returns {number} the program size of this pattern + */ + programSize(): number; + /** + * Returns the named group of the most recent match, or {@code null} if the group was not matched. + * @param {string|number} [group=0] + * @returns {string|null} + */ + group(group?: string | number): string | null; + /** + * Returns a dictionary map of all named capturing groups and their matched values. + * If a group was not matched, its value will be `null`. + * @returns {Record} + */ + getNamedGroups(): Record; + /** + * Returns the number of subgroups in this pattern. + * + * @returns {number} the number of subgroups; the overall match (group 0) does not count + */ + groupCount(): number; + /** + * Helper: finds subgroup information if needed for group. + * @param {number} group + * @private + */ + private loadGroup; + /** + * Matches the entire input against the pattern (anchored start and end). If there is a match, + * {@code matches} sets the match state to describe it. + * + * @returns {boolean} true if the entire input matches the pattern + */ + matches(): boolean; + /** + * Matches the beginning of input against the pattern (anchored start). If there is a match, + * {@code lookingAt} sets the match state to describe it. + * + * @returns {boolean} true if the beginning of the input matches the pattern + */ + lookingAt(): boolean; + /** + * Matches the input against the pattern (unanchored), starting at a specified position. If there + * is a match, {@code find} sets the match state to describe it. + * + * @param {number|null} [start=null] the input position where the search begins + * @returns {boolean} if it finds a match + * @throws IndexOutOfBoundsException if start is not a valid input position + */ + find(start?: number | null): boolean; + /** + * Helper: does match starting at start, with RE2 anchor flag. + * @param {number} startByte + * @param {number} anchor + * @returns {boolean} + * @private + */ + private genMatch; + /** + * Helper: return substring for [start, end). + * @param {number} start + * @param {number} end + * @returns {string} + */ + substring(start: number, end: number): string; + /** + * Helper for Pattern: return input length. + * @returns {number} + */ + inputLength(): number; + /** + * Appends to result two strings: the text from the append position up to the beginning of the + * most recent match, and then the replacement with submatch groups substituted for references of + * the form {@code $n}, where {@code n} is the group number in decimal. It advances the append + * position to where the most recent match ended. + * + * To embed a literal {@code $}, use \$ (actually {@code "\\$"} with string escapes). The escape + * is only necessary when {@code $} is followed by a digit, but it is always allowed. Only + * {@code $} and {@code \} need escaping, but any character can be escaped. + * + * The group number {@code n} in {@code $n} is always at least one digit and expands to use more + * digits as long as the resulting number is a valid group number for this pattern. To cut it off + * earlier, escape the first digit that should not be used. + * + * @param {string} replacement the replacement string + * @param {boolean} [javaMode=false] activate java mode (different behaviour for capture groups and special characters) + * @returns {string} + * @throws IllegalStateException if there was no most recent match + * @throws IndexOutOfBoundsException if replacement refers to an invalid group + * @private + */ + private appendReplacement; + /** + * @param {string} replacement - the replacement string + * @returns {string} + * @private + */ + private appendReplacementInternalJava; + /** + * @param {string} replacement - the replacement string + * @returns {string} + * @private + */ + private appendReplacementInternalJs; + /** + * Return the substring of the input from the append position to the end of the + * input. + * @returns {string} + */ + appendTail(): string; + /** + * Returns the input with all matches replaced by {@code replacement}, interpreted as for + * {@code appendReplacement}. + * + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} the input string with the matches replaced + * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true + */ + replaceAll(replacement: string | ((...args: any[]) => string), javaMode?: boolean): string; + /** + * Returns the input with the first match replaced by {@code replacement}, interpreted as for + * {@code appendReplacement}. + * + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} the input string with the first match replaced + * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true + */ + replaceFirst(replacement: string | ((...args: any[]) => string), javaMode?: boolean): string; + /** + * Helper: replaceAll/replaceFirst hybrid. + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [all=true] - replace all matches + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} + * @private + */ + private replace; + /** + * Evaluates a replacer function for the current match and appends the result, + * along with any un-matched preceding text, advancing the append position. + * @param {Function} replacer - the replacer function + * @param {boolean} hasNamedGroups - cached flag if pattern has named groups + * @param {string|Uint8Array|number[]} originalInput - the cached original input reference + * @returns {string} the evaluated string to append + * @private + */ + private appendReplacementFunc; + /** + * Builds the argument array for the replacer function matching the standard + * JS String.prototype.replace(regex, replacer) signature. + * @param {number} matchStart - the start index of the match + * @param {boolean} hasNamedGroups - cached flag if pattern has named groups + * @param {string|Uint8Array|number[]} originalInput - the cached original input reference + * @returns {Array} array of arguments + * @private + */ + private buildReplacerArgs; +} +export class RE2JSException extends Error { + /** @param {string} message */ + constructor(message: string); +} +/** + * An exception thrown by the parser if the pattern was invalid. + */ +export class RE2JSSyntaxException extends RE2JSException { + /** + * @param {string} error + * @param {string|null} [input=null] + */ + constructor(error: string, input?: string | null); + /** @type {string} */ + error: string; + /** @type {string|null} */ + input: string | null; + /** + * Retrieves the description of the error. + * @returns {string} + */ + getDescription(): string; + /** + * Retrieves the erroneous regular-expression pattern. + * @returns {string|null} + */ + getPattern(): string | null; +} +/** + * An exception thrown by the compiler + */ +export class RE2JSCompileException extends RE2JSException { +} +/** + * An exception thrown by using groups + */ +export class RE2JSGroupException extends RE2JSException { +} +/** + * An exception thrown by flags + */ +export class RE2JSFlagsException extends RE2JSException { +} +/** + * An exception thrown for internal engine errors, such as corrupted bytecodes. + */ +export class RE2JSInternalException extends RE2JSException { +} +declare class RE2 { + static initTest(expr: any): RE2; + /** + * Parses a regular expression and returns, if successful, an {@code RE2} instance that can be + * used to match against text. + * + * When matching against text, the regexp returns a match that begins as early as possible in the + * input (leftmost), and among those it chooses the one that a backtracking search would have + * found first. This so-called leftmost-first matching is the same semantics that Perl, Python, + * and other implementations use, although this package implements it without the expense of + * backtracking. For POSIX leftmost-longest matching, see {@link #compilePOSIX}. + */ + static compile(expr: any): RE2; + /** + * {@code compilePOSIX} is like {@link #compile} but restricts the regular expression to POSIX ERE + * (egrep) syntax and changes the match semantics to leftmost-longest. + * + * That is, when matching against text, the regexp returns a match that begins as early as + * possible in the input (leftmost), and among those it chooses a match that is as long as + * possible. This so-called leftmost-longest matching is the same semantics that early regular + * expression implementations used and that POSIX specifies. + * + * However, there can be multiple leftmost-longest matches, with different submatch choices, and + * here this package diverges from POSIX. Among the possible leftmost-longest matches, this + * package chooses the one that a backtracking search would have found first, while POSIX + * specifies that the match be chosen to maximize the length of the first subexpression, then the + * second, and so on from left to right. The POSIX rule is computationally prohibitive and not + * even well-defined. See http://swtch.com/~rsc/regexp/regexp2.html#posix + */ + static compilePOSIX(expr: any): RE2; + static compileImpl(expr: any, mode: any, longest: any): RE2; + /** + * Returns true iff textual regular expression {@code pattern} matches string {@code s}. + * + * More complicated queries need to use {@link #compile} and the full {@code RE2} interface. + */ + static match(pattern: any, s: any): boolean; + constructor(expr: any, prog: any, numSubexp?: number, longest?: number); + expr: any; + prog: any; + numSubexp: number; + longest: number; + cond: any; + prefix: any; + prefixUTF8: any; + prefixComplete: boolean; + prefixRune: number; + machinePool: any[]; + dfa: DFA; + onepass: { + start: any; + numCap: any; + inst: any[]; + }; + prefilter: any; + matchPrefixComplete(input: any, pos: any, anchor: any, ncap: any): number[]; + executeEngine(input: any, pos: any, anchor: any, ncap: any): any; + /** + * Returns the number of parenthesized subexpressions in this regular expression. + */ + numberOfCapturingGroups(): number; + /** + * Returns the number of instructions in this compiled regular expression program. + */ + numberOfInstructions(): any; + get(): any; + reset(): void; + put(m: any): void; + toString(): any; + doExecuteNFA(input: any, pos: any, anchor: any, ncap: any): any; + match(s: any): boolean; + /** + * Matches the regular expression against input starting at position start and ending at position + * end, with the given anchoring. Records the submatch boundaries in group, which is [start, end) + * pairs of byte offsets. The number of boundaries needed is inferred from the size of the group + * array. It is most efficient not to ask for submatch boundaries. + * + * @param input the input byte array + * @param start the beginning position in the input + * @param end the end position in the input + * @param anchor the anchoring flag (UNANCHORED, ANCHOR_START, ANCHOR_BOTH) + * @param group the array to fill with submatch positions + * @param ngroup the number of array pairs to fill in + * @returns true if a match was found + */ + matchWithGroup(input: any, start: any, end: any, anchor: any, ngroup: any): any[]; + matchMachineInput(input: any, start: any, end: any, anchor: any, ngroup: any): any[]; + /** + * Returns true iff this regexp matches the UTF-8 byte array {@code b}. + */ + matchUTF8(b: any): boolean; + /** + * Returns a copy of {@code src} in which all matches for this regexp have been replaced by + * {@code repl}. No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceAll(src: any, repl: any): string; + /** + * Returns a copy of {@code src} in which only the first match for this regexp has been replaced + * by {@code repl}. No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceFirst(src: any, repl: any): string; + /** + * Returns a copy of {@code src} in which at most {@code maxReplaces} matches for this regexp have + * been replaced by the return value of of function {@code repl} (whose first argument is the + * matched string). No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceAllFunc(src: any, replFunc: any, maxReplaces: any): string; + pad(a: any): any; + allMatches(input: any, n: any, deliverFun?: (v: any) => any): any[]; + /** + * Returns an array holding the text of the leftmost match in {@code b} of this regular + * expression. + * + * A return value of null indicates no match. + */ + findUTF8(b: any): any; + /** + * Returns a two-element array of integers defining the location of the leftmost match in + * {@code b} of this regular expression. The match itself is at {@code b[loc[0]...loc[1]]}. + * + * A return value of null indicates no match. + */ + findUTF8Index(b: any): any; + /** + * Returns a string holding the text of the leftmost match in {@code s} of this regular + * expression. + * + * If there is no match, the return value is an empty string, but it will also be empty if the + * regular expression successfully matches an empty string. Use {@link #findIndex} or + * {@link #findSubmatch} if it is necessary to distinguish these cases. + */ + find(s: any): any; + /** + * Returns a two-element array of integers defining the location of the leftmost match in + * {@code s} of this regular expression. The match itself is at + * {@code s.substring(loc[0], loc[1])}. + * + * A return value of null indicates no match. + */ + findIndex(s: any): any; + /** + * Returns an array of arrays the text of the leftmost match of the regular expression in + * {@code b} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findUTF8Submatch(b: any): any[]; + /** + * Returns an array holding the index pairs identifying the leftmost match of this regular + * expression in {@code b} and the matches, if any, of its subexpressions, as defined by the the + * Submatch and Index descriptions above. + * + * A return value of null indicates no match. + */ + findUTF8SubmatchIndex(b: any): any; + /** + * Returns an array of strings holding the text of the leftmost match of the regular expression in + * {@code s} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findSubmatch(s: any): any[]; + /** + * Returns an array holding the index pairs identifying the leftmost match of this regular + * expression in {@code s} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findSubmatchIndex(s: any): any; + /** + * {@code findAllUTF8()} is the All version of {@link #findUTF8}; it returns a + * list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + * + * TODO(adonovan): think about defining a byte slice view class, like a read-only Go slice backed + * by |b|. + */ + findAllUTF8(b: any, n: any): any[]; + /** + * {@code findAllUTF8Index} is the All version of {@link #findUTF8Index}; it + * returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8Index(b: any, n: any): any[]; + /** + * {@code findAll} is the All version of {@link #find}; it returns a list of up + * to {@code n} successive matches of the expression, as defined by the All + * description above. + * + * A return value of null indicates no match. + */ + findAll(s: any, n: any): any[]; + /** + * {@code findAllIndex} is the All version of {@link #findIndex}; it returns a + * list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllIndex(s: any, n: any): any[]; + /** + * {@code findAllUTF8Submatch} is the All version of {@link #findUTF8Submatch}; + * it returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8Submatch(b: any, n: any): any[]; + /** + * {@code findAllUTF8SubmatchIndex} is the All version of + * {@link #findUTF8SubmatchIndex}; it returns a list of up to {@code n} successive matches of the + * expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8SubmatchIndex(b: any, n: any): any[]; + /** + * {@code findAllSubmatch} is the All version of {@link #findSubmatch}; it + * returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllSubmatch(s: any, n: any): any[]; + /** + * {@code findAllSubmatchIndex} is the All version of + * {@link #findSubmatchIndex}; it returns a list of up to {@code n} successive matches of the + * expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllSubmatchIndex(s: any, n: any): any[]; +} +/** + * Creates an RE2JS regex directly from a template literal. + * @overload + * @param {TemplateStringsArray} stringsOrFlags - The raw string segments of the template literal. + * @param {...any} values - The interpolated values. + * @returns {RE2JS} + */ +export function re(stringsOrFlags: TemplateStringsArray, ...values: any[]): RE2JS; +/** + * Creates a template literal tag function with specific RE2JS flags. + * @overload + * @param {number} stringsOrFlags - The RE2JS flags to apply (e.g., RE2JS.CASE_INSENSITIVE). + * @returns {(strings: TemplateStringsArray, ...tagValues: any[]) => RE2JS} + */ +export function re(stringsOrFlags: number): (strings: TemplateStringsArray, ...tagValues: any[]) => RE2JS; +/** + * A compiled representation of an RE2 regular expression + * + * The matching functions take {@code String} arguments instead of the more general Java + * {@code CharSequence} since the latter doesn't provide UTF-16 decoding. + * + * + * @author rsc@google.com (Russ Cox) + * @class + */ +export class RE2JS { + /** + * Flag: case insensitive matching. + */ + static CASE_INSENSITIVE: number; + /** + * Flag: dot ({@code .}) matches all characters, including newline. + */ + static DOTALL: number; + /** + * Flag: multiline matching: {@code ^} and {@code $} match at beginning and end of line, not just + * beginning and end of input. + */ + static MULTILINE: number; + /** + * Flag: Unicode groups (e.g. {@code \p\ Greek\} ) will be syntax errors. + */ + static DISABLE_UNICODE_GROUPS: number; + /** + * Flag: matches longest possible string. + */ + static LONGEST_MATCH: number; + /** + * Flag: enable linear-time captureless lookbehinds. + */ + static LOOKBEHINDS: number; + /** + * Returns a literal pattern string for the specified string. + * + * This method produces a string that can be used to create a RE2JS that would + * match the string s as if it were a literal pattern. + * + * Metacharacters or escape sequences in the input sequence will be given no special meaning. + * + * @param {string} str The string to be literalized + * @returns {string} A literal string replacement + */ + static quote(str: string): string; + /** + * Quotes '\' and '$' in {@code str}, so that the returned string could be used in + * replacement methods as a literal replacement of {@code str}. + * + * This is a convenience delegation to {@link Matcher.quoteReplacement}. + * + * @param {string} str the string to be quoted + * @param {boolean} [javaMode=false] whether the replacement will be used in javaMode + * @returns {string} the quoted string + */ + static quoteReplacement(str: string, javaMode?: boolean): string; + /** + * Translates a given regular expression string to ensure compatibility with RE2JS. + * + * This function preprocesses the input regex string by applying necessary transformations, + * such as escaping special characters (e.g., `/`), converting named capture groups to + * RE2JS-compatible syntax, and handling Unicode sequences properly. It ensures that the + * resulting regex is safe and properly formatted before compilation. + * + * @param {string|RegExp} expr - The regular expression string to be translated. + * @returns {string} - The transformed regular expression string, ready for compilation. + */ + static translateRegExp(expr: string | RegExp): string; + /** + * Helper: create new RE2JS with given regex and flags. Flregex is the regex with flags applied. + * @param {string} regex + * @param {number} [flags=0] + * @returns {RE2JS} + */ + static compile(regex: string, flags?: number): RE2JS; + /** + * Matches a string against a regular expression. + * + * @param {string} regex the regular expression + * @param {string|number[]|Uint8Array} input the input + * @returns {boolean} true if the regular expression matches the entire input + * @throws RE2JSSyntaxException if the regular expression is malformed + */ + static matches(regex: string, input: string | number[] | Uint8Array): boolean; + /** + * This is visible for testing. + * @private + */ + private static initTest; + /** + * + * @param {string} pattern + * @param {number} flags + */ + constructor(pattern: string, flags: number); + patternInput: string; + flagsInput: number; + /** @type {import('./RE2.js').RE2} */ + re2Input: RE2; + /** + * Releases memory used by internal caches associated with this pattern. Does not change the + * observable behaviour. Useful for tests that detect memory leaks via allocation tracking. + */ + reset(): void; + /** + * Returns the flags used in the constructor. + * @returns {number} + */ + flags(): number; + /** + * Returns the pattern used in the constructor. + * @returns {string} + */ + pattern(): string; + re2(): RE2; + /** + * Matches a string against a regular expression. + * + * @param {string|number[]|Uint8Array} input the input + * @returns {boolean} true if the regular expression matches the entire input + */ + matches(input: string | number[] | Uint8Array): boolean; + /** + * Creates a new {@code Matcher} matching the pattern against the input. + * + * @param {string|number[]|Uint8Array|MatcherInputBase} input the input string + * @returns {Matcher} + */ + matcher(input: string | number[] | Uint8Array | MatcherInputBase): Matcher; + /** + * Tests whether the regular expression matches any part of the input string. + * Performance Note: This method is highly optimized. Because it only returns + * a boolean and does not extract capture groups, it bypasses the `Matcher` overhead + * and guarantees execution on the high-speed DFA engine whenever possible. + * + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to test against. + * @returns {boolean} `true` if the pattern is found anywhere in the input, `false` otherwise. + */ + test(input: string | number[] | Uint8Array): boolean; + /** + * Tests whether the regular expression matches the ENTIRE input string. + * * **Performance Note:** This operates identically to `.matches()`, but is significantly + * faster because it does not request capture group data. By requesting 0 capture groups, + * it securely routes execution through the DFA fast-path. + * + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to test against. + * @returns {boolean} `true` if the exact input string fully matches the pattern, `false` otherwise. + */ + testExact(input: string | number[] | Uint8Array): boolean; + /** + * Executes a search for a match in a specified string. + * Returns a result array, or null if no match is found. + * The returned array perfectly mirrors standard JavaScript `RegExpExecArray`, + * including `.index`, `.input`, and `.groups` properties. + * + * @param {string|number[]|Uint8Array} input the input string or byte array + * @returns {Array|null} the match array with index, input, and groups properties, or null + */ + exec(input: string | number[] | Uint8Array): any[] | null; + /** + * Splits input around instances of the regular expression. It returns an array giving the strings + * that occur before, between, and after instances of the regular expression. + * + * If {@code limit <= 0}, there is no limit on the size of the returned array. If + * {@code limit == 0}, empty strings that would occur at the end of the array are omitted. If + * {@code limit > 0}, at most limit strings are returned. The final string contains the remainder + * of the input, possibly including additional matches of the pattern. + * + * @param {string} input the input string to be split + * @param {number} [limit=0] the limit + * @returns {string[]} the split strings + */ + split(input: string, limit?: number): string[]; + /** + * Returns an iterator of all results matching a string against the regular expression, + * including capturing groups. + * + * @param {string|number[]|Uint8Array} input the input string or byte array + * @returns {IterableIterator} + */ + matchAll(input: string | number[] | Uint8Array): IterableIterator; + /** + * + * @returns {string} + */ + toString(): string; + /** + * Returns the program size of this pattern. + * + *

+ * Similar to the C++ implementation, the program size is a very approximate measure of a regexp's + * "cost". Larger numbers are more expensive than smaller numbers. + *

+ * + * @returns {number} the program size of this pattern + */ + programSize(): number; + /** + * Returns the number of capturing groups in this matcher's pattern. Group zero denotes the entire + * pattern and is excluded from this count. + * + * @returns {number} the number of capturing groups in this pattern + */ + groupCount(): number; + /** + * Return a map of the capturing groups in this matcher's pattern, where key is the name and value + * is the index of the group in the pattern. + * @returns {Record} + */ + namedGroups(): Record; + /** + * + * @param {*} other + * @returns {boolean} + */ + equals(other: any): boolean; +} + +export {}; diff --git a/package-lock.json b/package-lock.json index e1400b351..f9689002e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.84", + "re2js": "^2.8.6", "ws": "^8.21.0" }, "bin": { @@ -4531,6 +4532,15 @@ "node": ">= 0.10" } }, + "node_modules/re2js": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/re2js/-/re2js-2.8.6.tgz", + "integrity": "sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index a78ad067e..2cb3b2b0c 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "fast-check": "^4.8.0", "globals": "^16.5.0", "js-yaml": "^4.3.1", + "re2js": "^2.8.6", "typescript": "^6.0.3", "typescript-eslint": "^8.60.0" }, @@ -116,7 +117,7 @@ "lint:table-schema-drift": "node scripts/lint-table-schema-drift.cjs", "lint:frontmatter-scalar-broad-grep": "node scripts/lint-frontmatter-scalar-broad-grep.cjs", "lint:removed-but-needed": "node scripts/lint-removed-but-needed.cjs", - "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-emitted-drift-ack.cjs && node scripts/lint-portable-timeout.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-fix-has-regression-test.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs", + "lint:ci": "npm run lint && npm run lint:skill-deps && npm run lint:generated-sync && node scripts/lint-test-file-count.cjs && node scripts/lint-command-contract.cjs && node scripts/lint-pr-check-project-dir.cjs && npm run lint:legacy-name && node scripts/lint-regression-test-names.cjs && node scripts/lint-allow-test-rule-refs.cjs && node scripts/lint-resolution-provenance.cjs && node scripts/lint-emitted-drift-ack.cjs && node scripts/lint-portable-timeout.cjs && node scripts/validate-registry.cjs && node scripts/lint-table-schema-drift.cjs && node scripts/lint-fix-has-regression-test.cjs && node scripts/lint-example-parser-parity.cjs && node scripts/lint-docs-command-form.cjs && node scripts/lint-plan-count-drift.cjs && node scripts/lint-milestone-window-drift.cjs && node scripts/lint-phase-enumeration-drift.cjs && node scripts/lint-planning-prompt-drift.cjs && node scripts/lint-completion-ratio-drift.cjs && node scripts/lint-state-field-drift.cjs && node scripts/lint-state-write-path-drift.cjs && node scripts/lint-completion-predicate-drift.cjs && node scripts/lint-planning-snapshot-bypass-drift.cjs && node scripts/lint-health-diagnostic-rule-table.cjs && node scripts/lint-planning-artifact-writer-drift.cjs && node scripts/lint-frontmatter-scalar-broad-grep.cjs && node scripts/lint-removed-but-needed.cjs && node scripts/lint-no-adhoc-regex-escape.cjs && node scripts/lint-vendored-deps.cjs", "lint:allow-test-rule-refs": "node scripts/lint-allow-test-rule-refs.cjs", "lint:regression-names": "node scripts/lint-regression-test-names.cjs", "lint:descriptions": "node scripts/lint-descriptions.cjs", diff --git a/scripts/lint-eslint-glob-coverage.allowlist.json b/scripts/lint-eslint-glob-coverage.allowlist.json index eb6d24360..1588939f3 100644 --- a/scripts/lint-eslint-glob-coverage.allowlist.json +++ b/scripts/lint-eslint-glob-coverage.allowlist.json @@ -1,4 +1,12 @@ [ + { + "path": "gsd-core/bin/lib/vendor/re2js.d.cts", + "reason": "Vendored third-party type declaration (#3477): a .d.cts carries no executable code, so no ESLint rule is meaningful; the vendored .cjs it describes is globally ignored as verbatim upstream output. Freshness is enforced by scripts/lint-vendored-deps.cjs, not by lint rules." + }, + { + "path": "src/vendor/re2js.d.cts", + "reason": "Vendored third-party type declaration (#3477): a .d.cts carries no executable code, so no ESLint rule is meaningful; the vendored .cjs it describes is globally ignored as verbatim upstream output. Freshness is enforced by scripts/lint-vendored-deps.cjs, not by lint rules." + }, { "path": "tests/fixtures/brand-typing/bad-calibrated-as-sample-basis.cts", "reason": "Deliberate MUST-NOT-COMPILE type-error fixture (#3059): type-aware linting would fail by design; it exists to prove the compiler rejects it." diff --git a/scripts/lint-vendored-deps.cjs b/scripts/lint-vendored-deps.cjs new file mode 100644 index 000000000..e6f665e46 --- /dev/null +++ b/scripts/lint-vendored-deps.cjs @@ -0,0 +1,124 @@ +#!/usr/bin/env node +'use strict'; + +/** + * lint-vendored-deps.cjs — freshness gate for gsd-core/bin/lib/vendor/. + * + * #3477 follow-up: gsd-core/bin/** is copied by the installer into trees + * that have NO node_modules, so it must carry zero external requires + * (local/no-external-require-in-bin, eslint-rules/no-external-require-in-bin.cjs). + * `re2js` (src/pattern.cts's RE2 engine) is vendored verbatim under + * gsd-core/bin/lib/vendor/ instead — see gsd-core/bin/lib/vendor/README.md. + * + * A vendored artifact that silently drifts from its upstream package is + * just as dangerous as never vendoring it in the first place (a stale + * copy ships a different engine than the one actually reviewed/audited). + * This guard fails CI when: + * 1. gsd-core/bin/lib/vendor/re2js.cjs no longer matches + * node_modules/re2js/build/index.cjs byte-for-byte. + * 2. gsd-core/bin/lib/vendor/re2js.d.cts no longer matches + * node_modules/re2js/build/index.d.cts byte-for-byte. + * 3. src/vendor/re2js.d.cts (the source-side twin tsc needs to resolve + * types for src/pattern.cts's relative './vendor/re2js.cjs' import — + * module resolution for a .cts source is relative to src/, not the + * output dir) no longer matches gsd-core/bin/lib/vendor/re2js.d.cts. + * 4. The `re2js` version pinned in package.json `devDependencies` no + * longer matches the version actually installed at + * node_modules/re2js/package.json (read there, per the dispatch + * brief, rather than duplicating a second pin). + * + * Usage: node scripts/lint-vendored-deps.cjs + * Exit 0 when every vendored copy is fresh; 1 otherwise. + */ + +const fs = require('node:fs'); +const path = require('node:path'); +const { ExitError, runMain } = require('./lib/cli-exit.cjs'); + +const ROOT = path.join(__dirname, '..'); + +const REFRESH_COMMAND = + 'cp node_modules/re2js/build/index.cjs gsd-core/bin/lib/vendor/re2js.cjs && ' + + 'cp node_modules/re2js/build/index.d.cts gsd-core/bin/lib/vendor/re2js.d.cts && ' + + 'cp node_modules/re2js/build/index.d.cts src/vendor/re2js.d.cts'; + +/** + * Compare two files byte-for-byte. Returns null when equal, or a short + * mismatch description (missing file / byte-length delta) otherwise. + * @param {string} relA + * @param {string} relB + * @returns {string | null} + */ +function compareFiles(relA, relB) { + const absA = path.join(ROOT, relA); + const absB = path.join(ROOT, relB); + if (!fs.existsSync(absA)) return `${relA} does not exist`; + if (!fs.existsSync(absB)) return `${relB} does not exist`; + const a = fs.readFileSync(absA); + const b = fs.readFileSync(absB); + if (a.equals(b)) return null; + return `${relA} (${a.length} bytes) != ${relB} (${b.length} bytes)`; +} + +/** + * Strip a leading semver range operator (^, ~, >=, >, <=, <, =) from a + * package.json dependency spec, leaving a bare version. + * @param {string} spec + * @returns {string} + */ +function stripRangeOperator(spec) { + return String(spec || '').trim().replace(/^[\^~]|^>=|^<=|^>|^<|^=/, '').trim(); +} + +function main() { + const findings = []; + + const cjsDrift = compareFiles('gsd-core/bin/lib/vendor/re2js.cjs', 'node_modules/re2js/build/index.cjs'); + if (cjsDrift) findings.push(cjsDrift); + + const dctsDrift = compareFiles('gsd-core/bin/lib/vendor/re2js.d.cts', 'node_modules/re2js/build/index.d.cts'); + if (dctsDrift) findings.push(dctsDrift); + + const srcTwinDrift = compareFiles('src/vendor/re2js.d.cts', 'gsd-core/bin/lib/vendor/re2js.d.cts'); + if (srcTwinDrift) findings.push(srcTwinDrift); + + const pkgPath = path.join(ROOT, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + const pinnedSpec = pkg.devDependencies && pkg.devDependencies.re2js; + if (!pinnedSpec) { + findings.push('package.json devDependencies.re2js is missing'); + } else { + const installedPkgPath = path.join(ROOT, 'node_modules', 're2js', 'package.json'); + if (!fs.existsSync(installedPkgPath)) { + findings.push('node_modules/re2js/package.json does not exist (run npm install)'); + } else { + const installed = JSON.parse(fs.readFileSync(installedPkgPath, 'utf8')); + const pinned = stripRangeOperator(pinnedSpec); + if (pinned !== installed.version) { + findings.push( + `package.json devDependencies.re2js ("${pinnedSpec}" -> "${pinned}") != ` + + `node_modules/re2js/package.json version ("${installed.version}")`, + ); + } + } + } + + if (findings.length > 0) { + const detail = findings.map((f) => ` ${f}`).join('\n'); + throw new ExitError( + 1, + 'lint-vendored-deps: gsd-core/bin/lib/vendor/re2js.* has drifted from its\n' + + 'upstream package (or its version pin). Refresh with:\n' + + ` ${REFRESH_COMMAND}\n` + + 'Findings:\n' + + detail, + ); + } + + process.stdout.write('ok lint-vendored-deps: gsd-core/bin/lib/vendor/re2js.* matches node_modules/re2js and its pinned version\n'); + return 0; +} + +if (require.main === module) runMain(main); + +module.exports = { compareFiles, stripRangeOperator }; diff --git a/src/pattern.cts b/src/pattern.cts index c4b9bd44a..147ee3810 100644 --- a/src/pattern.cts +++ b/src/pattern.cts @@ -41,6 +41,12 @@ * migration-equivalence property sweep in tests/pattern.test.cjs (rows 15-17). */ +// re2js is vendored, not an npm dependency at runtime: gsd-core/bin/** is +// copied into installed trees that have no node_modules, so this module must +// carry zero external requires (eslint-rules/no-external-require-in-bin.cjs +// enforces it). See gsd-core/bin/lib/vendor/README.md. +import { RE2JS } from './vendor/re2js.cjs'; + // #3498: RegExp.escape is ES2026 (first shipped in Node 24). The gsd-test // matrix still runs a linux-node22 lane, and the build itself consumes this // module (scripts/gen-loop-host-contract.cjs), so a hard dependency breaks @@ -64,3 +70,66 @@ export function escapeRegex(value: string): string { export function literalPattern(value: string, flags?: string): RegExp { return new RegExp(escapeRegex(value), flags); } + +/** Max length for a user-supplied regex pattern before it is refused (ReDoS/compile-cost mitigation). */ +export const MAX_USER_PATTERN_LEN = 512; + +/** Reason an untrusted pattern was refused and never compiled, or `null` if it compiled. */ +export type UserPatternNeutralization = 'empty' | 'too-long' | 'unsupported'; + +export interface UserPatternResult { + /** Linear-time match via RE2. Returns false for every neutralized pattern, by construction. */ + test(input: string): boolean; + /** null when the pattern compiled; otherwise why it was refused. */ + neutralized: UserPatternNeutralization | null; +} + +/** Always-false matcher shared by every neutralization path — a refused pattern must never be able to report a match. */ +const NEVER_MATCH: UserPatternResult['test'] = () => false; + +/** + * Compile an UNTRUSTED, user-supplied pattern (e.g. plan frontmatter) via RE2 + * (re2js), whose matching is linear-time in input length by construction — + * there is no backtracking engine here to exploit, so the vulnerability class + * (catastrophic/exponential backtracking) is closed by the engine rather than + * detected by a heuristic scan of the pattern text. + * + * Never throws. A pattern that is empty, too long, or not valid RE2 syntax + * (backreferences and look-around are unsupported by RE2 — those are exactly + * the constructs that require backtracking) is REFUSED: `test()` always + * returns `false`, and `neutralized` reports why so callers can surface the + * refusal (#3477 follow-up: a neutralized pattern must not look like a plain + * "not found"). Restores the guards lost with + * `sdk/src/query/validate.ts:regexForKeyLinkPattern` (#3477); this revision + * (post-#3477-follow-up) replaces the hand-rolled backtracking-shape scanner + * with RE2's linear-time guarantee — a refused pattern is never re-attempted + * as a literal-escaped match, since guessing at a pattern we could not + * compile is what produced the prior false-pass regression. + * + * `pattern` is `unknown`, not `string`, because callers pull this straight off + * parsed plan frontmatter (untrusted YAML) — a non-string value must reach the + * `'empty'`/never-match branch rather than being force-cast by the caller. + */ +export function compileUserPattern(pattern: unknown): UserPatternResult { + if (typeof pattern !== 'string' || pattern.length === 0) { + return { test: NEVER_MATCH, neutralized: 'empty' }; + } + if (pattern.length > MAX_USER_PATTERN_LEN) { + // The cap now bounds compile cost/memory, not backtracking (RE2 has none) — + // an over-long pattern is refused outright rather than truncated-and-compiled. + return { test: NEVER_MATCH, neutralized: 'too-long' }; + } + try { + // translateRegExp accepts JS-flavored syntax (named groups, `/`-escaping, + // etc.) that RE2's own grammar doesn't, reducing spurious refusals of + // otherwise-safe, JS-authored patterns before compiling under RE2's + // linear-time engine. + const compiled = RE2JS.compile(RE2JS.translateRegExp(pattern)); + return { test: (input: string) => compiled.test(input), neutralized: null }; + } catch { + // Backreferences, look-around, or any other RE2-unsupported/malformed + // syntax. No literal-escape fallback: a pattern we could not compile is + // never guessed at — guessing produced the #3477 false-pass regression. + return { test: NEVER_MATCH, neutralized: 'unsupported' }; + } +} diff --git a/src/vendor/re2js.d.cts b/src/vendor/re2js.d.cts new file mode 100644 index 000000000..645999e8c --- /dev/null +++ b/src/vendor/re2js.d.cts @@ -0,0 +1,938 @@ +// Generated by dts-bundle-generator v9.5.1 + +declare class DFA { + static MAX_CACHE_CLEARS: number; + static STATE_MEMORY_ESTIMATE: number; + constructor(prog: any, maxMem?: number); + prog: any; + stateCache: Map; + stateCount: number; + startState: any; + stateLimit: number; + cacheClears: number; + failed: boolean; + clock: number; + computeClosure(pcs: any): { + pcs: Int32Array; + isMatch: boolean; + matchIDs: any[]; + }; + getState(pcs: any): any; + evictCache(): void; + step(state: any, charCode: any, anchor: any): any; + match(input: any, pos: any, anchor: any): boolean; + matchSet(input: any, pos: any, anchor: any): any[]; +} +declare class Prog { + inst: any[]; + start: number; + numCap: number; + lbStarts: any[]; + numLb: number; + getInst(pc: any): any; + numInst(): number; + addInst(op: any): void; + skipNop(pc: any): any; + prefix(): (string | boolean)[]; + startCond(): number; + patch(l: any, val: any): void; + append(l1: any, l2: any): any; + /** + * + * @returns {string} + */ + toString(): string; +} +export class RE2Set { + /** @type {number} */ + static UNANCHORED: number; + /** @type {number} */ + static ANCHOR_START: number; + /** @type {number} */ + static ANCHOR_BOTH: number; + /** + * Constructs a new RE2Set with the specified anchor mode and flags. + * @param {number} [anchor=RE2Set.UNANCHORED] - The anchoring mode (e.g., RE2Set.UNANCHORED). + * @param {number} [flags=0] - The public flags to apply to all patterns in the set. + * @param {number} [maxMem=8388608] - The maximum memory in bytes to use for the DFA (default 8MB). + */ + constructor(anchor?: number, flags?: number, maxMem?: number); + anchor: number; + jsFlags: number; + maxMem: number; + re2Flags: number; + regexps: any[]; + prog: Prog; + dfa: DFA; + dummyRe2: { + prog: Prog; + cond: number; + prefix: string; + prefixRune: number; + longest: boolean; + }; + /** + * Adds a new regular expression pattern to the set. + * Patterns cannot be added after the set has been compiled. + * @param {string} pattern - The regular expression pattern to add. + * @returns {number} The integer index assigned to the added pattern. + * @throws {RE2JSCompileException} If patterns are added after compilation. + */ + add(pattern: string): number; + /** + * Compiles the added patterns into a single state machine. + * This is automatically called on the first match if not called explicitly. + * @returns {void} + */ + compile(): void; + /** + * Matches the input against the compiled set of regular expressions. + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to match against. + * @returns {number[]} An array of indices representing the patterns that successfully matched the input. + */ + match(input: string | number[] | Uint8Array): number[]; +} +export class MatcherInput { + /** + * Return the MatcherInput for UTF_16 encoding. + * @returns {Utf16MatcherInput} + */ + static utf16(charSequence: any): Utf16MatcherInput; + /** + * Return the MatcherInput for UTF_8 encoding. + * @returns {Utf8MatcherInput} + */ + static utf8(input: any): Utf8MatcherInput; +} +/** + * Abstract the representations of input text supplied to Matcher. + */ +export class MatcherInputBase { + static Encoding: any; + getEncoding(): void; + /** @returns {string} */ + asCharSequence(): string; + /** @returns {Uint8Array|number[]} */ + asBytes(): Uint8Array | number[]; + /** @returns {number} */ + length(): number; + /** + * + * @returns {boolean} + */ + isUTF8Encoding(): boolean; + /** + * + * @returns {boolean} + */ + isUTF16Encoding(): boolean; +} +declare class Utf16MatcherInput extends MatcherInputBase { + /** @param {string|null} charSequence */ + constructor(charSequence?: string | null); + charSequence: string; + getEncoding(): any; + /** + * + * @returns {number[]} + */ + asBytes(): number[]; +} +declare class Utf8MatcherInput extends MatcherInputBase { + /** @param {Uint8Array|number[]|null} bytes */ + constructor(bytes?: Uint8Array | number[] | null); + bytes: number[] | Uint8Array; + getEncoding(): any; +} +/** + * A stateful iterator that interprets a regex {@code RE2JS} on a specific input. + * + * Conceptually, a Matcher consists of four parts: + *
    + *
  1. A compiled regular expression {@code RE2JS}, set at construction and fixed for the lifetime + * of the matcher.
  2. + * + *
  3. The remainder of the input string, set at construction or {@link #reset()} and advanced by + * each match operation such as {@link #find}, {@link #matches} or {@link #lookingAt}.
  4. + * + *
  5. The current match information, accessible via {@link #start}, {@link #end}, and + * {@link #group}, and updated by each match operation.
  6. + * + *
  7. The append position, used and advanced by {@link #appendReplacement} and {@link #appendTail} + * if performing a search and replace from the input to an external {@code StringBuffer}. + * + *
+ * + * + * @author rsc@google.com (Russ Cox) + */ +export class Matcher { + /** + * V8 and WebKit have historical hard limits on the number of arguments + * that can be passed to a function. We cap replacer arguments to prevent + * Call Stack Overflow (DoS) vulnerabilities on massive ASTs. + */ + static MAX_REPLACER_ARGS: number; + /** + * Quotes '\' and '$' in {@code s}, so that the returned string could be used in + * {@link #appendReplacement} as a literal replacement of {@code s}. + * + * @param {string} str the string to be quoted + * @param {boolean} [javaMode=false] whether the replacement will be used in javaMode + * @returns {string} the quoted string + */ + static quoteReplacement(str: string, javaMode?: boolean): string; + /** + * + * @param {import('./index.js').RE2JS} pattern + * @param {string|number[]|Uint8Array|MatcherInputBase} input + */ + constructor(pattern: RE2JS, input: string | number[] | Uint8Array | MatcherInputBase); + /** + * The pattern being matched. + * @type {import('./index.js').RE2JS} + */ + patternInput: RE2JS; + /** @type {number} */ + patternGroupCount: number; + /** @type {number[]} */ + groups: number[]; + /** @type {Record} */ + namedGroups: Record; + /** @type {number} */ + numberOfInstructions: number; + /** + * Returns the {@code RE2JS} associated with this {@code Matcher}. + * @returns {import('./index.js').RE2JS} + */ + pattern(): RE2JS; + /** + * Resets the {@code Matcher}, rewinding input and discarding any match information. + * + * @returns {Matcher} the {@code Matcher} itself, for chained method calls + */ + reset(): Matcher; + /** @type {number} */ + matcherInputLength: number; + /** @type {number} */ + appendPos: number; + hasMatch: boolean; + hasGroups: boolean; + anchorFlag: number; + /** + * Resets the {@code Matcher} and changes the input. + * @param {string|number[]|Uint8Array|MatcherInputBase} input + * @returns {Matcher} the {@code Matcher} itself, for chained method calls + */ + resetMatcherInput(input: string | number[] | Uint8Array | MatcherInputBase): Matcher; + matcherInput: MatcherInputBase; + /** + * Returns the start of the named group of the most recent match, or -1 if the group was not + * matched. + * @param {string|number} [group=0] + * @returns {number} + */ + start(group?: string | number): number; + /** + * Returns the end of the named group of the most recent match, or -1 if the group was not + * matched. + * @param {string|number} [group=0] + * @returns {number} + */ + end(group?: string | number): number; + /** + * Returns the program size of this pattern. + * + *

+ * Similar to the C++ implementation, the program size is a very approximate measure of a regexp's + * "cost". Larger numbers are more expensive than smaller numbers. + *

+ * + * @returns {number} the program size of this pattern + */ + programSize(): number; + /** + * Returns the named group of the most recent match, or {@code null} if the group was not matched. + * @param {string|number} [group=0] + * @returns {string|null} + */ + group(group?: string | number): string | null; + /** + * Returns a dictionary map of all named capturing groups and their matched values. + * If a group was not matched, its value will be `null`. + * @returns {Record} + */ + getNamedGroups(): Record; + /** + * Returns the number of subgroups in this pattern. + * + * @returns {number} the number of subgroups; the overall match (group 0) does not count + */ + groupCount(): number; + /** + * Helper: finds subgroup information if needed for group. + * @param {number} group + * @private + */ + private loadGroup; + /** + * Matches the entire input against the pattern (anchored start and end). If there is a match, + * {@code matches} sets the match state to describe it. + * + * @returns {boolean} true if the entire input matches the pattern + */ + matches(): boolean; + /** + * Matches the beginning of input against the pattern (anchored start). If there is a match, + * {@code lookingAt} sets the match state to describe it. + * + * @returns {boolean} true if the beginning of the input matches the pattern + */ + lookingAt(): boolean; + /** + * Matches the input against the pattern (unanchored), starting at a specified position. If there + * is a match, {@code find} sets the match state to describe it. + * + * @param {number|null} [start=null] the input position where the search begins + * @returns {boolean} if it finds a match + * @throws IndexOutOfBoundsException if start is not a valid input position + */ + find(start?: number | null): boolean; + /** + * Helper: does match starting at start, with RE2 anchor flag. + * @param {number} startByte + * @param {number} anchor + * @returns {boolean} + * @private + */ + private genMatch; + /** + * Helper: return substring for [start, end). + * @param {number} start + * @param {number} end + * @returns {string} + */ + substring(start: number, end: number): string; + /** + * Helper for Pattern: return input length. + * @returns {number} + */ + inputLength(): number; + /** + * Appends to result two strings: the text from the append position up to the beginning of the + * most recent match, and then the replacement with submatch groups substituted for references of + * the form {@code $n}, where {@code n} is the group number in decimal. It advances the append + * position to where the most recent match ended. + * + * To embed a literal {@code $}, use \$ (actually {@code "\\$"} with string escapes). The escape + * is only necessary when {@code $} is followed by a digit, but it is always allowed. Only + * {@code $} and {@code \} need escaping, but any character can be escaped. + * + * The group number {@code n} in {@code $n} is always at least one digit and expands to use more + * digits as long as the resulting number is a valid group number for this pattern. To cut it off + * earlier, escape the first digit that should not be used. + * + * @param {string} replacement the replacement string + * @param {boolean} [javaMode=false] activate java mode (different behaviour for capture groups and special characters) + * @returns {string} + * @throws IllegalStateException if there was no most recent match + * @throws IndexOutOfBoundsException if replacement refers to an invalid group + * @private + */ + private appendReplacement; + /** + * @param {string} replacement - the replacement string + * @returns {string} + * @private + */ + private appendReplacementInternalJava; + /** + * @param {string} replacement - the replacement string + * @returns {string} + * @private + */ + private appendReplacementInternalJs; + /** + * Return the substring of the input from the append position to the end of the + * input. + * @returns {string} + */ + appendTail(): string; + /** + * Returns the input with all matches replaced by {@code replacement}, interpreted as for + * {@code appendReplacement}. + * + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} the input string with the matches replaced + * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true + */ + replaceAll(replacement: string | ((...args: any[]) => string), javaMode?: boolean): string; + /** + * Returns the input with the first match replaced by {@code replacement}, interpreted as for + * {@code appendReplacement}. + * + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} the input string with the first match replaced + * @throws IndexOutOfBoundsException if replacement refers to an invalid group and javaMode is true + */ + replaceFirst(replacement: string | ((...args: any[]) => string), javaMode?: boolean): string; + /** + * Helper: replaceAll/replaceFirst hybrid. + * @param {string|((...args: any[]) => string)} replacement - the replacement string or a replacer function + * @param {boolean} [all=true] - replace all matches + * @param {boolean} [javaMode=false] - activate java mode (different behaviour for capture groups and special characters) + * @returns {string} + * @private + */ + private replace; + /** + * Evaluates a replacer function for the current match and appends the result, + * along with any un-matched preceding text, advancing the append position. + * @param {Function} replacer - the replacer function + * @param {boolean} hasNamedGroups - cached flag if pattern has named groups + * @param {string|Uint8Array|number[]} originalInput - the cached original input reference + * @returns {string} the evaluated string to append + * @private + */ + private appendReplacementFunc; + /** + * Builds the argument array for the replacer function matching the standard + * JS String.prototype.replace(regex, replacer) signature. + * @param {number} matchStart - the start index of the match + * @param {boolean} hasNamedGroups - cached flag if pattern has named groups + * @param {string|Uint8Array|number[]} originalInput - the cached original input reference + * @returns {Array} array of arguments + * @private + */ + private buildReplacerArgs; +} +export class RE2JSException extends Error { + /** @param {string} message */ + constructor(message: string); +} +/** + * An exception thrown by the parser if the pattern was invalid. + */ +export class RE2JSSyntaxException extends RE2JSException { + /** + * @param {string} error + * @param {string|null} [input=null] + */ + constructor(error: string, input?: string | null); + /** @type {string} */ + error: string; + /** @type {string|null} */ + input: string | null; + /** + * Retrieves the description of the error. + * @returns {string} + */ + getDescription(): string; + /** + * Retrieves the erroneous regular-expression pattern. + * @returns {string|null} + */ + getPattern(): string | null; +} +/** + * An exception thrown by the compiler + */ +export class RE2JSCompileException extends RE2JSException { +} +/** + * An exception thrown by using groups + */ +export class RE2JSGroupException extends RE2JSException { +} +/** + * An exception thrown by flags + */ +export class RE2JSFlagsException extends RE2JSException { +} +/** + * An exception thrown for internal engine errors, such as corrupted bytecodes. + */ +export class RE2JSInternalException extends RE2JSException { +} +declare class RE2 { + static initTest(expr: any): RE2; + /** + * Parses a regular expression and returns, if successful, an {@code RE2} instance that can be + * used to match against text. + * + * When matching against text, the regexp returns a match that begins as early as possible in the + * input (leftmost), and among those it chooses the one that a backtracking search would have + * found first. This so-called leftmost-first matching is the same semantics that Perl, Python, + * and other implementations use, although this package implements it without the expense of + * backtracking. For POSIX leftmost-longest matching, see {@link #compilePOSIX}. + */ + static compile(expr: any): RE2; + /** + * {@code compilePOSIX} is like {@link #compile} but restricts the regular expression to POSIX ERE + * (egrep) syntax and changes the match semantics to leftmost-longest. + * + * That is, when matching against text, the regexp returns a match that begins as early as + * possible in the input (leftmost), and among those it chooses a match that is as long as + * possible. This so-called leftmost-longest matching is the same semantics that early regular + * expression implementations used and that POSIX specifies. + * + * However, there can be multiple leftmost-longest matches, with different submatch choices, and + * here this package diverges from POSIX. Among the possible leftmost-longest matches, this + * package chooses the one that a backtracking search would have found first, while POSIX + * specifies that the match be chosen to maximize the length of the first subexpression, then the + * second, and so on from left to right. The POSIX rule is computationally prohibitive and not + * even well-defined. See http://swtch.com/~rsc/regexp/regexp2.html#posix + */ + static compilePOSIX(expr: any): RE2; + static compileImpl(expr: any, mode: any, longest: any): RE2; + /** + * Returns true iff textual regular expression {@code pattern} matches string {@code s}. + * + * More complicated queries need to use {@link #compile} and the full {@code RE2} interface. + */ + static match(pattern: any, s: any): boolean; + constructor(expr: any, prog: any, numSubexp?: number, longest?: number); + expr: any; + prog: any; + numSubexp: number; + longest: number; + cond: any; + prefix: any; + prefixUTF8: any; + prefixComplete: boolean; + prefixRune: number; + machinePool: any[]; + dfa: DFA; + onepass: { + start: any; + numCap: any; + inst: any[]; + }; + prefilter: any; + matchPrefixComplete(input: any, pos: any, anchor: any, ncap: any): number[]; + executeEngine(input: any, pos: any, anchor: any, ncap: any): any; + /** + * Returns the number of parenthesized subexpressions in this regular expression. + */ + numberOfCapturingGroups(): number; + /** + * Returns the number of instructions in this compiled regular expression program. + */ + numberOfInstructions(): any; + get(): any; + reset(): void; + put(m: any): void; + toString(): any; + doExecuteNFA(input: any, pos: any, anchor: any, ncap: any): any; + match(s: any): boolean; + /** + * Matches the regular expression against input starting at position start and ending at position + * end, with the given anchoring. Records the submatch boundaries in group, which is [start, end) + * pairs of byte offsets. The number of boundaries needed is inferred from the size of the group + * array. It is most efficient not to ask for submatch boundaries. + * + * @param input the input byte array + * @param start the beginning position in the input + * @param end the end position in the input + * @param anchor the anchoring flag (UNANCHORED, ANCHOR_START, ANCHOR_BOTH) + * @param group the array to fill with submatch positions + * @param ngroup the number of array pairs to fill in + * @returns true if a match was found + */ + matchWithGroup(input: any, start: any, end: any, anchor: any, ngroup: any): any[]; + matchMachineInput(input: any, start: any, end: any, anchor: any, ngroup: any): any[]; + /** + * Returns true iff this regexp matches the UTF-8 byte array {@code b}. + */ + matchUTF8(b: any): boolean; + /** + * Returns a copy of {@code src} in which all matches for this regexp have been replaced by + * {@code repl}. No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceAll(src: any, repl: any): string; + /** + * Returns a copy of {@code src} in which only the first match for this regexp has been replaced + * by {@code repl}. No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceFirst(src: any, repl: any): string; + /** + * Returns a copy of {@code src} in which at most {@code maxReplaces} matches for this regexp have + * been replaced by the return value of of function {@code repl} (whose first argument is the + * matched string). No support is provided for expressions (e.g. {@code \1} or {@code $1}) in the + * replacement string. + */ + replaceAllFunc(src: any, replFunc: any, maxReplaces: any): string; + pad(a: any): any; + allMatches(input: any, n: any, deliverFun?: (v: any) => any): any[]; + /** + * Returns an array holding the text of the leftmost match in {@code b} of this regular + * expression. + * + * A return value of null indicates no match. + */ + findUTF8(b: any): any; + /** + * Returns a two-element array of integers defining the location of the leftmost match in + * {@code b} of this regular expression. The match itself is at {@code b[loc[0]...loc[1]]}. + * + * A return value of null indicates no match. + */ + findUTF8Index(b: any): any; + /** + * Returns a string holding the text of the leftmost match in {@code s} of this regular + * expression. + * + * If there is no match, the return value is an empty string, but it will also be empty if the + * regular expression successfully matches an empty string. Use {@link #findIndex} or + * {@link #findSubmatch} if it is necessary to distinguish these cases. + */ + find(s: any): any; + /** + * Returns a two-element array of integers defining the location of the leftmost match in + * {@code s} of this regular expression. The match itself is at + * {@code s.substring(loc[0], loc[1])}. + * + * A return value of null indicates no match. + */ + findIndex(s: any): any; + /** + * Returns an array of arrays the text of the leftmost match of the regular expression in + * {@code b} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findUTF8Submatch(b: any): any[]; + /** + * Returns an array holding the index pairs identifying the leftmost match of this regular + * expression in {@code b} and the matches, if any, of its subexpressions, as defined by the the + * Submatch and Index descriptions above. + * + * A return value of null indicates no match. + */ + findUTF8SubmatchIndex(b: any): any; + /** + * Returns an array of strings holding the text of the leftmost match of the regular expression in + * {@code s} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findSubmatch(s: any): any[]; + /** + * Returns an array holding the index pairs identifying the leftmost match of this regular + * expression in {@code s} and the matches, if any, of its subexpressions, as defined by the Submatch description above. + * + * A return value of null indicates no match. + */ + findSubmatchIndex(s: any): any; + /** + * {@code findAllUTF8()} is the All version of {@link #findUTF8}; it returns a + * list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + * + * TODO(adonovan): think about defining a byte slice view class, like a read-only Go slice backed + * by |b|. + */ + findAllUTF8(b: any, n: any): any[]; + /** + * {@code findAllUTF8Index} is the All version of {@link #findUTF8Index}; it + * returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8Index(b: any, n: any): any[]; + /** + * {@code findAll} is the All version of {@link #find}; it returns a list of up + * to {@code n} successive matches of the expression, as defined by the All + * description above. + * + * A return value of null indicates no match. + */ + findAll(s: any, n: any): any[]; + /** + * {@code findAllIndex} is the All version of {@link #findIndex}; it returns a + * list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllIndex(s: any, n: any): any[]; + /** + * {@code findAllUTF8Submatch} is the All version of {@link #findUTF8Submatch}; + * it returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8Submatch(b: any, n: any): any[]; + /** + * {@code findAllUTF8SubmatchIndex} is the All version of + * {@link #findUTF8SubmatchIndex}; it returns a list of up to {@code n} successive matches of the + * expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllUTF8SubmatchIndex(b: any, n: any): any[]; + /** + * {@code findAllSubmatch} is the All version of {@link #findSubmatch}; it + * returns a list of up to {@code n} successive matches of the expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllSubmatch(s: any, n: any): any[]; + /** + * {@code findAllSubmatchIndex} is the All version of + * {@link #findSubmatchIndex}; it returns a list of up to {@code n} successive matches of the + * expression, as defined by the All description above. + * + * A return value of null indicates no match. + */ + findAllSubmatchIndex(s: any, n: any): any[]; +} +/** + * Creates an RE2JS regex directly from a template literal. + * @overload + * @param {TemplateStringsArray} stringsOrFlags - The raw string segments of the template literal. + * @param {...any} values - The interpolated values. + * @returns {RE2JS} + */ +export function re(stringsOrFlags: TemplateStringsArray, ...values: any[]): RE2JS; +/** + * Creates a template literal tag function with specific RE2JS flags. + * @overload + * @param {number} stringsOrFlags - The RE2JS flags to apply (e.g., RE2JS.CASE_INSENSITIVE). + * @returns {(strings: TemplateStringsArray, ...tagValues: any[]) => RE2JS} + */ +export function re(stringsOrFlags: number): (strings: TemplateStringsArray, ...tagValues: any[]) => RE2JS; +/** + * A compiled representation of an RE2 regular expression + * + * The matching functions take {@code String} arguments instead of the more general Java + * {@code CharSequence} since the latter doesn't provide UTF-16 decoding. + * + * + * @author rsc@google.com (Russ Cox) + * @class + */ +export class RE2JS { + /** + * Flag: case insensitive matching. + */ + static CASE_INSENSITIVE: number; + /** + * Flag: dot ({@code .}) matches all characters, including newline. + */ + static DOTALL: number; + /** + * Flag: multiline matching: {@code ^} and {@code $} match at beginning and end of line, not just + * beginning and end of input. + */ + static MULTILINE: number; + /** + * Flag: Unicode groups (e.g. {@code \p\ Greek\} ) will be syntax errors. + */ + static DISABLE_UNICODE_GROUPS: number; + /** + * Flag: matches longest possible string. + */ + static LONGEST_MATCH: number; + /** + * Flag: enable linear-time captureless lookbehinds. + */ + static LOOKBEHINDS: number; + /** + * Returns a literal pattern string for the specified string. + * + * This method produces a string that can be used to create a RE2JS that would + * match the string s as if it were a literal pattern. + * + * Metacharacters or escape sequences in the input sequence will be given no special meaning. + * + * @param {string} str The string to be literalized + * @returns {string} A literal string replacement + */ + static quote(str: string): string; + /** + * Quotes '\' and '$' in {@code str}, so that the returned string could be used in + * replacement methods as a literal replacement of {@code str}. + * + * This is a convenience delegation to {@link Matcher.quoteReplacement}. + * + * @param {string} str the string to be quoted + * @param {boolean} [javaMode=false] whether the replacement will be used in javaMode + * @returns {string} the quoted string + */ + static quoteReplacement(str: string, javaMode?: boolean): string; + /** + * Translates a given regular expression string to ensure compatibility with RE2JS. + * + * This function preprocesses the input regex string by applying necessary transformations, + * such as escaping special characters (e.g., `/`), converting named capture groups to + * RE2JS-compatible syntax, and handling Unicode sequences properly. It ensures that the + * resulting regex is safe and properly formatted before compilation. + * + * @param {string|RegExp} expr - The regular expression string to be translated. + * @returns {string} - The transformed regular expression string, ready for compilation. + */ + static translateRegExp(expr: string | RegExp): string; + /** + * Helper: create new RE2JS with given regex and flags. Flregex is the regex with flags applied. + * @param {string} regex + * @param {number} [flags=0] + * @returns {RE2JS} + */ + static compile(regex: string, flags?: number): RE2JS; + /** + * Matches a string against a regular expression. + * + * @param {string} regex the regular expression + * @param {string|number[]|Uint8Array} input the input + * @returns {boolean} true if the regular expression matches the entire input + * @throws RE2JSSyntaxException if the regular expression is malformed + */ + static matches(regex: string, input: string | number[] | Uint8Array): boolean; + /** + * This is visible for testing. + * @private + */ + private static initTest; + /** + * + * @param {string} pattern + * @param {number} flags + */ + constructor(pattern: string, flags: number); + patternInput: string; + flagsInput: number; + /** @type {import('./RE2.js').RE2} */ + re2Input: RE2; + /** + * Releases memory used by internal caches associated with this pattern. Does not change the + * observable behaviour. Useful for tests that detect memory leaks via allocation tracking. + */ + reset(): void; + /** + * Returns the flags used in the constructor. + * @returns {number} + */ + flags(): number; + /** + * Returns the pattern used in the constructor. + * @returns {string} + */ + pattern(): string; + re2(): RE2; + /** + * Matches a string against a regular expression. + * + * @param {string|number[]|Uint8Array} input the input + * @returns {boolean} true if the regular expression matches the entire input + */ + matches(input: string | number[] | Uint8Array): boolean; + /** + * Creates a new {@code Matcher} matching the pattern against the input. + * + * @param {string|number[]|Uint8Array|MatcherInputBase} input the input string + * @returns {Matcher} + */ + matcher(input: string | number[] | Uint8Array | MatcherInputBase): Matcher; + /** + * Tests whether the regular expression matches any part of the input string. + * Performance Note: This method is highly optimized. Because it only returns + * a boolean and does not extract capture groups, it bypasses the `Matcher` overhead + * and guarantees execution on the high-speed DFA engine whenever possible. + * + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to test against. + * @returns {boolean} `true` if the pattern is found anywhere in the input, `false` otherwise. + */ + test(input: string | number[] | Uint8Array): boolean; + /** + * Tests whether the regular expression matches the ENTIRE input string. + * * **Performance Note:** This operates identically to `.matches()`, but is significantly + * faster because it does not request capture group data. By requesting 0 capture groups, + * it securely routes execution through the DFA fast-path. + * + * @param {string|number[]|Uint8Array} input - The input string or UTF-8 byte array to test against. + * @returns {boolean} `true` if the exact input string fully matches the pattern, `false` otherwise. + */ + testExact(input: string | number[] | Uint8Array): boolean; + /** + * Executes a search for a match in a specified string. + * Returns a result array, or null if no match is found. + * The returned array perfectly mirrors standard JavaScript `RegExpExecArray`, + * including `.index`, `.input`, and `.groups` properties. + * + * @param {string|number[]|Uint8Array} input the input string or byte array + * @returns {Array|null} the match array with index, input, and groups properties, or null + */ + exec(input: string | number[] | Uint8Array): any[] | null; + /** + * Splits input around instances of the regular expression. It returns an array giving the strings + * that occur before, between, and after instances of the regular expression. + * + * If {@code limit <= 0}, there is no limit on the size of the returned array. If + * {@code limit == 0}, empty strings that would occur at the end of the array are omitted. If + * {@code limit > 0}, at most limit strings are returned. The final string contains the remainder + * of the input, possibly including additional matches of the pattern. + * + * @param {string} input the input string to be split + * @param {number} [limit=0] the limit + * @returns {string[]} the split strings + */ + split(input: string, limit?: number): string[]; + /** + * Returns an iterator of all results matching a string against the regular expression, + * including capturing groups. + * + * @param {string|number[]|Uint8Array} input the input string or byte array + * @returns {IterableIterator} + */ + matchAll(input: string | number[] | Uint8Array): IterableIterator; + /** + * + * @returns {string} + */ + toString(): string; + /** + * Returns the program size of this pattern. + * + *

+ * Similar to the C++ implementation, the program size is a very approximate measure of a regexp's + * "cost". Larger numbers are more expensive than smaller numbers. + *

+ * + * @returns {number} the program size of this pattern + */ + programSize(): number; + /** + * Returns the number of capturing groups in this matcher's pattern. Group zero denotes the entire + * pattern and is excluded from this count. + * + * @returns {number} the number of capturing groups in this pattern + */ + groupCount(): number; + /** + * Return a map of the capturing groups in this matcher's pattern, where key is the name and value + * is the index of the group in the pattern. + * @returns {Record} + */ + namedGroups(): Record; + /** + * + * @param {*} other + * @returns {boolean} + */ + equals(other: any): boolean; +} + +export {}; diff --git a/src/verify.cts b/src/verify.cts index 5a40278f0..d571ded9b 100644 --- a/src/verify.cts +++ b/src/verify.cts @@ -30,6 +30,7 @@ import { execGit, platformReadSync as safeReadFile } from './shell-command-proje import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs'; import { detectSchemaFiles, checkSchemaDrift } from './schema-detect.cjs'; import { extractTaggedBlocks } from './markdown-sectionizer.cjs'; +import { compileUserPattern, MAX_USER_PATTERN_LEN } from './pattern.cjs'; // eslint-disable-next-line @typescript-eslint/no-require-imports -- agent-install-check.cjs is an export= CommonJS module import agentInstallCheck = require('./agent-install-check.cjs'); const { checkAgentsInstalled, checkCodexModelPosture } = agentInstallCheck; @@ -1235,22 +1236,54 @@ function cmdVerifyKeyLinks(cwd: string, planFilePath: string, raw: boolean): voi check['detail'] = 'Source file not found (from: must be a relative file path; describe components/endpoints in via:)'; } } else if (link['pattern']) { - try { - const regex = new RegExp(link['pattern'] as string); - if (regex.test(sourceContent)) { - check['verified'] = true; - check['detail'] = 'Pattern found in source'; - } else { - const targetContent = safeReadFile(path.join(cwd, (link['to'] as string) || '')); - if (targetContent && regex.test(targetContent)) { - check['verified'] = true; - check['detail'] = 'Pattern found in target'; - } else { - check['detail'] = `Pattern "${link['pattern'] as string}" not found in source or target`; - } + const pat = compileUserPattern(link['pattern']); + if (pat.neutralized !== null) { + // A neutralized pattern was refused and never compiled — that is NOT + // the check the plan author wrote, so it must never report verified + // regardless of what an unrelated fallback might otherwise have + // matched (#3477 regression: pattern "(" previously neutralized to a + // literal-escaped match that matched nearly any source file, + // producing a false verified: true / all_verified: true). The engine + // itself now guarantees `test()` returns false for a refused + // pattern; this explicit branch exists to produce the good message. + // The match-and-report path below is skipped entirely rather than + // run and then overwritten, which used to leave a misleading + // "Pattern found in source" detail alongside the neutralization note. + check['pattern_neutralized'] = pat.neutralized; + let reason: string; + switch (pat.neutralized) { + case 'empty': + reason = 'pattern is not a usable string — no match attempted'; + break; + case 'too-long': + reason = `pattern exceeded ${MAX_USER_PATTERN_LEN} chars — not evaluated`; + break; + case 'unsupported': + reason = 'pattern is not valid RE2 syntax — backreferences and look-around are not supported'; + break; + } + check['detail'] = `Pattern not verified (${reason})`; + } else { + try { + if (pat.test(sourceContent)) { + check['verified'] = true; + check['detail'] = 'Pattern found in source'; + } else { + const targetContent = safeReadFile(path.join(cwd, (link['to'] as string) || '')); + if (targetContent && pat.test(targetContent)) { + check['verified'] = true; + check['detail'] = 'Pattern found in target'; + } else { + check['detail'] = `Pattern "${link['pattern'] as string}" not found in source or target`; + } + } + } catch (err) { + // Report the errno only — never the full error/message, which for a + // re-thrown non-ENOENT platformReadSync failure (e.g. EISDIR from an + // untrusted `to:` like "../..") embeds an absolute filesystem path. + const code = (err as NodeJS.ErrnoException)?.code ?? 'unknown'; + check['detail'] = `Pattern check failed: ${code}`; } - } catch { - check['detail'] = `Invalid regex pattern: ${link['pattern'] as string}`; } } else { if (sourceContent.includes((link['to'] as string) || '')) { diff --git a/tests/pattern.test.cjs b/tests/pattern.test.cjs index 9dc3cb858..fe7a1ed83 100644 --- a/tests/pattern.test.cjs +++ b/tests/pattern.test.cjs @@ -24,7 +24,7 @@ const assert = require('node:assert/strict'); // other *.property.test.cjs file. seed: 42, overridable via GSD_FC_SEED. const fc = require('./helpers/fast-check-setup.cjs'); -const { escapeRegex, literalPattern } = require('../gsd-core/bin/lib/pattern.cjs'); +const { escapeRegex, literalPattern, compileUserPattern, MAX_USER_PATTERN_LEN } = require('../gsd-core/bin/lib/pattern.cjs'); // ─── Section 1: escapeRegex — rows 1-10 ─────────────────────────────────── @@ -249,3 +249,164 @@ describe('escapeRegex without RegExp.escape (#3498 Node-22 fallback)', () => { assert.strictEqual(r.exitCode, 0, `post-load capture failed: ${r.stdout}\n${r.stderr}`); }); }); + +// ─── Section 5: compileUserPattern — RE2 linear-time engine (#3477) ──────── +// +// re2js guarantees match time linear in input length — there is no +// backtracking engine to exploit, so the vulnerability class is closed by +// the engine rather than detected by a heuristic scan of the pattern text +// (the hand-rolled `hasRiskyBacktrackingGroup` scanner this seam used to run +// is deleted). Assertions here are BEHAVIORAL ONLY — never against +// `RegExp.source` or any RegExp-shaped property, because the result is no +// longer a RegExp at all. + +describe('compileUserPattern', () => { + test('a plain safe pattern compiles and reports neutralized: null', () => { + const { test: matches, neutralized } = compileUserPattern('fetch.*api/feed'); + assert.strictEqual(matches('do a fetch of the api/feed endpoint'), true); + assert.strictEqual(matches('completely unrelated text'), false); + assert.strictEqual(neutralized, null); + }); + + test('boundary: a pattern at MAX_USER_PATTERN_LEN - 1 compiles', () => { + const p = 'a'.repeat(MAX_USER_PATTERN_LEN - 1); + const { test: matches, neutralized } = compileUserPattern(p); + assert.strictEqual(matches(p), true); + assert.strictEqual(matches('b'.repeat(MAX_USER_PATTERN_LEN - 1)), false); + assert.strictEqual(neutralized, null); + }); + + test('boundary: a pattern at exactly MAX_USER_PATTERN_LEN compiles', () => { + const p = 'a'.repeat(MAX_USER_PATTERN_LEN); + const { test: matches, neutralized } = compileUserPattern(p); + assert.strictEqual(matches(p), true); + assert.strictEqual(matches('b'.repeat(MAX_USER_PATTERN_LEN)), false); + assert.strictEqual(neutralized, null); + }); + + test('boundary: a pattern at MAX_USER_PATTERN_LEN + 1 (513 chars) is refused, never matches', () => { + const p = 'a'.repeat(MAX_USER_PATTERN_LEN + 1); + assert.strictEqual(p.length, 513); + const { test: matches, neutralized } = compileUserPattern(p); + assert.strictEqual(matches(p), false); + assert.strictEqual(matches(p.slice(0, MAX_USER_PATTERN_LEN)), false); + assert.strictEqual(neutralized, 'too-long'); + }); + + test('empty string input is refused, never matches', () => { + const { test: matches, neutralized } = compileUserPattern(''); + assert.strictEqual(matches(''), false); + assert.strictEqual(matches('anything'), false); + assert.strictEqual(neutralized, 'empty'); + }); + + test('non-string input is refused, never matches', () => { + for (const bad of [null, undefined, 42, {}, []]) { + const { test: matches, neutralized } = compileUserPattern(bad); + assert.strictEqual(matches(''), false); + assert.strictEqual(matches('anything'), false); + assert.strictEqual(neutralized, 'empty'); + } + }); + + test('property: for any string input, compileUserPattern returns a well-shaped result and never throws', () => { + // maxLength: 600 straddles MAX_USER_PATTERN_LEN (512) so fast-check actually + // exercises the >512 'too-long' branch. A neutralized result must never be + // able to report a match against arbitrary input — the security invariant. + const validNeutralizations = new Set(['empty', 'too-long', 'unsupported', null]); + fc.assert( + fc.property(fc.string({ maxLength: 600 }), fc.string({ maxLength: 50 }), (s, probe) => { + let result; + assert.doesNotThrow(() => { + result = compileUserPattern(s); + }); + assert.strictEqual(typeof result.test, 'function'); + assert.ok(validNeutralizations.has(result.neutralized)); + if (result.neutralized !== null) { + let matched; + assert.doesNotThrow(() => { + matched = result.test(probe); + }); + assert.strictEqual(matched, false, `neutralized (${result.neutralized}) result must never match: s=${JSON.stringify(s)} probe=${JSON.stringify(probe)}`); + } + }) + ); + }); + + test('a known-valid regex round-trips through the >512-length property test unmangled', () => { + // Companion assertion for the property test above: a legitimate long-ish + // regex must still compile (neutralized: null), not just "didn't throw" — + // pins that the length straddle doesn't accidentally neutralize everything + // under 512 chars too. + const p = 'fetch\\(.*\\)\\.then\\(' + 'x'.repeat(400) + '\\)'; + assert.ok(p.length < MAX_USER_PATTERN_LEN, 'sanity: fixture must stay under the length threshold'); + const { test: matches, neutralized } = compileUserPattern(p); + assert.strictEqual(neutralized, null); + assert.strictEqual(matches('fetch(url).then(' + 'x'.repeat(400) + ')'), true); + assert.strictEqual(matches('unrelated text'), false); + }); +}); + +// ─── Section 6: RE2 engine acceptance table (#3477 follow-up) ───────────── +// +// The prior hand-rolled screen either hung (patterns it missed, run live +// through the JS backtracking engine) or wrongly refused (patterns it +// flagged as risky-shaped that are actually linear) on the rows below. RE2 +// closes both failure modes: every MUST_COMPILE row below is evaluated for +// real, in linear time, with a correct match verdict — no heuristic, +// no false refusal. + +describe('compileUserPattern — RE2 engine acceptance table', () => { + // [pattern, subjectA, subjectB, expectedA, expectedB]. Every one of these + // patterns is plain JS-valid ERE syntax (no backreferences/look-around), so + // the expected/expectedB booleans are exactly what `new RegExp(p).test(...)` + // would report — computed offline rather than hand-assumed, since a + // `*`/`?`-quantified pattern with nothing required outside the optional + // part (e.g. `(a|a)*$`, `(abc)?`, `(abc)*`) trivially matches an unanchored + // `.test()` against ANY subject (zero-width match), which is correct + // JS-regex semantics, not a defect. The expectations are hardcoded rather + // than oracle-derived because running these patterns through the JS + // backtracking engine (`new RegExp(p).test(...)`) is the exact + // catastrophic-backtracking vulnerability this issue is about — the suite + // must never execute that engine against them, even with short subjects. + const MUST_COMPILE = [ + ['(a+)+$', 'aaaa', 'bbbb', true, false], + ['(a|a)*$', 'aaaa', 'aaab', true, true], + ['((a+))+$', 'aaaa', 'aaab', true, false], + ['(a+){2,}$', 'aaaa', 'a', true, false], + ['(a{1,3})+$', 'aaaaaa', 'bbbbbb', true, false], + ['^(\\s*\\w+)+$', 'foo bar baz', 'foo, bar', true, false], + ['fetch.*api/feed', 'do a fetch of the api/feed endpoint', 'completely unrelated text', true, false], + ['prisma\\.message\\.(find|create)', 'call prisma.message.find(x)', 'call prisma.other.find(x)', true, false], + ['(abc)?', 'abc', '', true, true], + ['(abc)*', 'abcabc', 'xyz', true, true], + ['^\\s*export\\s+function\\s+\\w+', ' export function foo() {}', 'const foo = 1', true, false], + ]; + + const MUST_REFUSE = ['(\\w+)\\1', '(?!x)a']; + + for (const [p, subjectA, subjectB, expectedA, expectedB] of MUST_COMPILE) { + test(`compiles and evaluates correctly: ${JSON.stringify(p)}`, () => { + const { test: matches, neutralized } = compileUserPattern(p); + assert.strictEqual(neutralized, null, `expected null for ${JSON.stringify(p)}, got ${JSON.stringify(neutralized)}`); + assert.strictEqual( + matches(subjectA), + expectedA, + `expected ${JSON.stringify(p)} against ${JSON.stringify(subjectA)} to match real regex semantics` + ); + assert.strictEqual( + matches(subjectB), + expectedB, + `expected ${JSON.stringify(p)} against ${JSON.stringify(subjectB)} to match real regex semantics` + ); + }); + } + + for (const p of MUST_REFUSE) { + test(`is refused (unsupported RE2 syntax): ${JSON.stringify(p)}`, () => { + const { test: matches, neutralized } = compileUserPattern(p); + assert.strictEqual(neutralized, 'unsupported', `expected 'unsupported' for ${JSON.stringify(p)}, got ${JSON.stringify(neutralized)}`); + assert.strictEqual(matches('anything'), false); + }); + } +}); diff --git a/tests/verify.test.cjs b/tests/verify.test.cjs index ce616a0ff..478fc4733 100644 --- a/tests/verify.test.cjs +++ b/tests/verify.test.cjs @@ -1760,6 +1760,60 @@ describe('verify key-links command', () => { ); }); + test('a formerly-ReDoS-shaped pattern (nested quantifiers) is evaluated normally via RE2 (#3477)', () => { + // Pre-RE2 this pattern was neutralized (hand-rolled screen) to avoid + // catastrophic backtracking in the JS regex engine. RE2 (re2js) matches + // in linear time by construction, so this is no longer a neutralization + // case at all — the pattern is compiled and evaluated for real. + writePlanWithKeyLinks(tmpDir, [ + '- from: "src/a.js"', + ' to: "src/b.js"', + ' pattern: "(a+)+$"', + ]); + fs.writeFileSync(path.join(tmpDir, 'src', 'a.js'), 'a'.repeat(25) + 'b\n'); + fs.writeFileSync(path.join(tmpDir, 'src', 'b.js'), 'module.exports = {};\n'); + + const result = runGsdTools('verify key-links .planning/phases/01-test/01-01-PLAN.md', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.links[0].verified, false, 'link should not be verified — subject does not end in "a"'); + assert.strictEqual(output.all_verified, false, `Expected all_verified false: ${JSON.stringify(output)}`); + assert.strictEqual( + output.links[0].pattern_neutralized, + undefined, + `RE2 evaluates this pattern normally — pattern_neutralized must be absent: ${JSON.stringify(output.links[0])}` + ); + }); + + test('a refused pattern (unsupported RE2 syntax) never reports verified: true (#3477 regression)', () => { + // pattern: "(?!x)a" is a negative lookahead — RE2 has no backtracking + // engine and does not support look-around, so this is refused outright + // (neutralized: 'unsupported') rather than guessed at via a literal + // fallback. Pre-#3477-fix, a similarly unparseable pattern neutralized to + // a literal-escaped match that happened to match nearly any source file, + // producing a false verified: true / all_verified: true. + writePlanWithKeyLinks(tmpDir, [ + '- from: "src/a.js"', + ' to: "src/b.js"', + ' pattern: "(?!x)a"', + ]); + fs.writeFileSync(path.join(tmpDir, 'src', 'a.js'), 'function f(x) { return x; }\n'); + fs.writeFileSync(path.join(tmpDir, 'src', 'b.js'), 'module.exports = {};\n'); + + const result = runGsdTools('verify key-links .planning/phases/01-test/01-01-PLAN.md', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.links[0].verified, false, 'a neutralized pattern must never report verified: true'); + assert.strictEqual(output.all_verified, false, `Expected all_verified false: ${JSON.stringify(output)}`); + assert.strictEqual( + output.links[0].pattern_neutralized, + 'unsupported', + `Expected pattern_neutralized: 'unsupported': ${JSON.stringify(output.links[0])}` + ); + }); + test('returns error when no key_links in frontmatter', () => { const content = [ '---',