parseman 0.47.0 → 0.47.1

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 (57) hide show
  1. package/dist/analysis/diagnostics.cjs +133 -45
  2. package/dist/analysis/diagnostics.cjs.map +2 -2
  3. package/dist/analysis/diagnostics.js +133 -45
  4. package/dist/analysis/diagnostics.js.map +2 -2
  5. package/dist/cli/index.cjs +133 -45
  6. package/dist/cli/index.cjs.map +2 -2
  7. package/dist/cli/index.js +133 -45
  8. package/dist/cli/index.js.map +2 -2
  9. package/dist/combinators/first-set.d.ts.map +1 -1
  10. package/dist/combinators/sequence.d.ts.map +1 -1
  11. package/dist/combinators/trivia-skip.d.ts +5 -0
  12. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  13. package/dist/index.cjs +134 -46
  14. package/dist/index.cjs.map +2 -2
  15. package/dist/index.js +134 -46
  16. package/dist/index.js.map +2 -2
  17. package/dist/language-service/index.cjs +3 -1
  18. package/dist/language-service/index.cjs.map +1 -1
  19. package/dist/language-service/index.js +3 -1
  20. package/dist/language-service/index.js.map +1 -1
  21. package/dist/plugin/index.cjs +134 -46
  22. package/dist/plugin/index.cjs.map +2 -2
  23. package/dist/plugin/index.js +134 -46
  24. package/dist/plugin/index.js.map +2 -2
  25. package/dist/spec/index.cjs +55 -7
  26. package/dist/spec/index.cjs.map +1 -1
  27. package/dist/spec/index.js +55 -7
  28. package/dist/spec/index.js.map +1 -1
  29. package/dist/table/assemble.d.ts.map +1 -1
  30. package/dist/table/emit-assembly.d.ts +2 -2
  31. package/dist/table/emit-assembly.d.ts.map +1 -1
  32. package/dist/table/emit.d.ts.map +1 -1
  33. package/dist/table/exec.d.ts.map +1 -1
  34. package/dist/table/index.cjs +1566 -1478
  35. package/dist/table/index.cjs.map +3 -3
  36. package/dist/table/index.js +1566 -1478
  37. package/dist/table/index.js.map +3 -3
  38. package/dist/table/ops.d.ts +8 -1
  39. package/dist/table/ops.d.ts.map +1 -1
  40. package/dist/table/program.d.ts +4 -1
  41. package/dist/table/program.d.ts.map +1 -1
  42. package/dist/version.d.ts +1 -1
  43. package/package.json +2 -1
  44. package/src/combinators/first-set.ts +29 -3
  45. package/src/combinators/sequence.ts +23 -7
  46. package/src/combinators/trivia-skip.ts +31 -0
  47. package/src/table/assemble.ts +30 -18
  48. package/src/table/child-slots.ts +2 -2
  49. package/src/table/emit-assembly.ts +36 -25
  50. package/src/table/emit.ts +12 -1
  51. package/src/table/encode.ts +4 -4
  52. package/src/table/exec.ts +20 -12
  53. package/src/table/ops.ts +9 -1
  54. package/src/table/program.ts +22 -1
  55. package/src/table/site-labels.ts +2 -2
  56. package/src/table/stamp.ts +2 -2
  57. package/src/version.ts +1 -1
@@ -9,7 +9,7 @@ import { asciiFoldKey, branchUsesRouted, parserUsesRouted } from '../combinators
9
9
  import {
10
10
  OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
11
11
  OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
12
- OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_EXPECT, OP_SEQX, OP_SCAN,
12
+ OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
13
13
  OP_LIVE, OP_ATTEMPT, OP_LABEL,
14
14
  OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD,
15
15
  OP_ADJ, OP_GREEDY, OP_REJECT, OP_ARMGATE, OP_COV,
@@ -610,7 +610,7 @@ class Encoder {
610
610
  // straight to the body's offset (`case 'lazy'`), so every recursive call and
611
611
  // every cross-rule reference would jump past the counter, and a grammar with
612
612
  // one entry would report one hit rule however much of it ran.
613
- this.rules[name] = amb === undefined ? body : this.emit(OP_SCOPE, this.triviaSlot(amb), body)
613
+ this.rules[name] = amb === undefined ? body : this.emit(OP_SCOPE_PLAIN, this.triviaSlot(amb), body)
614
614
  }
615
615
 
616
616
  /**
@@ -656,7 +656,7 @@ class Encoder {
656
656
  const amb = p._meta.grammarTrivia ?? target._meta.grammarTrivia
657
657
  if (amb === undefined || this.activeTrivia !== undefined) return ip
658
658
  if (!hasOwnTriviaBoundary(target)) return ip
659
- return this.emit(OP_SCOPE, this.triviaSlot(amb), ip)
659
+ return this.emit(OP_SCOPE_PLAIN, this.triviaSlot(amb), ip)
660
660
  }
661
661
 
662
662
  node(p: Combinator<unknown>): Emitted {
@@ -1399,7 +1399,7 @@ class Encoder {
1399
1399
  switch (this.code[ip]) {
1400
1400
  case OP_GATE: return [ip + 2]
1401
1401
  case OP_RULE: case OP_OPT: case OP_NOT: case OP_PEEK: case OP_EXPECT: case OP_ATTEMPT: case OP_LABEL: case OP_COV: return [ip + 1]
1402
- case OP_SCOPE: case OP_SCOPE_CAP: case OP_WITHCTX: case OP_XFORM: case OP_LEAF: case OP_NODE: case OP_NODE_TRACK: return [ip + 2]
1402
+ case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM: case OP_LEAF: case OP_NODE: case OP_NODE_TRACK: return [ip + 2]
1403
1403
  case OP_SEQ: case OP_SEQV: return Array.from({ length: this.code[ip + 1]! }, (_, i) => ip + 2 + i)
1404
1404
  case OP_SEQX: return Array.from({ length: this.code[ip + 2]! }, (_, i) => ip + 3 + i)
1405
1405
  // ARMS START AT ip+4. `ip+3` is the choice's own EXPECTED-SET index, and
package/src/table/exec.ts CHANGED
@@ -6,7 +6,7 @@ import { projectChild, unwrapChild } from '../combinators/node.ts'
6
6
  import { asciiFoldEq } from '../combinators/literal.ts'
7
7
  import { cstOutputHost } from '../compiler/build-arity.ts'
8
8
  import { consumeTrivia } from '../combinators/trivia-skip.ts'
9
- import { advanceTrivia, needsDeferredTriviaCommit, rollbackTrivia, rollbackTriviaAt, saveTriviaMark, scanTrivia, skipTriviaScanned, type FastTriviaScanner } from '../combinators/trivia-skip.ts'
9
+ import { advanceTrivia, needsDeferredTriviaCommit, rollbackScannedTriviaAt, rollbackTrivia, rollbackTriviaAt, saveTriviaMark, scanTrivia, skipTriviaScanned, type FastTriviaScanner } from '../combinators/trivia-skip.ts'
10
10
  import {
11
11
  beginCstNodeCapture, cstCaptureActive, cstLeavesLen, cstRawLen, cstTlLen,
12
12
  demoteCapturedToRaw, endCstNodeCapture, pushCstChild, pushCstLeaf,
@@ -14,7 +14,7 @@ import {
14
14
  import {
15
15
  OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
16
16
  OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
17
- OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_EXPECT, OP_SEQX, OP_SCAN,
17
+ OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
18
18
  OP_LIVE,
19
19
  OP_FIELD, OP_DISPATCH, OP_ROUTED, OP_LIT_CI, OP_LIT_CI_TRACK, OP_TOKEN, OP_WITHCTX, OP_GUARD, OP_ATTEMPT, OP_LABEL,
20
20
  OP_COV,
@@ -609,15 +609,21 @@ function makeDriver(
609
609
  usesRouted = spec.routed[arm] === 1
610
610
  }
611
611
 
612
- const savedRouted = ctx._routed
613
612
  let mark = saveTriviaMark(ctx)
613
+ let v: unknown
614
614
  if (usesRouted) {
615
+ const savedRouted = ctx._routed
615
616
  rollbackTrivia(ctx, selectorMark)
616
617
  mark = saveTriviaMark(ctx)
617
618
  ctx._routed = { value: key, span: { start: pos, end: selEnd } }
619
+ try {
620
+ v = exec(target, input, pos, ctx)
621
+ } finally {
622
+ ctx._routed = savedRouted
623
+ }
624
+ } else {
625
+ v = exec(target, input, selEnd, ctx)
618
626
  }
619
- const v = exec(target, input, usesRouted ? pos : selEnd, ctx)
620
- if (usesRouted) ctx._routed = savedRouted
621
627
  if (v === FAIL) {
622
628
  rollbackTrivia(ctx, mark)
623
629
  // The interpreter marks a failed dispatch branch COMMITTED: the
@@ -706,7 +712,8 @@ function makeDriver(
706
712
  }
707
713
 
708
714
  case OP_SCOPE:
709
- case OP_SCOPE_CAP: {
715
+ case OP_SCOPE_CAP:
716
+ case OP_SCOPE_PLAIN: {
710
717
  const ki = code[ip + 1]!
711
718
  const saved = ctx.trivia
712
719
  const savedLabels = ctx.triviaKindLabels
@@ -735,7 +742,7 @@ function makeDriver(
735
742
  // (`grammar.ts:141`), and the flag is restored here because the table
736
743
  // shares one ctx where `parser()` copies it. Bit 2 = the unclassified-scope
737
744
  // refusal `grammar.ts:98` raises.
738
- const policy = code[ip + 3]!
745
+ const policy = code[ip] === OP_SCOPE_PLAIN ? 0 : code[ip + 3]!
739
746
  if ((policy & 2) !== 0) refuseUnclassifiedRootScope(ctx._rootTriviaStrictScopes)
740
747
  const savedRootCap = ctx._rootTriviaCapture
741
748
  if ((policy & 1) !== 0) ctx._rootTriviaCapture = false
@@ -786,18 +793,19 @@ function makeDriver(
786
793
  // SCALAR MARKS — `saveTriviaMark` allocated TWICE per term (its own
787
794
  // seven-field object plus the five-field CST mark it delegates to).
788
795
  const need = rollbackNeeded(ctx)
789
- const mRaw = need ? cstRawLen(ctx) : 0
790
796
  const mTl = need ? cstTlLen(ctx) : 0
791
- const mLv = need ? cstLeavesLen(ctx) : 0
792
- const mFl = need ? ctx._fields?.length ?? 0 : 0
793
- const mEr = need ? ctx._errors?.length ?? 0 : 0
794
797
  const mLog = need ? ctx._triviaLog?.length ?? 0 : 0
795
798
  const mRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
796
799
  const scanEnd = skipTrivia(input, cur, ctx)
800
+ const scanTl = need ? cstTlLen(ctx) : 0
801
+ const scanLog = need ? ctx._triviaLog?.length ?? 0 : 0
802
+ const scanRoot = need ? ctx._rootTriviaLog?.length ?? 0 : 0
797
803
  const v = exec(child, input, scanEnd, ctx)
798
804
  if (v === FAIL) { if (REC) ctx._sync = inheritedSync; return FAIL }
799
805
  if (EC.e > scanEnd) cur = EC.e
800
- else if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
806
+ else if (need) rollbackScannedTriviaAt(
807
+ ctx, mTl, scanTl, mLog, scanLog, mRoot, scanRoot,
808
+ )
801
809
  if (values !== undefined) values.push(v)
802
810
  continue
803
811
  }
package/src/table/ops.ts CHANGED
@@ -104,12 +104,14 @@ export const OP_LIT_TRACK = 17
104
104
  export const OP_RX_TRACK = 18
105
105
  export const OP_NODE_TRACK = 19
106
106
  /**
107
- * `SCOPE k c` — a `parser({ trivia })` / `rules({ trivia })` scope.
107
+ * `SCOPE k c policy` — a policy-bearing `parser({ trivia })` scope.
108
108
  *
109
109
  * `k` is the scope's trivia COMBINATOR in the const pool. The driver installs it
110
110
  * on `ctx.trivia` for the duration and restores the outer one after, which is
111
111
  * how the runtime's own `advanceTrivia` fast scanner gets reached — the same
112
112
  * shared machinery the interpreter uses, not a second copy of it.
113
+ * Synthetic rule-entry/restoration scopes use `OP_SCOPE_PLAIN` so a zero
114
+ * policy stays a three-word row without changing this opcode's ABI.
113
115
  */
114
116
  export const OP_SCOPE = 20
115
117
  /**
@@ -425,6 +427,11 @@ export const OP_LABEL = 39
425
427
  * which is the granularity the IDs are minted at.
426
428
  */
427
429
  export const OP_COV = 40
430
+ /**
431
+ * `SCOPE_PLAIN k c` — a synthetic ambient-trivia scope with policy fixed to zero.
432
+ * A distinct opcode prevents the next row's opcode from being read as policy.
433
+ */
434
+ export const OP_SCOPE_PLAIN = 41
428
435
  /**
429
436
  * `DISPATCH sel d other otherRouted n a1 … an` — `dispatch()`.
430
437
  *
@@ -456,4 +463,5 @@ export const OP_NAMES: Record<number, string> = {
456
463
  [OP_ADJ]: 'ADJ',
457
464
  [OP_GREEDY]: 'GREEDY', [OP_REJECT]: 'REJECT', [OP_ARMGATE]: 'ARMGATE',
458
465
  [OP_LIVE]: 'LIVE', [OP_ATTEMPT]: 'ATTEMPT', [OP_LABEL]: 'LABEL', [OP_COV]: 'COV',
466
+ [OP_SCOPE_PLAIN]: 'SCOPE_PLAIN',
459
467
  }
@@ -124,7 +124,8 @@ export type TableProgram = {
124
124
  */
125
125
  readonly runtimeOnly?: readonly string[]
126
126
  /**
127
- * Trivia specs, referenced by index from `OP_SCOPE` and from the rule entries.
127
+ * Trivia specs, referenced by index from `OP_SCOPE`, `OP_SCOPE_CAP` and
128
+ * `OP_SCOPE_PLAIN` rule/restoration entries.
128
129
  *
129
130
  * `classifiedTrivia()` is `trivia(oneOrMore(choice(label(name, arm)…)))` with
130
131
  * regex arms — entirely structural, so it lowers to DATA and is rebuilt at
@@ -352,6 +353,26 @@ export type ResolvedDispatchSpec = {
352
353
  readonly expected: readonly string[]
353
354
  }
354
355
 
356
+ /** Refuse malformed dispatch arm references before closure/emitted linking. */
357
+ export function validateDispatchSpec(
358
+ spec: ResolvedDispatchSpec | undefined,
359
+ arity: number,
360
+ fallbackRouted: number,
361
+ ): asserts spec is ResolvedDispatchSpec {
362
+ const invalid = (arms: Iterable<number>): boolean => {
363
+ for (const arm of arms) if (!Number.isInteger(arm) || arm < 0 || arm >= arity) return true
364
+ return false
365
+ }
366
+ if (spec === undefined || !Number.isInteger(arity) || arity < 0
367
+ || (fallbackRouted !== 0 && fallbackRouted !== 1)
368
+ || spec.routed.length !== arity
369
+ || spec.routed.some(value => value !== 0 && value !== 1)
370
+ || invalid(spec.byKey.values()) || invalid(spec.byFold.values())
371
+ || spec.match.some(matcher => invalid([matcher[3]]))) {
372
+ throw new TypeError('table: malformed dispatch')
373
+ }
374
+ }
375
+
355
376
  /** A char class expanded for execution: O(1) for ASCII, ranges above it. */
356
377
  export type ResolvedClass = {
357
378
  readonly ascii: Uint8Array
@@ -62,7 +62,7 @@
62
62
  import {
63
63
  OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EXPECT, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF,
64
64
  OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE,
65
- OP_SCOPE, OP_SCOPE_CAP, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
65
+ OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
66
66
  } from './ops.ts'
67
67
  import { childSlots } from './child-slots.ts'
68
68
 
@@ -138,7 +138,7 @@ export function reachableSites(code: Int32Array, roots: Iterable<number>): Set<n
138
138
  */
139
139
  function transfer(code: Int32Array, ip: number, at: SiteLabel, hostCst: boolean): SiteLabel {
140
140
  const op = code[ip]
141
- if (op === OP_SCOPE || op === OP_SCOPE_CAP) {
141
+ if (op === OP_SCOPE || op === OP_SCOPE_CAP || op === OP_SCOPE_PLAIN) {
142
142
  const ki = code[ip + 1]!
143
143
  const tri = ki < 0 ? TRI_NONE : ki
144
144
  // `OP_SCOPE_CAP` is an OR with the inherited context, never a switch-off
@@ -14,7 +14,7 @@ import type { Combinator, ParseContext, ParseResult } from '../types.ts'
14
14
  import { FUSED_HOST_ELIDED, FUSED_HOST_MODE } from '../cst/host-mode.ts'
15
15
  import { GRAMMAR_COVERAGE_DEFINITIONS } from '../grammar-metadata.ts'
16
16
  import { reachableIps } from './inspect.ts'
17
- import { OP_NODE, OP_NODE_TRACK, OP_SCOPE } from './ops.ts'
17
+ import { OP_NODE, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_PLAIN } from './ops.ts'
18
18
  import { covDefinitions, resolveTable, type ResolvedTable, type TableProgram, type TableRule } from './program.ts'
19
19
 
20
20
  const EMPTY_FX: string[] = []
@@ -103,7 +103,7 @@ export function stampRuleMap(
103
103
  // `scanSkipOf` exists for). The owning driver passes its resolved table, so
104
104
  // this is the exact trivia array it already built.
105
105
  const triviaOfRule = (ip: number): Combinator<unknown> | undefined => {
106
- if (prog.code[ip] !== OP_SCOPE) return undefined
106
+ if (prog.code[ip] !== OP_SCOPE_PLAIN && prog.code[ip] !== OP_SCOPE) return undefined
107
107
  const slot = prog.code[ip + 1]!
108
108
  return slot < 0 ? undefined : resolved.trivia[slot]
109
109
  }
package/src/version.ts CHANGED
@@ -13,4 +13,4 @@
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 const PARSEMAN_VERSION = '0.47.0'
16
+ export const PARSEMAN_VERSION = '0.47.1'