parseman 0.58.2 → 0.59.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 (90) hide show
  1. package/dist/analysis/diagnostics.js +344 -130
  2. package/dist/analysis/diagnostics.js.map +3 -3
  3. package/dist/cli/index.js +346 -131
  4. package/dist/cli/index.js.map +3 -3
  5. package/dist/combinators/dispatch.d.ts.map +1 -1
  6. package/dist/combinators/field-trivia.d.ts.map +1 -1
  7. package/dist/combinators/first-set.d.ts.map +1 -1
  8. package/dist/combinators/keywords.d.ts.map +1 -1
  9. package/dist/combinators/literal.d.ts.map +1 -1
  10. package/dist/combinators/node.d.ts.map +1 -1
  11. package/dist/combinators/regex.d.ts.map +1 -1
  12. package/dist/combinators/scanTo.d.ts.map +1 -1
  13. package/dist/combinators/token.d.ts.map +1 -1
  14. package/dist/compiler/linker.d.ts.map +1 -1
  15. package/dist/compiler/token-capability.js +28 -4
  16. package/dist/compiler/token-capability.js.map +2 -2
  17. package/dist/compiler/token-scanner.d.ts.map +1 -1
  18. package/dist/cst/capture-buffer.d.ts.map +1 -1
  19. package/dist/cst/plain.js +69 -0
  20. package/dist/cst/plain.js.map +6 -0
  21. package/dist/index.js +350 -138
  22. package/dist/index.js.map +3 -3
  23. package/dist/language-service/index.js +29 -5
  24. package/dist/language-service/index.js.map +3 -3
  25. package/dist/lead-ranges.d.ts +26 -0
  26. package/dist/lead-ranges.d.ts.map +1 -0
  27. package/dist/line-index.d.ts +13 -0
  28. package/dist/line-index.d.ts.map +1 -1
  29. package/dist/plugin/entry-supercompiler.d.ts.map +1 -1
  30. package/dist/plugin/index.js +352 -140
  31. package/dist/plugin/index.js.map +3 -3
  32. package/dist/recovery/scan.d.ts.map +1 -1
  33. package/dist/regex/classes.d.ts +5 -3
  34. package/dist/regex/classes.d.ts.map +1 -1
  35. package/dist/regex/first-set.d.ts +7 -1
  36. package/dist/regex/first-set.d.ts.map +1 -1
  37. package/dist/run/index.js +14 -4
  38. package/dist/run/index.js.map +3 -3
  39. package/dist/spec/index.js +18 -1
  40. package/dist/spec/index.js.map +2 -2
  41. package/dist/table/assemble.d.ts.map +1 -1
  42. package/dist/table/compile-rule-map.d.ts.map +1 -1
  43. package/dist/table/emit-assembly.d.ts +9 -2
  44. package/dist/table/emit-assembly.d.ts.map +1 -1
  45. package/dist/table/emit.d.ts +6 -0
  46. package/dist/table/emit.d.ts.map +1 -1
  47. package/dist/table/encode-baseline.d.ts.map +1 -1
  48. package/dist/table/index.js +330 -129
  49. package/dist/table/index.js.map +3 -3
  50. package/dist/table/program.d.ts.map +1 -1
  51. package/dist/table/run-support.d.ts.map +1 -1
  52. package/dist/table/scan-shapes.d.ts +1 -1
  53. package/dist/table/scan-shapes.d.ts.map +1 -1
  54. package/dist/table/site-labels.d.ts.map +1 -1
  55. package/dist/table/trivia-gap.d.ts +11 -1
  56. package/dist/table/trivia-gap.d.ts.map +1 -1
  57. package/dist/version.d.ts +1 -1
  58. package/package.json +2 -1
  59. package/src/combinators/choice.ts +4 -0
  60. package/src/combinators/dispatch.ts +2 -1
  61. package/src/combinators/field-trivia.ts +6 -5
  62. package/src/combinators/first-set.ts +13 -4
  63. package/src/combinators/keywords.ts +3 -2
  64. package/src/combinators/literal.ts +5 -4
  65. package/src/combinators/node.ts +3 -1
  66. package/src/combinators/regex.ts +33 -13
  67. package/src/combinators/scanTo.ts +2 -1
  68. package/src/combinators/token.ts +3 -6
  69. package/src/compiler/linker.ts +4 -2
  70. package/src/compiler/token-scanner.ts +4 -1
  71. package/src/cst/capture-buffer.ts +13 -5
  72. package/src/cst/plain.d.ts +35 -0
  73. package/src/cst/plain.js +74 -0
  74. package/src/lead-ranges.ts +53 -0
  75. package/src/line-index.ts +37 -0
  76. package/src/plugin/entry-supercompiler.ts +3 -2
  77. package/src/recovery/scan.ts +4 -4
  78. package/src/regex/classes.ts +22 -7
  79. package/src/regex/first-set.ts +67 -12
  80. package/src/table/assemble.ts +75 -38
  81. package/src/table/compile-rule-map.ts +5 -3
  82. package/src/table/emit-assembly.ts +69 -32
  83. package/src/table/emit.ts +9 -1
  84. package/src/table/encode-baseline.ts +2 -1
  85. package/src/table/program.ts +2 -1
  86. package/src/table/run-support.ts +4 -7
  87. package/src/table/scan-shapes.ts +5 -2
  88. package/src/table/site-labels.ts +2 -1
  89. package/src/table/trivia-gap.ts +19 -0
  90. package/src/version.ts +1 -1
@@ -142,6 +142,9 @@ export const EMITTED_PARAMS = [
142
142
  // Appended for the pass-through collapse (`run-support.ts`); old factories
143
143
  // ignore it.
144
144
  'collapseEmpty',
145
+ // Appended for constructor-built tree objects (`cst/plain.d.ts`): a `new` call has
146
+ // no allocation site, so V8 cannot pretenure them. Old factories ignore them.
147
+ '_SP', '_LF', '_ND', '_FC', '_BUF',
145
148
  ] as const
146
149
 
147
150
  /**
@@ -180,9 +183,9 @@ if(s!==null)return skipTriviaScanned(s,input,cur,ctx)
180
183
  if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
181
184
  return advanceTrivia(input,cur,ctx)
182
185
  }
183
- function _pushLeaf(ctx,value,s,e){pushCstLeaf(ctx,{_tag:'leaf',value,span:{start:s,end:e}})}
186
+ function _pushLeaf(ctx,value,s,e){pushCstLeaf(ctx,new _LF(value,new _SP(s,e)))}
184
187
  function _pushLeafBuf(ctx,value,s,e){
185
- const l={_tag:'leaf',value,span:{start:s,end:e}}
188
+ const l=new _LF(value,new _SP(s,e))
186
189
  const b=ctx._cstBuf
187
190
  if(b.ch!==undefined)b.ch.push(l)
188
191
  else if(b.single!==undefined){b.ch=[b.single,l];b.single=undefined}
@@ -242,7 +245,7 @@ if(ctx._rootTriviaLog!==undefined&&ctx._rootTriviaLog.length!==rt)ctx._rootTrivi
242
245
  /** Printed only into assemblies that open a count-only raw buffer. */
243
246
  const NO_RAW_RUNTIME_PRELUDE = `
244
247
  function _pushLeafNoRawBuf(ctx,value,s,e){
245
- const l={_tag:'leaf',value,span:{start:s,end:e}}
248
+ const l=new _LF(value,new _SP(s,e))
246
249
  const b=ctx._cstBuf
247
250
  if(b.ch!==undefined)b.ch.push(l)
248
251
  else if(b.single!==undefined){b.ch=[b.single,l];b.single=undefined}
@@ -414,7 +417,7 @@ function emitRollback(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SI
414
417
  * only the first branch, a known slot keeps only the second (with the scope's own
415
418
  * scanner already bound into `skip`), and `TRI_UNKNOWN` keeps both.
416
419
  */
417
- function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string): string {
420
+ function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string, miss?: (c: string) => string): string {
418
421
  const fast = `const ${t}v=${callee}(input,cur,ctx)
419
422
  if(${t}v===FAIL)return FAIL
420
423
  ${dst}=${t}v
@@ -432,7 +435,14 @@ if(${t}v===FAIL)return FAIL
432
435
  ${dst}=${t}v
433
436
  if(EC.e>${t}s)cur=EC.e
434
437
  else{rollbackScannedTriviaAt(ctx,${t}tl,${t}stl,${t}lg,${t}slg,${t}rt,${t}srt)}`
435
- if (l.tri !== TRI_UNKNOWN) return scanned
438
+ // A character no trivia of this slot can start opens the empty gap, which
439
+ // records nothing, so the term runs at `cur` with no marks around a scan.
440
+ if (l.tri !== TRI_UNKNOWN) return miss === undefined ? scanned : `const ${t}c=input.charCodeAt(cur)
441
+ if(${miss(`${t}c`)}){
442
+ ${fast}
443
+ }else{
444
+ ${scanned}
445
+ }`
436
446
  return `if(ctx.trivia===undefined){
437
447
  ${fast}
438
448
  }else{
@@ -588,6 +598,13 @@ export function emitAssemblySource(
588
598
  extraIps: readonly number[] = [],
589
599
  staticBuild = false,
590
600
  select?: ReadonlySet<number>,
601
+ /**
602
+ * Leave the `_errors` bit of a lexical program over a scan out of the side-sink
603
+ * census below. A budgeted selection is priced and chosen on this lowering, so
604
+ * the marks that bit adds move no site in or out of a budget; the artifact is
605
+ * then emitted with them.
606
+ */
607
+ scanSinkNeutral = false,
591
608
  ): EmitResult {
592
609
  const { code, k, fx, disp, dsp, triviaLabelled } = t
593
610
  const swapLegal = !cfg.trackLines
@@ -733,13 +750,15 @@ export function emitAssemblySource(
733
750
  // that otherwise matched byte for byte.
734
751
  //
735
752
  // Any future row whose child is not an offset in `code` belongs here for the
736
- // same reason. `OP_LIVE` is the other such row, and it is unemittable.
753
+ // same reason. `OP_LIVE` is the other such row, and it is unemittable. An
754
+ // `OP_LEX_PROGRAM` over a scan (`t.lexPrograms[id].scan`, a whole `balanced()`
755
+ // lowered as one recognizer) runs that same interpreted scan, so it is one too.
737
756
  const sinkSites = reachable
738
757
  const sinkBits = new Map<number, number>()
739
758
  for (const ip of sinkSites) {
740
759
  const op = code[ip]
741
760
  sinkBits.set(ip,
742
- op === OP_SCAN ? 3
761
+ op === OP_SCAN || (!scanSinkNeutral && op === OP_LEX_PROGRAM && t.lexPrograms[code[ip + 1]!]!.scan !== undefined) ? 3
743
762
  : (op === OP_FIELD ? 1 : 0)
744
763
  | (op === OP_EXPECT || (REC && (op === OP_REP || op === OP_REPV)) ? 2 : 0))
745
764
  }
@@ -974,6 +993,19 @@ return 1
974
993
  return `if(${gateMissTest(child)}){${miss}}\n`
975
994
  }
976
995
 
996
+ /**
997
+ * The test that no trivia of slot `ki` can start at the code unit `c`: the
998
+ * slot's first characters, as code points, decide every code unit below the
999
+ * surrogates; a surrogate, or `NaN` past the end of input, is left to the scan.
1000
+ * `undefined` where the first set is unbounded or too wide to print.
1001
+ */
1002
+ function triviaMiss(ki: number): ((c: string) => string) | undefined {
1003
+ const leads = triviaLeadRanges(ki, t.trivia)
1004
+ if (leads === undefined || leads.length > 16) return undefined
1005
+ if (leads.length === 0) return c => `${c}<55296`
1006
+ return c => `${c}<55296&&!(${leads.map(r => r.lo === r.hi ? `${c}===${r.lo}` : `(${c}>=${r.lo}&&${c}<=${r.hi})`).join('||')})`
1007
+ }
1008
+
977
1009
  function skipFor(l: SiteLabel): string {
978
1010
  if (l.tri < 0) return '_skipTrivia'
979
1011
  const ki = l.tri
@@ -991,11 +1023,16 @@ return 1
991
1023
  if (!hasScan) {
992
1024
  // No installed scanner: the labelled/`trackLines` path, which records
993
1025
  // through `scanTrivia`. `needsDeferredTriviaCommit` is implied by an open
994
- // buffer, so an in-node site skips the call that asks.
1026
+ // buffer, so an in-node site skips the call that asks. A character no gap
1027
+ // can start answers first, with no call (`triviaMiss`).
1028
+ const miss = triviaMiss(ki)
1029
+ const pre = miss === undefined ? '' : `const c=input.charCodeAt(cur)
1030
+ if(${miss('c')})return cur
1031
+ `
995
1032
  skipDefs.push(l.buf
996
- ? `function ${nm}(input,cur,ctx){return skipDeferredTrivia(input,cur,ctx)}`
1033
+ ? `function ${nm}(input,cur,ctx){${pre === '' ? '' : `\n${pre}`}return skipDeferredTrivia(input,cur,ctx)}`
997
1034
  : `function ${nm}(input,cur,ctx){
998
- if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
1035
+ ${pre}if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
999
1036
  return advanceTrivia(input,cur,ctx)}`)
1000
1037
  return nm
1001
1038
  }
@@ -1397,7 +1434,7 @@ try{return ${child}(input,pos,ctx)}finally{ctx.state=saved}
1397
1434
  return `${head}
1398
1435
  const v=${child}(input,pos,ctx)
1399
1436
  if(v===FAIL)return FAIL
1400
- return ${fn}(v,{start:pos,end:EC.e})
1437
+ return ${fn}(v,new _SP(pos,EC.e))
1401
1438
  }`
1402
1439
  }
1403
1440
 
@@ -1427,7 +1464,7 @@ return v
1427
1464
  const v=${child}(input,pos,ctx)
1428
1465
  if(v===FAIL)return FAIL
1429
1466
  const f=ctx._fields
1430
- if(f!==undefined)f.push({name:${name},value:v,span:{start:pos,end:EC.e}})
1467
+ if(f!==undefined)f.push(new _FC(${name},v,new _SP(pos,EC.e)))
1431
1468
  return v
1432
1469
  }`
1433
1470
  }
@@ -1463,7 +1500,7 @@ return v
1463
1500
  return `${head}
1464
1501
  const v=${child}(input,pos,ctx)
1465
1502
  if(v!==FAIL)return v
1466
- const err={_tag:'parseError',span:${prog.lines === 1 ? 'spanLines(ctx,pos,pos)' : '{start:pos,end:pos}'},expected:${xf}}
1503
+ const err={_tag:'parseError',span:${prog.lines === 1 ? 'spanLines(ctx,pos,pos)' : 'new _SP(pos,pos)'},expected:${xf}}
1467
1504
  const es=ctx._errors
1468
1505
  if(es!==undefined)es.push(err)
1469
1506
  ${REC ? 'if(ctx._tolerant===true)captureError(ctx,err)\n' : ''}ctx._fc=false
@@ -1478,7 +1515,7 @@ return err
1478
1515
  // The SPAN is the routed item's own object, not a copy — `assemble.ts`
1479
1516
  // hands the same one to the leaf, and a fresh `{start,end}` here would
1480
1517
  // be a different tree for any consumer comparing by identity.
1481
- const push = `pushCstLeaf(ctx,{_tag:'leaf',value:it.value,span:it.span})`
1518
+ const push = `pushCstLeaf(ctx,new _LF(it.value,it.span))`
1482
1519
  return `${head}
1483
1520
  const it=ctx._routed
1484
1521
  if(it===undefined||pos!==it.span.start){
@@ -1587,8 +1624,8 @@ ctx._triviaLog=sOtl
1587
1624
  }
1588
1625
  if(v===FAIL)return FAIL
1589
1626
  const e=EC.e
1590
- const out=${isToken ? 'input.slice(pos,e)' : `${fn}(v,{start:pos,end:e})`}
1591
- if(wasCap)pushCstLeaf(ctx,{_tag:'leaf',value:${capturesSource ? 'input.slice(pos,e)' : 'out'},span:{start:pos,end:e}})
1627
+ const out=${isToken ? 'input.slice(pos,e)' : `${fn}(v,new _SP(pos,e))`}
1628
+ if(wasCap)pushCstLeaf(ctx,new _LF(${capturesSource ? 'input.slice(pos,e)' : 'out'},new _SP(pos,e)))
1592
1629
  EC.e=e
1593
1630
  return out
1594
1631
  }`
@@ -1613,7 +1650,7 @@ ${hasSuffix && (lineFlags & 2) !== 0 ? 'if(sm)_trackLines(ctx,input,e)' : ''}
1613
1650
  ${hasSuffix ? `if(!sm){ctx._fe=e;ctx._fx=${suffixExpected};if(ctx._probe!==undefined)failAt(ctx,${suffixExpected},e)}` : ''}
1614
1651
  const v=${pending ? 'tp?_pfTokValue:' : ''}input.slice(pos,e)
1615
1652
  ${pending ? 'if(tp){_pfTokBody=-1;_pfTokValue=undefined}' : ''}
1616
- if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
1653
+ if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,new _LF(v,new _SP(pos,e)))
1617
1654
  EC.e=e
1618
1655
  return v
1619
1656
  }`
@@ -1653,7 +1690,7 @@ ctx._rootTriviaLog=sRtl
1653
1690
  }
1654
1691
  if(e<0)return FAIL
1655
1692
  const v=input.slice(pos,e)
1656
- if(wasCap)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
1693
+ if(wasCap)pushCstLeaf(ctx,new _LF(v,new _SP(pos,e)))
1657
1694
  EC.e=e
1658
1695
  return v
1659
1696
  }`
@@ -1661,7 +1698,7 @@ return v
1661
1698
  const e=${run}(input,pos,ctx)
1662
1699
  if(e<0)return FAIL
1663
1700
  const v=input.slice(pos,e)
1664
- if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
1701
+ if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,new _LF(v,new _SP(pos,e)))
1665
1702
  EC.e=e
1666
1703
  return v
1667
1704
  }`
@@ -1803,7 +1840,7 @@ return v
1803
1840
  const close = (): string => REC ? `${parts.join('\n')}\n}finally{ctx._sync=${sy}}\n}` : `${parts.join('\n')}\n}`
1804
1841
  if (n === 1) {
1805
1842
  parts.push(fused
1806
- ? projection === 0 ? 'return v0' : `return ${fn}([v0],{start:pos,end:EC.e})`
1843
+ ? projection === 0 ? 'return v0' : `return ${fn}([v0],new _SP(pos,EC.e))`
1807
1844
  : wantValues ? 'return [v0]' : 'return undefined')
1808
1845
  return close()
1809
1846
  }
@@ -1830,13 +1867,13 @@ return v
1830
1867
  const callee = leadingTriviaFieldAt(code, k, childIp) ? `(LEAD.at=cur,${kids[i]!})` : kids[i]!
1831
1868
  parts.push(holdsGap(code, k, base, n, i, nullable)
1832
1869
  ? emitHoldTerm(callee, vn, tmp(), L, skipFor(L))
1833
- : emitTerm(callee, vn, tmp(), L, skipFor(L)))
1870
+ : emitTerm(callee, vn, tmp(), L, skipFor(L), L.tri >= 0 ? triviaMiss(L.tri) : undefined))
1834
1871
  }
1835
1872
  names.push(vn)
1836
1873
  }
1837
1874
  parts.push('EC.e=cur')
1838
1875
  parts.push(fused
1839
- ? projection >= 0 ? `return ${names[projection]}` : `return ${fn}([${names.join(',')}],{start:pos,end:cur})`
1876
+ ? projection >= 0 ? `return ${names[projection]}` : `return ${fn}([${names.join(',')}],new _SP(pos,cur))`
1840
1877
  : wantValues ? `return [${names.join(',')}]` : 'return undefined')
1841
1878
  return close()
1842
1879
  }
@@ -2176,7 +2213,7 @@ return FAIL
2176
2213
  const routedCall = (target: string): string => `{const savedRouted=ctx._routed
2177
2214
  ${emitRollback(m1, L.buf, L.raw, sinks)}
2178
2215
  ${emitMark(m2, L.buf, L.raw, sinks, false)}
2179
- ctx._routed={value:key,span:{start:pos,end:selEnd}}
2216
+ ctx._routed={value:key,span:new _SP(pos,selEnd)}
2180
2217
  try{v=${target}(input,pos,ctx)}finally{ctx._routed=savedRouted}
2181
2218
  break}`
2182
2219
  const plainCall = (target: string): string => `v=${target}(input,selEnd,ctx);break`
@@ -2445,8 +2482,8 @@ return out
2445
2482
  const end=${recognize}(input,pos)
2446
2483
  if(end<0){ctx._fe=pos;ctx._fx=${xf};return FAIL}
2447
2484
  const value=${value}
2448
- const leaf={_tag:'leaf',value,span:{start:pos,end}}
2449
- const kids=[leaf],rawKids=[leaf],span={start:pos,end}
2485
+ const leaf=new _LF(value,new _SP(pos,end))
2486
+ const kids=[leaf],rawKids=[leaf],span=new _SP(pos,end)
2450
2487
  EC.e=end
2451
2488
  const nd=${build}(kids,undefined,span,rawKids,EMPTY_TL,undefined)
2452
2489
  ${L.buf && L.raw === RAW_OMIT
@@ -2574,8 +2611,8 @@ ctx.captureTrivia=sCap
2574
2611
  if(v===FAIL)return FAIL
2575
2612
  const end=EC.e
2576
2613
  ${collapseChildren
2577
- ? `const nd=captured.length===1?captured[0]:${build}(captured,undefined,{start:pos,end},EMPTY_CH,EMPTY_TL,undefined)`
2578
- : `const nd=${build}(captured,${fields ? 'fieldMap' : 'undefined'},{start:pos,end},EMPTY_CH,EMPTY_TL,undefined)`}
2614
+ ? `const nd=captured.length===1?captured[0]:${build}(captured,undefined,new _SP(pos,end),EMPTY_CH,EMPTY_TL,undefined)`
2615
+ : `const nd=${build}(captured,${fields ? 'fieldMap' : 'undefined'},new _SP(pos,end),EMPTY_CH,EMPTY_TL,undefined)`}
2579
2616
  ${publish}
2580
2617
  EC.e=end
2581
2618
  return nd
@@ -2605,26 +2642,26 @@ return nd
2605
2642
  const direct = `${build}(kids,fieldMap,span,rawKids,${captureWide ? 'tlog' : 'EMPTY_TL'},st)`
2606
2643
  value = hostCst ? `nd=_pfHost!==undefined?${hostCall}:${direct}` : `nd=${direct}`
2607
2644
  } else {
2608
- value = `nd=_pfHost!==undefined?_pfHost(${ty},hostKids,fieldMap,span,rawKids,tlog,st,${tags}):{_tag:'node',type:${ty},span,state:st??null,children:kids}`
2645
+ value = `nd=_pfHost!==undefined?_pfHost(${ty},hostKids,fieldMap,span,rawKids,tlog,st,${tags}):new _ND(${ty},span,st??null,kids===EMPTY_CH?kids:[...kids])`
2609
2646
  }
2610
2647
  // HOST COLLAPSE applies wherever the node's VALUE comes from the host —
2611
2648
  // any node under a CST host, not only the builder-less ones.
2612
2649
  const collapsible = keepChildren && (hostCst || (build === undefined && proj < 0))
2613
2650
  const openCapture = omitsRaw
2614
- ? `const buf={noRaw:true,rawLen:0}
2651
+ ? `const buf=new _BUF(undefined,true,0)
2615
2652
  ctx._cstBuf=buf
2616
2653
  ctx._cstChildren=undefined
2617
2654
  ctx._cstLeaves=undefined
2618
2655
  ctx._cstRawChildren=undefined
2619
2656
  ctx._cstTriviaLog=undefined`
2620
2657
  : keepChildren
2621
- ? `const buf={}
2658
+ ? `const buf=new _BUF(undefined,undefined,0)
2622
2659
  ctx._cstBuf=buf
2623
2660
  ctx._cstChildren=undefined
2624
2661
  ctx._cstLeaves=undefined
2625
2662
  ctx._cstRawChildren=undefined
2626
2663
  ctx._cstTriviaLog=undefined`
2627
- : `const buf={rawOnly:true}
2664
+ : `const buf=new _BUF(true,undefined,0)
2628
2665
  ctx._cstBuf=buf
2629
2666
  ctx._cstChildren=undefined
2630
2667
  ctx._cstLeaves=undefined
@@ -2667,7 +2704,7 @@ ctx._cstTriviaLog=sTl
2667
2704
  ctx.captureTrivia=sCap
2668
2705
  if(v===FAIL)return FAIL
2669
2706
  const end=EC.e
2670
- const span=${tracked ? 'spanLines(ctx,pos,end)' : '{start:pos,end}'}
2707
+ const span=${tracked ? 'spanLines(ctx,pos,end)' : 'new _SP(pos,end)'}
2671
2708
  const st=${readsState ? 'ctx.state' : 'undefined'}
2672
2709
  let nd
2673
2710
  ${unwrap ? 'if(kids.length===1)nd=unwrapChild(kids[0])\nelse ' : ''}${collapse ? 'if(kids.length===1)nd=kids[0]\nelse ' : ''}${collapsible ? `if(_pfHost!==undefined&&_pfHost._parsemanCstCollapse!==undefined&&kids.length===1&&rawKids.length===1&&_pfHost._parsemanCstCollapse(${ty},kids[0],kids,rawKids))nd=kids[0]
package/src/table/emit.ts CHANGED
@@ -3,6 +3,14 @@ import type { FoldedProgram, TableProgram } from './program.ts'
3
3
  import { resolveTable } from './program.ts'
4
4
  import { EMITTED_PARAMS, Unemittable, emitAssemblySource } from './emit-assembly.ts'
5
5
  import { cfgKey, type RunCfg } from './assemble.ts'
6
+ import { PARSEMAN_VERSION } from '../version.ts'
7
+
8
+ /**
9
+ * Opens every printed assembly factory. A runtime older than the printer passes fewer
10
+ * helpers; refuse it when the assembly is built, instead of failing partway through the
11
+ * first parse on whichever helper is reached first. Runs once per assembly.
12
+ */
13
+ export const FACTORY_ARITY_GUARD = `if(arguments.length<${EMITTED_PARAMS.length})throw new Error(${JSON.stringify(`parseman: this parser was printed by parseman ${PARSEMAN_VERSION}; upgrade the parseman runtime to at least that version`)});`
6
14
 
7
15
  /**
8
16
  * Print a program as the module a build emits.
@@ -221,7 +229,7 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly AssemblyCfg[]): strin
221
229
  const plan = em.plan
222
230
  out.push('{'
223
231
  + `key:${key},`
224
- + `factory:function(${EMITTED_PARAMS.join(',')}){${em.source}\n},`
232
+ + `factory:function(${EMITTED_PARAMS.join(',')}){${FACTORY_ARITY_GUARD}${em.source}\n},`
225
233
  + 'plan:{'
226
234
  + `classes:[${plan.classes.map(r => `[${r.join(',')}]`).join(',')}],`
227
235
  + `armExpected:[${plan.armExpected.map(r => `[${r.join(',')}]`).join(',')}],`
@@ -1,5 +1,6 @@
1
1
  import type { Combinator, FirstSet, ParserDef } from '../types.ts'
2
2
  import { firstSetOf, matchesEmpty } from '../combinators/first-set.ts'
3
+ import { leadRanges } from '../lead-ranges.ts'
3
4
  import { deriveExpected } from '../combinators/expect.ts'
4
5
  import { buildReadsState, buildReadsTrivia } from '../compiler/build-arity.ts'
5
6
  import {
@@ -34,7 +35,7 @@ function disjointSets(sets: readonly FirstSet[]): boolean {
34
35
  const seen: Array<{ lo: number; hi: number }> = []
35
36
  for (const fs of sets) {
36
37
  if (fs.kind !== 'ranges' || fs.ranges.length === 0) return false
37
- for (const r of fs.ranges) {
38
+ for (const r of leadRanges(fs.ranges)) {
38
39
  for (const p of seen) if (r.lo <= p.hi && p.lo <= r.hi) return false
39
40
  seen.push(r)
40
41
  }
@@ -2,6 +2,7 @@ import type { Combinator, ParseContext, ParseResult } from '../types.ts'
2
2
  import { classifiedTrivia, trivia as triviaOf } from '../combinators/map.ts'
3
3
  import { fastTriviaScanner, type FastTriviaScanner } from '../combinators/trivia-skip.ts'
4
4
  import { regex } from '../combinators/regex.ts'
5
+ import { leadRanges } from '../lead-ranges.ts'
5
6
  import { choice } from '../combinators/choice.ts'
6
7
  import { many } from '../combinators/repeat.ts'
7
8
  import type { EmittedFactory, PoolPlan } from './emit-assembly.ts'
@@ -642,7 +643,7 @@ export function decodeClassSpec(spec: string): Array<{ lo: number; hi: number }>
642
643
  export function resolveClass(spec: string): ResolvedClass {
643
644
  const ascii = new Uint8Array(128)
644
645
  const hi: number[] = []
645
- for (const { lo, hi: up } of decodeClassSpec(spec)) {
646
+ for (const { lo, hi: up } of leadRanges(decodeClassSpec(spec))) {
646
647
  if (lo < 128) for (let c = lo; c <= Math.min(up, 127); c++) ascii[c] = 1
647
648
  if (up >= 128) hi.push(Math.max(lo, 128), up)
648
649
  }
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import type { ParseContext } from '../types.ts'
16
16
  import { popCstChild, pushCstChild } from '../cst/capture-buffer.ts'
17
+ import { PlainLeaf, PlainLineSpan, PlainSpan } from '../cst/plain.js'
17
18
 
18
19
  export type Span = { start: number; end: number }
19
20
  export type LineSpan = Span & {
@@ -37,7 +38,7 @@ export function lineCol(ctx: ParseContext, offset: number): [number, number] {
37
38
 
38
39
  export function spanLines(ctx: ParseContext, start: number, end: number): LineSpan {
39
40
  const s = lineCol(ctx, start), e = lineCol(ctx, end)
40
- return { start, end, startLine: s[0], startColumn: s[1], endLine: e[0], endColumn: e[1] }
41
+ return new PlainLineSpan(start, end, s[0], s[1], e[0], e[1])
41
42
  }
42
43
 
43
44
  /**
@@ -51,11 +52,7 @@ export function rawEntry(v: unknown, input: string, s: number, e: number): unkno
51
52
  const tg = (v as { _tag?: string })._tag
52
53
  if (tg === 'node' || tg === 'leaf' || tg === 'parseError') return v
53
54
  }
54
- return {
55
- _tag: 'leaf',
56
- value: typeof v === 'string' ? v : (typeof v === 'object' && v !== null ? input.slice(s, e) : ''),
57
- span: { start: s, end: e },
58
- }
55
+ return new PlainLeaf(typeof v === 'string' ? v : (typeof v === 'object' && v !== null ? input.slice(s, e) : ''), new PlainSpan(s, e))
59
56
  }
60
57
 
61
58
  /** `input`'s code point at `pos`, or −1 at EOF. Surrogate-pair aware. */
@@ -95,7 +92,7 @@ export function collapseEmpty(
95
92
  // A frame keeps the raw entry of the child it does not count, for a reducer
96
93
  // that reads `rawChildren`.
97
94
  const raw = readsRaw ? [rawEntry(undefined, input, pos, end)] : EMPTY_KIDS
98
- const nd = build(EMPTY_KIDS, undefined, { start: pos, end }, raw, EMPTY_TRIVIA, undefined)
95
+ const nd = build(EMPTY_KIDS, undefined, new PlainSpan(pos, end), raw, EMPTY_TRIVIA, undefined)
99
96
  if (collected) pushCstChild(ctx, nd, ctx._cstBuf?.noRaw === true ? undefined : rawEntry(nd, input, pos, end))
100
97
  return nd
101
98
  }
@@ -235,6 +235,9 @@ function parseSeqParts(source: string): SeqPart[] | null {
235
235
  let cp: number
236
236
  if (uni) { cp = uni.cp; i = uni.next }
237
237
  else if (e === undefined) return null
238
+ // A digit escape is a backreference or a legacy octal escape (`\012`); only a
239
+ // lone `\0` is NUL.
240
+ else if (e >= '0' && e <= '9' && (e !== '0' || (source[i + 2] !== undefined && source[i + 2]! >= '0' && source[i + 2]! <= '9'))) return null
238
241
  else if (e in CLASS_ESCAPES) { cp = CLASS_ESCAPES[e]!; i += 2 }
239
242
  else if ((e >= 'a' && e <= 'z') || (e >= 'A' && e <= 'Z')) return null
240
243
  else { cp = e.codePointAt(0)!; i += 2 }
@@ -1078,11 +1081,11 @@ function parseScanShapeCore(source: string): ScanShape | null {
1078
1081
  /**
1079
1082
  * Flag-aware wrapper around `parseScanShape`. Lowering to a raw code-point scan
1080
1083
  * assumes default regex semantics, so any flag that changes matching (`i` case
1081
- * folding, `u` surrogate handling, `m`/`s` anchor/dot behavior) disables it.
1084
+ * folding, `u`/`v` code points and sets, `m`/`s` anchor/dot behavior) disables it.
1082
1085
  * `g`/`y` are stickiness-only and safe.
1083
1086
  */
1084
1087
  export function scanShapeFromRegex(source: string, flags: string): ScanShape | null {
1085
- if (/[msu]/.test(flags)) return null
1088
+ if (/[msuv]/.test(flags)) return null
1086
1089
  if (/i/.test(flags)) return caseFoldShape(source)
1087
1090
  return parseScanShape(source)
1088
1091
  }
@@ -65,6 +65,7 @@ import {
65
65
  OP_SCAN, 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
+ import { leadRanges } from '../lead-ranges.ts'
68
69
 
69
70
  /** No scope on this path installs a known trivia — the lattice's top element. */
70
71
  export const TRI_UNKNOWN = -2
@@ -380,5 +381,5 @@ export function triviaLeadRanges(
380
381
  const fs = trivia[tri]?._meta.firstSet
381
382
  if (fs === undefined) return undefined
382
383
  if (fs.kind === 'empty') return []
383
- return fs.kind === 'ranges' && fs.ranges !== undefined ? fs.ranges : undefined
384
+ return fs.kind === 'ranges' && fs.ranges !== undefined ? leadRanges(fs.ranges) : undefined
384
385
  }
@@ -48,6 +48,25 @@ export function skipDeferredTrivia(input: string, cur: number, ctx: ParseContext
48
48
  return scan
49
49
  }
50
50
 
51
+ /**
52
+ * THE CHARACTERS A TRIVIA CAN START WITH, as a table over ASCII: `1` where a run
53
+ * may start, `0` where none can. `null` when the trivia's first set is unbounded.
54
+ *
55
+ * A gap at a character outside the table is the empty gap. It consumes nothing
56
+ * and records nothing in any sink (trivia log, root log, capture buffer, line
57
+ * table), so a skip there can answer `cur` without scanning. A character at or
58
+ * above 128, or past the end of input, is not decided by the table.
59
+ */
60
+ export function triviaLeadTable(triviaP: Combinator<unknown>): Uint8Array | null {
61
+ const fs = triviaP._meta.firstSet
62
+ if (fs.kind === 'any') return null
63
+ const table = new Uint8Array(128)
64
+ if (fs.kind === 'ranges') {
65
+ for (const r of fs.ranges) for (let c = Math.max(0, r.lo); c <= Math.min(127, r.hi); c++) table[c] = 1
66
+ }
67
+ return table
68
+ }
69
+
51
70
  /** The end of the run at `cur` when nothing root-selected lies in it, else −1. */
52
71
  function clearRunEnd(input: string, cur: number, ctx: ParseContext, triviaP: Combinator<unknown> | undefined, plain: boolean): number {
53
72
  const rootKinds = ctx._rootTriviaKindIndex
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.58.2'
16
+ export const PARSEMAN_VERSION = '0.59.0'