parseman 0.18.1 → 0.18.2

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/README.md CHANGED
@@ -123,7 +123,7 @@ Compared parsers: **Parséman**, [Peggy](https://peggyjs.org/), [Parsimmon](http
123
123
 
124
124
  Parséman has three modes — **interpreter** (zero setup, works anywhere), **macro build** (compiled by the bundler plugin at build time, zero runtime cost), and **`.compile()`** (optional runtime JIT). Most production use lands on one of the first two. The initialization section only shows parsers with a nonzero setup cost: `.compile()` costs 75–650 µs depending on grammar size; Chevrotain always costs 840–1,400 µs. Parsers not listed there start for free. (Init numbers are pinned on the charts — they're noisy run-to-run; warm-parse bars are the meaningful comparison.)
125
125
 
126
- On JSON, CSV, and GraphQL, Parséman macro beats every other library at every fixture size in the charts above — e.g. GraphQL large **154 µs** vs Peggy **423 µs**, JSON large **125 µs** vs Peggy **472 µs** / Chevrotain **1,946 µs**. Native `JSON.parse` is the one thing faster on JSON (**45 µs** large). Even the zero-setup interpreter is highly competitive: on JSON and CSV it's the fastest option after the macro build — ahead of Peggy and every other generator — with Peggy edging it out only on GraphQL. On the CST chart, macro build beats Lezer at every size (**344 µs** vs **587 µs** large, parse-only). Full write-up and how to refresh the charts: **[benchmarks guide](https://matthew-dean.github.io/parseman/guide/benchmarks)**. Grammar-level speed levers: [performance guide](https://matthew-dean.github.io/parseman/guide/performance); library-level codegen: [PERF_IDEAS.md](./notes/PERF_IDEAS.md).
126
+ On JSON, CSV, and GraphQL, Parséman macro beats every other library at every fixture size in the charts above — e.g. GraphQL large **154 µs** vs Peggy **423 µs**, JSON large **125 µs** vs Peggy **472 µs** / Chevrotain **1,946 µs**. Native `JSON.parse` is the one thing faster on JSON (**45 µs** large). Even the zero-setup interpreter is highly competitive: on JSON and CSV it's the fastest option after the macro build — ahead of Peggy and every other generator — with Peggy edging it out only on GraphQL. On the CST chart, macro build beats Lezer at every size (**226 µs** vs **626 µs** large, parse-only). Full write-up and how to refresh the charts: **[benchmarks guide](https://matthew-dean.github.io/parseman/guide/benchmarks)**. Grammar-level speed levers: [performance guide](https://matthew-dean.github.io/parseman/guide/performance); library-level codegen: [PERF_IDEAS.md](./notes/PERF_IDEAS.md).
127
127
 
128
128
  ---
129
129
 
@@ -134,7 +134,8 @@ pnpm install
134
134
  pnpm test # Vitest — interpreter + compiler parity + ordered-choice semantics
135
135
  pnpm typecheck # TypeScript 7
136
136
  pnpm build # ESM + CJS + .d.ts → dist/
137
- pnpm bench # full cross-parser suite + Parseman regression report
137
+ pnpm bench # parser-to-parser comparison
138
+ pnpm bench:parseman # Parseman interpreted vs compiled regression report
138
139
  pnpm bench:svg # chart-only benchmarks + refresh assets/bench-*.svg
139
140
  pnpm bench:compile-grammars # regenerate Peggy / Nearley / Jison parser output in bench/
140
141
  pnpm docs:dev # run this documentation site locally
@@ -1 +1 @@
1
- {"version":3,"file":"choice.d.ts","sourceRoot":"","sources":["../../src/combinators/choice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EAC+B,QAAQ,EAClD,MAAM,aAAa,CAAA;AAIpB,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;AACvH,KAAK,SAAS,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI;KACrE,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC,MAAM,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAEjD,wBAAgB,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EACxH,GAAG,IAAI,EAAE,CAAC,GACT,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAiI1B;AAwFD,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAKzE;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAMhG"}
1
+ {"version":3,"file":"choice.d.ts","sourceRoot":"","sources":["../../src/combinators/choice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EAC+B,QAAQ,EAClD,MAAM,aAAa,CAAA;AAIpB,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;AACvH,KAAK,SAAS,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI;KACrE,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC,MAAM,CAAC,SAAS,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;AAEjD,wBAAgB,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EACxH,GAAG,IAAI,EAAE,CAAC,GACT,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAqI1B;AAwFD,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAKzE;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAMhG"}
@@ -1 +1 @@
1
- {"version":3,"file":"literal.d.ts","sourceRoot":"","sources":["../../src/combinators/literal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAA;AAwBpF,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,UAAU,CAAC,MAAM,CAAC,CAqDpF"}
1
+ {"version":3,"file":"literal.d.ts","sourceRoot":"","sources":["../../src/combinators/literal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAA;AAwBpF,MAAM,MAAM,cAAc,GAAG;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,UAAU,CAAC,MAAM,CAAC,CAkDpF"}
@@ -1 +1 @@
1
- {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../src/combinators/regex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,QAAQ,EAAE,MAAM,aAAa,CAAA;AAK9F;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAA;AAIzG,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAE3E;AAQD,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,SAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAyB9E"}
1
+ {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../src/combinators/regex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,QAAQ,EAAE,MAAM,aAAa,CAAA;AAK9F;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAAE,CAAA;AAIzG,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAE3E;AAmGD,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,SAAK,GAAG,UAAU,CAAC,MAAM,CAAC,CAmC9E"}
@@ -1 +1 @@
1
- {"version":3,"file":"repeat.d.ts","sourceRoot":"","sources":["../../src/combinators/repeat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAA;AAkDpF,wBAAgB,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CA4BlE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CA+BvE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,CAoB3E;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CAyDhG"}
1
+ {"version":3,"file":"repeat.d.ts","sourceRoot":"","sources":["../../src/combinators/repeat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAyC,MAAM,aAAa,CAAA;AAmDpF,wBAAgB,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CA4BlE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CA+BvE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,CAwB3E;AAYD,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CAyDhG"}
@@ -1 +1 @@
1
- {"version":3,"file":"trivia-skip.d.ts","sourceRoot":"","sources":["../../src/combinators/trivia-skip.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAc/C;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,CAAA;AAE5D,iEAAiE;AACjE,MAAM,MAAM,kBAAkB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAI3F,oFAAoF;AACpF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAEpE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,kBAAkB,CAGpE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAGhF;AAiBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,CASnF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,UAAU,CA6BpF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,CAKnF"}
1
+ {"version":3,"file":"trivia-skip.d.ts","sourceRoot":"","sources":["../../src/combinators/trivia-skip.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,aAAa,CAAA;AAc3D;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAAE,CAAA;AAE5D,iEAAiE;AACjE,MAAM,MAAM,kBAAkB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAM3F,oFAAoF;AACpF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAEpE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,kBAAkB,CAGpE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAGhF;AAiBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,CAWnF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,UAAU,CAkCpF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,MAAM,CAKnF"}
package/dist/index.cjs CHANGED
@@ -446,13 +446,12 @@ function literal(value, opts = {}) {
446
446
  if (end > input.length) {
447
447
  return failAt(ctx, [JSON.stringify(value)], pos);
448
448
  }
449
- const slice = input.slice(pos, end);
450
- const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
451
- if (matched) {
449
+ const matchedValue = caseInsensitive ? input.slice(pos, end) : value;
450
+ if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
452
451
  const span = { start: pos, end };
453
- const leaf = { _tag: "leaf", value: slice, span };
452
+ const leaf = { _tag: "leaf", value: matchedValue, span };
454
453
  if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
455
- return { ok: true, value: slice, span };
454
+ return { ok: true, value: matchedValue, span };
456
455
  }
457
456
  return failAt(ctx, [JSON.stringify(value)], pos);
458
457
  }
@@ -465,10 +464,102 @@ function registerRegexAnalyzer(analyzer) {
465
464
  firstSetAnalyzer = analyzer;
466
465
  }
467
466
  var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
467
+ var SCAN_BAIL_AT = 64;
468
+ var SPACE_RANGES = [
469
+ [9, 13],
470
+ [32, 32],
471
+ [160, 160],
472
+ [5760, 5760],
473
+ [8192, 8202],
474
+ [8232, 8233],
475
+ [8239, 8239],
476
+ [8287, 8287],
477
+ [12288, 12288],
478
+ [65279, 65279]
479
+ ];
480
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
481
+ function shorthandRanges(ch) {
482
+ if (ch === "d") return [[48, 57]];
483
+ if (ch === "s") return SPACE_RANGES;
484
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
485
+ }
486
+ function inRanges(cp, ranges) {
487
+ for (let i = 0; i < ranges.length; i++) {
488
+ const [lo, hi] = ranges[i];
489
+ if (cp >= lo && cp <= hi) return true;
490
+ }
491
+ return false;
492
+ }
493
+ function readClassRanges(body) {
494
+ if (body.startsWith("^")) return null;
495
+ const ranges = [];
496
+ let i = 0;
497
+ const readAtom = () => {
498
+ const ch = body[i];
499
+ if (ch === void 0) return null;
500
+ if (ch === "\\") {
501
+ const esc = body[i + 1];
502
+ if (esc === void 0) return null;
503
+ i += 2;
504
+ if (esc === "d" || esc === "w" || esc === "s") return { ranges: shorthandRanges(esc) };
505
+ if (esc in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[esc] };
506
+ if (esc >= "a" && esc <= "z" || esc >= "A" && esc <= "Z") return null;
507
+ return { cp: esc.charCodeAt(0) };
508
+ }
509
+ i++;
510
+ return { cp: ch.charCodeAt(0) };
511
+ };
512
+ while (i < body.length) {
513
+ const lo = readAtom();
514
+ if (!lo) return null;
515
+ if ("ranges" in lo) {
516
+ ranges.push(...lo.ranges);
517
+ continue;
518
+ }
519
+ if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
520
+ i++;
521
+ const hi = readAtom();
522
+ if (!hi || "ranges" in hi) return null;
523
+ ranges.push([lo.cp, hi.cp]);
524
+ } else {
525
+ ranges.push([lo.cp, lo.cp]);
526
+ }
527
+ }
528
+ return ranges.length ? ranges : null;
529
+ }
530
+ function shortScanner(source, flags) {
531
+ if (/[imsuvy]/.test(flags)) return null;
532
+ let ranges = null;
533
+ let quant = "";
534
+ if (source[0] === "[") {
535
+ let end = 1;
536
+ while (end < source.length && source[end] !== "]") {
537
+ if (source[end] === "\\") end += 2;
538
+ else end++;
539
+ }
540
+ if (source[end] !== "]") return null;
541
+ ranges = readClassRanges(source.slice(1, end));
542
+ quant = source.slice(end + 1);
543
+ } else if (source[0] === "\\" && (source[1] === "d" || source[1] === "w" || source[1] === "s")) {
544
+ ranges = shorthandRanges(source[1]);
545
+ quant = source.slice(2);
546
+ }
547
+ if (!ranges || quant !== "+" && quant !== "*") return null;
548
+ const minOne = quant === "+";
549
+ return (input, pos) => {
550
+ let end = pos;
551
+ while (end < input.length && inRanges(input.charCodeAt(end), ranges)) {
552
+ end++;
553
+ if (end - pos >= SCAN_BAIL_AT) return void 0;
554
+ }
555
+ return minOne && end === pos ? null : end;
556
+ };
557
+ }
468
558
  function regex(pattern, flags = "") {
469
559
  const source = typeof pattern === "string" ? pattern : pattern.source;
470
560
  const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
471
561
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
562
+ const scan = shortScanner(source, resolvedFlags);
472
563
  const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
473
564
  const meta = { firstSet, canMatchNewline, isTrivia: false };
474
565
  return {
@@ -476,6 +567,15 @@ function regex(pattern, flags = "") {
476
567
  _meta: meta,
477
568
  _def: { tag: "regex", source, flags: resolvedFlags },
478
569
  parse(input, pos, ctx) {
570
+ const scanEnd = scan?.(input, pos);
571
+ if (scanEnd !== void 0) {
572
+ if (scanEnd === null) return failAt(ctx, [`/${source}/`], pos);
573
+ const value = input.slice(pos, scanEnd);
574
+ const span2 = { start: pos, end: scanEnd };
575
+ const leaf2 = { _tag: "leaf", value, span: span2 };
576
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf2);
577
+ return { ok: true, value, span: span2 };
578
+ }
479
579
  anchored.lastIndex = pos;
480
580
  const m = anchored.exec(input);
481
581
  if (m === null) {
@@ -659,13 +759,10 @@ function choice(...args) {
659
759
  };
660
760
  const strategy = disjoint || hasGates ? null : detectStrategy(parsers);
661
761
  const autoNot = !disjoint && !hasGates && strategy?.tag === "firstMatch" ? computeAutoNot(parsers) : parsers.map(() => null);
662
- let greedyRe = null;
663
762
  let greedyLitMap = null;
664
763
  let sortedParsers = null;
764
+ const asciiDispatch = disjoint ? buildAsciiDispatch(parsers) : null;
665
765
  if (strategy?.tag === "greedyClassify") {
666
- const regexDef = getCoreRegexDef(parsers[strategy.superIndex]);
667
- const flags = "y" + regexDef.flags.replace(/[gy]/g, "");
668
- greedyRe = new RegExp(regexDef.source, flags);
669
766
  greedyLitMap = /* @__PURE__ */ new Map();
670
767
  for (let i = 0; i < parsers.length; i++) {
671
768
  if (i === strategy.superIndex) continue;
@@ -690,14 +787,21 @@ function choice(...args) {
690
787
  const expected = [];
691
788
  if (disjoint && pos < input.length) {
692
789
  const code = input.codePointAt(pos);
693
- for (const parser2 of parsers) {
694
- if (inFirstSet(code, parser2._meta.firstSet)) {
695
- const result = parser2.parse(input, pos, ctx);
696
- if (result.ok) return result;
697
- expected.push(...result.expected);
698
- return { ok: false, expected, span: { start: pos, end: pos } };
790
+ let parser2 = code < 128 ? asciiDispatch?.[code] ?? null : null;
791
+ if (!parser2) {
792
+ for (const p of parsers) {
793
+ if (inFirstSet(code, p._meta.firstSet)) {
794
+ parser2 = p;
795
+ break;
796
+ }
699
797
  }
700
798
  }
799
+ if (parser2) {
800
+ const result = parser2.parse(input, pos, ctx);
801
+ if (result.ok) return result;
802
+ expected.push(...result.expected);
803
+ return { ok: false, expected, span: { start: pos, end: pos } };
804
+ }
701
805
  return {
702
806
  ok: false,
703
807
  expected: parsers.flatMap((p) => {
@@ -873,6 +977,19 @@ function areDisjoint(sets) {
873
977
  if (intersects(sets[i], sets[j])) return false;
874
978
  return true;
875
979
  }
980
+ function buildAsciiDispatch(parsers) {
981
+ const table = Array(128).fill(null);
982
+ for (const parser2 of parsers) {
983
+ const fs = parser2._meta.firstSet;
984
+ if (fs.kind !== "ranges") continue;
985
+ for (const { lo, hi } of fs.ranges) {
986
+ for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) {
987
+ table[code] = parser2;
988
+ }
989
+ }
990
+ }
991
+ return table;
992
+ }
876
993
 
877
994
  // src/cst/trivia-kinds.ts
878
995
  function unwrapTrivia(p) {
@@ -994,6 +1111,7 @@ function recordTriviaChunks(ctx, chunks) {
994
1111
  // src/combinators/trivia-skip.ts
995
1112
  var NOOP_COMMIT = () => {
996
1113
  };
1114
+ var fastTriviaCache = /* @__PURE__ */ new WeakMap();
997
1115
  function needsDeferredTriviaCommit(ctx) {
998
1116
  return ctx._triviaLog !== void 0 || ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0;
999
1117
  }
@@ -1020,6 +1138,8 @@ function scanWithLabels(input, cur, ctx) {
1020
1138
  function advanceTrivia(input, cur, ctx) {
1021
1139
  const triviaP = ctx.trivia;
1022
1140
  if (!triviaP) return cur;
1141
+ const fast = fastTriviaScanner(triviaP);
1142
+ if (fast) return fast(input, cur);
1023
1143
  if (ctx.triviaKindLabels) {
1024
1144
  const scan = scanWithLabels(input, cur, ctx);
1025
1145
  return scan.end;
@@ -1032,6 +1152,10 @@ function scanTrivia(input, cur, ctx) {
1032
1152
  if (!triviaP) return { end: cur, commit: NOOP_COMMIT };
1033
1153
  const log = ctx._triviaLog;
1034
1154
  const captureTl = ctx.captureTrivia && (ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0);
1155
+ const fast = !ctx.triviaKindLabels ? fastTriviaScanner(triviaP) : null;
1156
+ if (fast && log === void 0 && !captureTl) {
1157
+ return { end: fast(input, cur), commit: NOOP_COMMIT };
1158
+ }
1035
1159
  if (ctx.triviaKindLabels && (log !== void 0 || captureTl)) {
1036
1160
  return scanWithLabels(input, cur, ctx);
1037
1161
  }
@@ -1053,6 +1177,79 @@ function scanTrivia(input, cur, ctx) {
1053
1177
  const tr = triviaP.parse(input, cur, { trackLines: ctx.trackLines, state: ctx.state });
1054
1178
  return { end: tr.ok ? tr.span.end : cur, commit: NOOP_COMMIT };
1055
1179
  }
1180
+ function fastTriviaScanner(trivia2) {
1181
+ const cached = fastTriviaCache.get(trivia2);
1182
+ if (cached !== void 0) return cached;
1183
+ const scanner = buildFastTriviaScanner(trivia2);
1184
+ fastTriviaCache.set(trivia2, scanner);
1185
+ return scanner;
1186
+ }
1187
+ function buildFastTriviaScanner(trivia2) {
1188
+ const core = trivia2._def.tag === "trivia" ? trivia2._def.parser : trivia2;
1189
+ const direct = regexTriviaScanner(core);
1190
+ if (direct) return direct;
1191
+ const repeat = core._def.tag === "oneOrMore" || core._def.tag === "many" && core._def.min >= 1 ? core._def.parser : null;
1192
+ if (!repeat) return null;
1193
+ const one = regexTriviaScanner(repeat);
1194
+ if (one) return loopScanner([one]);
1195
+ if (repeat._def.tag !== "choice") return null;
1196
+ const arms = repeat._def.parsers.map(regexTriviaScanner);
1197
+ if (arms.some((s) => s === null)) return null;
1198
+ return loopScanner(arms);
1199
+ }
1200
+ function loopScanner(arms) {
1201
+ return (input, cur) => {
1202
+ let pos = cur;
1203
+ scan: while (pos < input.length) {
1204
+ for (const arm of arms) {
1205
+ const end = arm(input, pos);
1206
+ if (end > pos) {
1207
+ pos = end;
1208
+ continue scan;
1209
+ }
1210
+ }
1211
+ break;
1212
+ }
1213
+ return pos;
1214
+ };
1215
+ }
1216
+ function regexTriviaScanner(parser2) {
1217
+ if (parser2._def.tag !== "regex" || parser2._def.flags) return null;
1218
+ const ws = whitespaceSource(parser2._def.source);
1219
+ if (ws) return ws;
1220
+ return blockCommentSource(parser2._def.source) ? scanBlockComment : null;
1221
+ }
1222
+ function whitespaceSource(source) {
1223
+ const m = /^\[([ \\tnrf]+)\][*+]$/.exec(source);
1224
+ if (!m) return null;
1225
+ const chars = /* @__PURE__ */ new Set();
1226
+ for (let i = 0; i < m[1].length; i++) {
1227
+ const ch = m[1][i];
1228
+ if (ch !== "\\") {
1229
+ chars.add(ch.charCodeAt(0));
1230
+ continue;
1231
+ }
1232
+ const esc = m[1][++i];
1233
+ if (esc === "t") chars.add(9);
1234
+ else if (esc === "n") chars.add(10);
1235
+ else if (esc === "r") chars.add(13);
1236
+ else if (esc === "f") chars.add(12);
1237
+ else return null;
1238
+ }
1239
+ return (input, cur) => {
1240
+ let pos = cur;
1241
+ while (pos < input.length && chars.has(input.charCodeAt(pos))) pos++;
1242
+ return pos;
1243
+ };
1244
+ }
1245
+ function blockCommentSource(source) {
1246
+ return source === "\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/" || source === "\\/\\*[^]*?\\*\\/";
1247
+ }
1248
+ function scanBlockComment(input, cur) {
1249
+ if (input.charCodeAt(cur) !== 47 || input.charCodeAt(cur + 1) !== 42) return cur;
1250
+ const close = input.indexOf("*/", cur + 2);
1251
+ return close === -1 ? cur : close + 2;
1252
+ }
1056
1253
 
1057
1254
  // src/combinators/sequence.ts
1058
1255
  function sequence(...parsers) {
@@ -1191,11 +1388,15 @@ function optional(combinator) {
1191
1388
  canMatchNewline: combinator._meta.canMatchNewline,
1192
1389
  isTrivia: false
1193
1390
  };
1391
+ const firstSetSkippable = !matchesEmpty(combinator);
1194
1392
  return {
1195
1393
  _tag: "optional",
1196
1394
  _meta: meta,
1197
1395
  _def: { tag: "optional", parser: combinator },
1198
1396
  parse(input, pos, ctx) {
1397
+ if (firstSetSkippable && ctx._probe === void 0 && !startsFirstSet(combinator, input, pos)) {
1398
+ return { ok: true, value: null, span: { start: pos, end: pos } };
1399
+ }
1199
1400
  const mark = saveTriviaMark(ctx);
1200
1401
  const result = combinator.parse(input, pos, ctx);
1201
1402
  if (result.ok) return result;
@@ -1204,6 +1405,15 @@ function optional(combinator) {
1204
1405
  }
1205
1406
  };
1206
1407
  }
1408
+ function startsFirstSet(combinator, input, pos) {
1409
+ const fs = combinator._meta.firstSet;
1410
+ if (fs.kind === "any") return true;
1411
+ if (fs.kind === "empty") return false;
1412
+ const code = input.codePointAt(pos);
1413
+ if (code === void 0) return false;
1414
+ for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
1415
+ return false;
1416
+ }
1207
1417
  function sepBy(combinator, separator) {
1208
1418
  const meta = {
1209
1419
  firstSet: combinator._meta.firstSet,
@@ -1511,7 +1721,7 @@ function transform(combinator, fn) {
1511
1721
  parse(input, pos, ctx) {
1512
1722
  const result = combinator.parse(input, pos, ctx);
1513
1723
  if (!result.ok) return result;
1514
- return { ...result, value: fn(result.value, result.span) };
1724
+ return { ok: true, value: fn(result.value, result.span), span: result.span };
1515
1725
  }
1516
1726
  };
1517
1727
  }
@@ -1525,7 +1735,7 @@ function skip(main, skipped) {
1525
1735
  if (!result.ok) return result;
1526
1736
  const s = skipped.parse(input, result.span.end, ctx);
1527
1737
  if (!s.ok) return result;
1528
- return { ...result, span: { start: result.span.start, end: s.span.end } };
1738
+ return { ok: true, value: result.value, span: { start: result.span.start, end: s.span.end } };
1529
1739
  }
1530
1740
  };
1531
1741
  }
@@ -1549,7 +1759,7 @@ function label(name, combinator) {
1549
1759
  _def: { tag: "label", label: name, parser: combinator },
1550
1760
  parse(input, pos, ctx) {
1551
1761
  const result = combinator.parse(input, pos, ctx);
1552
- if (!result.ok) return { ...result, expected: [name] };
1762
+ if (!result.ok) return { ok: false, expected: [name], span: result.span };
1553
1763
  return result;
1554
1764
  }
1555
1765
  };
@@ -1763,9 +1973,9 @@ function expect(combinator, label2) {
1763
1973
 
1764
1974
  // src/compiler/scannable-run.ts
1765
1975
  var BACKSLASH = 92;
1766
- var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1976
+ var CLASS_ESCAPES2 = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1767
1977
  var META = new Set("()[]{}*+?|^$.".split(""));
1768
- var SPACE_RANGES = [
1978
+ var SPACE_RANGES2 = [
1769
1979
  [9, 13],
1770
1980
  [32, 32],
1771
1981
  [160, 160],
@@ -1778,9 +1988,9 @@ var SPACE_RANGES = [
1778
1988
  [12288, 12288],
1779
1989
  [65279, 65279]
1780
1990
  ];
1781
- function shorthandRanges(ch) {
1991
+ function shorthandRanges2(ch) {
1782
1992
  if (ch === "d") return [[48, 57]];
1783
- if (ch === "s") return SPACE_RANGES;
1993
+ if (ch === "s") return SPACE_RANGES2;
1784
1994
  return [[48, 57], [65, 90], [97, 122], [95, 95]];
1785
1995
  }
1786
1996
  function readUnicodeEscape(body, i) {
@@ -1804,8 +2014,8 @@ function parseClassRanges(body) {
1804
2014
  const e = body[i + 1];
1805
2015
  if (e === void 0) return null;
1806
2016
  i += 2;
1807
- if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1808
- if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
2017
+ if (e in CLASS_ESCAPES2) return { cp: CLASS_ESCAPES2[e] };
2018
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges2(e) };
1809
2019
  if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1810
2020
  return { cp: e.codePointAt(0) };
1811
2021
  }
@@ -1831,9 +2041,9 @@ function parseClassRanges(body) {
1831
2041
  return ranges.length ? ranges : null;
1832
2042
  }
1833
2043
  function classToRanges(cls) {
1834
- if (cls === "\\d") return shorthandRanges("d");
1835
- if (cls === "\\w") return shorthandRanges("w");
1836
- if (cls === "\\s") return shorthandRanges("s");
2044
+ if (cls === "\\d") return shorthandRanges2("d");
2045
+ if (cls === "\\w") return shorthandRanges2("w");
2046
+ if (cls === "\\s") return shorthandRanges2("s");
1837
2047
  const body = cls.slice(1, -1);
1838
2048
  if (body.startsWith("^")) return null;
1839
2049
  return parseClassRanges(body);
@@ -1851,7 +2061,7 @@ function literalCodePoints(frag) {
1851
2061
  if (ch === "\\") {
1852
2062
  const e = frag[i + 1];
1853
2063
  if (e === void 0) return null;
1854
- out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
2064
+ out.push(e in CLASS_ESCAPES2 ? CLASS_ESCAPES2[e] : e.codePointAt(0));
1855
2065
  i += 2;
1856
2066
  continue;
1857
2067
  }
@@ -1944,7 +2154,7 @@ function parseSeqParts(source) {
1944
2154
  if (e === "d" || e === "w" || e === "s") {
1945
2155
  flush();
1946
2156
  i += 2;
1947
- i += runFrom(shorthandRanges(e), false, source[i]);
2157
+ i += runFrom(shorthandRanges2(e), false, source[i]);
1948
2158
  continue;
1949
2159
  }
1950
2160
  const uni = readUnicodeEscape(source, i);
@@ -1953,8 +2163,8 @@ function parseSeqParts(source) {
1953
2163
  cp2 = uni.cp;
1954
2164
  i = uni.next;
1955
2165
  } else if (e === void 0) return null;
1956
- else if (e in CLASS_ESCAPES) {
1957
- cp2 = CLASS_ESCAPES[e];
2166
+ else if (e in CLASS_ESCAPES2) {
2167
+ cp2 = CLASS_ESCAPES2[e];
1958
2168
  i += 2;
1959
2169
  } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1960
2170
  else {
@@ -2061,7 +2271,7 @@ function rangesSubset(sub, sup) {
2061
2271
  }
2062
2272
  function parseClassOperand(body) {
2063
2273
  if (body === "\\d" || body === "\\w" || body === "\\s") {
2064
- return { ranges: shorthandRanges(body[1]), negated: false };
2274
+ return { ranges: shorthandRanges2(body[1]), negated: false };
2065
2275
  }
2066
2276
  if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
2067
2277
  let inner = body.slice(1, -1);
@@ -2494,7 +2704,7 @@ function parseScanShapeCore(source) {
2494
2704
  }
2495
2705
  m = /^\\([dws])([+*])$/.exec(source);
2496
2706
  if (m) {
2497
- return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2707
+ return { kind: "chars", ranges: shorthandRanges2(m[1]), minOne: m[2] === "+" };
2498
2708
  }
2499
2709
  m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2500
2710
  if (m) {