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.
@@ -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;AA2/CzH,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;AAkCD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAqGhG"}
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
- export type TriviaFastKind = 'wsComments' | 'wsOnly';
3
+ import { type ScanShape } from './scannable-run.ts';
4
4
  /**
5
- * Detect trivia shapes safe to lower to a hand-rolled scan loop in compiled output.
6
- * Matches CSS `rw` (`oneOrMore(choice(ws, blockComment))`) and ASCII ws-only grammars.
7
- * Single alternation regexes are excluded — one regex exec matches only one arm per call.
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>): TriviaFastKind | null;
10
- /** Emit a specialized `_tfN` that skips trivia without regex / combinator dispatch. */
11
- export declare function buildFastTriviaFnDecl(fnName: string, kind: TriviaFastKind, kindIndices?: {
12
- ws: number;
13
- comment: number;
14
- }): string;
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;AAG/D,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,QAAQ,CAAA;AAsCpD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,cAAc,GAAG,IAAI,CA6BxF;AAmED,uFAAuF;AACvF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,cAAc,EACpB,WAAW,CAAC,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5C,MAAM,CAeR;AAYD,kFAAkF;AAClF,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,CA+BR;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO5G;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"}
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 isWsClassSource(source) {
1394
- return WS_CLASS_SOURCES.has(source);
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 direct = getCoreRegexDef(core)?.source;
1402
- if (direct) {
1403
- if (isWsClassSource(direct)) return "wsOnly";
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 innerSrc = getCoreRegexDef(inner)?.source;
1409
- if (innerSrc && isWsClassSource(innerSrc)) return "wsOnly";
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 !== 2) return null;
1412
- let hasWs = false;
1413
- let hasComment = false;
1535
+ if (!arms || arms.length < 2) return null;
1536
+ const shapes = [];
1414
1537
  for (const arm of arms) {
1415
- const src = getCoreRegexDef(arm)?.source;
1416
- if (!src) return null;
1417
- if (isWsClassSource(src)) hasWs = true;
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
- if (hasWs && hasComment) return "wsComments";
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
- var WS_ONLY_LOOP = [
1431
- ` while (_e < input.length) {`,
1432
- ` const c = input.charCodeAt(_e)`,
1433
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) { _e++; continue }`,
1434
- ` break`,
1435
- ` }`
1436
- ].join("\n");
1437
- var WS_COMMENTS_LOOP = [
1438
- ` while (_e < input.length) {`,
1439
- ` const c = input.charCodeAt(_e)`,
1440
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) { _e++; continue }`,
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
- loop
1491
- ];
1492
- if (cap) lines.push(cap);
1493
- lines.push(` return _e`, `}`);
1494
- return lines.join("\n");
1563
+ composeFastLoop(shapes),
1564
+ CAP_RECORD,
1565
+ ` return _e`,
1566
+ `}`
1567
+ ].join("\n");
1495
1568
  }
1496
- function regexArmIndex(spec, isComment) {
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
- if (!src) return null;
1500
- const comment = src.includes("\\*") || src.startsWith("\\/\\/");
1501
- if (comment === isComment) return arm.kindIndex;
1575
+ const shape = src ? parseScanShape(src) : null;
1576
+ if (!shape) return null;
1577
+ out.push({ shape, kindIndex: arm.kindIndex });
1502
1578
  }
1503
- return null;
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 litCond = emitLiteralCondition(litVal, pos);
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} (${litCond}) {`);
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' })