parseman 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler/codegen.d.ts +33 -0
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/scannable-run.d.ts +46 -0
- package/dist/compiler/scannable-run.d.ts.map +1 -0
- package/dist/compiler/trivia-fast-path.d.ts +27 -14
- package/dist/compiler/trivia-fast-path.d.ts.map +1 -1
- package/dist/index.cjs +285 -114
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +285 -114
- package/dist/index.js.map +4 -4
- package/dist/plugin/evaluator.d.ts +18 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +453 -131
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +453 -131
- package/dist/plugin/index.js.map +4 -4
- package/package.json +1 -1
|
@@ -40,4 +40,37 @@ export type CompiledParser<T> = {
|
|
|
40
40
|
* @see https://www.greadme.com/blog/security/what-is-content-security-policy-complete-guide
|
|
41
41
|
*/
|
|
42
42
|
export declare function compile<T>(combinator: Combinator<T>, mapFnSources?: string[]): CompiledParser<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Compile every entry of a `rules(factory)` map's returned object using ONE
|
|
45
|
+
* shared codegen Ctx (regexes, named functions, map/build-fn arrays) instead
|
|
46
|
+
* of running `compile()` independently per entry.
|
|
47
|
+
*
|
|
48
|
+
* Why this exists: `compile()` per entry gives each entry its own namedParsers
|
|
49
|
+
* cache, so a sub-rule reachable from N different top-level entries (e.g.
|
|
50
|
+
* `valueList` reachable from `Declaration`, `CustomDeclaration`, `Guard`, …)
|
|
51
|
+
* gets fully re-compiled N times — on a richly cross-referential grammar
|
|
52
|
+
* (Less: ~125 rule-map entries, deep mutual reference) this multiplies total
|
|
53
|
+
* compiled size far past what the source grammar actually needs, independent
|
|
54
|
+
* of the per-ref single-use inlining `analyzeLazyUsage` already handles
|
|
55
|
+
* within one compile() call. Sharing one Ctx here means a sub-rule compiles
|
|
56
|
+
* exactly once for the WHOLE rule map, however many entries reach it.
|
|
57
|
+
*
|
|
58
|
+
* Returns a single `replacement` — ONE shared IIFE, evaluated once, whose
|
|
59
|
+
* result is the `{ key: fn, ... }` map — meant to replace the entire
|
|
60
|
+
* `rules(factory)` call-expression (not one expression per key; splicing a
|
|
61
|
+
* separate self-contained expression per key would either re-run the shared
|
|
62
|
+
* prelude once per entry or duplicate its text per entry, undoing the win).
|
|
63
|
+
* `keys` lists every entry compileRuleMap() saw, for the caller to validate
|
|
64
|
+
* against the source's own key list.
|
|
65
|
+
*
|
|
66
|
+
* Returns null (same all-or-nothing contract the per-entry `compile()` +
|
|
67
|
+
* `inlineExpression === null` check gave the plugin before) when the map
|
|
68
|
+
* contains anything that can't be inlined — a runtime-fallback parser, or an
|
|
69
|
+
* uncaptured transform/build closure source. The plugin's existing "warn and
|
|
70
|
+
* leave this rules() call interpreted" fallback covers this case unchanged.
|
|
71
|
+
*/
|
|
72
|
+
export declare function compileRuleMap(ruleMap: ReadonlyArray<readonly [string, Combinator<unknown>]>): {
|
|
73
|
+
keys: string[];
|
|
74
|
+
replacement: string;
|
|
75
|
+
} | null;
|
|
43
76
|
//# sourceMappingURL=codegen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/compiler/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,UAAU,EAAuB,WAAW,EAAE,YAAY,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/compiler/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,UAAU,EAAuB,WAAW,EAAE,YAAY,EAAE,UAAU,EAAkB,MAAM,aAAa,CAAA;AAoiDzH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAClD,kGAAkG;IAClG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAChF;;;;OAIG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG;QAAE,MAAM,EAAE,UAAU,EAAE,CAAA;KAAE,CAAA;IACvF,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC,CAAA;AAoJD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAsGhG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,GAC7D;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4EhD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural recognition of "scannable" parser arms — regex shapes that lower to
|
|
3
|
+
* a tight character-scan loop instead of `regex.exec` / combinator dispatch. Each
|
|
4
|
+
* shape is derived from the regex STRUCTURE, not from any hardcoded knowledge
|
|
5
|
+
* that a given regex "is whitespace" or "is a comment":
|
|
6
|
+
*
|
|
7
|
+
* [X]+ / [X]* → run while the char ∈ X (chars)
|
|
8
|
+
* <lit>[^X]* → consume <lit>, run until char ∈ X (until)
|
|
9
|
+
* <open>(?:…)*<close> → consume <open>, run to <close> literal (delimited)
|
|
10
|
+
*
|
|
11
|
+
* A `oneOrMore(choice(a, b, …))` where every arm is one of these compiles to a
|
|
12
|
+
* single char-dispatch loop with one branch per arm — any count/order, because
|
|
13
|
+
* the shapes dispatch on their first 1–2 chars and are checked in turn. Trivia
|
|
14
|
+
* (whitespace + comments) is just the value-discarded instance of this; nothing
|
|
15
|
+
* here is trivia-specific.
|
|
16
|
+
*/
|
|
17
|
+
export type ScanShape = {
|
|
18
|
+
kind: 'chars';
|
|
19
|
+
ranges: Array<[number, number]>;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'until';
|
|
22
|
+
open: number[];
|
|
23
|
+
stop: Array<[number, number]>;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'delimited';
|
|
26
|
+
open: number[];
|
|
27
|
+
close: number[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Recognize one scannable arm from its regex source, or null if it isn't one of
|
|
31
|
+
* the three structural shapes. Order matters: char-class run, then
|
|
32
|
+
* open-until-terminator, then delimited.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseScanShape(source: string): ScanShape | null;
|
|
35
|
+
/**
|
|
36
|
+
* One loop branch for a shape, dispatched on the current char `c` (= charCodeAt(_e)).
|
|
37
|
+
* Each branch advances `_e` past the matched token and `continue`s the scan loop.
|
|
38
|
+
*/
|
|
39
|
+
export declare function scanBranch(shape: ScanShape): string;
|
|
40
|
+
/**
|
|
41
|
+
* A labeled branch: consume the shape's full token and log one [start, end,
|
|
42
|
+
* kindIndex] trivia chunk. Unlike scanBranch this always consumes the whole run
|
|
43
|
+
* (a chunk must span the entire token) and captures inline per-chunk.
|
|
44
|
+
*/
|
|
45
|
+
export declare function scanBranchLabeled(shape: ScanShape, kindIndex: number): string;
|
|
46
|
+
//# sourceMappingURL=scannable-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scannable-run.d.ts","sourceRoot":"","sources":["../../src/compiler/scannable-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAwD1D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA4B/D;AAuDD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAQnD;AAYD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAM7E"}
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
import type { Combinator } from '../types.ts';
|
|
2
2
|
import type { LabeledTriviaSpec } from '../cst/trivia-kinds.ts';
|
|
3
|
-
|
|
3
|
+
import { type ScanShape } from './scannable-run.ts';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* A trivia parser that lowers to a hand-rolled char-scan loop: `oneOrMore(choice(
|
|
6
|
+
* …))` (or `many`, min≥1) where EVERY arm is a scannable shape (a `[X]+` run, a
|
|
7
|
+
* `<lit>[^X]*` open-until-terminator, or a `<open>(?:…)*<close>` delimited token
|
|
8
|
+
* — see scannable-run.ts). Returns the per-arm shapes, or a single-element list
|
|
9
|
+
* for a bare scannable regex, or null (→ caller falls back to the regex/generic
|
|
10
|
+
* trivia path). A single alternation regex is NOT scannable (one exec matches one
|
|
11
|
+
* arm per call, defeating the loop).
|
|
8
12
|
*/
|
|
9
|
-
export declare function analyzeTriviaFastPath(trivia: Combinator<unknown>):
|
|
10
|
-
/** Emit a specialized `_tfN` that skips trivia
|
|
11
|
-
export declare function buildFastTriviaFnDecl(fnName: string,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
export declare function analyzeTriviaFastPath(trivia: Combinator<unknown>): ScanShape[] | null;
|
|
14
|
+
/** Emit a specialized `_tfN` that skips (unlabeled) trivia via a char-scan loop. */
|
|
15
|
+
export declare function buildFastTriviaFnDecl(fnName: string, shapes: ScanShape[]): string;
|
|
16
|
+
/**
|
|
17
|
+
* A LABELED trivia parser whose every arm is scannable → per-arm shape + kind
|
|
18
|
+
* index, or null. Same recognition as analyzeTriviaFastPath, but keyed by the
|
|
19
|
+
* labeled arm kinds so the fast loop can log each chunk's kind. Generalizes what
|
|
20
|
+
* used to be a hardcoded ws-run + block-comment special case to any scannable
|
|
21
|
+
* shape set / arm count.
|
|
22
|
+
*/
|
|
23
|
+
export declare function analyzeLabeledScannableRun(trivia: Combinator<unknown>): Array<{
|
|
24
|
+
shape: ScanShape;
|
|
25
|
+
kindIndex: number;
|
|
26
|
+
}> | null;
|
|
27
|
+
/** Emit a labeled scannable trivia `_tfN`: char-scan loop with per-chunk kind capture. */
|
|
28
|
+
export declare function buildLabeledScannableTriviaFnDecl(fnName: string, arms: ReadonlyArray<{
|
|
29
|
+
shape: ScanShape;
|
|
30
|
+
kindIndex: number;
|
|
31
|
+
}>): string;
|
|
15
32
|
/** Labeled trivia with regex arms — per-chunk kind capture in compiled output. */
|
|
16
33
|
export declare function buildLabeledRegexTriviaFnDecl(fnName: string, spec: LabeledTriviaSpec, reNames: string[]): string;
|
|
17
|
-
export declare function labeledTriviaKindIndices(trivia: Combinator<unknown>): {
|
|
18
|
-
ws: number;
|
|
19
|
-
comment: number;
|
|
20
|
-
} | null;
|
|
21
34
|
export declare function labeledTriviaRegexArms(trivia: Combinator<unknown>): LabeledTriviaSpec | null;
|
|
22
35
|
/** Labeled trivia via per-arm runtime parsers (non-regex or mixed arms). */
|
|
23
36
|
export declare function buildLabeledRuntimeTriviaFnDecl(fnName: string, spec: LabeledTriviaSpec, rpStartIndex: number): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trivia-fast-path.d.ts","sourceRoot":"","sources":["../../src/compiler/trivia-fast-path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"trivia-fast-path.d.ts","sourceRoot":"","sources":["../../src/compiler/trivia-fast-path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE/D,OAAO,EAAE,KAAK,SAAS,EAAiD,MAAM,oBAAoB,CAAA;AA0BlG;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,SAAS,EAAE,GAAG,IAAI,CAyBrF;AAoBD,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CASjF;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAC1B,KAAK,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAWvD;AAED,0FAA0F;AAC1F,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,GAC3D,MAAM,CAYR;AAED,kFAAkF;AAClF,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,CA+BR;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,iBAAiB,GAAG,IAAI,CAO5F;AAED,4EAA4E;AAC5E,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,iBAAiB,EACvB,YAAY,EAAE,MAAM,GACnB,MAAM,CA8BR"}
|
package/dist/index.cjs
CHANGED
|
@@ -1367,14 +1367,140 @@ function parse(combinator, input, opts = {}) {
|
|
|
1367
1367
|
return withErrors;
|
|
1368
1368
|
}
|
|
1369
1369
|
|
|
1370
|
+
// src/compiler/scannable-run.ts
|
|
1371
|
+
var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
|
|
1372
|
+
var META = new Set("()[]{}*+?|^$.".split(""));
|
|
1373
|
+
function parseClassRanges(body) {
|
|
1374
|
+
const ranges = [];
|
|
1375
|
+
let i = 0;
|
|
1376
|
+
const readCp = () => {
|
|
1377
|
+
const ch = body[i];
|
|
1378
|
+
if (ch === void 0) return null;
|
|
1379
|
+
if (ch === "\\") {
|
|
1380
|
+
const e = body[i + 1];
|
|
1381
|
+
if (e === void 0) return null;
|
|
1382
|
+
i += 2;
|
|
1383
|
+
return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
|
|
1384
|
+
}
|
|
1385
|
+
i += ch.length;
|
|
1386
|
+
return ch.codePointAt(0);
|
|
1387
|
+
};
|
|
1388
|
+
while (i < body.length) {
|
|
1389
|
+
const lo = readCp();
|
|
1390
|
+
if (lo === null) return null;
|
|
1391
|
+
if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
|
|
1392
|
+
i += 1;
|
|
1393
|
+
const hi = readCp();
|
|
1394
|
+
if (hi === null) return null;
|
|
1395
|
+
ranges.push([lo, hi]);
|
|
1396
|
+
} else {
|
|
1397
|
+
ranges.push([lo, lo]);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return ranges.length ? ranges : null;
|
|
1401
|
+
}
|
|
1402
|
+
function literalCodePoints(frag) {
|
|
1403
|
+
const out = [];
|
|
1404
|
+
let i = 0;
|
|
1405
|
+
while (i < frag.length) {
|
|
1406
|
+
const ch = frag[i];
|
|
1407
|
+
if (ch === "\\") {
|
|
1408
|
+
const e = frag[i + 1];
|
|
1409
|
+
if (e === void 0) return null;
|
|
1410
|
+
out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
|
|
1411
|
+
i += 2;
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
if (META.has(ch)) return null;
|
|
1415
|
+
out.push(ch.codePointAt(0));
|
|
1416
|
+
i += 1;
|
|
1417
|
+
}
|
|
1418
|
+
return out.length ? out : null;
|
|
1419
|
+
}
|
|
1420
|
+
function parseScanShape(source) {
|
|
1421
|
+
let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
|
|
1422
|
+
if (m) {
|
|
1423
|
+
if (m[1].startsWith("^")) return null;
|
|
1424
|
+
const ranges = parseClassRanges(m[1]);
|
|
1425
|
+
return ranges ? { kind: "chars", ranges } : null;
|
|
1426
|
+
}
|
|
1427
|
+
m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
|
|
1428
|
+
if (m) {
|
|
1429
|
+
const open = literalCodePoints(m[1]);
|
|
1430
|
+
const stop = parseClassRanges(m[2]);
|
|
1431
|
+
if (open && stop) return { kind: "until", open, stop };
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
if (!source.includes("\\\\")) {
|
|
1435
|
+
m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
|
|
1436
|
+
if (m && m[1] && m[2]) {
|
|
1437
|
+
const open = literalCodePoints(m[1]);
|
|
1438
|
+
const close = literalCodePoints(m[2]);
|
|
1439
|
+
if (open && close) return { kind: "delimited", open, close };
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
|
|
1445
|
+
var litCond = (base, cps, firstVar) => cps.map(
|
|
1446
|
+
(cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
|
|
1447
|
+
).join(" && ");
|
|
1448
|
+
function advanceExpr(shape) {
|
|
1449
|
+
if (shape.kind === "chars") {
|
|
1450
|
+
return {
|
|
1451
|
+
setup: [
|
|
1452
|
+
` _e++`,
|
|
1453
|
+
` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
|
|
1454
|
+
],
|
|
1455
|
+
endVar: "_e"
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
if (shape.kind === "until") {
|
|
1459
|
+
return {
|
|
1460
|
+
setup: [
|
|
1461
|
+
` let j = _e + ${shape.open.length}`,
|
|
1462
|
+
` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
|
|
1463
|
+
],
|
|
1464
|
+
endVar: "j"
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
const n = shape.close.length;
|
|
1468
|
+
return {
|
|
1469
|
+
setup: [
|
|
1470
|
+
` let j = _e + ${shape.open.length}`,
|
|
1471
|
+
` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
|
|
1472
|
+
],
|
|
1473
|
+
endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function guardLine(shape) {
|
|
1477
|
+
const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
|
|
1478
|
+
return ` if (${cond}) {`;
|
|
1479
|
+
}
|
|
1480
|
+
function scanBranch(shape) {
|
|
1481
|
+
if (shape.kind === "chars") {
|
|
1482
|
+
return ` if (${classCond("c", shape.ranges)}) { _e++; continue }`;
|
|
1483
|
+
}
|
|
1484
|
+
const { setup, endVar } = advanceExpr(shape);
|
|
1485
|
+
return [guardLine(shape), ...setup, ` _e = ${endVar}`, ` continue`, ` }`].join("\n");
|
|
1486
|
+
}
|
|
1487
|
+
function captureChunk(startVar, kindIndex) {
|
|
1488
|
+
return [
|
|
1489
|
+
` if (_cap) {`,
|
|
1490
|
+
` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(${startVar}, _e, ${kindIndex})`,
|
|
1491
|
+
` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(${startVar}, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
|
|
1492
|
+
` }`
|
|
1493
|
+
];
|
|
1494
|
+
}
|
|
1495
|
+
function scanBranchLabeled(shape, kindIndex) {
|
|
1496
|
+
const { setup, endVar } = advanceExpr(shape);
|
|
1497
|
+
const lines = [guardLine(shape), ` const _cs = _e`, ...setup];
|
|
1498
|
+
if (endVar !== "_e") lines.push(` _e = ${endVar}`);
|
|
1499
|
+
lines.push(...captureChunk("_cs", kindIndex), ` continue`, ` }`);
|
|
1500
|
+
return lines.join("\n");
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1370
1503
|
// src/compiler/trivia-fast-path.ts
|
|
1371
|
-
var BLOCK_COMMENT = String.raw`\/\*(?:[^*]|\*(?!\/))*\*\/`;
|
|
1372
|
-
var WS_CLASS_SOURCES = /* @__PURE__ */ new Set([
|
|
1373
|
-
String.raw`[ \t\n\r\f]+`,
|
|
1374
|
-
String.raw`[ \t\n]+`,
|
|
1375
|
-
String.raw`[ \t]+`,
|
|
1376
|
-
String.raw`[\t\n\f\r ]+`
|
|
1377
|
-
]);
|
|
1378
1504
|
function unwrapTrivia2(p) {
|
|
1379
1505
|
let cur = p;
|
|
1380
1506
|
while (cur._def.tag === "trivia") cur = cur._def.parser;
|
|
@@ -1390,36 +1516,30 @@ function choiceArms(p) {
|
|
|
1390
1516
|
if (p._def.tag === "choice") return p._def.parsers;
|
|
1391
1517
|
return null;
|
|
1392
1518
|
}
|
|
1393
|
-
function
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
function isBlockCommentSource(source) {
|
|
1397
|
-
return source === BLOCK_COMMENT;
|
|
1519
|
+
function armShape(arm) {
|
|
1520
|
+
const src = getCoreRegexDef(arm)?.source;
|
|
1521
|
+
return src ? parseScanShape(src) : null;
|
|
1398
1522
|
}
|
|
1399
1523
|
function analyzeTriviaFastPath(trivia2) {
|
|
1400
1524
|
const core = unwrapTrivia2(trivia2);
|
|
1401
|
-
const
|
|
1402
|
-
if (
|
|
1403
|
-
|
|
1404
|
-
return null;
|
|
1525
|
+
const directSrc = getCoreRegexDef(core)?.source;
|
|
1526
|
+
if (directSrc) {
|
|
1527
|
+
const shape = parseScanShape(directSrc);
|
|
1528
|
+
return shape && shape.kind === "chars" ? [shape] : null;
|
|
1405
1529
|
}
|
|
1406
1530
|
const inner = oneOrMoreInner(core);
|
|
1407
1531
|
if (!inner) return null;
|
|
1408
|
-
const
|
|
1409
|
-
if (
|
|
1532
|
+
const innerShape = armShape(inner);
|
|
1533
|
+
if (innerShape) return innerShape.kind === "chars" ? [innerShape] : null;
|
|
1410
1534
|
const arms = choiceArms(inner);
|
|
1411
|
-
if (!arms || arms.length
|
|
1412
|
-
|
|
1413
|
-
let hasComment = false;
|
|
1535
|
+
if (!arms || arms.length < 2) return null;
|
|
1536
|
+
const shapes = [];
|
|
1414
1537
|
for (const arm of arms) {
|
|
1415
|
-
const
|
|
1416
|
-
if (!
|
|
1417
|
-
|
|
1418
|
-
else if (isBlockCommentSource(src)) hasComment = true;
|
|
1419
|
-
else return null;
|
|
1538
|
+
const shape = armShape(arm);
|
|
1539
|
+
if (!shape) return null;
|
|
1540
|
+
shapes.push(shape);
|
|
1420
1541
|
}
|
|
1421
|
-
|
|
1422
|
-
return null;
|
|
1542
|
+
return shapes;
|
|
1423
1543
|
}
|
|
1424
1544
|
var CAP_RECORD = [
|
|
1425
1545
|
` if (_cap && _e > _pos) {`,
|
|
@@ -1427,80 +1547,49 @@ var CAP_RECORD = [
|
|
|
1427
1547
|
` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_pos, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0)`,
|
|
1428
1548
|
` }`
|
|
1429
1549
|
].join("\n");
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
` if (c === 47 && input.charCodeAt(_e + 1) === 42) {`,
|
|
1442
|
-
` let j = _e + 2`,
|
|
1443
|
-
` while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++`,
|
|
1444
|
-
` _e = j + 2 <= input.length ? j + 2 : input.length`,
|
|
1445
|
-
` continue`,
|
|
1446
|
-
` }`,
|
|
1447
|
-
` break`,
|
|
1448
|
-
` }`
|
|
1449
|
-
].join("\n");
|
|
1450
|
-
var CAP_CHUNK = (wsKind, commentKind) => [
|
|
1451
|
-
` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) {`,
|
|
1452
|
-
` const _cs = _e`,
|
|
1453
|
-
` _e++`,
|
|
1454
|
-
` while (_e < input.length) {`,
|
|
1455
|
-
` const c2 = input.charCodeAt(_e)`,
|
|
1456
|
-
` if (c2 === 32 || c2 === 9 || c2 === 10 || c2 === 13 || c2 === 12) _e++`,
|
|
1457
|
-
` else break`,
|
|
1458
|
-
` }`,
|
|
1459
|
-
` if (_cap) {`,
|
|
1460
|
-
` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_cs, _e, ${wsKind})`,
|
|
1461
|
-
` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_cs, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${wsKind})`,
|
|
1462
|
-
` }`,
|
|
1463
|
-
` continue`,
|
|
1464
|
-
` }`,
|
|
1465
|
-
` if (c === 47 && input.charCodeAt(_e + 1) === 42) {`,
|
|
1466
|
-
` const _cs = _e`,
|
|
1467
|
-
` let j = _e + 2`,
|
|
1468
|
-
` while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++`,
|
|
1469
|
-
` _e = j + 2 <= input.length ? j + 2 : input.length`,
|
|
1470
|
-
` if (_cap) {`,
|
|
1471
|
-
` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_cs, _e, ${commentKind})`,
|
|
1472
|
-
` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_cs, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${commentKind})`,
|
|
1473
|
-
` }`,
|
|
1474
|
-
` continue`,
|
|
1475
|
-
` }`
|
|
1476
|
-
].join("\n");
|
|
1477
|
-
var WS_COMMENTS_LOOP_LABELED = (wsKind, commentKind) => [
|
|
1478
|
-
` while (_e < input.length) {`,
|
|
1479
|
-
` const c = input.charCodeAt(_e)`,
|
|
1480
|
-
CAP_CHUNK(wsKind, commentKind),
|
|
1481
|
-
` break`,
|
|
1482
|
-
` }`
|
|
1483
|
-
].join("\n");
|
|
1484
|
-
function buildFastTriviaFnDecl(fnName, kind, kindIndices) {
|
|
1485
|
-
const loop = kind === "wsComments" && kindIndices ? WS_COMMENTS_LOOP_LABELED(kindIndices.ws, kindIndices.comment) : kind === "wsComments" ? WS_COMMENTS_LOOP : WS_ONLY_LOOP;
|
|
1486
|
-
const cap = kindIndices ? "" : CAP_RECORD;
|
|
1487
|
-
const lines = [
|
|
1550
|
+
function composeFastLoop(shapes) {
|
|
1551
|
+
return [
|
|
1552
|
+
` while (_e < input.length) {`,
|
|
1553
|
+
` const c = input.charCodeAt(_e)`,
|
|
1554
|
+
...shapes.map(scanBranch),
|
|
1555
|
+
` break`,
|
|
1556
|
+
` }`
|
|
1557
|
+
].join("\n");
|
|
1558
|
+
}
|
|
1559
|
+
function buildFastTriviaFnDecl(fnName, shapes) {
|
|
1560
|
+
return [
|
|
1488
1561
|
`function ${fnName}(input, _pos, _ctx, _cap) {`,
|
|
1489
1562
|
` let _e = _pos`,
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1563
|
+
composeFastLoop(shapes),
|
|
1564
|
+
CAP_RECORD,
|
|
1565
|
+
` return _e`,
|
|
1566
|
+
`}`
|
|
1567
|
+
].join("\n");
|
|
1495
1568
|
}
|
|
1496
|
-
function
|
|
1569
|
+
function analyzeLabeledScannableRun(trivia2) {
|
|
1570
|
+
const spec = analyzeLabeledTrivia(trivia2);
|
|
1571
|
+
if (!spec) return null;
|
|
1572
|
+
const out = [];
|
|
1497
1573
|
for (const arm of spec.arms) {
|
|
1498
1574
|
const src = getCoreRegexDef(arm.parser)?.source;
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1575
|
+
const shape = src ? parseScanShape(src) : null;
|
|
1576
|
+
if (!shape) return null;
|
|
1577
|
+
out.push({ shape, kindIndex: arm.kindIndex });
|
|
1502
1578
|
}
|
|
1503
|
-
return
|
|
1579
|
+
return out;
|
|
1580
|
+
}
|
|
1581
|
+
function buildLabeledScannableTriviaFnDecl(fnName, arms) {
|
|
1582
|
+
return [
|
|
1583
|
+
`function ${fnName}(input, _pos, _ctx, _cap) {`,
|
|
1584
|
+
` let _e = _pos`,
|
|
1585
|
+
` while (_e < input.length) {`,
|
|
1586
|
+
` const c = input.charCodeAt(_e)`,
|
|
1587
|
+
...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
|
|
1588
|
+
` break`,
|
|
1589
|
+
` }`,
|
|
1590
|
+
` return _e`,
|
|
1591
|
+
`}`
|
|
1592
|
+
].join("\n");
|
|
1504
1593
|
}
|
|
1505
1594
|
function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
|
|
1506
1595
|
const tryArms = spec.arms.map((arm, i) => {
|
|
@@ -1533,14 +1622,6 @@ function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
|
|
|
1533
1622
|
`}`
|
|
1534
1623
|
].join("\n");
|
|
1535
1624
|
}
|
|
1536
|
-
function labeledTriviaKindIndices(trivia2) {
|
|
1537
|
-
const spec = analyzeLabeledTrivia(trivia2);
|
|
1538
|
-
if (!spec || spec.arms.length !== 2) return null;
|
|
1539
|
-
const ws = regexArmIndex(spec, false);
|
|
1540
|
-
const comment = regexArmIndex(spec, true);
|
|
1541
|
-
if (ws === null || comment === null) return null;
|
|
1542
|
-
return { ws, comment };
|
|
1543
|
-
}
|
|
1544
1625
|
function labeledTriviaRegexArms(trivia2) {
|
|
1545
1626
|
const spec = analyzeLabeledTrivia(trivia2);
|
|
1546
1627
|
if (!spec) return null;
|
|
@@ -1788,14 +1869,19 @@ function ensureTriviaFn(ctx) {
|
|
|
1788
1869
|
const fnName = `_tf${ctx.triviaFnNames.size}`;
|
|
1789
1870
|
ctx.triviaFnNames.set(trivia2, fnName);
|
|
1790
1871
|
ctx.triviaCaptureNames.set(trivia2, fnName);
|
|
1791
|
-
const fastKind = analyzeTriviaFastPath(trivia2);
|
|
1792
|
-
const kindIndices = labeledTriviaKindIndices(trivia2);
|
|
1793
|
-
if (fastKind) {
|
|
1794
|
-
ctx.namedFnDecls.push(buildFastTriviaFnDecl(fnName, fastKind, kindIndices ?? void 0));
|
|
1795
|
-
return fnName;
|
|
1796
|
-
}
|
|
1797
1872
|
const labeledSpec = analyzeLabeledTrivia(trivia2);
|
|
1798
|
-
if (labeledSpec) {
|
|
1873
|
+
if (!labeledSpec) {
|
|
1874
|
+
const fastShapes = analyzeTriviaFastPath(trivia2);
|
|
1875
|
+
if (fastShapes) {
|
|
1876
|
+
ctx.namedFnDecls.push(buildFastTriviaFnDecl(fnName, fastShapes));
|
|
1877
|
+
return fnName;
|
|
1878
|
+
}
|
|
1879
|
+
} else {
|
|
1880
|
+
const labeledShapes = analyzeLabeledScannableRun(trivia2);
|
|
1881
|
+
if (labeledShapes) {
|
|
1882
|
+
ctx.namedFnDecls.push(buildLabeledScannableTriviaFnDecl(fnName, labeledShapes));
|
|
1883
|
+
return fnName;
|
|
1884
|
+
}
|
|
1799
1885
|
const regexSpec = labeledTriviaRegexArms(trivia2);
|
|
1800
1886
|
if (regexSpec) {
|
|
1801
1887
|
const reNames = [];
|
|
@@ -2233,11 +2319,11 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
|
|
|
2233
2319
|
const p = def.parsers[idx];
|
|
2234
2320
|
const litVal = getCoreLiteralValue(p);
|
|
2235
2321
|
const litLen = litVal.length;
|
|
2236
|
-
const
|
|
2322
|
+
const litCond2 = emitLiteralCondition(litVal, pos);
|
|
2237
2323
|
const kw = first ? "if" : "else if";
|
|
2238
2324
|
first = false;
|
|
2239
2325
|
const litEnd = `${pos} + ${litLen}`;
|
|
2240
|
-
stmts.push(`${ind(ctx)}${kw} (${
|
|
2326
|
+
stmts.push(`${ind(ctx)}${kw} (${litCond2}) {`);
|
|
2241
2327
|
ctx.indent++;
|
|
2242
2328
|
const tR = emitTransformChain(p, JSON.stringify(litVal), litEnd, pos, ctx);
|
|
2243
2329
|
stmts.push(
|
|
@@ -2605,6 +2691,16 @@ function emitRuntimeFallback(parser2, ctx, pos) {
|
|
|
2605
2691
|
return { stmts, valueVar: vv, endVar: ev };
|
|
2606
2692
|
}
|
|
2607
2693
|
function emitLazy(p, def, ctx, pos) {
|
|
2694
|
+
const usage = ctx.lazyUsage;
|
|
2695
|
+
if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
|
|
2696
|
+
let resolved;
|
|
2697
|
+
try {
|
|
2698
|
+
resolved = def.thunk();
|
|
2699
|
+
} catch {
|
|
2700
|
+
return emitRuntimeFallback(p, ctx, pos);
|
|
2701
|
+
}
|
|
2702
|
+
return emit(resolved, ctx, pos);
|
|
2703
|
+
}
|
|
2608
2704
|
if (!ctx.namedParsers.has(p)) {
|
|
2609
2705
|
const fnName2 = `_pf${ctx.namedParsers.size}`;
|
|
2610
2706
|
ctx.namedParsers.set(p, fnName2);
|
|
@@ -2869,6 +2965,80 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2869
2965
|
return false;
|
|
2870
2966
|
}
|
|
2871
2967
|
}
|
|
2968
|
+
function childrenOf(def) {
|
|
2969
|
+
switch (def.tag) {
|
|
2970
|
+
case "sequence":
|
|
2971
|
+
case "choice":
|
|
2972
|
+
return def.parsers;
|
|
2973
|
+
case "many":
|
|
2974
|
+
case "optional":
|
|
2975
|
+
case "transform":
|
|
2976
|
+
case "trivia":
|
|
2977
|
+
case "label":
|
|
2978
|
+
case "grammar":
|
|
2979
|
+
case "not":
|
|
2980
|
+
case "node":
|
|
2981
|
+
case "withCtx":
|
|
2982
|
+
case "expect":
|
|
2983
|
+
return [def.parser];
|
|
2984
|
+
// emitMany's oneOrMore branch and emitSepBy both codegen `def.parser` TWICE
|
|
2985
|
+
// (a mandatory first match, then again inside the repeat loop) — two real
|
|
2986
|
+
// emit() call sites, so the usage analysis must see two edges here or it
|
|
2987
|
+
// undercounts a single-use `parser` ref as inline-safe when it's actually
|
|
2988
|
+
// referenced from two positions within this one compiled function.
|
|
2989
|
+
case "oneOrMore":
|
|
2990
|
+
return [def.parser, def.parser];
|
|
2991
|
+
case "sepBy":
|
|
2992
|
+
return [def.parser, def.parser, def.separator];
|
|
2993
|
+
case "skip":
|
|
2994
|
+
return [def.main, def.skipped];
|
|
2995
|
+
case "recover":
|
|
2996
|
+
return [def.parser, def.sentinel];
|
|
2997
|
+
case "scanTo":
|
|
2998
|
+
return [def.sentinel, ...def.skip];
|
|
2999
|
+
case "lazy":
|
|
3000
|
+
case "literal":
|
|
3001
|
+
case "regex":
|
|
3002
|
+
case "keywords":
|
|
3003
|
+
case "guard":
|
|
3004
|
+
case "unknown":
|
|
3005
|
+
return [];
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
function analyzeLazyUsage(root) {
|
|
3009
|
+
return analyzeLazyUsageMulti([root]);
|
|
3010
|
+
}
|
|
3011
|
+
function analyzeLazyUsageMulti(roots) {
|
|
3012
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3013
|
+
const recursive = /* @__PURE__ */ new Set();
|
|
3014
|
+
const descended = /* @__PURE__ */ new Set();
|
|
3015
|
+
const active = /* @__PURE__ */ new Set();
|
|
3016
|
+
function walk(p) {
|
|
3017
|
+
const def = p._def;
|
|
3018
|
+
if (def.tag === "lazy") {
|
|
3019
|
+
counts.set(p, (counts.get(p) ?? 0) + 1);
|
|
3020
|
+
if (active.has(p)) {
|
|
3021
|
+
recursive.add(p);
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
if (descended.has(p)) return;
|
|
3025
|
+
descended.add(p);
|
|
3026
|
+
let resolved;
|
|
3027
|
+
try {
|
|
3028
|
+
resolved = def.thunk();
|
|
3029
|
+
} catch {
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
active.add(p);
|
|
3033
|
+
walk(resolved);
|
|
3034
|
+
active.delete(p);
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
for (const child of childrenOf(def)) walk(child);
|
|
3038
|
+
}
|
|
3039
|
+
for (const root of roots) walk(root);
|
|
3040
|
+
return { counts, recursive };
|
|
3041
|
+
}
|
|
2872
3042
|
function compile(combinator, mapFnSources) {
|
|
2873
3043
|
const ctx = {
|
|
2874
3044
|
vars: 0,
|
|
@@ -2885,7 +3055,8 @@ function compile(combinator, mapFnSources) {
|
|
|
2885
3055
|
triviaCaptureNames: /* @__PURE__ */ new Map(),
|
|
2886
3056
|
triviaFnNames: /* @__PURE__ */ new Map(),
|
|
2887
3057
|
namedFnDecls: [],
|
|
2888
|
-
capturing: hasNodeDef(combinator)
|
|
3058
|
+
capturing: hasNodeDef(combinator),
|
|
3059
|
+
lazyUsage: analyzeLazyUsage(combinator)
|
|
2889
3060
|
};
|
|
2890
3061
|
const r = emit(combinator, ctx, "_pos");
|
|
2891
3062
|
const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
|