@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
package/src/Parser.ts CHANGED
@@ -45,6 +45,7 @@ import type { ErrorKind } from "./ParseError.js"
45
45
  import * as ParseError from "./ParseError.js"
46
46
  import * as Registry from "./internal/compiler/registry.js"
47
47
  import * as Entries from "./internal/entries.js"
48
+ import { Key } from "./internal/key.js"
48
49
  import * as Resume from "./internal/resume.js"
49
50
  import * as Run from "./internal/run.js"
50
51
  import * as Stack from "./internal/stack.js"
@@ -82,6 +83,8 @@ export interface Parser<out A, out E = never, out R = never, in I extends Input
82
83
  /** A human-readable name, used by `toString` and tracing. */
83
84
  readonly name: string | undefined
84
85
  [Symbol.iterator](): ParserIterator<Parser<A, E, R, I>>
86
+ /** Describes the parser: `Parser(<name>)`, or `Parser` when it has no name. */
87
+ toString(): string
85
88
  }
86
89
 
87
90
  /**
@@ -273,6 +276,8 @@ const isTwoParserArgs = (args: IArguments) => isParser(args[0]) && isParser(args
273
276
  * The step function must either return the parsed value, having advanced
274
277
  * `state.pos` past the consumed input, or return the result of one of
275
278
  * `state.backtrack`, `state.cut`, `state.incomplete`, ... to signal failure.
279
+ * A step that moves the cursor backwards (to retry, or after a lookahead)
280
+ * calls `state.rewind(offset)` rather than assigning `state.pos`.
276
281
  *
277
282
  * @example
278
283
  * ```ts
@@ -723,7 +728,7 @@ export const tryMap: {
723
728
  if (a === Fail) return Fail
724
729
  const r = f(a)
725
730
  if (Result.isSuccess(r)) return r.success
726
- state.pos = start
731
+ state.rewind(start)
727
732
  return state.failCause("Backtrack", "Verify", r.failure)
728
733
  },
729
734
  self.name,
@@ -740,8 +745,8 @@ export const tryMap: {
740
745
  * @category mapping
741
746
  */
742
747
  export const tryMapUnknown: {
743
- <A, B>(f: (a: A) => B): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<B, E | unknown, R, I>
744
- <A, E, R, I extends Input, B>(self: Parser<A, E, R, I>, f: (a: A) => B): Parser<B, E | unknown, R, I>
748
+ <A, B>(f: (a: A) => B): <E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<B, unknown, R, I>
749
+ <A, E, R, I extends Input, B>(self: Parser<A, E, R, I>, f: (a: A) => B): Parser<B, unknown, R, I>
745
750
  } = /*#__PURE__*/ dual(
746
751
  2,
747
752
  <A, E, R, I extends Input, B>(self: Parser<A, E, R, I>, f: (a: A) => B): Parser<B, unknown, R, I> =>
@@ -777,7 +782,7 @@ export const filterMap: {
777
782
  if (a === Fail) return Fail
778
783
  const o = f(a)
779
784
  if (Option.isSome(o)) return o.value
780
- state.pos = start
785
+ state.rewind(start)
781
786
  return state.backtrack("Verify")
782
787
  },
783
788
  self.name,
@@ -808,7 +813,7 @@ export const filter: {
808
813
  const a = step(state)
809
814
  if (a === Fail) return Fail
810
815
  if (predicate(a)) return a
811
- state.pos = start
816
+ state.rewind(start)
812
817
  return state.backtrack("Verify")
813
818
  },
814
819
  self.name,
@@ -852,7 +857,7 @@ export const filterOrFail: {
852
857
  const a = step(state)
853
858
  if (a === Fail) return Fail
854
859
  if (predicate(a)) return a
855
- state.pos = start
860
+ state.rewind(start)
856
861
  return state.failCause("Backtrack", "Verify", orFailWith(a))
857
862
  },
858
863
  self.name,
@@ -1032,7 +1037,7 @@ export const complete = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Pa
1032
1037
  const start = state.pos
1033
1038
  const a = step(state)
1034
1039
  if (a === Fail && state.mode === "Incomplete") {
1035
- state.pos = start
1040
+ state.rewind(start)
1036
1041
  return state.backtrack("Complete")
1037
1042
  }
1038
1043
  return a
@@ -1223,7 +1228,7 @@ export const orElseSucceed: {
1223
1228
 
1224
1229
  /** Rewinds after a recoverable failure, remembering it as a hint. */
1225
1230
  const recover = (state: State<any>, start: number): void => {
1226
- state.pos = start
1231
+ state.rewind(start)
1227
1232
  const f = state.failure
1228
1233
  if (f.offset === start) {
1229
1234
  state.hintOffset = start
@@ -1297,7 +1302,7 @@ export const peek = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser
1297
1302
  if (a !== Fail) {
1298
1303
  // Failures inside a lookahead that succeeded are about input that was
1299
1304
  // never consumed, so they are not reported as the furthest failure
1300
- state.pos = start
1305
+ state.rewind(start)
1301
1306
  state.hintOffset = hintOffset
1302
1307
  state.hintExpected = hintExpected
1303
1308
  state.deepest = deepest
@@ -1327,12 +1332,12 @@ export const not = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<
1327
1332
  const a = step(state)
1328
1333
  if (a !== Fail) {
1329
1334
  // The failures inside `self` are not this parser's failure
1330
- state.pos = start
1335
+ state.rewind(start)
1331
1336
  state.deepest = deepest
1332
1337
  return state.backtrack("Not")
1333
1338
  }
1334
1339
  if (state.mode !== "Backtrack") return Fail
1335
- state.pos = start
1340
+ state.rewind(start)
1336
1341
  state.hintOffset = hintOffset
1337
1342
  state.hintExpected = hintExpected
1338
1343
  state.deepest = deepest
@@ -1807,7 +1812,7 @@ export const alt = <const Ps extends ReadonlyArray<Parser.Any>>(
1807
1812
  return a
1808
1813
  }
1809
1814
  if (state.mode !== "Backtrack") return Fail
1810
- state.pos = start
1815
+ state.rewind(start)
1811
1816
  // The branch's failure, whose own ties (from a nested `alt`) can be taken over unmerged
1812
1817
  const head = state.failureHead
1813
1818
  if (failure === undefined || head.offset > failure.offset) {
@@ -1906,7 +1911,7 @@ export const dispatch: {
1906
1911
  if (key === Fail) return Fail
1907
1912
  const parser = choose(key)
1908
1913
  if (parser === undefined) {
1909
- state.pos = start
1914
+ state.rewind(start)
1910
1915
  return state.backtrack("Dispatch")
1911
1916
  }
1912
1917
  return parser.step(state)
@@ -1961,7 +1966,7 @@ export const permutation = <const Ps extends ReadonlyArray<Parser.Any>>(
1961
1966
  break
1962
1967
  }
1963
1968
  if (state.mode !== "Backtrack") return Fail
1964
- state.pos = start
1969
+ state.rewind(start)
1965
1970
  ties.add(state)
1966
1971
  }
1967
1972
  // Some parser is not done yet, and each one that fails is folded in
@@ -1995,7 +2000,7 @@ interface FoldCheckpoint<B> {
1995
2000
  readonly pos: number
1996
2001
  }
1997
2002
 
1998
- const saveFold = <B>(state: State<any>, key: object, entry: number, acc: B, pos: number): void =>
2003
+ const saveFold = <B>(state: State<any>, key: Key, entry: number, acc: B, pos: number): void =>
1999
2004
  Resume.save(state, key, entry, { acc, count: 0, pos } satisfies FoldCheckpoint<B>)
2000
2005
 
2001
2006
  const foldImpl = <A, B, E, R, I extends Input>(
@@ -2010,7 +2015,7 @@ const foldImpl = <A, B, E, R, I extends Input>(
2010
2015
  const [min, max] = normalizeRange(range)
2011
2016
  const step = self.step
2012
2017
  const checkLoop = max === Infinity
2013
- const key = {}
2018
+ const key = new Key()
2014
2019
  return described(
2015
2020
  (state) => {
2016
2021
  const entry = state.pos
@@ -2189,7 +2194,7 @@ export const repeatTill: {
2189
2194
  if (b !== Fail) return [acc, b]
2190
2195
  if (state.mode !== "Backtrack") return Fail
2191
2196
  endFailure = state.failure
2192
- state.pos = start
2197
+ state.rewind(start)
2193
2198
  if (acc.length >= max) return Fail
2194
2199
  }
2195
2200
  const a = step(state)
@@ -2252,7 +2257,7 @@ export const separated: {
2252
2257
  const step = self.step
2253
2258
  const sepStep = separator.step
2254
2259
  const checkLoop = max === Infinity
2255
- const key = {}
2260
+ const key = new Key()
2256
2261
  return described(
2257
2262
  (state) => {
2258
2263
  const entry = state.pos
@@ -2295,7 +2300,7 @@ export const separated: {
2295
2300
  Resume.save(state, key, entry, { acc, pos: start } satisfies SeparatedCheckpoint<A>)
2296
2301
  }
2297
2302
  if (state.mode !== "Backtrack" || acc.length < min) return Fail
2298
- state.pos = start
2303
+ state.rewind(start)
2299
2304
  return Resume.done(state, key, entry, resumed !== undefined, acc)
2300
2305
  }
2301
2306
  acc.push(a)
@@ -2336,7 +2341,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2336
2341
  ): Parser<A, E | E2, R | R2, I & I2> => {
2337
2342
  const step = self.step
2338
2343
  const sepStep = separator.step
2339
- const key = {}
2344
+ const key = new Key()
2340
2345
  return described(
2341
2346
  (state) => {
2342
2347
  const entry = state.pos
@@ -2367,7 +2372,7 @@ export const separatedFoldl1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2367
2372
  if (a === Fail) {
2368
2373
  if (state.mode === "Incomplete" && state.resume !== undefined) saveFold(state, key, entry, acc, start)
2369
2374
  if (state.mode !== "Backtrack") return Fail
2370
- state.pos = start
2375
+ state.rewind(start)
2371
2376
  return Resume.done(state, key, entry, resumed !== undefined, acc as A)
2372
2377
  }
2373
2378
  acc = op(acc, s, a)
@@ -2412,7 +2417,7 @@ export const separatedFoldr1 = <A, E, R, I extends Input, S, E2, R2, I2 extends
2412
2417
  const a = step(state)
2413
2418
  if (a === Fail) {
2414
2419
  if (state.mode !== "Backtrack") return Fail
2415
- state.pos = start
2420
+ state.rewind(start)
2416
2421
  break
2417
2422
  }
2418
2423
  seps.push(s)
@@ -2584,7 +2589,7 @@ export const resumeAfter: {
2584
2589
  if (state.recovered === undefined || state.mode === "Incomplete") return Fail
2585
2590
  const failure = state.failure
2586
2591
  const mode = state.mode
2587
- state.pos = start
2592
+ state.rewind(start)
2588
2593
  if (recoverStep(state) === Fail) return state.failWith(mode, failure)
2589
2594
  state.recovered.push(failure)
2590
2595
  state.syntax?.skipped(start, failure, mode)
@@ -2631,7 +2636,7 @@ export const retryAfter: {
2631
2636
  if (state.recovered === undefined || state.mode === "Incomplete") return Fail
2632
2637
  const failure = state.failure
2633
2638
  const mode = state.mode
2634
- state.pos = start
2639
+ state.rewind(start)
2635
2640
  if (recoverStep(state) === Fail || state.pos === start) return state.failWith(mode, failure)
2636
2641
  state.recovered.push(failure)
2637
2642
  state.syntax?.skipped(start, failure, mode)
@@ -2708,7 +2713,7 @@ const preview = (state: State<any>, offset: number): string => {
2708
2713
  */
2709
2714
  export const consoleTracer = (event: TraceEvent): void => {
2710
2715
  const indent = " ".repeat(event.depth)
2711
- if (event._tag === "Enter") {
2716
+ if (Predicate.isTagged(event, "Enter")) {
2712
2717
  console.log(`${indent}> ${event.name} @${event.offset} | ${preview(event.state, event.offset)}`)
2713
2718
  } else {
2714
2719
  const consumed = (event.endOffset ?? event.offset) - event.offset
@@ -431,7 +431,7 @@ const isAnnotated = (ast: SchemaAST.AST): boolean => lookup(ast, annotationKey)
431
431
 
432
432
  const withStop = <T>(ctx: Context<T>, text: string | undefined): Context<T> => {
433
433
  if (text === undefined || text === "" || ctx.stop.includes(text[0])) return ctx
434
- return { ...ctx, stop: Arr.sort([...ctx.stop, text[0]], Order.String).join("") }
434
+ return { ...ctx, stop: Arr.sort([...ctx.stop.split(""), text[0]], Order.String).join("") }
435
435
  }
436
436
 
437
437
  const at = <T>(ctx: Context<T>, key: PropertyKey): Context<T> => ({
@@ -36,6 +36,7 @@ import * as Registry from "./internal/compiler/registry.js"
36
36
  import type * as Resume from "./internal/resume.js"
37
37
  import * as Run from "./internal/run.js"
38
38
  import * as Stack from "./internal/stack.js"
39
+ import { isEmptyUnits } from "./internal/text.js"
39
40
  import type { Parser } from "./Parser.js"
40
41
 
41
42
  /**
@@ -46,10 +47,12 @@ import type { Parser } from "./Parser.js"
46
47
  */
47
48
  export type Chunkable = string | Uint8Array | ReadonlyArray<unknown>
48
49
 
49
- const concat = <I extends Chunkable>(a: I, b: I): I => {
50
+ const concat = <I extends Chunkable>(first: I, b: I): I => {
51
+ // Widened so the checks below narrow it (a type parameter does not narrow).
52
+ const a: Chunkable = first
50
53
  if (Predicate.isString(a)) return (a + (b as string)) as I
51
54
  if (a instanceof Uint8Array) {
52
- if (a.length === 0) return b
55
+ if (a.byteLength === 0) return b
53
56
  const out = new Uint8Array(a.length + (b as Uint8Array).length)
54
57
  out.set(a)
55
58
  out.set(b as Uint8Array, a.length)
@@ -174,6 +177,7 @@ class Incremental<A, E, I extends Chunkable> {
174
177
  /** The input length at the last attempt, and where that attempt ran out of input. */
175
178
  private attempted = 0
176
179
  private restart = 0
180
+ private pushed = 0
177
181
  /** Set once an attempt settles: a success (with its end offset) or a definite failure. */
178
182
  private settled: { readonly value: A; readonly pos: number } | "failed" | undefined
179
183
 
@@ -221,9 +225,14 @@ class Incremental<A, E, I extends Chunkable> {
221
225
  }
222
226
 
223
227
  push(chunk: I): void {
224
- if (chunk.length === 0) return
228
+ if (isEmptyUnits(chunk)) return
225
229
  this.append(chunk)
226
230
  if (this.settled !== undefined) return
231
+ // The first attempt waits for a second chunk: an input that arrives in
232
+ // one chunk (most small responses) is parsed once, by `finish`, instead
233
+ // of once in partial mode and again in complete mode, since a repetition
234
+ // that completes without resuming keeps no checkpoint
235
+ if (++this.pushed === 1) return
227
236
  // An attempt re-reads what the last one could not finish, from `restart`;
228
237
  // waiting until at least half as much new input has arrived keeps the
229
238
  // total work linear in the input. Strings are also copied when they are
@@ -234,9 +243,12 @@ class Incremental<A, E, I extends Chunkable> {
234
243
  this.attempt()
235
244
  }
236
245
 
237
- /** The step attempts run: the parser's resumable compiled code if it was compiled, else the interpreter. */
238
- private step(state: State<I>): unknown {
239
- return Stack.run(Registry.stepFor(this.parser, state, true) ?? this.parser.step, state)
246
+ /**
247
+ * The step attempts run: the parser's compiled code if it was compiled
248
+ * (the resumable build, unless `resumable` is false), else the interpreter.
249
+ */
250
+ private step(state: State<I>, resumable = true): unknown {
251
+ return Stack.run(Registry.stepFor(this.parser, state, resumable) ?? this.parser.step, state)
240
252
  }
241
253
 
242
254
  private attempt(): void {
@@ -258,10 +270,12 @@ class Incremental<A, E, I extends Chunkable> {
258
270
  if (settled !== undefined && settled !== "failed") {
259
271
  if (settled.pos === this.length) return Effect.succeed(settled.value)
260
272
  } else if (settled === undefined) {
261
- // The last attempt, in complete mode, resumes from the checkpoints too
273
+ // The last attempt, in complete mode, resumes from the checkpoints too;
274
+ // with no attempt before it, it is an ordinary parse
262
275
  const state = new State(this.input(), { context: this.context })
263
- state.resume = this.resume
264
- const a = this.step(state)
276
+ const resumable = this.attempted > 0
277
+ if (resumable) state.resume = this.resume
278
+ const a = this.step(state, resumable)
265
279
  if (a !== Fail && state.pos === state.end) return Effect.succeed(a as A)
266
280
  }
267
281
  // The error comes from an ordinary parse of the whole input, so it is the
package/src/Syntax.ts CHANGED
@@ -46,17 +46,19 @@
46
46
  *
47
47
  * @since 0.2.0
48
48
  */
49
+ import * as Str from "effect/String"
49
50
  import * as Predicate from "effect/Predicate"
50
51
  import type * as Context from "effect/Context"
51
52
  import { dual } from "effect/Function"
52
53
  import * as Option from "effect/Option"
53
54
  import type { Input, Slice, Source } from "./Input.js"
54
- import { Fail, fromSource } from "./Input.js"
55
+ import { Fail, fromSource, State } from "./Input.js"
55
56
  import * as ParseError from "./ParseError.js"
56
57
  import { nodeOf } from "./internal/node.js"
58
+ import { Key } from "./internal/key.js"
57
59
  import * as Stack from "./internal/stack.js"
58
60
  import type { ErrorInfo, Green, Previous, Segment } from "./internal/syntax.js"
59
- import { absolute, errorKind, SyntaxState } from "./internal/syntax.js"
61
+ import { absolute, errorKind, SyntaxBuilder } from "./internal/syntax.js"
60
62
  import type { Parser } from "./Parser.js"
61
63
  import { described, isParser } from "./Parser.js"
62
64
 
@@ -67,7 +69,7 @@ import { described, isParser } from "./Parser.js"
67
69
  const syntaxNode = (self: Parser.Any, kind: string, named: boolean): Parser.Any => {
68
70
  const step = self.step
69
71
  // Identifies the nodes this parser builds, so a reparse can reuse them
70
- const key = {}
72
+ const key = new Key()
71
73
  return described<unknown, unknown, unknown, Input>(
72
74
  (state) => {
73
75
  const builder = state.syntax
@@ -133,7 +135,7 @@ export const token: {
133
135
 
134
136
  const defaultTokenKind = (self: Parser.Any): string => {
135
137
  const n = nodeOf(self)
136
- if (n._tag === "Literal" && Predicate.isString(n.literal) && !n.caseless) return n.literal
138
+ if (Predicate.isTagged(n, "Literal") && Predicate.isString(n.literal) && !n.caseless) return n.literal
137
139
  return self.name ?? "token"
138
140
  }
139
141
 
@@ -577,7 +579,7 @@ export class SyntaxNode<I extends Input = string> {
577
579
  let out = ""
578
580
  const visit = (green: Green, field: string | undefined) => {
579
581
  if (green.named || green === this.green) {
580
- if (out.length > 0) out += " "
582
+ if (Str.isNonEmpty(out)) out += " "
581
583
  if (field !== undefined) out += `${field}: `
582
584
  out += `(${green.kind}`
583
585
  for (let i = 0; i < green.children.length; i++) visit(green.children[i], green.fields?.[i])
@@ -708,7 +710,8 @@ export class TreeCursor<I extends Input = string> {
708
710
  const expectedEof = ["end of input"]
709
711
 
710
712
  const run = <A, E, I extends Input>(config: Config, input: I, previous: Previous | undefined): Tree<A, E, I> => {
711
- const state = new SyntaxState<I>(input, { context: config.context }, previous)
713
+ const state = new State<I>(input, { context: config.context })
714
+ const builder = new SyntaxBuilder(state, previous)
712
715
  const a = Stack.run(config.inner.step, state)
713
716
  // Text parsers record the encoding when they fail, which they may have done inside reused nodes
714
717
  if (state.encoding === undefined && previous !== undefined) state.encoding = previous.encoding
@@ -722,8 +725,8 @@ const run = <A, E, I extends Input>(config: Config, input: I, previous: Previous
722
725
  } else {
723
726
  value = Option.some(a as A)
724
727
  }
725
- const green = state.finish(config.kind, Option.getOrUndefined(value), fatal, state.pos)
726
- return new Tree<A, E, I>(input, value, state.reused, green, config, state.encoding)
728
+ const green = builder.finish(config.kind, Option.getOrUndefined(value), fatal, state.pos)
729
+ return new Tree<A, E, I>(input, value, builder.reused, green, config, state.encoding)
727
730
  }
728
731
 
729
732
  /**
@@ -756,7 +759,7 @@ export const parse: {
756
759
  options?: ParseOptions & { readonly context?: Context.Context<R> }
757
760
  ): Tree<A, E, I> => {
758
761
  const n = nodeOf(grammar)
759
- const isRoot = n._tag === "Syntax" && n.kind !== undefined && n.named
762
+ const isRoot = Predicate.isTagged(n, "Syntax") && n.kind !== undefined && n.named
760
763
  const config: Config = {
761
764
  kind: isRoot ? n.kind : (options?.kind ?? "document"),
762
765
  inner: isRoot ? (n.self as Parser.Any) : grammar,