@thomasfosterau/effect-parse 0.3.0 → 0.4.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 (142) hide show
  1. package/README.md +3 -1
  2. package/dist/Ascii.d.ts +0 -2
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +6 -6
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +0 -2
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +1 -2
  9. package/dist/Bits.d.ts +0 -2
  10. package/dist/Bits.d.ts.map +1 -1
  11. package/dist/Bits.js +2 -3
  12. package/dist/Bits.js.map +1 -1
  13. package/dist/Compiler.d.ts +0 -2
  14. package/dist/Compiler.d.ts.map +1 -1
  15. package/dist/Compiler.js +1 -2
  16. package/dist/Encoding.d.ts +0 -2
  17. package/dist/Encoding.d.ts.map +1 -1
  18. package/dist/Encoding.js +1 -2
  19. package/dist/Expression.d.ts +0 -2
  20. package/dist/Expression.d.ts.map +1 -1
  21. package/dist/Expression.js +44 -8
  22. package/dist/Expression.js.map +1 -1
  23. package/dist/Input.d.ts +32 -4
  24. package/dist/Input.d.ts.map +1 -1
  25. package/dist/Input.js +47 -8
  26. package/dist/Input.js.map +1 -1
  27. package/dist/Nom.d.ts +0 -2
  28. package/dist/Nom.d.ts.map +1 -1
  29. package/dist/Nom.js +1 -2
  30. package/dist/ParseError.d.ts +2 -4
  31. package/dist/ParseError.d.ts.map +1 -1
  32. package/dist/ParseError.js +6 -9
  33. package/dist/ParseError.js.map +1 -1
  34. package/dist/Parser.d.ts +6 -4
  35. package/dist/Parser.d.ts.map +1 -1
  36. package/dist/Parser.js +26 -24
  37. package/dist/Parser.js.map +1 -1
  38. package/dist/ParserSchema.d.ts +0 -2
  39. package/dist/ParserSchema.d.ts.map +1 -1
  40. package/dist/ParserSchema.js +3 -4
  41. package/dist/ParserSchema.js.map +1 -1
  42. package/dist/ParserStream.d.ts +0 -2
  43. package/dist/ParserStream.d.ts.map +1 -1
  44. package/dist/ParserStream.js +17 -10
  45. package/dist/ParserStream.js.map +1 -1
  46. package/dist/Syntax.d.ts +0 -2
  47. package/dist/Syntax.d.ts.map +1 -1
  48. package/dist/Syntax.js +13 -11
  49. package/dist/Syntax.js.map +1 -1
  50. package/dist/Text.d.ts +0 -2
  51. package/dist/Text.d.ts.map +1 -1
  52. package/dist/Text.js +1 -2
  53. package/dist/Token.d.ts +0 -2
  54. package/dist/Token.d.ts.map +1 -1
  55. package/dist/Token.js +1 -2
  56. package/dist/internal/compiler/closure.d.ts +5 -2
  57. package/dist/internal/compiler/closure.d.ts.map +1 -1
  58. package/dist/internal/compiler/closure.js +160 -92
  59. package/dist/internal/compiler/closure.js.map +1 -1
  60. package/dist/internal/compiler/entries.d.ts +0 -2
  61. package/dist/internal/compiler/entries.d.ts.map +1 -1
  62. package/dist/internal/compiler/entries.js +6 -7
  63. package/dist/internal/compiler/entries.js.map +1 -1
  64. package/dist/internal/compiler/first.d.ts +0 -2
  65. package/dist/internal/compiler/first.d.ts.map +1 -1
  66. package/dist/internal/compiler/first.js +10 -10
  67. package/dist/internal/compiler/first.js.map +1 -1
  68. package/dist/internal/compiler/jit.d.ts +0 -2
  69. package/dist/internal/compiler/jit.d.ts.map +1 -1
  70. package/dist/internal/compiler/jit.js +52 -51
  71. package/dist/internal/compiler/jit.js.map +1 -1
  72. package/dist/internal/compiler/registry.d.ts +0 -2
  73. package/dist/internal/compiler/registry.d.ts.map +1 -1
  74. package/dist/internal/compiler/registry.js +1 -2
  75. package/dist/internal/compiler/sets.d.ts +2 -2
  76. package/dist/internal/compiler/sets.d.ts.map +1 -1
  77. package/dist/internal/compiler/sets.js +8 -3
  78. package/dist/internal/compiler/sets.js.map +1 -1
  79. package/dist/internal/entries.d.ts +0 -2
  80. package/dist/internal/entries.d.ts.map +1 -1
  81. package/dist/internal/entries.js +2 -4
  82. package/dist/internal/entries.js.map +1 -1
  83. package/dist/internal/key.d.ts +10 -0
  84. package/dist/internal/key.d.ts.map +1 -0
  85. package/dist/internal/key.js +11 -0
  86. package/dist/internal/key.js.map +1 -0
  87. package/dist/internal/node.d.ts +11 -4
  88. package/dist/internal/node.d.ts.map +1 -1
  89. package/dist/internal/node.js +15 -3
  90. package/dist/internal/node.js.map +1 -1
  91. package/dist/internal/numbers.d.ts +0 -2
  92. package/dist/internal/numbers.d.ts.map +1 -1
  93. package/dist/internal/numbers.js +1 -2
  94. package/dist/internal/resume.d.ts +5 -6
  95. package/dist/internal/resume.d.ts.map +1 -1
  96. package/dist/internal/resume.js +1 -2
  97. package/dist/internal/resume.js.map +1 -1
  98. package/dist/internal/run.d.ts +0 -2
  99. package/dist/internal/run.d.ts.map +1 -1
  100. package/dist/internal/run.js +2 -3
  101. package/dist/internal/run.js.map +1 -1
  102. package/dist/internal/stack.d.ts +0 -2
  103. package/dist/internal/stack.d.ts.map +1 -1
  104. package/dist/internal/stack.js +1 -2
  105. package/dist/internal/syntax.d.ts +30 -20
  106. package/dist/internal/syntax.d.ts.map +1 -1
  107. package/dist/internal/syntax.js +73 -66
  108. package/dist/internal/syntax.js.map +1 -1
  109. package/dist/internal/text.d.ts +5 -2
  110. package/dist/internal/text.d.ts.map +1 -1
  111. package/dist/internal/text.js +14 -5
  112. package/dist/internal/text.js.map +1 -1
  113. package/dist/internal/ties.d.ts +0 -2
  114. package/dist/internal/ties.d.ts.map +1 -1
  115. package/dist/internal/ties.js +1 -2
  116. package/dist/internal/tokens.d.ts +10 -9
  117. package/dist/internal/tokens.d.ts.map +1 -1
  118. package/dist/internal/tokens.js +4 -8
  119. package/dist/internal/tokens.js.map +1 -1
  120. package/package.json +7 -7
  121. package/src/Ascii.ts +6 -5
  122. package/src/Bits.ts +1 -1
  123. package/src/Expression.ts +7 -6
  124. package/src/Input.ts +50 -11
  125. package/src/ParseError.ts +13 -9
  126. package/src/Parser.ts +30 -25
  127. package/src/ParserSchema.ts +1 -1
  128. package/src/ParserStream.ts +23 -9
  129. package/src/Syntax.ts +12 -9
  130. package/src/internal/compiler/closure.ts +202 -90
  131. package/src/internal/compiler/entries.ts +14 -11
  132. package/src/internal/compiler/first.ts +9 -8
  133. package/src/internal/compiler/jit.ts +95 -74
  134. package/src/internal/compiler/sets.ts +7 -0
  135. package/src/internal/entries.ts +2 -2
  136. package/src/internal/key.ts +11 -0
  137. package/src/internal/node.ts +19 -2
  138. package/src/internal/resume.ts +5 -4
  139. package/src/internal/run.ts +1 -1
  140. package/src/internal/syntax.ts +78 -87
  141. package/src/internal/text.ts +24 -4
  142. package/src/internal/tokens.ts +20 -13
@@ -16,26 +16,29 @@
16
16
  * the interpreter for what they can't compile) on the shared state, with
17
17
  * `state.pos` kept in sync.
18
18
  */
19
+ import * as Arr from "effect/Array"
19
20
  import * as Predicate from "effect/Predicate"
20
21
  import * as Option from "effect/Option"
22
+ import * as Result from "effect/Result"
21
23
  import type { Encoding } from "../../Encoding.js"
22
24
  import type { State } from "../../Input.js"
23
25
  import { Fail } from "../../Input.js"
24
26
  import type { Node, NodeOf, ParserLike } from "../node.js"
25
27
  import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
26
- import { children, nodeOf } from "../node.js"
28
+ import { children, nodeOf, scansFloat } from "../node.js"
27
29
  import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js"
28
30
  import { compileAny } from "../tokens.js"
31
+ import { Key } from "../key.js"
29
32
  import * as Resume from "../resume.js"
30
33
  import { charOf, fastLimit, latin1, string, utf16be, utf16le, utf8, validate } from "../text.js"
31
- import type { ClosureCompiler, Fallback, Step } from "./closure.js"
34
+ import type { ClosureCompiler, CompiledRoot, Fallback, Step } from "./closure.js"
32
35
  import { backend as closureBackend, createClosureCompiler } from "./closure.js"
33
36
  import { buildsEntries, pairOf } from "./entries.js"
34
37
  import type { Backend } from "./registry.js"
35
38
  import type { First } from "./first.js"
36
39
  import { planAlt, Strict } from "./first.js"
37
40
  import type { CharSet } from "./sets.js"
38
- import { kindOfEncoding, reachesAbove, TableSize, textSet, tokenSet } from "./sets.js"
41
+ import { acceptTable, kindOfEncoding, reachesAbove, TableSize, textSet, tokenSet } from "./sets.js"
39
42
 
40
43
  /** Helpers available to generated code as `R`. */
41
44
  const runtime = {
@@ -43,7 +46,7 @@ const runtime = {
43
46
  save: Resume.save,
44
47
  Done: Resume.Done,
45
48
  /** Records the result of a resumed repetition (see `Resume.done`). */
46
- done: (state: State<any>, key: object, entry: number, value: unknown, pos: number): void =>
49
+ done: (state: State<any>, key: Key, entry: number, value: unknown, pos: number): void =>
47
50
  Resume.save(state, key, entry, new Resume.Done(value, pos)),
48
51
  some: Option.some,
49
52
  none: Option.none,
@@ -77,11 +80,11 @@ let supported = false
77
80
  const jitSupported = (): boolean => {
78
81
  if (checked !== globalThis.Function) {
79
82
  checked = globalThis.Function
80
- try {
81
- supported = checked("return true")() === true
82
- } catch {
83
- supported = false
84
- }
83
+ const fn = checked
84
+ supported = Result.getOrElse(
85
+ Result.try(() => fn("return true")() === true),
86
+ () => false
87
+ )
85
88
  }
86
89
  return supported
87
90
  }
@@ -141,7 +144,7 @@ const runsOf = (table: Uint8Array): Array<[number, number]> => {
141
144
 
142
145
  /** A JavaScript expression testing `v` against ranges, or `undefined` if too long. */
143
146
  const rangeTest = (runs: ReadonlyArray<readonly [number, number]>, v: string): string | undefined => {
144
- if (runs.length === 0) return "false"
147
+ if (Arr.isReadonlyArrayEmpty(runs)) return "false"
145
148
  if (runs.length > 4) return undefined
146
149
  return runs.map(([a, b]) => (a === b ? `${v}===${a}` : `(${v}>=${a}&&${v}<=${b})`)).join("||")
147
150
  }
@@ -172,8 +175,8 @@ const runCost = ([a, b]: readonly [number, number], size: number): number =>
172
175
  const tableTest = (table: Uint8Array, v: string, size: number): string | undefined => {
173
176
  const accept = runsWhere(table, size, 1)
174
177
  const reject = runsWhere(table, size, 0)
175
- if (accept.length === 0) return "false"
176
- if (reject.length === 0) return "true"
178
+ if (Arr.isReadonlyArrayEmpty(accept)) return "false"
179
+ if (Arr.isReadonlyArrayEmpty(reject)) return "true"
177
180
  const cost = (runs: ReadonlyArray<readonly [number, number]>) => runs.reduce((n, r) => n + runCost(r, size), 0)
178
181
  const acceptCost = cost(accept)
179
182
  const rejectCost = cost(reject)
@@ -309,7 +312,7 @@ class Emitter {
309
312
  const done = this.label()
310
313
  const body = this.inline(p, used, out, `break ${done};`, fn, 0)
311
314
  this.functions.push(
312
- `function ${name}(p){const I=$I,E=$E;${fn.vars.length > 0 ? `let ${fn.vars.join(",")};` : ""}` +
315
+ `function ${name}(p){const I=$I,E=$E;${Arr.isArrayNonEmpty(fn.vars) ? `let ${fn.vars.join(",")};` : ""}` +
313
316
  `${done}:{${body}P=p;return ${out ?? "void 0"}}return F}`
314
317
  )
315
318
  return name
@@ -321,7 +324,7 @@ class Emitter {
321
324
  */
322
325
  gen(p: ParserLike, used: boolean, out: string | null, fail: string, fn: Fn, depth: number): string {
323
326
  const node = nodeOf(p)
324
- if (node._tag === "Suspend") return this.call(this.rule(node.resolve(), used), out, fail)
327
+ if (Predicate.isTagged(node, "Suspend")) return this.call(this.rule(node.resolve(), used), out, fail)
325
328
  if (!isLeaf(node) && (((this.refs.get(p) ?? 0) > 1 && !this.small(p)) || depth > 32)) {
326
329
  return this.call(this.rule(p, used), out, fail)
327
330
  }
@@ -333,7 +336,13 @@ class Emitter {
333
336
  let budget = 6
334
337
  const visit = (q: ParserLike): boolean => {
335
338
  const node = nodeOf(q)
336
- if (--budget < 0 || node._tag === "Suspend" || node._tag === "Escaped" || node._tag === "Dispatch") return false
339
+ if (
340
+ --budget < 0 ||
341
+ Predicate.isTagged(node, "Suspend") ||
342
+ Predicate.isTagged(node, "Escaped") ||
343
+ Predicate.isTagged(node, "Dispatch")
344
+ )
345
+ return false
337
346
  return children(node).every(visit)
338
347
  }
339
348
  return visit(p)
@@ -477,8 +486,7 @@ class Emitter {
477
486
  case "Literal":
478
487
  return this.leaf(p, node, used, out, fail, fn, node.literal.length * 4)
479
488
  case "Escaped": {
480
- // Streaming builds use the closure compiler's escapes, which keep checkpoints
481
- if (!this.native(node.encoding) || this.resumable) return this.delegate(p, used, out, fail, fn)
489
+ if (!this.native(node.encoding)) return this.delegate(p, used, out, fail, fn)
482
490
  return this.escaped(p, node, out, fail, fn, d)
483
491
  }
484
492
  case "Map": {
@@ -504,7 +512,7 @@ class Emitter {
504
512
  case "FilterMap": {
505
513
  const t = this.v(fn)
506
514
  const r = this.v(fn)
507
- const [tag, field] = node._tag === "TryMap" ? ["Success", "success"] : ["Some", "value"]
515
+ const [tag, field] = Predicate.isTagged(node, "TryMap") ? ["Success", "success"] : ["Some", "value"]
508
516
  return (
509
517
  this.gen(node.self, true, t, fail, fn, d) +
510
518
  `${r}=${this.const(node.f)}(${t});if(${r}._tag!=="${tag}"){${BT}${fail}}${set(`${r}.${field}`)}`
@@ -542,7 +550,7 @@ class Emitter {
542
550
  case "WithSpan": {
543
551
  const st = this.v(fn)
544
552
  const t = this.v(fn)
545
- const extra = node._tag === "WithSpan" ? `{start:${st},end:p}` : `$S.slice(${st},p)`
553
+ const extra = Predicate.isTagged(node, "WithSpan") ? `{start:${st},end:p}` : `$S.slice(${st},p)`
546
554
  return `${st}=p;` + this.gen(node.self, true, t, fail, fn, d) + set(`[${t},${extra}]`)
547
555
  }
548
556
  case "Span": {
@@ -576,8 +584,8 @@ class Emitter {
576
584
  const ok = this.label()
577
585
  const bad = this.label()
578
586
  const t = out === null ? null : this.v(fn)
579
- const some = node._tag === "Opt" ? `R.some(${t})` : `${t}`
580
- const orElse = node._tag === "Opt" ? "R.none()" : `${this.const(node.value)}()`
587
+ const some = Predicate.isTagged(node, "Opt") ? `R.some(${t})` : `${t}`
588
+ const orElse = Predicate.isTagged(node, "Opt") ? "R.none()" : `${this.const(node.value)}()`
581
589
  return (
582
590
  `${st}=p;${ok}:{${bad}:{${this.gen(node.self, used, t, `break ${bad};`, fn, d)}${set(some)}break ${ok};}` +
583
591
  `if($S.mode!=="Backtrack"){${fail}}p=${st};${set(orElse)}}`
@@ -641,12 +649,7 @@ class Emitter {
641
649
  */
642
650
  private float(node: NodeOf<"Map">, out: string, fail: string, fn: Fn, d: number): string | undefined {
643
651
  if (!floatTexts.has(node.self) || this.kind === 2) return undefined
644
- let taken = nodeOf(node.self)
645
- while (taken._tag === "Expected") taken = nodeOf(taken.self)
646
- if (taken._tag !== "Taken") return undefined
647
- const enc = taken.encoding
648
- // Decimal notation is ASCII, which these encodings read unit by unit
649
- if (enc !== undefined && !(this.native(enc) && enc.unit === 1 && enc.asciiCompatible)) return undefined
652
+ if (!scansFloat(node.self, (enc) => this.native(enc))) return undefined
650
653
  const t = this.v(fn)
651
654
  const scan = this.const(floatScan)
652
655
  // Near the end of a partial input, the parser may have to report `Incomplete`
@@ -681,7 +684,7 @@ class Emitter {
681
684
 
682
685
  alt(node: NodeOf<"Alt">, used: boolean, out: string | null, fail: string, fn: Fn, d: number): string {
683
686
  const parsers = node.parsers
684
- if (parsers.length === 0) return `${BT}${fail}`
687
+ if (Arr.isReadonlyArrayEmpty(parsers)) return `${BT}${fail}`
685
688
  if (parsers.length === 1) return this.gen(parsers[0], used, out, fail, fn, d)
686
689
  const firsts = this.kind === 2 ? undefined : parsers.map((q) => this.closure.firsts.of(q))
687
690
  const prune = firsts !== undefined && planAlt(firsts) !== undefined
@@ -755,9 +758,9 @@ class Emitter {
755
758
  return code + `default:{$S.pos=p;${r}=${q}.step($S);if(${r}===F){${fail}}p=$S.pos;}}`
756
759
  }
757
760
  let sel = nodeOf(node.selector)
758
- const peek = sel._tag === "Peek"
761
+ const peek = Predicate.isTagged(sel, "Peek")
759
762
  if (peek) sel = nodeOf((sel as NodeOf<"Peek">).self)
760
- if (sel._tag !== "Any" || !this.native(sel.encoding)) return general(true)
763
+ if (!Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) return general(true)
761
764
  const enc = sel.encoding
762
765
  const r = enc === undefined ? undefined : this.reader(enc)
763
766
  const limit = r === undefined ? (this.kind === 0 ? 0xd800 : TableSize) : Math.min(r.fast, 0xd800)
@@ -802,7 +805,7 @@ class Emitter {
802
805
  const bad = this.label()
803
806
  const n = this.v(fn)
804
807
  const st = this.v(fn)
805
- const isFold = node._tag === "Fold"
808
+ const isFold = Predicate.isTagged(node, "Fold")
806
809
  const cond = isFold ? (node as NodeOf<"Fold">).while : undefined
807
810
  const keep = out !== null && used && collect === undefined
808
811
  // `while` needs the accumulator even when the output is unused
@@ -848,7 +851,7 @@ class Emitter {
848
851
  * of a `Done` one; a resumed repetition records its result when it ends.
849
852
  */
850
853
  private resumePoint(fn: Fn) {
851
- const key = this.const({})
854
+ const key = this.const(new Key())
852
855
  const entry = this.v(fn)
853
856
  const resumed = this.v(fn)
854
857
  const done = this.label()
@@ -970,7 +973,7 @@ class Emitter {
970
973
  `else ${deferred}=R.addEntry(${out},${deferred},${key},${value});`,
971
974
  finish: `if(${deferred}!==void 0)R.flushEntries(${out},${deferred});`
972
975
  }
973
- return node._tag === "Repeat"
976
+ return Predicate.isTagged(node, "Repeat")
974
977
  ? this.repeat(node, true, out, fail, fn, d, collect)
975
978
  : this.separated(node, true, out, fail, fn, d, collect)
976
979
  }
@@ -1190,21 +1193,32 @@ class Emitter {
1190
1193
  return `${head}if(${c}<${small}){if(!(${member}))break;p++}else ${rest}${count}}${finish}`
1191
1194
  }
1192
1195
 
1196
+ /**
1197
+ * `escaped`. In a resumable build it keeps the closure compiler's
1198
+ * checkpoints: where the next segment starts and, unless raw, the text
1199
+ * so far, saved when a segment or an escape runs out of input.
1200
+ */
1193
1201
  escaped(p: ParserLike, node: NodeOf<"Escaped">, out: string | null, fail: string, fn: Fn, d: number): string {
1194
1202
  const enc = node.encoding
1195
1203
  const ctrl = node.control
1196
- const r = enc === undefined ? undefined : this.reader(enc)
1197
- if (r !== undefined && !(ctrl < r.fast)) return this.delegate(p, !node.raw, out, fail, fn)
1204
+ const rd = enc === undefined ? undefined : this.reader(enc)
1205
+ if (rd !== undefined && !(ctrl < rd.fast)) return this.delegate(p, !node.raw, out, fail, fn)
1198
1206
  const raw = node.raw
1207
+ const r = this.resumable ? this.resumePoint(fn) : undefined
1199
1208
  const acc = raw ? null : (out ?? this.v(fn))
1200
1209
  const x = raw ? null : this.v(fn)
1201
1210
  const b = this.v(fn)
1202
- const st = this.v(fn)
1211
+ const st = r?.entry ?? this.v(fn)
1203
1212
  const okv = this.v(fn)
1204
1213
  const loop = this.label()
1205
1214
  const bad = this.label()
1215
+ /** Saves the checkpoint of a segment starting at `pos`, when the parse ran out of input. */
1216
+ const save = (pos: string) =>
1217
+ r === undefined
1218
+ ? ""
1219
+ : `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},${raw ? pos : `{out:${acc},pos:${pos}}`});`
1206
1220
  const normal = this.gen(node.normal, !raw, x, `break ${bad};`, fn, d)
1207
- const escape = this.gen(node.escape, !raw, x, fail, fn, d)
1221
+ const escape = this.gen(node.escape, !raw, x, `${save(b)}${fail}`, fn, d)
1208
1222
  const onNormal = raw
1209
1223
  ? `if(p===${b}){$S.mode="Cut";${fail}}continue ${loop};`
1210
1224
  : `if(p!==${b}){${acc}+=${x};continue ${loop}}`
@@ -1214,37 +1228,40 @@ class Emitter {
1214
1228
  : this.kind === 0
1215
1229
  ? `I.slice(${st},p)`
1216
1230
  : `I.subarray(${st},p)`
1217
- const ctrlTest = r === undefined ? `p<E&&${this.unit("p")}===${ctrl}` : `${r.guard("p")}&&${r.unit("p")}===${ctrl}`
1218
- return (
1219
- `${raw ? `${st}=p;` : `${acc}="";`}${loop}:while(p<E){${b}=p;${okv}=false;` +
1231
+ const ctrlTest =
1232
+ rd === undefined ? `p<E&&${this.unit("p")}===${ctrl}` : `${rd.guard("p")}&&${rd.unit("p")}===${ctrl}`
1233
+ // In a resumable build, running out of a partial input (not stopping
1234
+ // before a non-control character) leaves the text incomplete
1235
+ const stop = r === undefined ? loop : this.label()
1236
+ const loopCode =
1237
+ `${loop}:while(p<E){${b}=p;${okv}=false;` +
1220
1238
  `${bad}:{${normal}${okv}=true;}` +
1221
- `if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${fail}}p=${b}}` +
1222
- `if(${ctrlTest}){p+=${r?.width ?? 1};${escape}${raw ? "" : `${acc}+=${x};`}}else break ${loop};}` +
1223
- (raw && out !== null ? `${out}=${slice};` : "")
1224
- )
1239
+ `if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${save(b)}${fail}}p=${b}}` +
1240
+ `if(${ctrlTest}){p+=${rd?.width ?? 1};${escape}${raw ? "" : `${acc}+=${x};`}}else break ${stop};}`
1241
+ const body =
1242
+ r === undefined ? loopCode : `${stop}:{${loopCode}if($S.partial){$S.mode="Incomplete";${save("p")}${fail}}}`
1243
+ if (r === undefined) {
1244
+ return `${raw ? `${st}=p;` : `${acc}="";`}${body}${raw && out !== null ? `${out}=${slice};` : ""}`
1245
+ }
1246
+ // `acc` is set unless raw
1247
+ if (acc !== null) return r.wrap(acc, `${acc}=cp.out;p=cp.pos;`, `${acc}="";`, body)
1248
+ const value = out ?? this.v(fn)
1249
+ return r.wrap(value, "p=cp;", "", `${body}${value}=${out === null ? "void 0" : slice};`)
1225
1250
  }
1226
1251
  }
1227
1252
 
1228
1253
  /** The UTF-16 code units of a string. */
1229
1254
  const unitsOf = (s: string): Array<number> => Array.from({ length: s.length }, (_, i) => s.charCodeAt(i))
1230
1255
 
1231
- /** 1 where a small code is accepted (taking `negate` into account). */
1232
- const acceptTable = (set: CharSet, negate: boolean): Uint8Array => {
1233
- const t = new Uint8Array(TableSize)
1234
- for (let c = 0; c < TableSize; c++) t[c] = (set.table[c] === 1) !== negate ? 1 : 0
1235
- return t
1236
- }
1237
-
1238
- /** Generates the source of a compiled parser, without building it. */
1239
- const generate = (
1240
- root: ParserLike,
1241
- kind: 0 | 1 | 2,
1242
- resumable = false
1243
- ): {
1256
+ /** The source of a compiled parser, with the values it reads as `C`. */
1257
+ interface Generated {
1244
1258
  readonly source: string
1245
1259
  readonly constants: ReadonlyArray<unknown>
1246
1260
  readonly fallbacks: ReadonlyArray<Fallback>
1247
- } => {
1261
+ }
1262
+
1263
+ /** Generates the source of a compiled parser, without building it. */
1264
+ const generate = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): Generated => {
1248
1265
  const e = new Emitter(root, kind, resumable)
1249
1266
  const main = e.rule(root, true)
1250
1267
  const source =
@@ -1254,16 +1271,13 @@ const generate = (
1254
1271
  return { source, constants: e.constants, fallbacks: e.fallbacks }
1255
1272
  }
1256
1273
 
1257
- /** Compiles a root parser for one input kind with `new Function`. */
1258
- const compileJit = (
1259
- root: ParserLike,
1260
- kind: 0 | 1 | 2,
1261
- resumable = false
1262
- ): {
1263
- readonly step: Step
1264
- readonly fallbacks: ReadonlyArray<Fallback>
1274
+ /** A root parser compiled with `new Function`, with its generated source. */
1275
+ interface JitCompiled extends CompiledRoot {
1265
1276
  readonly source: string
1266
- } => {
1277
+ }
1278
+
1279
+ /** Compiles a root parser for one input kind with `new Function`. */
1280
+ const compileJit = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): JitCompiled => {
1267
1281
  const { constants, fallbacks, source } = generate(root, kind, resumable)
1268
1282
  const run = globalThis.Function("C", "F", "R", source)(constants, Fail, runtime) as Step
1269
1283
  // Generated code reads past `end` expecting nothing to match there, which
@@ -1283,12 +1297,19 @@ const compileJit = (
1283
1297
  export const backend: Backend = {
1284
1298
  build: (p, kind, resumable) => {
1285
1299
  if (!jitSupported()) return closureBackend.build(p, kind, resumable)
1286
- try {
1287
- const { fallbacks, source, step } = compileJit(p, kind, resumable)
1288
- return { backend: "jit", step, fallbacks, error: undefined, source }
1289
- } catch (error) {
1290
- // Fall back to the closure compiler, remembering why
1291
- return { ...closureBackend.build(p, kind, resumable), jitError: error }
1292
- }
1300
+ return Result.match(
1301
+ Result.try(() => compileJit(p, kind, resumable)),
1302
+ {
1303
+ onSuccess: ({ fallbacks, source, step }) => ({
1304
+ backend: "jit" as const,
1305
+ step,
1306
+ fallbacks,
1307
+ error: undefined,
1308
+ source
1309
+ }),
1310
+ // Fall back to the closure compiler, remembering why
1311
+ onFailure: (error) => ({ ...closureBackend.build(p, kind, resumable), jitError: error })
1312
+ }
1313
+ )
1293
1314
  }
1294
1315
  }
@@ -21,6 +21,13 @@ export interface CharSet {
21
21
  readonly ranges: ReadonlyArray<readonly [number, number]> | undefined
22
22
  }
23
23
 
24
+ /** 1 where a small code is accepted (taking `negate` into account). */
25
+ export const acceptTable = (set: CharSet, negate: boolean): Uint8Array => {
26
+ const t = new Uint8Array(TableSize)
27
+ for (let c = 0; c < TableSize; c++) t[c] = (set.table[c] === 1) !== negate ? 1 : 0
28
+ return t
29
+ }
30
+
24
31
  const isRange = (u: unknown): u is { readonly from: string | number; readonly to: string | number } =>
25
32
  Predicate.isObjectOrArray(u) && (u as any)._tag === "TokenRange"
26
33
 
@@ -11,7 +11,6 @@ export const fromEntries: <K extends PropertyKey, V>(entries: Iterable<readonly
11
11
  Object.fromEntries as any
12
12
 
13
13
  const objectPrototype = Object.prototype
14
- const hasOwn = objectPrototype.hasOwnProperty
15
14
 
16
15
  /**
17
16
  * Whether `o[key] = value` on a new plain object creates the same property
@@ -19,7 +18,8 @@ const hasOwn = objectPrototype.hasOwnProperty
19
18
  * property): `key` is a string that `Object.prototype` has no property for,
20
19
  * so there is no setter (`__proto__`) or read-only property to run into.
21
20
  */
22
- export const isPlainKey = (key: unknown): key is string => Predicate.isString(key) && !hasOwn.call(objectPrototype, key)
21
+ export const isPlainKey = (key: unknown): key is string =>
22
+ Predicate.isString(key) && !Object.hasOwn(objectPrototype, key)
23
23
 
24
24
  const define = (o: object, key: unknown, value: unknown): void => {
25
25
  Object.defineProperty(o, key as PropertyKey, { value, writable: true, enumerable: true, configurable: true })
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Identity keys: a parser that keeps state across parses (resume
3
+ * checkpoints, reusable syntax nodes) creates one `Key` when it is built and
4
+ * looks its state up by that key's reference.
5
+ */
6
+
7
+ /** A per-parser identity, compared by reference. */
8
+ export class Key {
9
+ // Nominal: an empty class would accept any object
10
+ declare private readonly brand: void
11
+ }
@@ -7,6 +7,8 @@
7
7
  * `Suspend` is just a reference.
8
8
  */
9
9
  import * as Predicate from "effect/Predicate"
10
+ import type * as Option from "effect/Option"
11
+ import type * as Result from "effect/Result"
10
12
  import type { Encoding } from "../Encoding.js"
11
13
  import type { ErrMode, Input, State } from "../Input.js"
12
14
 
@@ -67,8 +69,8 @@ export type Node =
67
69
  | { readonly _tag: "Map"; readonly self: P; readonly f: (a: any) => unknown }
68
70
  | { readonly _tag: "As"; readonly self: P; readonly value: unknown }
69
71
  | { readonly _tag: "Tap"; readonly self: P; readonly f: (a: any) => void }
70
- | { readonly _tag: "TryMap"; readonly self: P; readonly f: (a: any) => unknown }
71
- | { readonly _tag: "FilterMap"; readonly self: P; readonly f: (a: any) => unknown }
72
+ | { readonly _tag: "TryMap"; readonly self: P; readonly f: (a: any) => Result.Result<unknown, unknown> }
73
+ | { readonly _tag: "FilterMap"; readonly self: P; readonly f: (a: any) => Option.Option<unknown> }
72
74
  | { readonly _tag: "Filter"; readonly self: P; readonly predicate: (a: any) => boolean }
73
75
  | { readonly _tag: "FlatMap"; readonly self: P; readonly f: (a: any) => P }
74
76
  // Consumed input
@@ -160,6 +162,21 @@ export const bounds = (
160
162
  return { min: range.min ?? 0, max: range.max ?? Infinity }
161
163
  }
162
164
 
165
+ /**
166
+ * Whether the compilers' one-pass float scan applies to `p`, the text
167
+ * parser of `Ascii.float` / `Text.float`: a `taken` (under any `expected`
168
+ * labels) in an encoding that `native` accepts, reading one ASCII-compatible
169
+ * unit at a time, or in no encoding at all.
170
+ */
171
+ export const scansFloat = (p: ParserLike, native: (enc: Encoding<any>) => boolean): boolean => {
172
+ let taken = nodeOf(p)
173
+ while (Predicate.isTagged(taken, "Expected")) taken = nodeOf(taken.self)
174
+ if (!Predicate.isTagged(taken, "Taken")) return false
175
+ const enc = taken.encoding
176
+ // Decimal notation is ASCII, which these encodings read unit by unit
177
+ return enc === undefined || (native(enc) && enc.unit === 1 && enc.asciiCompatible)
178
+ }
179
+
163
180
  /** The child parsers of a node, in evaluation order. */
164
181
  export const children = (node: Node): ReadonlyArray<P> => {
165
182
  switch (node._tag) {
@@ -19,12 +19,13 @@
19
19
  * it all again. `Done` checkpoints stay in place when taken.
20
20
  */
21
21
  import type { State } from "../Input.js"
22
+ import type { Key } from "./key.js"
22
23
 
23
24
  /** The checkpoints of a resumable parse. */
24
- export type Checkpoints = Map<object, Map<number, unknown>>
25
+ export type Checkpoints = Map<Key, Map<number, unknown>>
25
26
 
26
27
  /** Takes (and removes) the checkpoint of `key` entered at `offset`, if any. */
27
- export const take = (state: State<any>, key: object, offset: number): unknown => {
28
+ export const take = (state: State<any>, key: Key, offset: number): unknown => {
28
29
  const byKey = state.resume?.get(key)
29
30
  if (byKey === undefined) return undefined
30
31
  const checkpoint = byKey.get(offset)
@@ -44,13 +45,13 @@ export class Done {
44
45
  * Returns `value`, the result of a repetition entered at `entry`; when the
45
46
  * repetition was resumed from a checkpoint, records it as {@link Done}.
46
47
  */
47
- export const done = <A>(state: State<any>, key: object, entry: number, resumed: boolean, value: A): A => {
48
+ export const done = <A>(state: State<any>, key: Key, entry: number, resumed: boolean, value: A): A => {
48
49
  if (resumed && state.resume !== undefined) save(state, key, entry, new Done(value, state.pos))
49
50
  return value
50
51
  }
51
52
 
52
53
  /** Records the checkpoint of `key` entered at `offset`. */
53
- export const save = (state: State<any>, key: object, offset: number, checkpoint: unknown): void => {
54
+ export const save = (state: State<any>, key: Key, offset: number, checkpoint: unknown): void => {
54
55
  const resume = state.resume
55
56
  if (resume === undefined) return
56
57
  let byKey = resume.get(key)
@@ -33,7 +33,7 @@ export const runOn = <A>(
33
33
  // SAFETY: the compiled step returns what the parser's step returns
34
34
  return a as A
35
35
  }
36
- state.pos = start
36
+ state.rewind(start)
37
37
  if (!diagnostics) return Fail
38
38
  }
39
39
  }