@thomasfosterau/effect-parse 0.1.0 → 0.2.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 (155) hide show
  1. package/README.md +247 -230
  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 +143 -131
  25. package/dist/Expression.js.map +1 -1
  26. package/dist/Input.d.ts +206 -177
  27. package/dist/Input.d.ts.map +1 -1
  28. package/dist/Input.js +317 -299
  29. package/dist/Input.js.map +1 -1
  30. package/dist/Nom.d.ts +44 -84
  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 +233 -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 +2010 -1802
  41. package/dist/Parser.js.map +1 -1
  42. package/dist/ParserSchema.d.ts +8 -7
  43. package/dist/ParserSchema.d.ts.map +1 -1
  44. package/dist/ParserSchema.js +49 -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 +26 -0
  111. package/dist/internal/stack.js.map +1 -0
  112. package/dist/internal/syntax.d.ts +146 -0
  113. package/dist/internal/syntax.d.ts.map +1 -0
  114. package/dist/internal/syntax.js +305 -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/tokens.d.ts +25 -30
  121. package/dist/internal/tokens.d.ts.map +1 -1
  122. package/dist/internal/tokens.js +369 -363
  123. package/dist/internal/tokens.js.map +1 -1
  124. package/package.json +42 -28
  125. package/src/Ascii.ts +278 -187
  126. package/src/Binary.ts +150 -44
  127. package/src/Bits.ts +6 -3
  128. package/src/Compiler.ts +98 -0
  129. package/src/Expression.ts +10 -5
  130. package/src/Input.ts +106 -28
  131. package/src/Nom.ts +4 -3
  132. package/src/ParseError.ts +48 -51
  133. package/src/Parser.ts +1163 -795
  134. package/src/ParserStream.ts +104 -80
  135. package/src/Syntax.ts +900 -0
  136. package/src/Text.ts +272 -187
  137. package/src/Token.ts +180 -87
  138. package/src/index.ts +2 -0
  139. package/src/internal/compiler/closure.ts +121 -88
  140. package/src/internal/compiler/entries.ts +21 -8
  141. package/src/internal/compiler/first.ts +17 -15
  142. package/src/internal/compiler/jit.ts +360 -157
  143. package/src/internal/compiler/registry.ts +12 -33
  144. package/src/internal/compiler/sets.ts +17 -11
  145. package/src/internal/entries.ts +4 -2
  146. package/src/internal/node.ts +51 -37
  147. package/src/internal/numbers.ts +22 -20
  148. package/src/internal/resume.ts +8 -4
  149. package/src/internal/run.ts +41 -0
  150. package/src/internal/stack.ts +36 -0
  151. package/src/internal/syntax.ts +421 -0
  152. package/src/internal/text.ts +32 -25
  153. package/src/internal/tokens.ts +96 -51
  154. package/dist/index.d.ts.map +0 -1
  155. package/dist/index.js.map +0 -1
@@ -0,0 +1,421 @@
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 setHint(offset: number, expected: ReadonlyArray<string>): void {
227
+ if (offset > this.highWater) this.highWater = offset
228
+ super.setHint(offset, expected)
229
+ }
230
+
231
+ node(state: State<any>, step: (state: State<any>) => unknown, key: object, kind: string, named: boolean): unknown {
232
+ const start = this.cursor
233
+ const stack = this.stack
234
+ if (this.previous !== undefined) {
235
+ const green = this.reusable(this.previous, key, start)
236
+ if (green !== undefined) {
237
+ this.reused++
238
+ const end = start + green.width
239
+ state.pos = end
240
+ const looked = start + green.lookahead
241
+ if (looked > this.highWater) this.highWater = looked
242
+ const d = green.diagnostics
243
+ if (d !== undefined) {
244
+ if (d.deepest !== undefined) this.deepen(start === d.base ? d.deepest : rebase(d.deepest, d.base - start))
245
+ if (d.hintExpected !== undefined) this.setHint(start + d.hintOffset, d.hintExpected)
246
+ }
247
+ stack.push(new Child(green, start, end, undefined))
248
+ return green.value
249
+ }
250
+ }
251
+ const mark = stack.length
252
+ const outer = this.highWater
253
+ const outerDeepest = this.deepest
254
+ const hintOffset = this.hintOffset
255
+ const hintExpected = this.hintExpected
256
+ this.highWater = start
257
+ // Collect the furthest failure of this node alone, then merge it as `failWith` would have
258
+ this.deepest = undefined
259
+ const a = step(state)
260
+ const looked = this.highWater
261
+ if (outer > looked) this.highWater = outer
262
+ const deepest = this.deepest as Failure | undefined
263
+ this.deepest = outerDeepest
264
+ if (deepest !== undefined) this.deepen(deepest)
265
+ if (a === Fail) {
266
+ if (stack.length > mark) stack.length = mark
267
+ return a
268
+ }
269
+ const end = this.cursor
270
+ // Like Tree-sitter's rules, nodes are never empty: an empty match adds nothing to the tree
271
+ if (end === start) return a
272
+ const hinted = this.hintOffset !== hintOffset || this.hintExpected !== hintExpected
273
+ const diagnostics: Diagnostics | undefined =
274
+ deepest === undefined && !hinted
275
+ ? undefined
276
+ : {
277
+ deepest,
278
+ base: start,
279
+ hintOffset: this.hintOffset - start,
280
+ hintExpected: hinted ? this.hintExpected : undefined
281
+ }
282
+ const entries = stack.splice(mark)
283
+ stack.push(
284
+ new Child(build(kind, named, key, start, end, looked, entries, a, undefined, diagnostics), start, end, undefined)
285
+ )
286
+ return a
287
+ }
288
+
289
+ read(end: number): void {
290
+ // `highWater` is the last position looked at, `end` is exclusive
291
+ if (end - 1 > this.highWater) this.highWater = end - 1
292
+ }
293
+
294
+ /** Records `failure` as the furthest failure if it is, as `failWith` does. */
295
+ private deepen(failure: Failure): void {
296
+ if (this.deepest === undefined || failure.offset >= this.deepest.offset) this.deepest = failure
297
+ }
298
+
299
+ field(state: State<any>, step: (state: State<any>) => unknown, name: string): unknown {
300
+ const stack = this.stack
301
+ const mark = stack.length
302
+ const a = step(state)
303
+ if (a !== Fail) for (let i = mark; i < stack.length; i++) stack[i].field = name
304
+ return a
305
+ }
306
+
307
+ skipped(start: number, failure: Failure, mode: ErrMode): void {
308
+ const stack = this.stack
309
+ let i = stack.length
310
+ while (i > 0 && stack[i - 1].start >= start && stack[i - 1].end > start) i--
311
+ const end = this.cursor
312
+ const green = build(errorKind, true, undefined, start, end, this.highWater, stack.splice(i), undefined, {
313
+ failure: rebase(failure, start),
314
+ mode
315
+ })
316
+ stack.push(new Child(green, start, end, undefined))
317
+ }
318
+
319
+ /**
320
+ * Builds the root over the whole input from the nodes left on the stack.
321
+ * When the parse failed, or stopped before the end, the rest of the input
322
+ * becomes an `ERROR` node.
323
+ */
324
+ finish(kind: string, value: unknown, fatal: ErrorInfo | undefined, errorStart: number): Green {
325
+ const entries = this.stack.splice(0)
326
+ const end = this.end
327
+ if (fatal !== undefined) {
328
+ const last = entries.length > 0 ? entries[entries.length - 1].end : 0
329
+ const start = Math.max(Math.min(errorStart, end), last)
330
+ const error = build(errorKind, true, undefined, start, end, end, [], undefined, {
331
+ failure: rebase(fatal.failure, start),
332
+ mode: fatal.mode
333
+ })
334
+ entries.push(new Child(error, start, end, undefined))
335
+ }
336
+ return build(kind, true, undefined, 0, end, Math.max(end, this.highWater), entries, value, undefined)
337
+ }
338
+
339
+ /**
340
+ * The outermost node of the previous tree built by `key` that starts at
341
+ * `offset`. Reused nodes are mostly siblings, found one after the other,
342
+ * so the search starts from the parent of the last one found when
343
+ * `offset` lies inside it: no ancestor of that parent can start there.
344
+ */
345
+ private find(root: Green, offset: number, key: object): Green | undefined {
346
+ let node = root
347
+ let start = 0
348
+ const last = this.lastParent
349
+ if (last !== undefined && offset > this.lastParentStart && offset < this.lastParentStart + last.width) {
350
+ node = last
351
+ start = this.lastParentStart
352
+ }
353
+ while (true) {
354
+ const offsets = node.offsets
355
+ // The last child starting at or before `offset`
356
+ let lo = 0
357
+ let hi = offsets.length - 1
358
+ let found = -1
359
+ while (lo <= hi) {
360
+ const mid = (lo + hi) >> 1
361
+ if (start + offsets[mid] <= offset) {
362
+ found = mid
363
+ lo = mid + 1
364
+ } else {
365
+ hi = mid - 1
366
+ }
367
+ }
368
+ if (found < 0) return undefined
369
+ const child = node.children[found]
370
+ const childStart = start + offsets[found]
371
+ if (offset >= childStart + child.width) return undefined
372
+ if (childStart === offset && child.key === key) {
373
+ this.lastParent = node
374
+ this.lastParentStart = start
375
+ return child
376
+ }
377
+ node = child
378
+ start = childStart
379
+ }
380
+ }
381
+
382
+ /** The node of the previous tree built by `key` at `pos` (in the new input), if it can be reused. */
383
+ private reusable(previous: Previous, key: object, pos: number): Green | undefined {
384
+ const segments = previous.segments
385
+ // The segment containing `pos`
386
+ let lo = 0
387
+ let hi = segments.length - 1
388
+ while (lo <= hi) {
389
+ const mid = (lo + hi) >> 1
390
+ const seg = segments[mid]
391
+ if (pos < seg.newStart) hi = mid - 1
392
+ else if (pos >= seg.newStart + seg.length) lo = mid + 1
393
+ else {
394
+ const old = seg.oldStart + (pos - seg.newStart)
395
+ const green = this.find(previous.root, old, key)
396
+ if (green === undefined) return undefined
397
+ const segEnd = seg.oldStart + seg.length
398
+ // The input it looked at must be unchanged; past the end of both inputs counts as unchanged
399
+ if (old + green.lookahead + tokenWidth(previous.input, old + green.lookahead) + previous.extra <= segEnd) {
400
+ return green
401
+ }
402
+ if (segEnd === previous.oldLength && seg.newStart + seg.length === previous.newLength) return green
403
+ return undefined
404
+ }
405
+ }
406
+ return undefined
407
+ }
408
+ }
409
+
410
+ /**
411
+ * How many units a token parser may read at `offset`, where a parse
412
+ * stopped looking: one code point. Of a string, that is two units at a high
413
+ * surrogate. Of bytes in an unknown encoding, up to four.
414
+ */
415
+ const tokenWidth = (input: Input, offset: number): number => {
416
+ if (Predicate.isString(input)) {
417
+ const c = input.charCodeAt(offset)
418
+ return c >= 0xd800 && c <= 0xdbff ? 2 : 1
419
+ }
420
+ return input instanceof Uint8Array ? 4 : 1
421
+ }
@@ -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