@thomasfosterau/effect-parse 0.1.0 → 0.3.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 (161) hide show
  1. package/README.md +291 -231
  2. package/dist/Ascii.d.ts +32 -53
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +576 -571
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +6 -29
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +129 -116
  9. package/dist/Binary.js.map +1 -1
  10. package/dist/Bits.d.ts +4 -2
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +121 -123
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +39 -0
  15. package/dist/Compiler.d.ts.map +1 -0
  16. package/dist/Compiler.js +48 -0
  17. package/dist/Compiler.js.map +1 -0
  18. package/dist/Encoding.d.ts +29 -46
  19. package/dist/Encoding.d.ts.map +1 -1
  20. package/dist/Encoding.js +76 -75
  21. package/dist/Encoding.js.map +1 -1
  22. package/dist/Expression.d.ts +15 -49
  23. package/dist/Expression.d.ts.map +1 -1
  24. package/dist/Expression.js +151 -132
  25. package/dist/Expression.js.map +1 -1
  26. package/dist/Input.d.ts +251 -177
  27. package/dist/Input.d.ts.map +1 -1
  28. package/dist/Input.js +409 -299
  29. package/dist/Input.js.map +1 -1
  30. package/dist/Nom.d.ts +45 -85
  31. package/dist/Nom.d.ts.map +1 -1
  32. package/dist/Nom.js +423 -453
  33. package/dist/Nom.js.map +1 -1
  34. package/dist/ParseError.d.ts +54 -49
  35. package/dist/ParseError.d.ts.map +1 -1
  36. package/dist/ParseError.js +232 -275
  37. package/dist/ParseError.js.map +1 -1
  38. package/dist/Parser.d.ts +345 -313
  39. package/dist/Parser.d.ts.map +1 -1
  40. package/dist/Parser.js +2034 -1802
  41. package/dist/Parser.js.map +1 -1
  42. package/dist/ParserSchema.d.ts +206 -7
  43. package/dist/ParserSchema.d.ts.map +1 -1
  44. package/dist/ParserSchema.js +678 -49
  45. package/dist/ParserSchema.js.map +1 -1
  46. package/dist/ParserStream.d.ts +26 -20
  47. package/dist/ParserStream.d.ts.map +1 -1
  48. package/dist/ParserStream.js +267 -255
  49. package/dist/ParserStream.js.map +1 -1
  50. package/dist/Syntax.d.ts +401 -0
  51. package/dist/Syntax.d.ts.map +1 -0
  52. package/dist/Syntax.js +717 -0
  53. package/dist/Syntax.js.map +1 -0
  54. package/dist/Text.d.ts +98 -134
  55. package/dist/Text.d.ts.map +1 -1
  56. package/dist/Text.js +534 -496
  57. package/dist/Text.js.map +1 -1
  58. package/dist/Token.d.ts +60 -42
  59. package/dist/Token.d.ts.map +1 -1
  60. package/dist/Token.js +331 -265
  61. package/dist/Token.js.map +1 -1
  62. package/dist/index.d.ts +16 -17
  63. package/dist/index.js +16 -17
  64. package/dist/internal/compiler/closure.d.ts +14 -16
  65. package/dist/internal/compiler/closure.d.ts.map +1 -1
  66. package/dist/internal/compiler/closure.js +1458 -1637
  67. package/dist/internal/compiler/closure.js.map +1 -1
  68. package/dist/internal/compiler/entries.d.ts +11 -11
  69. package/dist/internal/compiler/entries.d.ts.map +1 -1
  70. package/dist/internal/compiler/entries.js +36 -26
  71. package/dist/internal/compiler/entries.js.map +1 -1
  72. package/dist/internal/compiler/first.d.ts +19 -19
  73. package/dist/internal/compiler/first.d.ts.map +1 -1
  74. package/dist/internal/compiler/first.js +251 -227
  75. package/dist/internal/compiler/first.js.map +1 -1
  76. package/dist/internal/compiler/jit.d.ts +6 -17
  77. package/dist/internal/compiler/jit.d.ts.map +1 -1
  78. package/dist/internal/compiler/jit.js +860 -988
  79. package/dist/internal/compiler/jit.js.map +1 -1
  80. package/dist/internal/compiler/registry.d.ts +20 -21
  81. package/dist/internal/compiler/registry.d.ts.map +1 -1
  82. package/dist/internal/compiler/registry.js +21 -41
  83. package/dist/internal/compiler/registry.js.map +1 -1
  84. package/dist/internal/compiler/sets.d.ts +9 -13
  85. package/dist/internal/compiler/sets.d.ts.map +1 -1
  86. package/dist/internal/compiler/sets.js +76 -73
  87. package/dist/internal/compiler/sets.js.map +1 -1
  88. package/dist/internal/entries.d.ts +3 -3
  89. package/dist/internal/entries.d.ts.map +1 -1
  90. package/dist/internal/entries.js +38 -30
  91. package/dist/internal/entries.js.map +1 -1
  92. package/dist/internal/node.d.ts +177 -173
  93. package/dist/internal/node.d.ts.map +1 -1
  94. package/dist/internal/node.js +77 -66
  95. package/dist/internal/node.js.map +1 -1
  96. package/dist/internal/numbers.d.ts +7 -8
  97. package/dist/internal/numbers.d.ts.map +1 -1
  98. package/dist/internal/numbers.js +246 -276
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/resume.d.ts +6 -24
  101. package/dist/internal/resume.d.ts.map +1 -1
  102. package/dist/internal/resume.js +30 -29
  103. package/dist/internal/resume.js.map +1 -1
  104. package/dist/internal/run.d.ts +15 -0
  105. package/dist/internal/run.d.ts.map +1 -0
  106. package/dist/internal/run.js +36 -0
  107. package/dist/internal/run.js.map +1 -0
  108. package/dist/internal/stack.d.ts +16 -0
  109. package/dist/internal/stack.d.ts.map +1 -0
  110. package/dist/internal/stack.js +36 -0
  111. package/dist/internal/stack.js.map +1 -0
  112. package/dist/internal/syntax.d.ts +147 -0
  113. package/dist/internal/syntax.d.ts.map +1 -0
  114. package/dist/internal/syntax.js +309 -0
  115. package/dist/internal/syntax.js.map +1 -0
  116. package/dist/internal/text.d.ts +10 -2
  117. package/dist/internal/text.d.ts.map +1 -1
  118. package/dist/internal/text.js +295 -345
  119. package/dist/internal/text.js.map +1 -1
  120. package/dist/internal/ties.d.ts +30 -0
  121. package/dist/internal/ties.d.ts.map +1 -0
  122. package/dist/internal/ties.js +67 -0
  123. package/dist/internal/ties.js.map +1 -0
  124. package/dist/internal/tokens.d.ts +25 -30
  125. package/dist/internal/tokens.d.ts.map +1 -1
  126. package/dist/internal/tokens.js +369 -363
  127. package/dist/internal/tokens.js.map +1 -1
  128. package/package.json +42 -28
  129. package/src/Ascii.ts +278 -187
  130. package/src/Binary.ts +150 -44
  131. package/src/Bits.ts +6 -3
  132. package/src/Compiler.ts +98 -0
  133. package/src/Expression.ts +24 -16
  134. package/src/Input.ts +210 -35
  135. package/src/Nom.ts +4 -3
  136. package/src/ParseError.ts +49 -52
  137. package/src/Parser.ts +1199 -799
  138. package/src/ParserSchema.ts +982 -23
  139. package/src/ParserStream.ts +104 -80
  140. package/src/Syntax.ts +900 -0
  141. package/src/Text.ts +272 -187
  142. package/src/Token.ts +180 -87
  143. package/src/index.ts +2 -0
  144. package/src/internal/compiler/closure.ts +121 -88
  145. package/src/internal/compiler/entries.ts +21 -8
  146. package/src/internal/compiler/first.ts +17 -15
  147. package/src/internal/compiler/jit.ts +360 -157
  148. package/src/internal/compiler/registry.ts +12 -33
  149. package/src/internal/compiler/sets.ts +17 -11
  150. package/src/internal/entries.ts +4 -2
  151. package/src/internal/node.ts +51 -37
  152. package/src/internal/numbers.ts +22 -20
  153. package/src/internal/resume.ts +8 -4
  154. package/src/internal/run.ts +41 -0
  155. package/src/internal/stack.ts +41 -0
  156. package/src/internal/syntax.ts +426 -0
  157. package/src/internal/text.ts +32 -25
  158. package/src/internal/ties.ts +83 -0
  159. package/src/internal/tokens.ts +96 -51
  160. package/dist/index.d.ts.map +0 -1
  161. package/dist/index.js.map +0 -1
@@ -0,0 +1,426 @@
1
+ /**
2
+ * The tree builder behind the `Syntax` module (see `docs/DESIGN.md`,
3
+ * "Syntax trees").
4
+ *
5
+ * Trees are stored as *green* nodes, as in Roslyn and rust-analyzer: a green
6
+ * node knows its kind, its width and its children's offsets relative to its
7
+ * own start, but not where it is in the input or who its parent is. A subtree
8
+ * that an edit doesn't touch can therefore be shared, unchanged, by the tree
9
+ * before the edit and the tree after it. `Syntax.SyntaxNode` is the *red*
10
+ * view: a green node, its absolute start and its parent, built lazily while
11
+ * navigating.
12
+ *
13
+ * While parsing, completed nodes wait on a stack until the node around them
14
+ * completes and adopts them. Nodes built by an attempt that is later
15
+ * abandoned must not end up in the tree. Every combinator that abandons an
16
+ * attempt moves the cursor back to (or before) where the attempt started,
17
+ * and every node has a positive width, so the nodes of an abandoned attempt
18
+ * are exactly those that end after the position the cursor moves back to:
19
+ * the `pos` setter of {@link SyntaxState} drops them. This also covers
20
+ * lookahead (`peek`, `not`) and custom parsers written with `Parser.make`.
21
+ *
22
+ * For incremental parsing, every node records how far its parse looked: the
23
+ * furthest position the cursor reached and the furthest failure recorded
24
+ * while it was being parsed (its *lookahead*). A node from the previous tree
25
+ * is reused at a position when it was built by the same `Syntax.node`, and
26
+ * the input it looked at, plus a margin for the tokens that read a little
27
+ * past where they stop, is unchanged.
28
+ */
29
+ import * as Predicate from "effect/Predicate"
30
+ import type * as Context from "effect/Context"
31
+ import type { Encoding } from "../Encoding.js"
32
+ import type { ErrMode, Input, Source } from "../Input.js"
33
+ import { Fail, Failure, State } from "../Input.js"
34
+
35
+ /** What a failure inside an `ERROR` node was. Offsets are relative to the node. */
36
+ export interface ErrorInfo {
37
+ readonly failure: Failure
38
+ readonly mode: ErrMode
39
+ }
40
+
41
+ /** A position-independent syntax tree node. */
42
+ export class Green {
43
+ constructor(
44
+ readonly kind: string,
45
+ readonly named: boolean,
46
+ /** The `Syntax.node` that built it; `undefined` for roots and errors, which are never reused alone. */
47
+ readonly key: object | undefined,
48
+ readonly width: number,
49
+ /** How far its parse looked, relative to its start (at least `width`). */
50
+ readonly lookahead: number,
51
+ readonly children: ReadonlyArray<Green>,
52
+ /** The children's starts, relative to this node's start. */
53
+ readonly offsets: ReadonlyArray<number>,
54
+ /** The children's field names, when any child has one. */
55
+ readonly fields: ReadonlyArray<string | undefined> | undefined,
56
+ readonly value: unknown,
57
+ /** Set on `ERROR` nodes. */
58
+ readonly error: ErrorInfo | undefined,
59
+ /** Whether this node or a descendant is an `ERROR` node. */
60
+ readonly hasError: boolean,
61
+ /** What its parse left behind for error reporting, replayed when it is reused. */
62
+ readonly diagnostics: Diagnostics | undefined
63
+ ) {}
64
+ }
65
+
66
+ /**
67
+ * The error-reporting state a node's parse leaves behind (see
68
+ * `State.deepest` and `State.setHint`). A reused node replays it, so that
69
+ * a reparse reports the errors a fresh parse does.
70
+ */
71
+ export interface Diagnostics {
72
+ /** The furthest failure, at its offset in the parse that built the node... */
73
+ readonly deepest: Failure | undefined
74
+ /** ...which started the node at `base`. */
75
+ readonly base: number
76
+ /** Relative to the node's start. */
77
+ readonly hintOffset: number
78
+ readonly hintExpected: ReadonlyArray<string> | undefined
79
+ }
80
+
81
+ /** A completed node waiting for its parent, at an absolute position. */
82
+ class Child {
83
+ constructor(
84
+ readonly green: Green,
85
+ readonly start: number,
86
+ readonly end: number,
87
+ public field: string | undefined
88
+ ) {}
89
+ }
90
+
91
+ export const errorKind = "ERROR"
92
+
93
+ const rebase = (f: Failure, start: number): Failure =>
94
+ new Failure(f.offset - start, f.kind, f.expected, f.context, f.hasCause, f.cause, f.message, f.needed)
95
+
96
+ /** Moves a failure stored relative to a node back to absolute offsets. */
97
+ export const absolute = (f: Failure, start: number): Failure => rebase(f, -start)
98
+
99
+ /**
100
+ * Builds a green node over `[start, end)` from the children waiting on the
101
+ * stack.
102
+ */
103
+ const build = (
104
+ kind: string,
105
+ named: boolean,
106
+ key: object | undefined,
107
+ start: number,
108
+ end: number,
109
+ lookahead: number,
110
+ entries: ReadonlyArray<Child>,
111
+ value: unknown,
112
+ error: ErrorInfo | undefined,
113
+ diagnostics?: Diagnostics
114
+ ): Green => {
115
+ const n = entries.length
116
+ const children = new Array<Green>(n)
117
+ const offsets = new Array<number>(n)
118
+ let fields: Array<string | undefined> | undefined
119
+ let hasError = error !== undefined
120
+ for (let i = 0; i < n; i++) {
121
+ const e = entries[i]
122
+ children[i] = e.green
123
+ offsets[i] = e.start - start
124
+ if (e.green.hasError) hasError = true
125
+ if (e.field !== undefined) (fields ??= new Array(n).fill(undefined))[i] = e.field
126
+ }
127
+ const width = end - start
128
+ const looked = Math.max(lookahead, end) - start
129
+ return new Green(kind, named, key, width, looked, children, offsets, fields, value, error, hasError, diagnostics)
130
+ }
131
+
132
+ /**
133
+ * A stretch of the new input copied unchanged from the previous one:
134
+ * `[newStart, newStart + length)` is `[oldStart, oldStart + length)`.
135
+ */
136
+ export interface Segment {
137
+ readonly newStart: number
138
+ readonly oldStart: number
139
+ readonly length: number
140
+ }
141
+
142
+ /** What a reparse may reuse. */
143
+ export interface Previous {
144
+ readonly root: Green
145
+ readonly oldLength: number
146
+ readonly newLength: number
147
+ readonly segments: ReadonlyArray<Segment>
148
+ readonly input: Input
149
+ /** How far past its lookahead a parser may have read, besides a token at the lookahead. */
150
+ readonly extra: number
151
+ readonly encoding: Encoding<any> | undefined
152
+ }
153
+
154
+ /** The hooks that `Syntax` combinators and `Parser.resumeAfter`/`retryAfter` call. */
155
+ export interface Builder {
156
+ node(state: State<any>, step: (state: State<any>) => unknown, key: object, kind: string, named: boolean): unknown
157
+ field(state: State<any>, step: (state: State<any>) => unknown, name: string): unknown
158
+ /** After error recovery: `[start, pos)` was skipped after `failure`. */
159
+ skipped(start: number, failure: Failure, mode: ErrMode): void
160
+ /**
161
+ * Called by token parsers that read input up to `end` (exclusive) beyond
162
+ * where they leave the cursor or record a failure.
163
+ */
164
+ read(end: number): void
165
+ }
166
+
167
+ /** The parse state of a `Syntax` parse. */
168
+ export class SyntaxState<I extends Input = Input> extends State<I> implements Builder {
169
+ // Declared, not defined: `State`'s constructor sets `pos` through the
170
+ // accessor below before these could be initialized
171
+ declare cursor: number
172
+ /** The furthest position the cursor reached, or failure recorded, since the innermost open node started. */
173
+ declare highWater: number
174
+ /** Completed nodes waiting for their parent, in input order. */
175
+ declare stack: Array<Child>
176
+ declare previous: Previous | undefined
177
+ /** How many nodes were reused from the previous tree. */
178
+ declare reused: number
179
+ /** Where {@link find} found the last node it found. */
180
+ declare lastParent: Green | undefined
181
+ declare lastParentStart: number
182
+
183
+ static {
184
+ Object.defineProperty(this.prototype, "pos", {
185
+ get(this: SyntaxState) {
186
+ return this.cursor
187
+ },
188
+ set(this: SyntaxState, p: number) {
189
+ if (p < this.cursor) {
190
+ // An attempt was abandoned (or a lookahead finished): drop its nodes
191
+ const stack = this.stack
192
+ while (stack.length > 0 && stack[stack.length - 1].end > p) stack.pop()
193
+ }
194
+ if (p > this.highWater) this.highWater = p
195
+ this.cursor = p
196
+ },
197
+ configurable: true,
198
+ enumerable: true
199
+ })
200
+ }
201
+
202
+ constructor(
203
+ source: I | Source<I>,
204
+ options: {
205
+ readonly context?: Context.Context<never> | undefined
206
+ },
207
+ previous: Previous | undefined
208
+ ) {
209
+ super(source, { context: options.context })
210
+ this.highWater = this.cursor
211
+ this.stack = []
212
+ this.previous = previous
213
+ this.reused = 0
214
+ this.lastParent = undefined
215
+ this.lastParentStart = 0
216
+ this.syntax = this
217
+ // Error recovery is always on: like Tree-sitter, a syntax parse always produces a tree
218
+ this.recovered = []
219
+ }
220
+
221
+ override failWith(mode: ErrMode, failure: Failure): Fail {
222
+ if (failure.offset > this.highWater) this.highWater = failure.offset
223
+ return super.failWith(mode, failure)
224
+ }
225
+
226
+ override failTies(head: Failure, tie1: Failure, tie2: Failure | undefined): Fail {
227
+ if (head.offset > this.highWater) this.highWater = head.offset
228
+ return super.failTies(head, tie1, tie2)
229
+ }
230
+
231
+ override setHint(offset: number, expected: ReadonlyArray<string>): void {
232
+ if (offset > this.highWater) this.highWater = offset
233
+ super.setHint(offset, expected)
234
+ }
235
+
236
+ node(state: State<any>, step: (state: State<any>) => unknown, key: object, kind: string, named: boolean): unknown {
237
+ const start = this.cursor
238
+ const stack = this.stack
239
+ if (this.previous !== undefined) {
240
+ const green = this.reusable(this.previous, key, start)
241
+ if (green !== undefined) {
242
+ this.reused++
243
+ const end = start + green.width
244
+ state.pos = end
245
+ const looked = start + green.lookahead
246
+ if (looked > this.highWater) this.highWater = looked
247
+ const d = green.diagnostics
248
+ if (d !== undefined) {
249
+ if (d.deepest !== undefined) this.deepen(start === d.base ? d.deepest : rebase(d.deepest, d.base - start))
250
+ if (d.hintExpected !== undefined) this.setHint(start + d.hintOffset, d.hintExpected)
251
+ }
252
+ stack.push(new Child(green, start, end, undefined))
253
+ return green.value
254
+ }
255
+ }
256
+ const mark = stack.length
257
+ const outer = this.highWater
258
+ const outerDeepest = this.deepest
259
+ const hintOffset = this.hintOffset
260
+ const hintExpected = this.hintExpected
261
+ this.highWater = start
262
+ // Collect the furthest failure of this node alone, then merge it as `failWith` would have
263
+ this.deepest = undefined
264
+ const a = step(state)
265
+ const looked = this.highWater
266
+ if (outer > looked) this.highWater = outer
267
+ const deepest = this.deepest as Failure | undefined
268
+ this.deepest = outerDeepest
269
+ if (deepest !== undefined) this.deepen(deepest)
270
+ if (a === Fail) {
271
+ if (stack.length > mark) stack.length = mark
272
+ return a
273
+ }
274
+ const end = this.cursor
275
+ // Like Tree-sitter's rules, nodes are never empty: an empty match adds nothing to the tree
276
+ if (end === start) return a
277
+ const hinted = this.hintOffset !== hintOffset || this.hintExpected !== hintExpected
278
+ const diagnostics: Diagnostics | undefined =
279
+ deepest === undefined && !hinted
280
+ ? undefined
281
+ : {
282
+ deepest,
283
+ base: start,
284
+ hintOffset: this.hintOffset - start,
285
+ hintExpected: hinted ? this.hintExpected : undefined
286
+ }
287
+ const entries = stack.splice(mark)
288
+ stack.push(
289
+ new Child(build(kind, named, key, start, end, looked, entries, a, undefined, diagnostics), start, end, undefined)
290
+ )
291
+ return a
292
+ }
293
+
294
+ read(end: number): void {
295
+ // `highWater` is the last position looked at, `end` is exclusive
296
+ if (end - 1 > this.highWater) this.highWater = end - 1
297
+ }
298
+
299
+ /** Records `failure` as the furthest failure if it is, as `failWith` does. */
300
+ private deepen(failure: Failure): void {
301
+ if (this.deepest === undefined || failure.offset >= this.deepest.offset) this.deepest = failure
302
+ }
303
+
304
+ field(state: State<any>, step: (state: State<any>) => unknown, name: string): unknown {
305
+ const stack = this.stack
306
+ const mark = stack.length
307
+ const a = step(state)
308
+ if (a !== Fail) for (let i = mark; i < stack.length; i++) stack[i].field = name
309
+ return a
310
+ }
311
+
312
+ skipped(start: number, failure: Failure, mode: ErrMode): void {
313
+ const stack = this.stack
314
+ let i = stack.length
315
+ while (i > 0 && stack[i - 1].start >= start && stack[i - 1].end > start) i--
316
+ const end = this.cursor
317
+ const green = build(errorKind, true, undefined, start, end, this.highWater, stack.splice(i), undefined, {
318
+ failure: rebase(failure, start),
319
+ mode
320
+ })
321
+ stack.push(new Child(green, start, end, undefined))
322
+ }
323
+
324
+ /**
325
+ * Builds the root over the whole input from the nodes left on the stack.
326
+ * When the parse failed, or stopped before the end, the rest of the input
327
+ * becomes an `ERROR` node.
328
+ */
329
+ finish(kind: string, value: unknown, fatal: ErrorInfo | undefined, errorStart: number): Green {
330
+ const entries = this.stack.splice(0)
331
+ const end = this.end
332
+ if (fatal !== undefined) {
333
+ const last = entries.length > 0 ? entries[entries.length - 1].end : 0
334
+ const start = Math.max(Math.min(errorStart, end), last)
335
+ const error = build(errorKind, true, undefined, start, end, end, [], undefined, {
336
+ failure: rebase(fatal.failure, start),
337
+ mode: fatal.mode
338
+ })
339
+ entries.push(new Child(error, start, end, undefined))
340
+ }
341
+ return build(kind, true, undefined, 0, end, Math.max(end, this.highWater), entries, value, undefined)
342
+ }
343
+
344
+ /**
345
+ * The outermost node of the previous tree built by `key` that starts at
346
+ * `offset`. Reused nodes are mostly siblings, found one after the other,
347
+ * so the search starts from the parent of the last one found when
348
+ * `offset` lies inside it: no ancestor of that parent can start there.
349
+ */
350
+ private find(root: Green, offset: number, key: object): Green | undefined {
351
+ let node = root
352
+ let start = 0
353
+ const last = this.lastParent
354
+ if (last !== undefined && offset > this.lastParentStart && offset < this.lastParentStart + last.width) {
355
+ node = last
356
+ start = this.lastParentStart
357
+ }
358
+ while (true) {
359
+ const offsets = node.offsets
360
+ // The last child starting at or before `offset`
361
+ let lo = 0
362
+ let hi = offsets.length - 1
363
+ let found = -1
364
+ while (lo <= hi) {
365
+ const mid = (lo + hi) >> 1
366
+ if (start + offsets[mid] <= offset) {
367
+ found = mid
368
+ lo = mid + 1
369
+ } else {
370
+ hi = mid - 1
371
+ }
372
+ }
373
+ if (found < 0) return undefined
374
+ const child = node.children[found]
375
+ const childStart = start + offsets[found]
376
+ if (offset >= childStart + child.width) return undefined
377
+ if (childStart === offset && child.key === key) {
378
+ this.lastParent = node
379
+ this.lastParentStart = start
380
+ return child
381
+ }
382
+ node = child
383
+ start = childStart
384
+ }
385
+ }
386
+
387
+ /** The node of the previous tree built by `key` at `pos` (in the new input), if it can be reused. */
388
+ private reusable(previous: Previous, key: object, pos: number): Green | undefined {
389
+ const segments = previous.segments
390
+ // The segment containing `pos`
391
+ let lo = 0
392
+ let hi = segments.length - 1
393
+ while (lo <= hi) {
394
+ const mid = (lo + hi) >> 1
395
+ const seg = segments[mid]
396
+ if (pos < seg.newStart) hi = mid - 1
397
+ else if (pos >= seg.newStart + seg.length) lo = mid + 1
398
+ else {
399
+ const old = seg.oldStart + (pos - seg.newStart)
400
+ const green = this.find(previous.root, old, key)
401
+ if (green === undefined) return undefined
402
+ const segEnd = seg.oldStart + seg.length
403
+ // The input it looked at must be unchanged; past the end of both inputs counts as unchanged
404
+ if (old + green.lookahead + tokenWidth(previous.input, old + green.lookahead) + previous.extra <= segEnd) {
405
+ return green
406
+ }
407
+ if (segEnd === previous.oldLength && seg.newStart + seg.length === previous.newLength) return green
408
+ return undefined
409
+ }
410
+ }
411
+ return undefined
412
+ }
413
+ }
414
+
415
+ /**
416
+ * How many units a token parser may read at `offset`, where a parse
417
+ * stopped looking: one code point. Of a string, that is two units at a high
418
+ * surrogate. Of bytes in an unknown encoding, up to four.
419
+ */
420
+ const tokenWidth = (input: Input, offset: number): number => {
421
+ if (Predicate.isString(input)) {
422
+ const c = input.charCodeAt(offset)
423
+ return c >= 0xd800 && c <= 0xdbff ? 2 : 1
424
+ }
425
+ return input instanceof Uint8Array ? 4 : 1
426
+ }
@@ -20,17 +20,24 @@ export const End = -3
20
20
  * Units below this value decode to themselves with width 1, so hot loops can
21
21
  * skip `decode`. Defaults to 0x80 for ASCII compatible encodings.
22
22
  */
23
- const FastLimit: unique symbol = Symbol.for("effect-parse/Encoding/FastLimit")
23
+ const FastLimit: unique symbol = /*#__PURE__*/ Symbol.for("effect-parse/Encoding/FastLimit")
24
24
 
25
25
  /** @internal */
26
26
  export const fastLimit = (enc: Encoding<any>): number =>
27
27
  (enc as any)[FastLimit] ?? (enc.asciiCompatible && enc.unit === 1 ? 0x80 : 0)
28
28
 
29
- const asciiChars: ReadonlyArray<string> = Array.from({ length: 128 }, (_, i) => String.fromCharCode(i))
29
+ const asciiChars: ReadonlyArray<string> = /*#__PURE__*/ Array.from({ length: 128 }, (_, i) => String.fromCharCode(i))
30
+
31
+ /**
32
+ * The code point that starts `s`: for a string known to hold one, such as
33
+ * one element of `Array.from(string)`. On an empty string it is `NaN`, which,
34
+ * like the `undefined` of `codePointAt(0)`, matches no code point.
35
+ */
36
+ export const firstCodePoint = (s: string): number => s.codePointAt(0) ?? Number.NaN
30
37
 
31
38
  /** `String.fromCodePoint`, with the ASCII characters cached. */
32
39
  export const charOf = (cp: number): string =>
33
- cp < 128 ? asciiChars[cp]! : cp < 0x10000 ? String.fromCharCode(cp) : String.fromCodePoint(cp)
40
+ cp < 128 ? asciiChars[cp] : cp < 0x10000 ? String.fromCharCode(cp) : String.fromCodePoint(cp)
34
41
 
35
42
  const decodeString = (s: string, i: number, end: number): number => {
36
43
  const c = s.charCodeAt(i)
@@ -52,40 +59,40 @@ export const string: Encoding<string> = {
52
59
  unit: 1,
53
60
  asciiCompatible: true,
54
61
  decode: decodeString,
55
- width: (cp) => cp > 0xffff ? 2 : 1,
62
+ width: (cp) => (cp > 0xffff ? 2 : 1),
56
63
  encode: (text) => text,
57
64
  text: (s, start, end) => s.slice(start, end),
58
65
  [FastLimit]: 0xd800
59
66
  } as Encoding<string>
60
67
 
61
68
  const decodeUtf8 = (b: Uint8Array, i: number, end: number): number => {
62
- const c = b[i]!
69
+ const c = b[i]
63
70
  if (c < 0x80) return c
64
71
  if (c < 0xc2) return Invalid
65
72
  if (c < 0xe0) {
66
73
  if (i + 1 >= end) return Truncated
67
- const d = b[i + 1]!
74
+ const d = b[i + 1]
68
75
  return (d & 0xc0) === 0x80 ? ((c & 0x1f) << 6) | (d & 0x3f) : Invalid
69
76
  }
70
77
  if (c < 0xf0) {
71
78
  if (i + 1 >= end) return Truncated
72
- const d = b[i + 1]!
79
+ const d = b[i + 1]
73
80
  // Reject overlong encodings (E0 80..9F) and surrogates (ED A0..BF)
74
81
  if (c === 0xe0 ? d < 0xa0 || d > 0xbf : c === 0xed ? d < 0x80 || d > 0x9f : (d & 0xc0) !== 0x80) return Invalid
75
82
  if (i + 2 >= end) return Truncated
76
- const e = b[i + 2]!
83
+ const e = b[i + 2]
77
84
  return (e & 0xc0) === 0x80 ? ((c & 0x0f) << 12) | ((d & 0x3f) << 6) | (e & 0x3f) : Invalid
78
85
  }
79
86
  if (c < 0xf5) {
80
87
  if (i + 1 >= end) return Truncated
81
- const d = b[i + 1]!
88
+ const d = b[i + 1]
82
89
  // Reject overlong encodings (F0 80..8F) and code points above U+10FFFF (F4 90..BF)
83
90
  if (c === 0xf0 ? d < 0x90 || d > 0xbf : c === 0xf4 ? d < 0x80 || d > 0x8f : (d & 0xc0) !== 0x80) return Invalid
84
91
  if (i + 2 >= end) return Truncated
85
- const e = b[i + 2]!
92
+ const e = b[i + 2]
86
93
  if ((e & 0xc0) !== 0x80) return Invalid
87
94
  if (i + 3 >= end) return Truncated
88
- const f = b[i + 3]!
95
+ const f = b[i + 3]
89
96
  return (f & 0xc0) === 0x80 ? ((c & 0x07) << 18) | ((d & 0x3f) << 12) | ((e & 0x3f) << 6) | (f & 0x3f) : Invalid
90
97
  }
91
98
  return Invalid
@@ -95,7 +102,7 @@ const decodeUtf8 = (b: Uint8Array, i: number, end: number): number => {
95
102
  // `TextDecoder` call, which has a high fixed cost.
96
103
  const SHORT = 24
97
104
 
98
- const utf8Decoder = new TextDecoder("utf-8", { ignoreBOM: true })
105
+ const utf8Decoder = /*#__PURE__*/ new TextDecoder("utf-8", { ignoreBOM: true })
99
106
 
100
107
  /**
101
108
  * Decodes `b[start, end)`. Some platforms' `TextDecoder` rejects views of a
@@ -105,7 +112,7 @@ const decodeRange = (decoder: TextDecoder, b: Uint8Array, start: number, end: nu
105
112
  const view = b.subarray(start, end)
106
113
  return decoder.decode(b.buffer instanceof ArrayBuffer ? view : view.slice())
107
114
  }
108
- const utf8Encoder = new TextEncoder()
115
+ const utf8Encoder = /*#__PURE__*/ new TextEncoder()
109
116
 
110
117
  /** UTF-8 bytes. */
111
118
  export const utf8: Encoding<Uint8Array> = {
@@ -113,13 +120,13 @@ export const utf8: Encoding<Uint8Array> = {
113
120
  unit: 1,
114
121
  asciiCompatible: true,
115
122
  decode: decodeUtf8,
116
- width: (cp) => cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4,
123
+ width: (cp) => (cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4),
117
124
  encode: (text) => utf8Encoder.encode(text),
118
125
  text: (b, start, end) => {
119
126
  if (end - start > SHORT) return decodeRange(utf8Decoder, b, start, end)
120
127
  let out = ""
121
128
  for (let i = start; i < end;) {
122
- const c = b[i]!
129
+ const c = b[i]
123
130
  if (c < 0x80) {
124
131
  out += asciiChars[c]
125
132
  i++
@@ -143,14 +150,14 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
143
150
  asciiCompatible: false,
144
151
  decode: (b, i, end) => {
145
152
  if (i + 1 >= end) return Truncated
146
- const c = b[i + lo]! | (b[i + hi]! << 8)
153
+ const c = b[i + lo] | (b[i + hi] << 8)
147
154
  if ((c & 0xf800) !== 0xd800) return c
148
155
  if (c > 0xdbff) return Invalid
149
156
  if (i + 3 >= end) return Truncated
150
- const d = b[i + 2 + lo]! | (b[i + 2 + hi]! << 8)
157
+ const d = b[i + 2 + lo] | (b[i + 2 + hi] << 8)
151
158
  return (d & 0xfc00) === 0xdc00 ? ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000 : Invalid
152
159
  },
153
- width: (cp) => cp > 0xffff ? 4 : 2,
160
+ width: (cp) => (cp > 0xffff ? 4 : 2),
154
161
  encode: (text) => {
155
162
  const out = new Uint8Array(text.length * 2)
156
163
  for (let i = 0; i < text.length; i++) {
@@ -164,7 +171,7 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
164
171
  if (end - start <= SHORT * 2) {
165
172
  // The range is well formed, so surrogate pairs can be copied unit by unit
166
173
  let out = ""
167
- for (let i = start; i < end; i += 2) out += String.fromCharCode(b[i + lo]! | (b[i + hi]! << 8))
174
+ for (let i = start; i < end; i += 2) out += String.fromCharCode(b[i + lo] | (b[i + hi] << 8))
168
175
  return out
169
176
  }
170
177
  decoder ??= new TextDecoder(littleEndian ? "utf-16le" : "utf-16be", { ignoreBOM: true })
@@ -174,17 +181,17 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
174
181
  }
175
182
 
176
183
  /** UTF-16 bytes, little endian. */
177
- export const utf16le: Encoding<Uint8Array> = makeUtf16(true)
184
+ export const utf16le: Encoding<Uint8Array> = /*#__PURE__*/ makeUtf16(true)
178
185
 
179
186
  /** UTF-16 bytes, big endian. */
180
- export const utf16be: Encoding<Uint8Array> = makeUtf16(false)
187
+ export const utf16be: Encoding<Uint8Array> = /*#__PURE__*/ makeUtf16(false)
181
188
 
182
189
  /** ISO-8859-1: every byte is the code point of the same value. */
183
190
  export const latin1: Encoding<Uint8Array> = {
184
191
  name: "ISO-8859-1",
185
192
  unit: 1,
186
193
  asciiCompatible: true,
187
- decode: (b, i) => b[i]!,
194
+ decode: (b, i) => b[i],
188
195
  width: () => 1,
189
196
  encode: (text) => {
190
197
  const out = new Uint8Array(text.length)
@@ -303,7 +310,7 @@ export const scan = (
303
310
  stop = End
304
311
  break
305
312
  }
306
- const c = b[i]!
313
+ const c = b[i]
307
314
  if (c < fast) {
308
315
  if (test(c) === negate) break
309
316
  i++
@@ -345,10 +352,10 @@ export const validate = (enc: Encoding<any>, state: State<any>, start: number, e
345
352
  // Case folding
346
353
  // -----------------------------------------------------------------------------
347
354
 
348
- const foldCache = new Map<number, number>()
355
+ const foldCache = /*#__PURE__*/ new Map<number, number>()
349
356
 
350
357
  const single = (s: string): number => {
351
- const cp = s.codePointAt(0)!
358
+ const cp = firstCodePoint(s)
352
359
  return s.length === (cp > 0xffff ? 2 : 1) ? cp : -1
353
360
  }
354
361
 
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Tied failures, merged only when read.
3
+ *
4
+ * A combinator that tries several parsers at the same position (an
5
+ * alternation, a permutation round, an operator table) reports the merge
6
+ * of their failures, left to right, as `mergeFailures` would. Most of the
7
+ * time one of the later attempts succeeds and the merge is thrown away, so
8
+ * {@link TiedFailures} keeps the last two failures tied with the running
9
+ * one unmerged, and {@link TiedFailures.fail} hands them to the state with
10
+ * `State.failTies`, which merges them on first read (#38, #44).
11
+ *
12
+ * `Parser.alt` does the same fold in locals, since it is the hottest
13
+ * combinator and cannot afford an accumulator per call.
14
+ */
15
+ import type { Failure, Fail, Input, State } from "../Input.js"
16
+ import { mergeExpected, mergeTiedFailures } from "../Input.js"
17
+
18
+ /** The failures of a round of attempts, folded lazily. */
19
+ export class TiedFailures {
20
+ /** The running failure, before the pending ties are merged into it. */
21
+ failure: Failure | undefined = undefined
22
+ /** The oldest pending tie, at `failure`'s offset. */
23
+ tie1: Failure | undefined = undefined
24
+ /** The newest pending tie, at `failure`'s offset. */
25
+ tie2: Failure | undefined = undefined
26
+
27
+ /** Forgets every failure, for a new round. */
28
+ clear(): void {
29
+ this.failure = undefined
30
+ this.tie1 = undefined
31
+ this.tie2 = undefined
32
+ }
33
+
34
+ /**
35
+ * Folds in the failure the latest attempt left on `state`, taking over
36
+ * its own unmerged ties when it is the furthest.
37
+ */
38
+ add<I extends Input>(state: State<I>): void {
39
+ const head = state.failureHead
40
+ const running = this.failure
41
+ if (running === undefined || head.offset > running.offset) {
42
+ this.failure = head
43
+ this.tie1 = state.failureTie1
44
+ this.tie2 = state.failureTie2
45
+ return
46
+ }
47
+ if (head.offset < running.offset) return
48
+ const failure = state.failure
49
+ const tie1 = this.tie1
50
+ if (tie1 === undefined) this.tie1 = failure
51
+ else if (this.tie2 === undefined) this.tie2 = failure
52
+ else {
53
+ // A third tie: fold the oldest pending one in, keeping the order
54
+ this.failure = mergeTiedFailures(running, tie1, mergeExpected(running.expected, tie1.expected))
55
+ this.tie1 = this.tie2
56
+ this.tie2 = failure
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Fails `state` with the folded failure (recoverably), or with its
62
+ * current failure when nothing was folded.
63
+ */
64
+ fail<I extends Input>(state: State<I>): Fail {
65
+ const failure = this.failure
66
+ if (failure === undefined) return state.failWith("Backtrack", state.failure)
67
+ const tie1 = this.tie1
68
+ return tie1 === undefined ? state.failWith("Backtrack", failure) : state.failTies(failure, tie1, this.tie2)
69
+ }
70
+
71
+ /**
72
+ * Records the folded expectations as a hint when the folded failure lies
73
+ * where `state` stopped (an attempt that fails without consuming input).
74
+ */
75
+ hint<I extends Input>(state: State<I>): void {
76
+ const failure = this.failure
77
+ if (failure === undefined || failure.offset !== state.pos) return
78
+ let expected = failure.expected
79
+ if (this.tie1 !== undefined) expected = mergeExpected(expected, this.tie1.expected)
80
+ if (this.tie2 !== undefined) expected = mergeExpected(expected, this.tie2.expected)
81
+ state.setHint(failure.offset, expected)
82
+ }
83
+ }