@thomasfosterau/effect-parse 0.4.0 → 0.5.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 (148) hide show
  1. package/README.md +40 -14
  2. package/dist/Ascii.d.ts +11 -4
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +7 -1
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts.map +1 -1
  7. package/dist/Binary.js +64 -35
  8. package/dist/Binary.js.map +1 -1
  9. package/dist/Bits.d.ts.map +1 -1
  10. package/dist/Bits.js +7 -4
  11. package/dist/Bits.js.map +1 -1
  12. package/dist/Compiler.d.ts +74 -2
  13. package/dist/Compiler.d.ts.map +1 -1
  14. package/dist/Compiler.js +78 -1
  15. package/dist/Compiler.js.map +1 -1
  16. package/dist/Expression.d.ts.map +1 -1
  17. package/dist/Expression.js +86 -74
  18. package/dist/Expression.js.map +1 -1
  19. package/dist/Input.d.ts +37 -4
  20. package/dist/Input.d.ts.map +1 -1
  21. package/dist/Input.js +42 -0
  22. package/dist/Input.js.map +1 -1
  23. package/dist/Nom.d.ts +5 -15
  24. package/dist/Nom.d.ts.map +1 -1
  25. package/dist/Nom.js.map +1 -1
  26. package/dist/ParseError.d.ts +2 -2
  27. package/dist/ParseError.d.ts.map +1 -1
  28. package/dist/ParseError.js +1 -1
  29. package/dist/ParseError.js.map +1 -1
  30. package/dist/Parser.d.ts +370 -28
  31. package/dist/Parser.d.ts.map +1 -1
  32. package/dist/Parser.js +390 -19
  33. package/dist/Parser.js.map +1 -1
  34. package/dist/ParserSchema.js +2 -2
  35. package/dist/ParserSchema.js.map +1 -1
  36. package/dist/ParserStream.d.ts +3 -2
  37. package/dist/ParserStream.d.ts.map +1 -1
  38. package/dist/ParserStream.js +15 -10
  39. package/dist/ParserStream.js.map +1 -1
  40. package/dist/Syntax.d.ts +23 -5
  41. package/dist/Syntax.d.ts.map +1 -1
  42. package/dist/Syntax.js.map +1 -1
  43. package/dist/Text.d.ts.map +1 -1
  44. package/dist/Text.js +9 -9
  45. package/dist/Text.js.map +1 -1
  46. package/dist/Token.d.ts +55 -10
  47. package/dist/Token.d.ts.map +1 -1
  48. package/dist/Token.js +59 -21
  49. package/dist/Token.js.map +1 -1
  50. package/dist/internal/compiler/closure.d.ts.map +1 -1
  51. package/dist/internal/compiler/closure.js +257 -97
  52. package/dist/internal/compiler/closure.js.map +1 -1
  53. package/dist/internal/compiler/entries.d.ts +3 -1
  54. package/dist/internal/compiler/entries.d.ts.map +1 -1
  55. package/dist/internal/compiler/entries.js +3 -2
  56. package/dist/internal/compiler/entries.js.map +1 -1
  57. package/dist/internal/compiler/first.d.ts.map +1 -1
  58. package/dist/internal/compiler/first.js +7 -1
  59. package/dist/internal/compiler/first.js.map +1 -1
  60. package/dist/internal/compiler/graph.d.ts +17 -0
  61. package/dist/internal/compiler/graph.d.ts.map +1 -0
  62. package/dist/internal/compiler/graph.js +56 -0
  63. package/dist/internal/compiler/graph.js.map +1 -0
  64. package/dist/internal/compiler/jit.d.ts +5 -0
  65. package/dist/internal/compiler/jit.d.ts.map +1 -1
  66. package/dist/internal/compiler/jit.js +185 -96
  67. package/dist/internal/compiler/jit.js.map +1 -1
  68. package/dist/internal/compiler/registry.d.ts +10 -2
  69. package/dist/internal/compiler/registry.d.ts.map +1 -1
  70. package/dist/internal/compiler/registry.js.map +1 -1
  71. package/dist/internal/compiler/search.d.ts +32 -0
  72. package/dist/internal/compiler/search.d.ts.map +1 -0
  73. package/dist/internal/compiler/search.js +68 -0
  74. package/dist/internal/compiler/search.js.map +1 -0
  75. package/dist/internal/compiler/sets.d.ts.map +1 -1
  76. package/dist/internal/compiler/sets.js +1 -1
  77. package/dist/internal/compiler/sets.js.map +1 -1
  78. package/dist/internal/compiler/wasm.d.ts +8 -0
  79. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  80. package/dist/internal/compiler/wasm.js +2769 -0
  81. package/dist/internal/compiler/wasm.js.map +1 -0
  82. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  83. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  84. package/dist/internal/compiler/wasmAsm.js +427 -0
  85. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  86. package/dist/internal/entries.d.ts +3 -1
  87. package/dist/internal/entries.d.ts.map +1 -1
  88. package/dist/internal/entries.js +1 -2
  89. package/dist/internal/entries.js.map +1 -1
  90. package/dist/internal/node.d.ts +58 -2
  91. package/dist/internal/node.d.ts.map +1 -1
  92. package/dist/internal/node.js +13 -3
  93. package/dist/internal/node.js.map +1 -1
  94. package/dist/internal/numbers.d.ts.map +1 -1
  95. package/dist/internal/numbers.js.map +1 -1
  96. package/dist/internal/run.d.ts +12 -0
  97. package/dist/internal/run.d.ts.map +1 -1
  98. package/dist/internal/run.js +51 -11
  99. package/dist/internal/run.js.map +1 -1
  100. package/dist/internal/stack.d.ts +6 -0
  101. package/dist/internal/stack.d.ts.map +1 -1
  102. package/dist/internal/stack.js +8 -2
  103. package/dist/internal/stack.js.map +1 -1
  104. package/dist/internal/syntax.d.ts +4 -4
  105. package/dist/internal/syntax.d.ts.map +1 -1
  106. package/dist/internal/syntax.js +18 -10
  107. package/dist/internal/syntax.js.map +1 -1
  108. package/dist/internal/text.d.ts +32 -0
  109. package/dist/internal/text.d.ts.map +1 -1
  110. package/dist/internal/text.js +68 -14
  111. package/dist/internal/text.js.map +1 -1
  112. package/dist/internal/tokens.d.ts +13 -0
  113. package/dist/internal/tokens.d.ts.map +1 -1
  114. package/dist/internal/tokens.js +59 -39
  115. package/dist/internal/tokens.js.map +1 -1
  116. package/package.json +5 -4
  117. package/src/Ascii.ts +42 -9
  118. package/src/Binary.ts +151 -71
  119. package/src/Bits.ts +12 -6
  120. package/src/Compiler.ts +101 -10
  121. package/src/Expression.ts +94 -81
  122. package/src/Input.ts +65 -6
  123. package/src/Nom.ts +15 -5
  124. package/src/ParseError.ts +9 -4
  125. package/src/Parser.ts +682 -129
  126. package/src/ParserSchema.ts +1 -1
  127. package/src/ParserStream.ts +40 -19
  128. package/src/Syntax.ts +60 -36
  129. package/src/Text.ts +16 -15
  130. package/src/Token.ts +138 -29
  131. package/src/internal/compiler/closure.ts +362 -135
  132. package/src/internal/compiler/entries.ts +4 -2
  133. package/src/internal/compiler/first.ts +10 -0
  134. package/src/internal/compiler/graph.ts +68 -0
  135. package/src/internal/compiler/jit.ts +284 -154
  136. package/src/internal/compiler/registry.ts +10 -2
  137. package/src/internal/compiler/search.ts +68 -0
  138. package/src/internal/compiler/sets.ts +7 -3
  139. package/src/internal/compiler/wasm.ts +3203 -0
  140. package/src/internal/compiler/wasmAsm.ts +484 -0
  141. package/src/internal/entries.ts +10 -4
  142. package/src/internal/node.ts +54 -4
  143. package/src/internal/numbers.ts +3 -1
  144. package/src/internal/run.ts +71 -12
  145. package/src/internal/stack.ts +8 -1
  146. package/src/internal/syntax.ts +39 -18
  147. package/src/internal/text.ts +97 -9
  148. package/src/internal/tokens.ts +96 -14
@@ -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> => ({
@@ -47,19 +47,31 @@ import type { Parser } from "./Parser.js"
47
47
  */
48
48
  export type Chunkable = string | Uint8Array | ReadonlyArray<unknown>
49
49
 
50
- const concat = <I extends Chunkable>(a: I, b: I): I => {
51
- if (Predicate.isString(a)) return (a + (b as string)) as 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
53
+ if (Predicate.isString(a)) {
54
+ // SAFETY: both are chunks of one stream of `I`, so `b` is a string when `a` is, and so is their concatenation
55
+ return (a + (b as string)) as I
56
+ }
52
57
  if (a instanceof Uint8Array) {
53
58
  if (a.byteLength === 0) return b
59
+ // SAFETY: both are chunks of one stream of `I`, so `b` is a `Uint8Array` when `a` is
54
60
  const out = new Uint8Array(a.length + (b as Uint8Array).length)
55
61
  out.set(a)
62
+ // SAFETY: both are chunks of one stream of `I`, so `b` is a `Uint8Array` when `a` is
56
63
  out.set(b as Uint8Array, a.length)
64
+ // SAFETY: `I` is `a`'s type, a `Uint8Array` here, and `out` is a new `Uint8Array`
57
65
  return out as I
58
66
  }
59
- return [...a, ...(b as ReadonlyArray<unknown>)] as unknown as I
67
+ // SAFETY: both are chunks of one stream of `I`, so `b` is an array when `a` is
68
+ const joined: Chunkable = [...a, ...(b as ReadonlyArray<unknown>)]
69
+ // SAFETY: `I` is `a`'s type, an array here, and `joined` is a new array of both chunks' elements
70
+ return joined as I
60
71
  }
61
72
 
62
73
  const sliceFrom = <I extends Chunkable>(input: I, offset: number): I =>
74
+ // SAFETY: slicing a string, `Uint8Array` or array returns a value of the same kind
63
75
  (Predicate.isString(input) ? input.slice(offset) : input.slice(offset)) as I
64
76
 
65
77
  const noProgress = (input: Input, offset: number) =>
@@ -90,8 +102,9 @@ export interface ParseManyOptions<I> {
90
102
  *
91
103
  * A parser compiled with the `Compiler` module runs compiled, with the
92
104
  * build that handles partial input (see `Parser.run`); a value that fails
93
- * or needs more input is parsed again interpreted, so errors and
94
- * `Incomplete` are the interpreter's.
105
+ * is parsed again interpreted, so errors are the interpreter's. A value
106
+ * cut short by its chunk is not: compiled code reports `Incomplete` where
107
+ * the interpreter does.
95
108
  *
96
109
  * @since 0.1.0
97
110
  * @category combinators
@@ -126,9 +139,12 @@ export const parseMany: {
126
139
  let offset = 0
127
140
  while (offset < input.length) {
128
141
  state.reset(offset, { partial, context })
129
- const a = Run.runOn(parser, step, state)
142
+ // A compiled `Incomplete` is the interpreter's: only an error needs the interpreted run
143
+ const a = Run.runOn(parser, step, state, false)
130
144
  if (a === Fail) {
131
145
  if (partial && state.mode === "Incomplete") break
146
+ state.reset(offset, { partial, context })
147
+ Run.runOn(parser, step, state)
132
148
  return Effect.fail(ParseError.fromFailure<E>(input, state.reportedFailure(), state.mode, state.encoding))
133
149
  }
134
150
  if (state.pos === offset) return Effect.fail(noProgress(input, offset))
@@ -211,15 +227,19 @@ class Incremental<A, E, I extends Chunkable> {
211
227
  }
212
228
 
213
229
  private input(): I {
214
- switch (this.kind) {
215
- case 1:
216
- return this.bytes.subarray(0, this.length) as I
217
- case 2:
218
- // Items are only appended between attempts, after each state has read its `end`
219
- return this.items as unknown as I
220
- default:
221
- return this.text as I
230
+ if (this.kind === 1) {
231
+ // SAFETY: `kind` 1 is set by the first chunk being a `Uint8Array`, so `I` is `Uint8Array`
232
+ return this.bytes.subarray(0, this.length) as I
233
+ }
234
+ if (this.kind === 2) {
235
+ // Items are only appended between attempts, after each state has read its `end`
236
+ const items: Chunkable = this.items
237
+ // SAFETY: `kind` 2 is set by the first chunk being an array, so `I` is an array of the items' type
238
+ return items as I
222
239
  }
240
+ // SAFETY: `kind` 0 means the first chunk was a string, so `I` is `string`; while `kind` is unset (no
241
+ // chunk arrived) `I` is unknown and the empty string stands for the empty input
242
+ return this.text as I
223
243
  }
224
244
 
225
245
  push(chunk: I): void {
@@ -245,8 +265,9 @@ class Incremental<A, E, I extends Chunkable> {
245
265
  * The step attempts run: the parser's compiled code if it was compiled
246
266
  * (the resumable build, unless `resumable` is false), else the interpreter.
247
267
  */
248
- private step(state: State<I>, resumable = true): unknown {
249
- return Stack.run(Registry.stepFor(this.parser, state, resumable) ?? this.parser.step, state)
268
+ private step(state: State<I>, resumable = true): A | Fail {
269
+ // SAFETY: a compiled step (typed `any` by the compilers) returns what the parser's own step returns
270
+ return Stack.run(Registry.stepFor(this.parser, state, resumable) ?? this.parser.step, state) as A | Fail
250
271
  }
251
272
 
252
273
  private attempt(): void {
@@ -255,7 +276,7 @@ class Incremental<A, E, I extends Chunkable> {
255
276
  const a = this.step(state)
256
277
  this.attempted = this.length
257
278
  if (a !== Fail) {
258
- this.settled = { value: a as A, pos: state.pos }
279
+ this.settled = { value: a, pos: state.pos }
259
280
  } else if (state.mode === "Incomplete") {
260
281
  this.restart = state.failure.offset
261
282
  } else {
@@ -274,7 +295,7 @@ class Incremental<A, E, I extends Chunkable> {
274
295
  const resumable = this.attempted > 0
275
296
  if (resumable) state.resume = this.resume
276
297
  const a = this.step(state, resumable)
277
- if (a !== Fail && state.pos === state.end) return Effect.succeed(a as A)
298
+ if (a !== Fail && state.pos === state.end) return Effect.succeed(a)
278
299
  }
279
300
  // The error comes from an ordinary parse of the whole input, so it is the
280
301
  // same as if the input had been parsed in one go
@@ -329,7 +350,7 @@ export const parseAll: {
329
350
  Effect.flatMap(Effect.context<R>(), (context) =>
330
351
  Effect.suspend(() => {
331
352
  const empty = options?.empty === undefined ? undefined : fromSource<I>(options.empty)
332
- const incremental = new Incremental(parser, context as Context.Context<never>, empty)
353
+ const incremental = new Incremental(parser, context, empty)
333
354
  return Effect.flatMap(
334
355
  Stream.runForEach(self, (chunk) => Effect.sync(() => incremental.push(fromSource<I>(chunk)))),
335
356
  () => Effect.suspend(() => incremental.finish())
package/src/Syntax.ts CHANGED
@@ -66,11 +66,15 @@ import { described, isParser } from "./Parser.js"
66
66
  // Grammar annotations
67
67
  // -----------------------------------------------------------------------------
68
68
 
69
- const syntaxNode = (self: Parser.Any, kind: string, named: boolean): Parser.Any => {
69
+ const syntaxNode = <A, E, R, I extends Input>(
70
+ self: Parser<A, E, R, I>,
71
+ kind: string,
72
+ named: boolean
73
+ ): Parser<A, E, R, I> => {
70
74
  const step = self.step
71
75
  // Identifies the nodes this parser builds, so a reparse can reuse them
72
76
  const key = new Key()
73
- return described<unknown, unknown, unknown, Input>(
77
+ return described<A, E, R, I>(
74
78
  (state) => {
75
79
  const builder = state.syntax
76
80
  return builder === undefined ? step(state) : builder.node(state, step, key, kind, named)
@@ -101,10 +105,8 @@ const syntaxNode = (self: Parser.Any, kind: string, named: boolean): Parser.Any
101
105
  export const node: {
102
106
  (kind: string): <A, E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<A, E, R, I>
103
107
  <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I>
104
- } = /*#__PURE__*/ dual(
105
- 2,
106
- <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I> =>
107
- syntaxNode(self, kind, true) as Parser<A, E, R, I>
108
+ } = /*#__PURE__*/ dual(2, <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I> =>
109
+ syntaxNode(self, kind, true)
108
110
  )
109
111
 
110
112
  /**
@@ -130,7 +132,7 @@ export const token: {
130
132
  } = /*#__PURE__*/ dual(
131
133
  (args) => isParser(args[0]),
132
134
  <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind?: string): Parser<A, E, R, I> =>
133
- syntaxNode(self, kind ?? defaultTokenKind(self), false) as Parser<A, E, R, I>
135
+ syntaxNode(self, kind ?? defaultTokenKind(self), false)
134
136
  )
135
137
 
136
138
  const defaultTokenKind = (self: Parser.Any): string => {
@@ -168,7 +170,7 @@ export const field: {
168
170
  return described<A, E, R, I>(
169
171
  (state) => {
170
172
  const builder = state.syntax
171
- return (builder === undefined ? step(state) : builder.field(state, step, name)) as A | typeof Fail
173
+ return builder === undefined ? step(state) : builder.field(state, step, name)
172
174
  },
173
175
  self.name,
174
176
  { _tag: "Syntax", self, kind: undefined, named: false }
@@ -346,6 +348,18 @@ export class Tree<out A, out E = never, I extends Input = string> {
346
348
  }
347
349
  }
348
350
 
351
+ /**
352
+ * What `JSON.stringify` writes for a {@link SyntaxNode}: its kind and span.
353
+ *
354
+ * @since 0.5.0
355
+ * @category models
356
+ */
357
+ export interface SyntaxNodeJson {
358
+ readonly kind: string
359
+ readonly start: number
360
+ readonly end: number
361
+ }
362
+
349
363
  /**
350
364
  * A node of a syntax tree: its kind, span and value, and its place in the
351
365
  * tree. Nodes are views built on demand; two views of the same node are
@@ -403,10 +417,11 @@ export class SyntaxNode<I extends Input = string> {
403
417
 
404
418
  /** The input the node spans. */
405
419
  get text(): Slice<I> {
406
- const input = this.tree.input as any
407
- return input instanceof Uint8Array
408
- ? (input.subarray(this.start, this.end) as any)
409
- : input.slice(this.start, this.end)
420
+ const input: Input = this.tree.input
421
+ // SAFETY: a `subarray` of a `Uint8Array`, or a `slice` of a string or array, has the input's own kind, `Slice<I>`
422
+ return (
423
+ input instanceof Uint8Array ? input.subarray(this.start, this.end) : input.slice(this.start, this.end)
424
+ ) as Slice<I>
410
425
  }
411
426
 
412
427
  /** Whether this is an `ERROR` node. */
@@ -592,7 +607,7 @@ export class SyntaxNode<I extends Input = string> {
592
607
  return out
593
608
  }
594
609
 
595
- toJSON(): unknown {
610
+ toJSON(): SyntaxNodeJson {
596
611
  return { kind: this.kind, start: this.start, end: this.end }
597
612
  }
598
613
 
@@ -723,6 +738,8 @@ const run = <A, E, I extends Input>(config: Config, input: I, previous: Previous
723
738
  state.backtrack("Eof", expectedEof)
724
739
  fatal = { failure: state.reportedFailure(), mode: "Backtrack" }
725
740
  } else {
741
+ // SAFETY: `config.inner` is the `Parser<A, ...>` given to `parse` or the parser its root `node` wraps, which returns
742
+ // the same `A` (a `reparse` reuses the tree's config)
726
743
  value = Option.some(a as A)
727
744
  }
728
745
  const green = builder.finish(config.kind, Option.getOrUndefined(value), fatal, state.pos)
@@ -730,19 +747,12 @@ const run = <A, E, I extends Input>(config: Config, input: I, previous: Previous
730
747
  }
731
748
 
732
749
  /**
733
- * Parses an input into a syntax {@link Tree}. The grammar's output is the
734
- * tree's `value`, and its {@link node}s are the tree's nodes. When the
735
- * grammar is itself a {@link node}, that node is the root; otherwise the
736
- * root is a node of kind `options.kind` (by default `"document"`).
737
- *
738
- * The parse never fails: recovered failures (see `Parser.resumeAfter`), a
739
- * fatal failure and unparsed input at the end all become `ERROR` nodes, and
740
- * are listed in `tree.errors`. The parse is interpreted, never compiled.
750
+ * The overloads of {@link parse}.
741
751
  *
742
- * @since 0.2.0
752
+ * @since 0.5.0
743
753
  * @category parsing
744
754
  */
745
- export const parse: {
755
+ export interface ParseFn {
746
756
  <A, E, I extends Input>(
747
757
  grammar: Parser<A, E, never, I>,
748
758
  input: NoInfer<Source<I>>,
@@ -753,7 +763,22 @@ export const parse: {
753
763
  input: NoInfer<Source<I>>,
754
764
  options: ParseOptionsWithContext<R>
755
765
  ): Tree<A, E, I>
756
- } = <A, E, R, I extends Input>(
766
+ }
767
+
768
+ /**
769
+ * Parses an input into a syntax {@link Tree}. The grammar's output is the
770
+ * tree's `value`, and its {@link node}s are the tree's nodes. When the
771
+ * grammar is itself a {@link node}, that node is the root; otherwise the
772
+ * root is a node of kind `options.kind` (by default `"document"`).
773
+ *
774
+ * The parse never fails: recovered failures (see `Parser.resumeAfter`), a
775
+ * fatal failure and unparsed input at the end all become `ERROR` nodes, and
776
+ * are listed in `tree.errors`. The parse is interpreted, never compiled.
777
+ *
778
+ * @since 0.2.0
779
+ * @category parsing
780
+ */
781
+ export const parse: ParseFn = <A, E, R, I extends Input>(
757
782
  grammar: Parser<A, E, R, I>,
758
783
  input: I | Source<I>,
759
784
  options?: ParseOptions & { readonly context?: Context.Context<R> }
@@ -762,11 +787,12 @@ export const parse: {
762
787
  const isRoot = Predicate.isTagged(n, "Syntax") && n.kind !== undefined && n.named
763
788
  const config: Config = {
764
789
  kind: isRoot ? n.kind : (options?.kind ?? "document"),
790
+ // SAFETY: a `Syntax` node's `self` is the parser that `node` wrapped, a parser built by this library
765
791
  inner: isRoot ? (n.self as Parser.Any) : grammar,
766
792
  lookahead: options?.lookahead ?? 0,
767
- context: options?.context as Context.Context<never> | undefined
793
+ context: options?.context
768
794
  }
769
- return run(config, fromSource(input as Input) as I, undefined)
795
+ return run(config, fromSource<I>(input), undefined)
770
796
  }
771
797
 
772
798
  /**
@@ -803,13 +829,8 @@ export const reparse = <A, E, I extends Input>(
803
829
  input: NoInfer<Source<I>>,
804
830
  edits?: Edit | ReadonlyArray<Edit>
805
831
  ): Tree<A, E, I> => {
806
- const next = fromSource(input as Input) as I
807
- const list =
808
- edits === undefined
809
- ? diffList(tree.input, next)
810
- : "start" in edits
811
- ? [edits as Edit]
812
- : (edits as ReadonlyArray<Edit>)
832
+ const next = fromSource<I>(input)
833
+ const list = edits === undefined ? diffList(tree.input, next) : "start" in edits ? [edits] : edits
813
834
  const previous: Previous = {
814
835
  root: tree.green,
815
836
  oldLength: tree.input.length,
@@ -854,9 +875,12 @@ export const replace = <A, E>(
854
875
  * @category parsing
855
876
  */
856
877
  export const diff = <I extends Input>(before: I, after: I): Edit | undefined => {
857
- const a = before as any
858
- const b = after as any
859
- const at = Predicate.isString(a) ? (s: string, i: number) => s.charCodeAt(i) : (s: any, i: number) => s[i]
878
+ const a: Input = before
879
+ const b: Input = after
880
+ // SAFETY: the string reader is chosen only when `a` is a string, and `b` has the same type `I`
881
+ const at = Predicate.isString(a)
882
+ ? (s: Input, i: number) => (s as string).charCodeAt(i)
883
+ : (s: Input, i: number) => s[i]
860
884
  const max = Math.min(a.length, b.length)
861
885
  let prefix = 0
862
886
  while (prefix < max && at(a, prefix) === at(b, prefix)) prefix++
package/src/Text.ts CHANGED
@@ -39,7 +39,7 @@
39
39
  import * as Predicate from "effect/Predicate"
40
40
  import type { Encoding } from "./Encoding.js"
41
41
  import * as Encodings from "./Encoding.js"
42
- import type { Input, State } from "./Input.js"
42
+ import type { State } from "./Input.js"
43
43
  import { Fail } from "./Input.js"
44
44
  import { escapedImpl, overflow, takeEscapedImpl, toFloat } from "./Ascii.js"
45
45
  import { codePointPredicate, compile, indexOf, matchLiteral } from "./internal/tokens.js"
@@ -392,7 +392,7 @@ const expectedIdentifier = ["identifier"]
392
392
  * @category constructors
393
393
  */
394
394
  export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): TextParsers<I> => {
395
- const enc = encoding as Encoding<any>
395
+ const enc = encoding
396
396
  const fast = internal.fastLimit(enc)
397
397
 
398
398
  // Each parser records its encoding on byte input (`state.encoding`), so
@@ -410,7 +410,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
410
410
  ): string | Fail => {
411
411
  const count = internal.validate(enc, state, start, end)
412
412
  if (count < 0) return backtrack(state, kind, expected, -1 - count)
413
- return enc.text(state.input, start, end)
413
+ return internal.textIn(enc, state, start, end)
414
414
  }
415
415
 
416
416
  const takeWhileText = (
@@ -438,7 +438,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
438
438
  // More matching characters would have been accepted where we stopped
439
439
  if (hint && count < max) state.setHint(offset, expected)
440
440
  state.pos = offset
441
- return enc.text(state.input, start, offset)
441
+ return internal.textIn(enc, state, start, offset)
442
442
  },
443
443
  name,
444
444
  { _tag: "TakeWhile", set, negate, min, max, encoding: enc }
@@ -471,6 +471,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
471
471
  if (state.inputKind === 1) state.encoding = enc
472
472
  if (pos >= state.end) return state.eoi("Literal", expected, 1)
473
473
  const input = state.input
474
+ // SAFETY: a `Text` parser runs on strings or `Uint8Array`s, and `inputKind` 0 is set only for a string
474
475
  if ((state.inputKind === 0 ? (input as string).charCodeAt(pos) : (input as Uint8Array)[pos]) !== unit) {
475
476
  return backtrack(state, "Literal", expected)
476
477
  }
@@ -518,7 +519,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
518
519
  pos += internal.width
519
520
  }
520
521
  state.pos = pos
521
- return enc.text(state.input, start, pos)
522
+ return internal.textIn(enc, state, start, pos)
522
523
  },
523
524
  expected[0],
524
525
  { _tag: "Literal", literal: lit, caseless: true, encoding: enc }
@@ -559,7 +560,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
559
560
  pos += internal.width
560
561
  }
561
562
  state.pos = pos
562
- return enc.text(state.input, start, pos)
563
+ return internal.textIn(enc, state, start, pos)
563
564
  },
564
565
  "take",
565
566
  { _tag: "Take", count, encoding: enc }
@@ -575,9 +576,9 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
575
576
  (state: State<any>) => {
576
577
  if (state.inputKind === 1) state.encoding = enc
577
578
  const start = state.pos
578
- let idx = indexOf(state, needle as Input, start)
579
+ let idx = indexOf(state, needle, start)
579
580
  // Multi-unit encodings: only accept matches on a character boundary
580
- while (unit > 1 && idx >= 0 && (idx - start) % unit !== 0) idx = indexOf(state, needle as Input, idx + 1)
581
+ while (unit > 1 && idx >= 0 && (idx - start) % unit !== 0) idx = indexOf(state, needle, idx + 1)
581
582
  if (idx < 0) {
582
583
  if (state.partial) return state.incomplete(0, expected)
583
584
  return backtrack(state, "TakeUntil", expected, state.end)
@@ -590,7 +591,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
590
591
  if (count < min) return backtrack(state, "TakeUntil", expected, idx)
591
592
  if (count > max) return backtrack(state, "TakeUntil", expected)
592
593
  state.pos = idx
593
- return enc.text(state.input, start, idx)
594
+ return internal.textIn(enc, state, start, idx)
594
595
  },
595
596
  "takeUntil",
596
597
  { _tag: "TakeUntil", literal: lit, min, max, encoding: enc }
@@ -607,7 +608,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
607
608
  if (internal.scanStop === internal.Truncated) return state.incomplete(1, expectedAny)
608
609
  if (internal.scanStop === internal.Invalid) return backtrack(state, "Token", expectedAny, offset)
609
610
  state.pos = offset
610
- return enc.text(state.input, start, offset)
611
+ return internal.textIn(enc, state, start, offset)
611
612
  },
612
613
  "rest",
613
614
  { _tag: "Rest", encoding: enc }
@@ -628,7 +629,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
628
629
  }
629
630
 
630
631
  const cls = (test: CharPredicate, min: 0 | 1, description: string, kind: ErrorKind, hint = true) =>
631
- takeWhileText(test as any, { min }, false, `${kind.toLowerCase()}${min}`, [description], kind, hint)
632
+ takeWhileText(test, { min }, false, `${kind.toLowerCase()}${min}`, [description], kind, hint)
632
633
 
633
634
  const identifierStart = (cp: number) => cp === 95 || isIdStart(cp)
634
635
  const identifier: Parser<string, never, never, I> = P.described<string, never, never, I>(
@@ -638,13 +639,13 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
638
639
  const cp = internal.next(enc, state)
639
640
  if (cp < 0) return internal.failAt(state, cp, "TakeWhile", expectedIdentifier)
640
641
  if (!identifierStart(cp)) return state.backtrack("TakeWhile", expectedIdentifier)
641
- internal.scan(enc, state, isIdContinue as any, false, Infinity, start + internal.width, fast)
642
+ internal.scan(enc, state, isIdContinue, false, Infinity, start + internal.width, fast)
642
643
  const stop = internal.scanStop
643
644
  if (state.partial && (stop === internal.End || stop === internal.Truncated)) {
644
645
  return state.incomplete(1, expectedIdentifier)
645
646
  }
646
647
  state.pos = internal.scanOffset
647
- return enc.text(state.input, start, state.pos)
648
+ return internal.textIn(enc, state, start, state.pos)
648
649
  },
649
650
  "identifier",
650
651
  { _tag: "Identifier", encoding: enc }
@@ -691,7 +692,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
691
692
  if (after !== 10) return backtrack(state, "LineEnding", expectedLineEnding, offset)
692
693
  }
693
694
  state.pos = offset
694
- return enc.text(state.input, start, offset)
695
+ return internal.textIn(enc, state, start, offset)
695
696
  },
696
697
  "tillLineEnding",
697
698
  { _tag: "TillLineEnding", encoding: enc }
@@ -783,7 +784,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
783
784
  normal,
784
785
  matchControl(control),
785
786
  escapable,
786
- (state, start) => enc.text(state.input, start, state.pos),
787
+ (state, start) => internal.textIn(enc, state, start, state.pos),
787
788
  internal.firstCodePoint(control),
788
789
  enc
789
790
  ),