parseman 0.58.2 → 0.58.3

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 (56) hide show
  1. package/dist/analysis/diagnostics.js +203 -37
  2. package/dist/analysis/diagnostics.js.map +3 -3
  3. package/dist/cli/index.js +203 -37
  4. package/dist/cli/index.js.map +3 -3
  5. package/dist/combinators/first-set.d.ts.map +1 -1
  6. package/dist/combinators/regex.d.ts.map +1 -1
  7. package/dist/compiler/token-capability.js +28 -4
  8. package/dist/compiler/token-capability.js.map +2 -2
  9. package/dist/compiler/token-scanner.d.ts.map +1 -1
  10. package/dist/index.js +207 -41
  11. package/dist/index.js.map +3 -3
  12. package/dist/language-service/index.js +10 -3
  13. package/dist/language-service/index.js.map +2 -2
  14. package/dist/lead-ranges.d.ts +26 -0
  15. package/dist/lead-ranges.d.ts.map +1 -0
  16. package/dist/plugin/index.js +207 -41
  17. package/dist/plugin/index.js.map +3 -3
  18. package/dist/recovery/scan.d.ts.map +1 -1
  19. package/dist/regex/classes.d.ts +5 -3
  20. package/dist/regex/classes.d.ts.map +1 -1
  21. package/dist/regex/first-set.d.ts +7 -1
  22. package/dist/regex/first-set.d.ts.map +1 -1
  23. package/dist/run/index.js +10 -3
  24. package/dist/run/index.js.map +2 -2
  25. package/dist/table/assemble.d.ts.map +1 -1
  26. package/dist/table/compile-rule-map.d.ts.map +1 -1
  27. package/dist/table/emit-assembly.d.ts +8 -1
  28. package/dist/table/emit-assembly.d.ts.map +1 -1
  29. package/dist/table/encode-baseline.d.ts.map +1 -1
  30. package/dist/table/index.js +206 -40
  31. package/dist/table/index.js.map +3 -3
  32. package/dist/table/program.d.ts.map +1 -1
  33. package/dist/table/scan-shapes.d.ts +1 -1
  34. package/dist/table/scan-shapes.d.ts.map +1 -1
  35. package/dist/table/site-labels.d.ts.map +1 -1
  36. package/dist/table/trivia-gap.d.ts +11 -1
  37. package/dist/table/trivia-gap.d.ts.map +1 -1
  38. package/dist/version.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/src/combinators/choice.ts +4 -0
  41. package/src/combinators/first-set.ts +13 -4
  42. package/src/combinators/regex.ts +28 -9
  43. package/src/compiler/token-scanner.ts +4 -1
  44. package/src/lead-ranges.ts +53 -0
  45. package/src/recovery/scan.ts +2 -3
  46. package/src/regex/classes.ts +22 -7
  47. package/src/regex/first-set.ts +67 -12
  48. package/src/table/assemble.ts +42 -3
  49. package/src/table/compile-rule-map.ts +5 -3
  50. package/src/table/emit-assembly.ts +42 -8
  51. package/src/table/encode-baseline.ts +2 -1
  52. package/src/table/program.ts +2 -1
  53. package/src/table/scan-shapes.ts +5 -2
  54. package/src/table/site-labels.ts +2 -1
  55. package/src/table/trivia-gap.ts +19 -0
  56. package/src/version.ts +1 -1
@@ -69,7 +69,7 @@ import type { Combinator, FieldMap, FirstSet, ParseContext, ParseResult, ParserD
69
69
  import { balanced, scanTo } from '../combinators/scanTo.ts'
70
70
  import { buildFieldMap } from '../compiler/fields.ts'
71
71
  import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA, LEAD, triviaFieldCapture } from '../combinators/field-trivia.ts'
72
- import { skipDeferredTrivia } from './trivia-gap.ts'
72
+ import { skipDeferredTrivia, triviaLeadTable } from './trivia-gap.ts'
73
73
  import { asciiFoldKey } from '../combinators/dispatch.ts'
74
74
  import { projectChild, unwrapChild } from '../combinators/node.ts'
75
75
  import { cstOutputHost } from '../compiler/build-arity.ts'
@@ -959,7 +959,31 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
959
959
  return out
960
960
  }
961
961
 
962
+ /**
963
+ * NO TRIVIA CAN START AT `cur` — `triviaLeadTable` for the installed trivia. A
964
+ * gap there is empty: nothing to scan, nothing recorded, so nothing for a
965
+ * sequence term to mark. One table per trivia slot, built with the assembly;
966
+ * the installed trivia is matched to its slot only when it changes (a trivia
967
+ * the caller passed that is no slot gets its own table then).
968
+ */
969
+ const LEAD_TABLES = trivia.map(tp => tp === undefined ? null : triviaLeadTable(tp))
970
+ let LEAD_OF: Combinator<unknown> | undefined
971
+ let LEAD_TABLE: Uint8Array | null = null
972
+ function cannotStartTrivia(input: string, cur: number, triviaP: Combinator<unknown>): boolean {
973
+ if (triviaP !== LEAD_OF) {
974
+ LEAD_OF = triviaP
975
+ const slot = trivia.indexOf(triviaP)
976
+ LEAD_TABLE = slot >= 0 ? LEAD_TABLES[slot]! : triviaLeadTable(triviaP)
977
+ }
978
+ const table = LEAD_TABLE
979
+ if (table === null) return false
980
+ const c = input.charCodeAt(cur)
981
+ return c < 128 && table[c] === 0
982
+ }
983
+
962
984
  function skipTrivia(input: string, cur: number, ctx: ParseContext): number {
985
+ const t = ctx.trivia
986
+ if (t !== undefined && cannotStartTrivia(input, cur, t)) return cur
963
987
  const s = SCAN
964
988
  if (s !== null
965
989
  && ctx._triviaLog === undefined
@@ -1041,7 +1065,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
1041
1065
  * into them.
1042
1066
  */
1043
1067
  function nextTerm(child: Piece, input: string, cur: number, ctx: ParseContext): number {
1044
- if (ctx.trivia === undefined) {
1068
+ const t = ctx.trivia
1069
+ if (t === undefined || cannotStartTrivia(input, cur, t)) {
1045
1070
  const v = child(input, cur, ctx)
1046
1071
  if (v === FAIL) return -1
1047
1072
  TERMV = v
@@ -1089,7 +1114,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
1089
1114
 
1090
1115
  /** A non-first term at a site proven to run under an open node capture buffer. */
1091
1116
  function nextTermBuffered(child: Piece, input: string, cur: number, ctx: ParseContext): number {
1092
- if (ctx.trivia === undefined) {
1117
+ const t = ctx.trivia
1118
+ if (t === undefined || cannotStartTrivia(input, cur, t)) {
1093
1119
  const v = child(input, cur, ctx)
1094
1120
  if (v === FAIL) return -1
1095
1121
  TERMV = v
@@ -2016,6 +2042,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2016
2042
  // known here, so the scope piece holds the scanner it installs as a `const`.
2017
2043
  const scanFor: FastTriviaScanner | null =
2018
2044
  swapLegal && ki >= 0 && !triviaLabelled[ki]! ? triviaScan[ki]! : null
2045
+ // The scope installs its slot's lead table with its trivia, so the gate
2046
+ // (`cannotStartTrivia`) never has to look the slot up.
2047
+ const scopeLead = ki < 0 ? null : LEAD_TABLES[ki]!
2019
2048
  const child = link(code[ip + 2]!)
2020
2049
  // TWO PIECES, SELECTED HERE — not one piece that tests the opcode. Capture
2021
2050
  // is fixed for the whole parse, so `code[ip] === OP_SCOPE_CAP` is an
@@ -2032,6 +2061,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2032
2061
  const savedScan = SCAN
2033
2062
  const savedCap = ctx.captureTrivia
2034
2063
  SCAN = scanFor
2064
+ const savedLeadOf = LEAD_OF, savedLead = LEAD_TABLE
2065
+ LEAD_OF = scopeTrivia
2066
+ LEAD_TABLE = scopeLead
2035
2067
  ctx.trivia = scopeTrivia
2036
2068
  ctx.triviaKindLabels = scopeLabels
2037
2069
  ctx.captureTrivia = true
@@ -2040,6 +2072,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2040
2072
  ctx.trivia = saved
2041
2073
  ctx.triviaKindLabels = savedLabels
2042
2074
  SCAN = savedScan
2075
+ LEAD_OF = savedLeadOf
2076
+ LEAD_TABLE = savedLead
2043
2077
  return v
2044
2078
  }
2045
2079
  : (input, pos, ctx) => {
@@ -2047,12 +2081,17 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
2047
2081
  const savedLabels = ctx.triviaKindLabels
2048
2082
  const savedScan = SCAN
2049
2083
  SCAN = scanFor
2084
+ const savedLeadOf = LEAD_OF, savedLead = LEAD_TABLE
2085
+ LEAD_OF = scopeTrivia
2086
+ LEAD_TABLE = scopeLead
2050
2087
  ctx.trivia = scopeTrivia
2051
2088
  ctx.triviaKindLabels = scopeLabels
2052
2089
  const v = child(input, pos, ctx)
2053
2090
  ctx.trivia = saved
2054
2091
  ctx.triviaKindLabels = savedLabels
2055
2092
  SCAN = savedScan
2093
+ LEAD_OF = savedLeadOf
2094
+ LEAD_TABLE = savedLead
2056
2095
  return v
2057
2096
  }
2058
2097
  return scopeRootPolicy(scopePiece, code[ip] === OP_SCOPE_PLAIN ? 0 : code[ip + 3]!)
@@ -355,10 +355,12 @@ export function compileRuleMap(
355
355
  const roots = assemblyExtraIps(artifact)
356
356
  let siteBytes, fixed
357
357
  try {
358
- siteBytes = emitAssemblySource(table, artifact, cfg, roots, true).siteBytes
358
+ // Priced without the `_errors` marks a lexical program over a scan adds
359
+ // (`scanSinkNeutral`), so they move no site in or out of the budget.
360
+ siteBytes = emitAssemblySource(table, artifact, cfg, roots, true, undefined, true).siteBytes
359
361
  // What any selective assembly costs before its first site body: the shared
360
362
  // prelude and the factory's tail. A budget that cannot cover it buys nothing.
361
- fixed = emitAssemblySource(table, artifact, cfg, roots, true, new Set()).source.length
363
+ fixed = emitAssemblySource(table, artifact, cfg, roots, true, new Set(), true).source.length
362
364
  } catch (e) {
363
365
  if (e instanceof Unemittable) return { ...none, refusal: e.construct }
364
366
  throw e
@@ -384,7 +386,7 @@ export function compileRuleMap(
384
386
  refusal: `a site within its ${Math.floor(ceiling)} B selection ceiling (the prelude alone is ${fixed} B)`,
385
387
  }
386
388
  }
387
- const bytes = emitAssemblySource(table, artifact, cfg, roots, true, sites).source.length
389
+ const bytes = emitAssemblySource(table, artifact, cfg, roots, true, sites, true).source.length
388
390
  if (bytes <= ceiling) return { sites, bytes: Math.ceil(bytes * SUPERCOMPILE_GROWTH) }
389
391
  // Scale by the overshoot, and by at least 1%: near the ceiling a site is
390
392
  // the unit, and the ratio alone can stall a few bytes over it.
@@ -414,7 +414,7 @@ function emitRollback(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SI
414
414
  * only the first branch, a known slot keeps only the second (with the scope's own
415
415
  * scanner already bound into `skip`), and `TRI_UNKNOWN` keeps both.
416
416
  */
417
- function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string): string {
417
+ function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string, miss?: (c: string) => string): string {
418
418
  const fast = `const ${t}v=${callee}(input,cur,ctx)
419
419
  if(${t}v===FAIL)return FAIL
420
420
  ${dst}=${t}v
@@ -432,7 +432,14 @@ if(${t}v===FAIL)return FAIL
432
432
  ${dst}=${t}v
433
433
  if(EC.e>${t}s)cur=EC.e
434
434
  else{rollbackScannedTriviaAt(ctx,${t}tl,${t}stl,${t}lg,${t}slg,${t}rt,${t}srt)}`
435
- if (l.tri !== TRI_UNKNOWN) return scanned
435
+ // A character no trivia of this slot can start opens the empty gap, which
436
+ // records nothing, so the term runs at `cur` with no marks around a scan.
437
+ if (l.tri !== TRI_UNKNOWN) return miss === undefined ? scanned : `const ${t}c=input.charCodeAt(cur)
438
+ if(${miss(`${t}c`)}){
439
+ ${fast}
440
+ }else{
441
+ ${scanned}
442
+ }`
436
443
  return `if(ctx.trivia===undefined){
437
444
  ${fast}
438
445
  }else{
@@ -588,6 +595,13 @@ export function emitAssemblySource(
588
595
  extraIps: readonly number[] = [],
589
596
  staticBuild = false,
590
597
  select?: ReadonlySet<number>,
598
+ /**
599
+ * Leave the `_errors` bit of a lexical program over a scan out of the side-sink
600
+ * census below. A budgeted selection is priced and chosen on this lowering, so
601
+ * the marks that bit adds move no site in or out of a budget; the artifact is
602
+ * then emitted with them.
603
+ */
604
+ scanSinkNeutral = false,
591
605
  ): EmitResult {
592
606
  const { code, k, fx, disp, dsp, triviaLabelled } = t
593
607
  const swapLegal = !cfg.trackLines
@@ -733,13 +747,15 @@ export function emitAssemblySource(
733
747
  // that otherwise matched byte for byte.
734
748
  //
735
749
  // 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.
750
+ // same reason. `OP_LIVE` is the other such row, and it is unemittable. An
751
+ // `OP_LEX_PROGRAM` over a scan (`t.lexPrograms[id].scan`, a whole `balanced()`
752
+ // lowered as one recognizer) runs that same interpreted scan, so it is one too.
737
753
  const sinkSites = reachable
738
754
  const sinkBits = new Map<number, number>()
739
755
  for (const ip of sinkSites) {
740
756
  const op = code[ip]
741
757
  sinkBits.set(ip,
742
- op === OP_SCAN ? 3
758
+ op === OP_SCAN || (!scanSinkNeutral && op === OP_LEX_PROGRAM && t.lexPrograms[code[ip + 1]!]!.scan !== undefined) ? 3
743
759
  : (op === OP_FIELD ? 1 : 0)
744
760
  | (op === OP_EXPECT || (REC && (op === OP_REP || op === OP_REPV)) ? 2 : 0))
745
761
  }
@@ -974,6 +990,19 @@ return 1
974
990
  return `if(${gateMissTest(child)}){${miss}}\n`
975
991
  }
976
992
 
993
+ /**
994
+ * The test that no trivia of slot `ki` can start at the code unit `c`: the
995
+ * slot's first characters, as code points, decide every code unit below the
996
+ * surrogates; a surrogate, or `NaN` past the end of input, is left to the scan.
997
+ * `undefined` where the first set is unbounded or too wide to print.
998
+ */
999
+ function triviaMiss(ki: number): ((c: string) => string) | undefined {
1000
+ const leads = triviaLeadRanges(ki, t.trivia)
1001
+ if (leads === undefined || leads.length > 16) return undefined
1002
+ if (leads.length === 0) return c => `${c}<55296`
1003
+ return c => `${c}<55296&&!(${leads.map(r => r.lo === r.hi ? `${c}===${r.lo}` : `(${c}>=${r.lo}&&${c}<=${r.hi})`).join('||')})`
1004
+ }
1005
+
977
1006
  function skipFor(l: SiteLabel): string {
978
1007
  if (l.tri < 0) return '_skipTrivia'
979
1008
  const ki = l.tri
@@ -991,11 +1020,16 @@ return 1
991
1020
  if (!hasScan) {
992
1021
  // No installed scanner: the labelled/`trackLines` path, which records
993
1022
  // through `scanTrivia`. `needsDeferredTriviaCommit` is implied by an open
994
- // buffer, so an in-node site skips the call that asks.
1023
+ // buffer, so an in-node site skips the call that asks. A character no gap
1024
+ // can start answers first, with no call (`triviaMiss`).
1025
+ const miss = triviaMiss(ki)
1026
+ const pre = miss === undefined ? '' : `const c=input.charCodeAt(cur)
1027
+ if(${miss('c')})return cur
1028
+ `
995
1029
  skipDefs.push(l.buf
996
- ? `function ${nm}(input,cur,ctx){return skipDeferredTrivia(input,cur,ctx)}`
1030
+ ? `function ${nm}(input,cur,ctx){${pre === '' ? '' : `\n${pre}`}return skipDeferredTrivia(input,cur,ctx)}`
997
1031
  : `function ${nm}(input,cur,ctx){
998
- if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
1032
+ ${pre}if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
999
1033
  return advanceTrivia(input,cur,ctx)}`)
1000
1034
  return nm
1001
1035
  }
@@ -1830,7 +1864,7 @@ return v
1830
1864
  const callee = leadingTriviaFieldAt(code, k, childIp) ? `(LEAD.at=cur,${kids[i]!})` : kids[i]!
1831
1865
  parts.push(holdsGap(code, k, base, n, i, nullable)
1832
1866
  ? emitHoldTerm(callee, vn, tmp(), L, skipFor(L))
1833
- : emitTerm(callee, vn, tmp(), L, skipFor(L)))
1867
+ : emitTerm(callee, vn, tmp(), L, skipFor(L), L.tri >= 0 ? triviaMiss(L.tri) : undefined))
1834
1868
  }
1835
1869
  names.push(vn)
1836
1870
  }
@@ -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
  }
@@ -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.58.3'