parseman 0.57.0 → 0.58.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.
Files changed (64) hide show
  1. package/dist/analysis/diagnostics.js +1799 -962
  2. package/dist/analysis/diagnostics.js.map +3 -3
  3. package/dist/cli/index.js +1928 -1091
  4. package/dist/cli/index.js.map +3 -3
  5. package/dist/combinators/dispatch.d.ts.map +1 -1
  6. package/dist/compiler/token-alphabet.d.ts +11 -1
  7. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  8. package/dist/compiler/token-capability.js +69 -16
  9. package/dist/compiler/token-capability.js.map +2 -2
  10. package/dist/cst/capture-buffer.d.ts +6 -0
  11. package/dist/cst/capture-buffer.d.ts.map +1 -1
  12. package/dist/index.js +1016 -177
  13. package/dist/index.js.map +3 -3
  14. package/dist/plugin/index.d.ts +25 -1
  15. package/dist/plugin/index.d.ts.map +1 -1
  16. package/dist/plugin/index.js +1209 -236
  17. package/dist/plugin/index.js.map +3 -3
  18. package/dist/table/assemble.d.ts.map +1 -1
  19. package/dist/table/compile-rule-map.d.ts.map +1 -1
  20. package/dist/table/emit-assembly.d.ts +1 -1
  21. package/dist/table/emit-assembly.d.ts.map +1 -1
  22. package/dist/table/emit.d.ts.map +1 -1
  23. package/dist/table/encode.d.ts.map +1 -1
  24. package/dist/table/exec.d.ts.map +1 -1
  25. package/dist/table/factor-choice.d.ts +42 -0
  26. package/dist/table/factor-choice.d.ts.map +1 -0
  27. package/dist/table/fail-clean.d.ts +3 -0
  28. package/dist/table/fail-clean.d.ts.map +1 -0
  29. package/dist/table/hold-gap.d.ts +8 -0
  30. package/dist/table/hold-gap.d.ts.map +1 -0
  31. package/dist/table/index.js +1369 -259
  32. package/dist/table/index.js.map +3 -3
  33. package/dist/table/lex-program.d.ts +7 -3
  34. package/dist/table/lex-program.d.ts.map +1 -1
  35. package/dist/table/ops.d.ts +8 -0
  36. package/dist/table/ops.d.ts.map +1 -1
  37. package/dist/table/run-support.d.ts +19 -0
  38. package/dist/table/run-support.d.ts.map +1 -1
  39. package/dist/table/scalar-terminal.d.ts +2 -0
  40. package/dist/table/scalar-terminal.d.ts.map +1 -1
  41. package/dist/table/site-labels.d.ts +66 -0
  42. package/dist/table/site-labels.d.ts.map +1 -1
  43. package/dist/version.d.ts +1 -1
  44. package/package.json +1 -1
  45. package/src/combinators/dispatch.ts +12 -3
  46. package/src/compiler/token-alphabet.ts +57 -17
  47. package/src/cst/capture-buffer.ts +25 -0
  48. package/src/plugin/index.ts +230 -35
  49. package/src/table/assemble.ts +252 -62
  50. package/src/table/child-slots.ts +2 -2
  51. package/src/table/compile-rule-map.ts +14 -5
  52. package/src/table/emit-assembly.ts +112 -23
  53. package/src/table/emit.ts +3 -1
  54. package/src/table/encode.ts +80 -14
  55. package/src/table/exec.ts +27 -8
  56. package/src/table/factor-choice.ts +173 -0
  57. package/src/table/fail-clean.ts +130 -0
  58. package/src/table/hold-gap.ts +68 -0
  59. package/src/table/lex-program.ts +197 -43
  60. package/src/table/ops.ts +9 -1
  61. package/src/table/run-support.ts +35 -0
  62. package/src/table/scalar-terminal.ts +8 -0
  63. package/src/table/site-labels.ts +135 -4
  64. package/src/version.ts +1 -1
@@ -13,11 +13,15 @@ export declare const LEX_NODE_SEQUENCE2 = 1;
13
13
  export declare const LEX_NODE_SEQUENCE3 = 2;
14
14
  export declare const LEX_NODE_SEQUENCE4 = 3;
15
15
  export declare const LEX_NODE_SEQUENCE5 = 4;
16
- export declare const LEX_NODE_CHOICE2 = 5;
17
- export declare const LEX_NODE_CHOICE4 = 6;
18
- export declare const LEX_NODE_CHOICE8 = 7;
16
+ /**
17
+ * `[op, armCount, exclusive, choiceFx, (armClass, armFx) × armCount]`: any arity,
18
+ * no padding. `exclusive` is the character choice's one-arm dispatch decision.
19
+ */
20
+ export declare const LEX_NODE_CHOICE = 5;
19
21
  export declare const LEX_NODE_NOT = 8;
20
22
  export declare const LEX_NODE_OPTIONAL = 9;
23
+ /** `[op, min, max]` after the item: a separator-less repeat, `max` −1 unbounded. */
24
+ export declare const LEX_NODE_REPEAT = 10;
21
25
  export declare function lexProgramDigest(words: readonly number[]): number;
22
26
  /** Decode one fixed selected-body tuple. There is no lexical opcode loop on
23
27
  * the parse path: each supported tuple becomes one scalar closure here. */
@@ -1 +1 @@
1
- {"version":3,"file":"lex-program.d.ts","sourceRoot":"","sources":["../../src/table/lex-program.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjE;;0EAE0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,CAAA;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAClC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAElC,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAOjE;AA+QD;2EAC2E;AAC3E,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,QAAQ,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GACvC,gBAAgB,CAyHlB"}
1
+ {"version":3,"file":"lex-program.d.ts","sourceRoot":"","sources":["../../src/table/lex-program.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjE;;0EAE0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,GAAG,MAAM,CAAA;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAClC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC,eAAO,MAAM,kBAAkB,IAAI,CAAA;AACnC;;;GAGG;AACH,eAAO,MAAM,eAAe,IAAI,CAAA;AAChC,eAAO,MAAM,YAAY,IAAI,CAAA;AAC7B,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAClC,oFAAoF;AACpF,eAAO,MAAM,eAAe,KAAK,CAAA;AAEjC,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAOjE;AAoaD;2EAC2E;AAC3E,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,SAAS,iBAAiB,EAAE,EACtC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,QAAQ,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GACvC,gBAAgB,CAyHlB"}
@@ -477,6 +477,14 @@ export declare const OP_LEX_BODY = 42;
477
477
  export declare const OP_LEX_PROGRAM = 43;
478
478
  /** `SOURCE_LEAF f c` — `sourceLeaf()`: reduce semantically, capture source text. */
479
479
  export declare const OP_SOURCE_LEAF = 44;
480
+ /**
481
+ * `RGATE cls child fx` — a choice arm that opens with the token its `dispatch()`
482
+ * already routed. `cls` is what may follow that token (the arm's next term,
483
+ * with the sequence's trivia), tested at the routed token's END; a miss fails
484
+ * there with the next term's set `fx`, as running the arm would. No routed token
485
+ * at this position, a completions probe, or tolerant recovery enters the arm.
486
+ */
487
+ export declare const OP_RGATE = 45;
480
488
  /**
481
489
  * `DISPATCH sel d other otherRouted n a1 … an` — `dispatch()`.
482
490
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/table/ops.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ops.d.ts","sourceRoot":"","sources":["../../src/table/ops.ts"],"names":[],"mappings":"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"}
@@ -35,4 +35,23 @@ export declare function spanLines(ctx: ParseContext, start: number, end: number)
35
35
  export declare function rawEntry(v: unknown, input: string, s: number, e: number): unknown;
36
36
  /** `input`'s code point at `pos`, or −1 at EOF. Surrogate-pair aware. */
37
37
  export declare function lead(input: string, pos: number): number;
38
+ type CollapseBuild = (children: readonly unknown[], fields: undefined, span: {
39
+ start: number;
40
+ end: number;
41
+ }, rawChildren: readonly unknown[], triviaLog: readonly number[], state: undefined) => unknown;
42
+ /**
43
+ * A PASS-THROUGH COLLAPSE whose one child built `undefined` (`site-labels.ts`
44
+ * `passCollapse`).
45
+ *
46
+ * A collapse over a single node is that node: the node publishes its value into
47
+ * the collector the collapse's parent opened, exactly where the collapse would
48
+ * have published it, over the same span. So both engines run the body with no
49
+ * frame of the collapse's own and hand its value up. The one value a frame would
50
+ * have treated differently is `undefined`, which a frame does not count as a
51
+ * child: the collapse then builds from no children. This is that case — take the
52
+ * child's publication back and publish the build instead, as the frame would
53
+ * have. The caller has already set `EC.e`.
54
+ */
55
+ export declare function collapseEmpty(ctx: ParseContext, build: CollapseBuild, input: string, pos: number, end: number, readsRaw: boolean): unknown;
56
+ export {};
38
57
  //# sourceMappingURL=run-support.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-support.d.ts","sourceRoot":"","sources":["../../src/table/run-support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,MAAM,IAAI,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AACjD,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAS3E;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAGjF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAUjF;AAED,yEAAyE;AACzE,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKvD"}
1
+ {"version":3,"file":"run-support.d.ts","sourceRoot":"","sources":["../../src/table/run-support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG/C,MAAM,MAAM,IAAI,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AACjD,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAS3E;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAGjF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAUjF;AAED,yEAAyE;AACzE,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAKvD;AAKD,KAAK,aAAa,GAAG,CACnB,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACrF,WAAW,EAAE,SAAS,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,KAC5E,OAAO,CAAA;AAEZ;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAClG,OAAO,CAST"}
@@ -10,6 +10,8 @@ export declare function makeScalarRecognizer(op: number, spec: unknown): ScalarR
10
10
  export declare function scalarTerminalNodeChild(code: ArrayLike<number>, ip: number): number;
11
11
  /** Direct untracked terminal whose recognition `not()` can inspect without materializing it. */
12
12
  export declare function scalarTerminalNotChild(code: ArrayLike<number>, ip: number): number;
13
+ /** The same terminal under `peek()`: its match decides the lookahead, and nothing it captured survives it. */
14
+ export declare function scalarTerminalPeekChild(code: ArrayLike<number>, ip: number): number;
13
15
  /**
14
16
  * A direct scalar terminal that every successful execution of `ip` must match
15
17
  * first, through wrappers that cannot consume or select before their child.
@@ -1 +1 @@
1
- {"version":3,"file":"scalar-terminal.d.ts","sourceRoot":"","sources":["../../src/table/scalar-terminal.ts"],"names":[],"mappings":"AAMA,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;AAEpF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAwB5F;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAKnF;AAED,gGAAgG;AAChG,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,SAAI,EAAE,cAAc,UAAO,EAAE,WAAW,UAAQ,GAC5F,MAAM,CA4CR;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAClC,qBAAqB,GAAG,SAAS,CAmEnC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,EAClE,QAAQ,SAAI,EAAE,KAAK,SAAI,GACtB,SAAS,MAAM,EAAE,GAAG,SAAS,CAwD/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GACpE,SAAS,MAAM,EAAE,GAAG,SAAS,CAW/B"}
1
+ {"version":3,"file":"scalar-terminal.d.ts","sourceRoot":"","sources":["../../src/table/scalar-terminal.ts"],"names":[],"mappings":"AAMA,4FAA4F;AAC5F,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;AAEpF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAwB5F;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAKnF;AAED,gGAAgG;AAChG,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAKlF;AAED,8GAA8G;AAC9G,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAKnF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,SAAI,EAAE,cAAc,UAAO,EAAE,WAAW,UAAQ,GAC5F,MAAM,CA4CR;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,GAClC,qBAAqB,GAAG,SAAS,CAmEnC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,OAAO,EAAE,EAAE,EAAE,EAAE,MAAM,EAClE,QAAQ,SAAI,EAAE,KAAK,SAAI,GACtB,SAAS,MAAM,EAAE,GAAG,SAAS,CAwD/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GACpE,SAAS,MAAM,EAAE,GAAG,SAAS,CAW/B"}
@@ -46,6 +46,28 @@ export declare const TOP: SiteLabel;
46
46
  * unemittable regardless.
47
47
  */
48
48
  export declare function reachableSites(code: Int32Array, roots: Iterable<number>): Set<number>;
49
+ /**
50
+ * A node both engines lower as a PASS-THROUGH COLLAPSE (`run-support.ts`
51
+ * `collapseEmpty`): `collapse: true` over a direct reducer, no projection, no CST
52
+ * host, no line tracking, whose body is one node behind wrappers that consume and
53
+ * capture nothing (a first-set gate, a trivia scope, a rule reference). One
54
+ * predicate, so the two engines and the labels cannot disagree on a site.
55
+ */
56
+ export declare function passCollapse(code: Int32Array, ip: number, hostCst: boolean): boolean;
57
+ /**
58
+ * A collapse that a FIRST CHARACTER can reduce to a pass-through: its body is a
59
+ * sequence of `optional`/`not` terms, each behind a first-character gate, then
60
+ * one node. Where the character at the collapse's start opens none of those
61
+ * terms (nor its sequence's trivia — the engines test that against the site's
62
+ * label), every one of them matches empty there and the body is that node alone.
63
+ * `leads` are the gate rows, `lits` the constant indices of direct literal terms,
64
+ * `last` the node term. Not a `passCollapse`.
65
+ */
66
+ export declare function gatedPassCollapse(code: Int32Array, ip: number, hostCst: boolean): {
67
+ leads: number[];
68
+ lits: number[];
69
+ last: number;
70
+ } | undefined;
49
71
  /** A site's label, or `TOP` for anything the walk did not reach. */
50
72
  export type SiteLabels = {
51
73
  at: (ip: number) => SiteLabel;
@@ -63,4 +85,48 @@ export type SiteLabels = {
63
85
  * lattice has height three and the walk terminates.
64
86
  */
65
87
  export declare function computeSiteLabels(code: Int32Array, roots: Iterable<number>, hostCst: boolean): SiteLabels;
88
+ /**
89
+ * Does a piece entered at `ip` need the caller's INSTALLED trivia scanner handed
90
+ * across an engine boundary?
91
+ *
92
+ * Each table engine keeps its own installed-scanner slot, and the two hand theirs
93
+ * across at every call between them. A site whose label names its trivia (`tri`)
94
+ * reads no slot: both engines lower its skips onto that trivia's own scanner, and
95
+ * a call out of it can hand that scanner across instead of the slot. Only a site
96
+ * whose trivia the labels cannot name (`TRI_UNKNOWN`) reads the slot. So the
97
+ * handoff is needed exactly when such a site is reachable from `ip` before a
98
+ * scope installs a scanner of its own (an aliased scope, which installs what is
99
+ * already installed, does not). `false` is also the answer for a body with no
100
+ * trivia at all.
101
+ */
102
+ /**
103
+ * A trivia scope that writes only what its label proves is already in force, so
104
+ * it runs as its child. A slot scope under that same slot is one; so is a scope
105
+ * with no trivia where the label proves none (inside `token()` or another such
106
+ * scope), since every way to `TRI_NONE` leaves no trivia, no kind labels and no
107
+ * scanner — what this scope would write. `OP_SCOPE_PLAIN` has no root policy; a
108
+ * policy-bearing `OP_SCOPE` is one only with policy zero, and `OP_SCOPE_CAP`
109
+ * never is, since it raises `captureTrivia`. `TRI_UNKNOWN` matches nothing.
110
+ */
111
+ export declare function scopeIsIdentity(code: Int32Array, ip: number, at: SiteLabel): boolean;
112
+ export declare function scanHandoffNeeded(code: Int32Array, ip: number, labels: SiteLabels): boolean;
113
+ /**
114
+ * The first characters that can start trivia at a site labelled `tri`, as code
115
+ * point ranges: none for `TRI_NONE`, `undefined` where the labels cannot name the
116
+ * trivia or its first set is unbounded.
117
+ */
118
+ export declare function triviaLeadRanges(tri: number, trivia: readonly ({
119
+ _meta: {
120
+ firstSet: {
121
+ kind: string;
122
+ ranges?: readonly {
123
+ lo: number;
124
+ hi: number;
125
+ }[];
126
+ };
127
+ };
128
+ } | undefined)[]): readonly {
129
+ lo: number;
130
+ hi: number;
131
+ }[] | undefined;
66
132
  //# sourceMappingURL=site-labels.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"site-labels.d.ts","sourceRoot":"","sources":["../../src/table/site-labels.ts"],"names":[],"mappings":"AAoEA,iFAAiF;AACjF,eAAO,MAAM,WAAW,KAAK,CAAA;AAC7B,qDAAqD;AACrD,eAAO,MAAM,QAAQ,KAAK,CAAA;AAC1B,0EAA0E;AAE1E,qDAAqD;AACrD,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,yDAAyD;AACzD,eAAO,MAAM,OAAO,IAAI,CAAA;AACxB,uDAAuD;AACvD,eAAO,MAAM,MAAM,IAAI,CAAA;AAEvB,0DAA0D;AAC1D,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,uCAAuC;AACvC,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,oDAAoD;AACpD,eAAO,MAAM,QAAQ,IAAI,CAAA;AAEzB,MAAM,MAAM,SAAS,GAAG;IACtB,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;IACrB,kDAAkD;IAClD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,wDAAwD;AACxD,eAAO,MAAM,GAAG,EAAE,SAAgF,CAAA;AAWlG;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAarF;AA8DD,oEAAoE;AACpE,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,SAAS,CAAA;CAAE,CAAA;AAE1D;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,OAAO,EAAE,OAAO,GACf,UAAU,CA6BZ"}
1
+ {"version":3,"file":"site-labels.d.ts","sourceRoot":"","sources":["../../src/table/site-labels.ts"],"names":[],"mappings":"AAoEA,iFAAiF;AACjF,eAAO,MAAM,WAAW,KAAK,CAAA;AAC7B,qDAAqD;AACrD,eAAO,MAAM,QAAQ,KAAK,CAAA;AAC1B,0EAA0E;AAE1E,qDAAqD;AACrD,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,yDAAyD;AACzD,eAAO,MAAM,OAAO,IAAI,CAAA;AACxB,uDAAuD;AACvD,eAAO,MAAM,MAAM,IAAI,CAAA;AAEvB,0DAA0D;AAC1D,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,uCAAuC;AACvC,eAAO,MAAM,WAAW,IAAI,CAAA;AAC5B,oDAAoD;AACpD,eAAO,MAAM,QAAQ,IAAI,CAAA;AAEzB,MAAM,MAAM,SAAS,GAAG;IACtB,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;IACrB,kDAAkD;IAClD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,wDAAwD;AACxD,eAAO,MAAM,GAAG,EAAE,SAAgF,CAAA;AAWlG;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAarF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAYpF;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAuB/I;AAqED,oEAAoE;AACpE,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,SAAS,CAAA;CAAE,CAAA;AAE1D;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,OAAO,EAAE,OAAO,GACf,UAAU,CA6BZ;AAED;;;;;;;;;;;;;GAaG;AACH;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAKpF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAqB3F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC;IAAE,KAAK,EAAE;QAAE,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,SAAS;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,EAAE,EAAE,MAAM,CAAA;aAAE,EAAE,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,CAAC,EAAE,GACtI,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,SAAS,CAOnD"}
package/dist/version.d.ts CHANGED
@@ -13,5 +13,5 @@
13
13
  * Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
14
14
  * the build if this constant drifts from the published version.
15
15
  */
16
- export declare const PARSEMAN_VERSION = "0.57.0";
16
+ export declare const PARSEMAN_VERSION = "0.58.0";
17
17
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parseman",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "engines": {
6
6
  "node": "^20.19.0 || >=22.13.0"
@@ -105,10 +105,19 @@ export function branchUsesRouted(branch: { parser: Combinator<unknown>; usesRout
105
105
  }
106
106
 
107
107
  export function asciiFoldKey(key: string): string {
108
- let out = ''
109
- for (let i = 0; i < key.length; i++) {
108
+ // A key with no ASCII capital is its own fold: every dispatch on a lowercase
109
+ // word asks, and building the same string again a character at a time cost an
110
+ // allocation per character.
111
+ let i = 0
112
+ for (; i < key.length; i++) {
110
113
  const c = key.charCodeAt(i)
111
- out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c)
114
+ if (c >= 65 && c <= 90) break
115
+ }
116
+ if (i === key.length) return key
117
+ let out = key.slice(0, i)
118
+ for (; i < key.length; i++) {
119
+ const c = key.charCodeAt(i)
120
+ out += c >= 65 && c <= 90 ? String.fromCharCode(c + 32) : key[i]!
112
121
  }
113
122
  return out
114
123
  }
@@ -37,6 +37,7 @@ import type { BalancedSpec } from '../combinators/scanTo.ts'
37
37
  import { deriveExpected } from '../combinators/expect.ts'
38
38
  import { assertionFailureExpected, directTerminalFailureExpected } from '../combinators/expected.ts'
39
39
  import { keywordsRegExp } from '../combinators/keywords.ts'
40
+ import { hasOwnTriviaBoundary } from '../combinators/trivia-boundary.ts'
40
41
 
41
42
  /** A terminal in the derived alphabet, with its globally assigned id. */
42
43
  export type TokenTerminal =
@@ -135,12 +136,17 @@ export type ExecutableLexNode =
135
136
  | {
136
137
  readonly kind: 'choice'
137
138
  readonly arms: readonly ExecutableLexNode[]
138
- readonly firstSets: readonly FirstSet[]
139
+ /** The authored arms: the encoder classifies them as the character choice would. */
140
+ readonly parsers: readonly Combinator<unknown>[]
141
+ /** Inside a no-trivia scope of the token, where the encoder's trivia is none. */
142
+ readonly cleared: boolean
139
143
  readonly armExpected: readonly (readonly string[])[]
140
144
  readonly expected: readonly string[]
141
145
  }
142
146
  | { readonly kind: 'not'; readonly body: ExecutableLexNode; readonly expected: readonly string[] }
143
147
  | { readonly kind: 'optional'; readonly body: ExecutableLexNode }
148
+ /** A separator-less `many`/`oneOrMore`; `max` −1 is unbounded. */
149
+ | { readonly kind: 'repeat'; readonly body: ExecutableLexNode; readonly min: number; readonly max: number }
144
150
 
145
151
  export type ExecutableFixedLexBody = {
146
152
  readonly kind: 'fixed-tree'
@@ -204,9 +210,22 @@ export function selectOptionalSuffixLexBody(ir: LexicalIr): LexBodyCandidate {
204
210
  /** Unwrap only scopes that token() has already installed identically. The
205
211
  * returned parser is still the authored terminal/sequence; no effectful wrapper
206
212
  * or final named winner is guessed from construction metadata. */
213
+ /**
214
+ * A rule reference made with no trivia in force reinstalls the rule's own
215
+ * ambient trivia when the rule has a position that reads it (`ref.ts`, the
216
+ * encoder's `scopedRef`), and inside a token none is in force. Such a rule's
217
+ * terms are then trivia-separated, which a lexical body does not model, so the
218
+ * reference is not lowered.
219
+ */
220
+ function reinstallsRuleTrivia(reference: Combinator<unknown>, target: Combinator<unknown>): boolean {
221
+ return (reference._meta.grammarTrivia ?? target._meta.grammarTrivia) !== undefined
222
+ && hasOwnTriviaBoundary(target)
223
+ }
224
+
207
225
  function directTokenBodyParser(
208
226
  parser: Combinator<unknown>,
209
227
  resolve?: (name: string) => Combinator<unknown> | undefined,
228
+ scope?: { cleared: boolean },
210
229
  ): Combinator<unknown> | undefined {
211
230
  const outer = parser._def
212
231
  if (outer.tag !== 'token') return undefined
@@ -220,12 +239,13 @@ function directTokenBodyParser(
220
239
  || wrapper.captureTrivia === true || wrapper.rootCapture === 'opaque'
221
240
  || wrapper.constructionTrackLines !== 'inherit'
222
241
  || wrapper.constructionCaptureTriviaKinds !== undefined) return undefined
242
+ if (scope !== undefined) scope.cleared = true
223
243
  body = wrapper.parser
224
244
  continue
225
245
  }
226
246
  if (wrapper.tag === 'lazy') {
227
247
  const target = resolvedLazyTarget(body, resolve)
228
- if (target === undefined) return undefined
248
+ if (target === undefined || reinstallsRuleTrivia(body, target)) return undefined
229
249
  body = target
230
250
  continue
231
251
  }
@@ -436,6 +456,7 @@ function directFixedLexNode(
436
456
  resolve: ((name: string) => Combinator<unknown> | undefined) | undefined,
437
457
  active: Set<Combinator<unknown>>,
438
458
  pureOnly = false,
459
+ cleared = false,
439
460
  ): ExecutableLexNode | undefined {
440
461
  let current = parser
441
462
  const wrappers = new Set<Combinator<unknown>>()
@@ -444,7 +465,7 @@ function directFixedLexNode(
444
465
  const def = current._def
445
466
  if (def.tag === 'lazy') {
446
467
  const target = resolvedLazyTarget(current, resolve)
447
- if (target === undefined) return undefined
468
+ if (target === undefined || reinstallsRuleTrivia(current, target)) return undefined
448
469
  current = target
449
470
  continue
450
471
  }
@@ -459,43 +480,57 @@ function directFixedLexNode(
459
480
  if (terminal !== undefined) return { kind: 'terminal', terminal }
460
481
  const def = current._def
461
482
  if (def.tag === 'grammar') {
483
+ // A scope with no trivia of its own writes nothing under a token that is
484
+ // not already in force: a cleared one runs as its child in the table
485
+ // (`scopeIsIdentity`) and an inheriting one encodes no row. Its line
486
+ // policy is the table's own (`hasScopedTrackLines`). So it is its child
487
+ // at any position.
488
+ if (def.triviaParser === undefined && def.captureTrivia !== true
489
+ && def.rootCapture !== 'opaque' && def.constructionCaptureTriviaKinds === undefined) {
490
+ return directFixedLexNode(def.parser, resolve, active, pureOnly, cleared || def.clearTrivia === true)
491
+ }
462
492
  if (!pureOnly || (def.parser._def.tag !== 'token'
463
493
  && (def.clearTrivia !== true || def.triviaParser !== undefined
464
494
  || def.captureTrivia === true || def.rootCapture === 'opaque'))) return undefined
465
- return directFixedLexNode(def.parser, resolve, active, true)
495
+ return directFixedLexNode(def.parser, resolve, active, true, cleared)
466
496
  }
467
497
  if (def.tag === 'sequence') {
468
498
  if (def.parsers.length < 2 || def.parsers.length > 5) return undefined
469
- const parts = def.parsers.map(part => directFixedLexNode(part, resolve, active, pureOnly))
499
+ const parts = def.parsers.map(part => directFixedLexNode(part, resolve, active, pureOnly, cleared))
470
500
  if (parts.some(part => part === undefined)) return undefined
471
501
  return { kind: 'sequence', parts: parts as ExecutableLexNode[] }
472
502
  }
473
503
  if (def.tag === 'choice') {
474
- if ((def.parsers.length !== 2 && def.parsers.length !== 4 && def.parsers.length !== 8)
504
+ if (def.parsers.length < 2
475
505
  || def.strategy.tag !== 'firstMatch'
476
506
  || def.gates.some(gate => gate !== null)
477
507
  || def.autoNot.some(check => check !== null)) return undefined
478
- const arms = def.parsers.map(arm => directFixedLexNode(arm, resolve, active, pureOnly))
508
+ const arms = def.parsers.map(arm => directFixedLexNode(arm, resolve, active, pureOnly, cleared))
479
509
  if (arms.some(arm => arm === undefined)) return undefined
480
- const final = classifyFinalChoice(def.parsers, resolve)
481
510
  return {
482
511
  kind: 'choice', arms: arms as ExecutableLexNode[],
483
- firstSets: final.firstSets,
512
+ parsers: def.parsers, cleared,
484
513
  armExpected: def.parsers.map(deriveExpected),
485
514
  expected: def.parsers.flatMap(deriveExpected),
486
515
  }
487
516
  }
488
517
  if (def.tag === 'not') {
489
- const body = directFixedLexNode(def.parser, resolve, active, true)
518
+ const body = directFixedLexNode(def.parser, resolve, active, true, cleared)
490
519
  return body === undefined ? undefined : {
491
520
  kind: 'not', body,
492
521
  expected: assertionFailureExpected(false, def.parser._tag),
493
522
  }
494
523
  }
495
524
  if (def.tag === 'optional') {
496
- const body = directFixedLexNode(def.parser, resolve, active, pureOnly)
525
+ const body = directFixedLexNode(def.parser, resolve, active, pureOnly, cleared)
497
526
  return body === undefined ? undefined : { kind: 'optional', body }
498
527
  }
528
+ if (def.tag === 'many' || def.tag === 'oneOrMore') {
529
+ const body = directFixedLexNode(def.parser, resolve, active, pureOnly, cleared)
530
+ return body === undefined ? undefined : {
531
+ kind: 'repeat', body, min: def.tag === 'many' ? 0 : def.min, max: def.max ?? -1,
532
+ }
533
+ }
499
534
  return undefined
500
535
  } finally {
501
536
  active.delete(current)
@@ -506,9 +541,10 @@ export function directFixedTokenBody(
506
541
  parser: Combinator<unknown>,
507
542
  resolve?: (name: string) => Combinator<unknown> | undefined,
508
543
  ): ExecutableFixedLexBody | undefined {
509
- const body = directTokenBodyParser(parser, resolve)
544
+ const scope = { cleared: false }
545
+ const body = directTokenBodyParser(parser, resolve, scope)
510
546
  if (body === undefined) return undefined
511
- const root = directFixedLexNode(body, resolve, new Set())
547
+ const root = directFixedLexNode(body, resolve, new Set(), false, scope.cleared)
512
548
  return root === undefined ? undefined : { kind: 'fixed-tree', root }
513
549
  }
514
550
 
@@ -1556,8 +1592,11 @@ function normalizeLexical(
1556
1592
  if (stack.has(parser)) return { refusal: 'recursive token body' }
1557
1593
  stack.add(parser)
1558
1594
  try {
1559
- return withLexicalControl(session, parser._def.tag, () =>
1560
- normalizeLexicalBody(parser, resolve, stack, session))
1595
+ // A nested `balanced()` is its scanner machine, not the transform that
1596
+ // builds its value — `sequence('@', balanced('{', '}'))` as a skipper.
1597
+ return normalizeBalancedLexical(parser, resolve, session)
1598
+ ?? withLexicalControl(session, parser._def.tag, () =>
1599
+ normalizeLexicalBody(parser, resolve, stack, session))
1561
1600
  } finally {
1562
1601
  stack.delete(parser)
1563
1602
  }
@@ -1841,8 +1880,9 @@ function fixedBindingProjection(
1841
1880
  // and the emitted adapter captures that site's named body after linking every
1842
1881
  // scan root. Only ambient trivia/scanSkip remain live runtime callbacks; they
1843
1882
  // are scanner POLICY, never binding authority for these authored own children.
1844
- // Recovery/withCtx still have no equivalent named template.
1845
- if (edge.parentTag === 'recover' || edge.parentTag === 'withCtx') readerMask = 0b011
1883
+ // Recovery still has no equivalent named template. `withCtx` has one: the
1884
+ // emitter's OP_WITHCTX body calls its child by the name `link()` minted.
1885
+ if (edge.parentTag === 'recover') readerMask = 0b011
1846
1886
  // Sequence strict, adjacency, and recovery bodies now all use scalar block
1847
1887
  // chains, so no supported variant is withheld here.
1848
1888
  const words = [
@@ -111,6 +111,31 @@ export function pushCstChild(ctx: ParseContext, built: unknown, rawEntry: unknow
111
111
  if (ctx._cstRawChildren) ctx._cstRawChildren.push(rawEntry)
112
112
  }
113
113
 
114
+ /**
115
+ * Undo the LAST `pushCstChild` into the active collector, whichever form it took:
116
+ * a child the lazy buffer's empty sentinel dropped (`undefined` first) leaves no
117
+ * semantic entry to remove, only its raw one.
118
+ */
119
+ export function popCstChild(ctx: ParseContext): void {
120
+ const b = ctx._cstBuf
121
+ if (b) {
122
+ const ch = b.ch
123
+ if (b.rawOnly !== true && ch) {
124
+ ch.pop()
125
+ if (ch.length === 1) { b.single = ch[0]; b.ch = undefined }
126
+ }
127
+ if (b.noRaw === true) b.rawLen = (b.rawLen ?? 1) - 1
128
+ else if (b.raw) {
129
+ b.raw.pop()
130
+ if (b.raw.length === 1) { b.rawSingle = b.raw[0]; b.raw = undefined }
131
+ } else b.rawSingle = undefined
132
+ return
133
+ }
134
+ if (ctx._cstChildren) ctx._cstChildren.pop()
135
+ else if (ctx._cstLeaves) ctx._cstLeaves.pop()
136
+ if (ctx._cstRawChildren) ctx._cstRawChildren.pop()
137
+ }
138
+
114
139
  export function cstRawLen(ctx: ParseContext): number {
115
140
  const b = ctx._cstBuf
116
141
  if (b) {