@thomasfosterau/effect-parse 0.4.1 → 0.6.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 (157) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +56 -14
  3. package/dist/Ascii.d.ts +11 -4
  4. package/dist/Ascii.d.ts.map +1 -1
  5. package/dist/Ascii.js +12 -4
  6. package/dist/Ascii.js.map +1 -1
  7. package/dist/Binary.d.ts +5 -2
  8. package/dist/Binary.d.ts.map +1 -1
  9. package/dist/Binary.js +83 -37
  10. package/dist/Binary.js.map +1 -1
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +11 -4
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +74 -2
  15. package/dist/Compiler.d.ts.map +1 -1
  16. package/dist/Compiler.js +78 -1
  17. package/dist/Compiler.js.map +1 -1
  18. package/dist/Expression.d.ts.map +1 -1
  19. package/dist/Expression.js +86 -74
  20. package/dist/Expression.js.map +1 -1
  21. package/dist/Input.d.ts +56 -5
  22. package/dist/Input.d.ts.map +1 -1
  23. package/dist/Input.js +70 -1
  24. package/dist/Input.js.map +1 -1
  25. package/dist/Nom.d.ts +5 -15
  26. package/dist/Nom.d.ts.map +1 -1
  27. package/dist/Nom.js.map +1 -1
  28. package/dist/ParseError.d.ts +69 -11
  29. package/dist/ParseError.d.ts.map +1 -1
  30. package/dist/ParseError.js +129 -28
  31. package/dist/ParseError.js.map +1 -1
  32. package/dist/Parser.d.ts +375 -29
  33. package/dist/Parser.d.ts.map +1 -1
  34. package/dist/Parser.js +394 -37
  35. package/dist/Parser.js.map +1 -1
  36. package/dist/ParserSchema.d.ts.map +1 -1
  37. package/dist/ParserSchema.js +2 -3
  38. package/dist/ParserSchema.js.map +1 -1
  39. package/dist/ParserStream.d.ts +122 -14
  40. package/dist/ParserStream.d.ts.map +1 -1
  41. package/dist/ParserStream.js +293 -62
  42. package/dist/ParserStream.js.map +1 -1
  43. package/dist/Syntax.d.ts +23 -5
  44. package/dist/Syntax.d.ts.map +1 -1
  45. package/dist/Syntax.js +1 -1
  46. package/dist/Syntax.js.map +1 -1
  47. package/dist/Text.d.ts.map +1 -1
  48. package/dist/Text.js +21 -15
  49. package/dist/Text.js.map +1 -1
  50. package/dist/Token.d.ts +65 -11
  51. package/dist/Token.d.ts.map +1 -1
  52. package/dist/Token.js +73 -26
  53. package/dist/Token.js.map +1 -1
  54. package/dist/internal/compiler/closure.d.ts +1 -1
  55. package/dist/internal/compiler/closure.d.ts.map +1 -1
  56. package/dist/internal/compiler/closure.js +263 -113
  57. package/dist/internal/compiler/closure.js.map +1 -1
  58. package/dist/internal/compiler/entries.d.ts +3 -1
  59. package/dist/internal/compiler/entries.d.ts.map +1 -1
  60. package/dist/internal/compiler/entries.js +3 -2
  61. package/dist/internal/compiler/entries.js.map +1 -1
  62. package/dist/internal/compiler/first.d.ts.map +1 -1
  63. package/dist/internal/compiler/first.js +9 -2
  64. package/dist/internal/compiler/first.js.map +1 -1
  65. package/dist/internal/compiler/graph.d.ts +39 -0
  66. package/dist/internal/compiler/graph.d.ts.map +1 -0
  67. package/dist/internal/compiler/graph.js +78 -0
  68. package/dist/internal/compiler/graph.js.map +1 -0
  69. package/dist/internal/compiler/jit.d.ts +5 -0
  70. package/dist/internal/compiler/jit.d.ts.map +1 -1
  71. package/dist/internal/compiler/jit.js +208 -106
  72. package/dist/internal/compiler/jit.js.map +1 -1
  73. package/dist/internal/compiler/registry.d.ts +11 -3
  74. package/dist/internal/compiler/registry.d.ts.map +1 -1
  75. package/dist/internal/compiler/registry.js.map +1 -1
  76. package/dist/internal/compiler/search.d.ts +32 -0
  77. package/dist/internal/compiler/search.d.ts.map +1 -0
  78. package/dist/internal/compiler/search.js +68 -0
  79. package/dist/internal/compiler/search.js.map +1 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -1
  81. package/dist/internal/compiler/sets.js +1 -1
  82. package/dist/internal/compiler/sets.js.map +1 -1
  83. package/dist/internal/compiler/wasm.d.ts +8 -0
  84. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  85. package/dist/internal/compiler/wasm.js +2840 -0
  86. package/dist/internal/compiler/wasm.js.map +1 -0
  87. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  88. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  89. package/dist/internal/compiler/wasmAsm.js +427 -0
  90. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  91. package/dist/internal/entries.d.ts +3 -1
  92. package/dist/internal/entries.d.ts.map +1 -1
  93. package/dist/internal/entries.js.map +1 -1
  94. package/dist/internal/node.d.ts +77 -3
  95. package/dist/internal/node.d.ts.map +1 -1
  96. package/dist/internal/node.js +54 -10
  97. package/dist/internal/node.js.map +1 -1
  98. package/dist/internal/numbers.d.ts.map +1 -1
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/run.d.ts +14 -0
  101. package/dist/internal/run.d.ts.map +1 -1
  102. package/dist/internal/run.js +53 -11
  103. package/dist/internal/run.js.map +1 -1
  104. package/dist/internal/stack.d.ts +14 -0
  105. package/dist/internal/stack.d.ts.map +1 -1
  106. package/dist/internal/stack.js +24 -2
  107. package/dist/internal/stack.js.map +1 -1
  108. package/dist/internal/syntax.d.ts +4 -4
  109. package/dist/internal/syntax.d.ts.map +1 -1
  110. package/dist/internal/syntax.js +18 -10
  111. package/dist/internal/syntax.js.map +1 -1
  112. package/dist/internal/text.d.ts +32 -0
  113. package/dist/internal/text.d.ts.map +1 -1
  114. package/dist/internal/text.js +68 -14
  115. package/dist/internal/text.js.map +1 -1
  116. package/dist/internal/tokens.d.ts +20 -0
  117. package/dist/internal/tokens.d.ts.map +1 -1
  118. package/dist/internal/tokens.js +72 -39
  119. package/dist/internal/tokens.js.map +1 -1
  120. package/dist/internal/tuple.d.ts +6 -0
  121. package/dist/internal/tuple.d.ts.map +1 -0
  122. package/dist/internal/tuple.js +170 -0
  123. package/dist/internal/tuple.js.map +1 -0
  124. package/package.json +18 -10
  125. package/src/Ascii.ts +47 -12
  126. package/src/Binary.ts +174 -73
  127. package/src/Bits.ts +16 -6
  128. package/src/Compiler.ts +101 -10
  129. package/src/Expression.ts +94 -81
  130. package/src/Input.ts +99 -7
  131. package/src/Nom.ts +15 -5
  132. package/src/ParseError.ts +209 -52
  133. package/src/Parser.ts +684 -143
  134. package/src/ParserSchema.ts +1 -2
  135. package/src/ParserStream.ts +361 -68
  136. package/src/Syntax.ts +60 -36
  137. package/src/Text.ts +32 -27
  138. package/src/Token.ts +152 -34
  139. package/src/internal/compiler/closure.ts +371 -153
  140. package/src/internal/compiler/entries.ts +4 -2
  141. package/src/internal/compiler/first.ts +16 -0
  142. package/src/internal/compiler/graph.ts +96 -0
  143. package/src/internal/compiler/jit.ts +319 -169
  144. package/src/internal/compiler/registry.ts +10 -2
  145. package/src/internal/compiler/search.ts +68 -0
  146. package/src/internal/compiler/sets.ts +7 -3
  147. package/src/internal/compiler/wasm.ts +3276 -0
  148. package/src/internal/compiler/wasmAsm.ts +484 -0
  149. package/src/internal/entries.ts +8 -2
  150. package/src/internal/node.ts +103 -7
  151. package/src/internal/numbers.ts +3 -1
  152. package/src/internal/run.ts +73 -12
  153. package/src/internal/stack.ts +25 -1
  154. package/src/internal/syntax.ts +39 -18
  155. package/src/internal/text.ts +97 -9
  156. package/src/internal/tokens.ts +106 -14
  157. package/src/internal/tuple.ts +141 -0
@@ -1,5 +1,3 @@
1
- // fallow-ignore-file duplicate-export -- `End` and `scan` here are the token-set versions of the text
2
- // helpers in ./text.ts; both are internal modules, imported by path, never re-exported from one barrel.
3
1
  import * as Predicate from "effect/Predicate"
4
2
  import * as Result from "effect/Result"
5
3
  import type { Input, State } from "../Input.js"
@@ -22,7 +20,7 @@ export interface TokenRangeLike {
22
20
  readonly to: string | number
23
21
  }
24
22
 
25
- const isRange = (u: unknown): u is TokenRangeLike => Predicate.isObjectOrArray(u) && (u as any)._tag === "TokenRange"
23
+ const isRange = (u: unknown): u is TokenRangeLike => Predicate.isObjectOrArray(u) && Predicate.isTagged(u, "TokenRange")
26
24
 
27
25
  const toCode = (u: string | number): number => (Predicate.isNumber(u) ? u : firstCodePoint(u))
28
26
 
@@ -67,7 +65,10 @@ const compileCode = (u: unknown): ((code: number) => boolean) => {
67
65
  return bitmap(codes)
68
66
  }
69
67
  if (Predicate.isNumber(u)) return (x) => x === u
70
- if (Predicate.isFunction(u)) return u as (code: number) => boolean
68
+ if (Predicate.isFunction(u)) {
69
+ // SAFETY: a predicate compiled for codes is a byte, code point or `codePoints` predicate (`compile` wraps string predicates first)
70
+ return u as (code: number) => boolean
71
+ }
71
72
  if (u instanceof Uint8Array) return bitmap(Array.from(u))
72
73
  if (isRange(u)) {
73
74
  const from = toCode(u.from)
@@ -98,7 +99,10 @@ const bitmap = (codes: ReadonlyArray<number>): ((code: number) => boolean) => {
98
99
  }
99
100
 
100
101
  const compileToken = (u: unknown): ((token: unknown) => boolean) => {
101
- if (Predicate.isFunction(u)) return u as (token: unknown) => boolean
102
+ if (Predicate.isFunction(u)) {
103
+ // SAFETY: a predicate in an array input's token set takes the array's elements, the tokens tested here
104
+ return u as (token: unknown) => boolean
105
+ }
102
106
  if (Array.isArray(u)) {
103
107
  const set = new Set(u)
104
108
  return (t) => set.has(t)
@@ -130,8 +134,13 @@ export const codePointPredicate = <F extends (c: never) => boolean>(
130
134
  ranges?: ReadonlyArray<readonly [number, number]>
131
135
  ): F => {
132
136
  Object.defineProperty(f, "name", { value: name })
133
- ;(f as any)[CodePointPredicate] = true
134
- if (ranges !== undefined) (f as any)[PredicateRanges] = ranges
137
+ // SAFETY: `f` is a function object, which takes new properties; the intersection only names the two markers set here
138
+ const marked = f as F & {
139
+ [CodePointPredicate]?: true
140
+ [PredicateRanges]?: ReadonlyArray<readonly [number, number]>
141
+ }
142
+ marked[CodePointPredicate] = true
143
+ if (ranges !== undefined) marked[PredicateRanges] = ranges
135
144
  return f
136
145
  }
137
146
 
@@ -144,6 +153,7 @@ export const compile = (set: unknown, kind: "string" | "code" = "code"): Matcher
144
153
  const description = describeSet(set)
145
154
  let code: (code: number) => boolean
146
155
  if (Predicate.isFunction(set) && kind === "string" && !(CodePointPredicate in set)) {
156
+ // SAFETY: an unmarked predicate in a string token set takes each character as a string (`StringTokenSet`)
147
157
  const pred = set as (c: string) => boolean
148
158
  code = (x) => pred(charOf(x))
149
159
  } else {
@@ -227,6 +237,7 @@ const compileAnyImpl = (set: unknown): AnyMatcher => {
227
237
  */
228
238
  export const splitSurrogate = (state: State<Input>, i: number): boolean => {
229
239
  if (!state.partial || i !== state.end - 1) return false
240
+ // SAFETY: called only on string input (the `inputKind === 0` paths of `nextIf`, `scan`, `advance` and `Token.any`)
230
241
  const c = (state.input as string).charCodeAt(i)
231
242
  return c >= 0xd800 && c <= 0xdbff
232
243
  }
@@ -253,6 +264,7 @@ export const nextIf = (state: State<Input>, m: AnyMatcher, negate: boolean): unk
253
264
  if (pos >= state.end) return End
254
265
  switch (state.inputKind) {
255
266
  case 0: {
267
+ // SAFETY: `inputKind` 0 is set only for a string input
256
268
  const s = state.input as string
257
269
  if (splitSurrogate(state, pos)) return End
258
270
  const cp = codePointAt(s, pos)
@@ -262,12 +274,14 @@ export const nextIf = (state: State<Input>, m: AnyMatcher, negate: boolean): unk
262
274
  return len === 1 ? s[pos] : s.slice(pos, pos + 2)
263
275
  }
264
276
  case 1: {
277
+ // SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
265
278
  const b = (state.input as Uint8Array)[pos]
266
279
  if (m.forCode.code(b) === negate) return NoMatch
267
280
  state.pos = pos + 1
268
281
  return b
269
282
  }
270
283
  default: {
284
+ // SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
271
285
  const t = (state.input as ReadonlyArray<unknown>)[pos]
272
286
  if (m.forCode.token(t) === negate) return NoMatch
273
287
  state.pos = pos + 1
@@ -282,10 +296,32 @@ export interface Scanned extends Advanced {
282
296
  readonly hitEnd: boolean
283
297
  }
284
298
 
299
+ /** The one record {@link scan} fills; see there. */
300
+ class ScanResult implements Scanned {
301
+ offset = 0
302
+ count = 0
303
+ hitEnd = false
304
+ }
305
+
306
+ const scanned = /*#__PURE__*/ new ScanResult()
307
+
308
+ /** Fills {@link scanned} and returns it. */
309
+ const scannedAt = (offset: number, count: number, hitEnd: boolean): Scanned => {
310
+ scanned.offset = offset
311
+ scanned.count = count
312
+ scanned.hitEnd = hitEnd
313
+ return scanned
314
+ }
315
+
285
316
  /**
286
317
  * Scans forward while tokens satisfy the matcher (xor `negate`), up to `max`
287
318
  * tokens. Returns the offset reached, the number of tokens matched, and
288
319
  * whether the end of input was hit before `max` tokens.
320
+ *
321
+ * The result is one shared record, overwritten by the next scan: read it
322
+ * before running anything else. Allocating one per call cost the
323
+ * interpreter a record per token, since `scan` (shared by every `takeWhile`)
324
+ * is too polymorphic for V8 to inline and remove it.
289
325
  */
290
326
  export const scan = (state: State<Input>, m: AnyMatcher, negate: boolean, max: number): Scanned => {
291
327
  let i = state.pos
@@ -293,45 +329,48 @@ export const scan = (state: State<Input>, m: AnyMatcher, negate: boolean, max: n
293
329
  let count = 0
294
330
  switch (state.inputKind) {
295
331
  case 0: {
332
+ // SAFETY: `inputKind` 0 is set only for a string input
296
333
  const s = state.input as string
297
334
  const test = m.forString.code
298
335
  while (count < max) {
299
- if (i >= end) return { offset: i, count, hitEnd: true }
336
+ if (i >= end) return scannedAt(i, count, true)
300
337
  const c = s.charCodeAt(i)
301
338
  if (c < 0xd800 || c > 0xdbff) {
302
339
  if (test(c) === negate) break
303
340
  i++
304
341
  } else {
305
- if (splitSurrogate(state, i)) return { offset: i, count, hitEnd: true }
342
+ if (splitSurrogate(state, i)) return scannedAt(i, count, true)
306
343
  const cp = codePointAt(s, i)
307
344
  if (test(cp) === negate) break
308
345
  i += cp > 0xffff ? 2 : 1
309
346
  }
310
347
  count++
311
348
  }
312
- return { offset: i, count, hitEnd: false }
349
+ return scannedAt(i, count, false)
313
350
  }
314
351
  case 1: {
352
+ // SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
315
353
  const bytes = state.input as Uint8Array
316
354
  const test = m.forCode.code
317
355
  while (count < max) {
318
- if (i >= end) return { offset: i, count, hitEnd: true }
356
+ if (i >= end) return scannedAt(i, count, true)
319
357
  if (test(bytes[i]) === negate) break
320
358
  i++
321
359
  count++
322
360
  }
323
- return { offset: i, count, hitEnd: false }
361
+ return scannedAt(i, count, false)
324
362
  }
325
363
  default: {
364
+ // SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
326
365
  const arr = state.input as ReadonlyArray<unknown>
327
366
  const test = m.forCode.token
328
367
  while (count < max) {
329
- if (i >= end) return { offset: i, count, hitEnd: true }
368
+ if (i >= end) return scannedAt(i, count, true)
330
369
  if (test(arr[i]) === negate) break
331
370
  i++
332
371
  count++
333
372
  }
334
- return { offset: i, count, hitEnd: false }
373
+ return scannedAt(i, count, false)
335
374
  }
336
375
  }
337
376
  }
@@ -348,6 +387,7 @@ export const advance = (state: State<Input>, start: number, n: number): Advanced
348
387
  const offset = Math.min(state.end, start + n)
349
388
  return { offset, count: offset - start }
350
389
  }
390
+ // SAFETY: the other input kinds returned above, and `inputKind` 0 is set only for a string input
351
391
  const s = state.input as string
352
392
  let i = start
353
393
  let count = 0
@@ -363,6 +403,7 @@ export const advance = (state: State<Input>, start: number, n: number): Advanced
363
403
  /** Counts tokens between two offsets. */
364
404
  export const countTokens = (state: State<Input>, start: number, end: number): number => {
365
405
  if (state.inputKind !== 0) return end - start
406
+ // SAFETY: the other input kinds returned above, and `inputKind` 0 is set only for a string input
366
407
  const s = state.input as string
367
408
  let count = 0
368
409
  for (let i = start; i < end; i++) {
@@ -373,6 +414,46 @@ export const countTokens = (state: State<Input>, start: number, end: number): nu
373
414
  return count
374
415
  }
375
416
 
417
+ /**
418
+ * The first index at or after `from` of any of `needles` within
419
+ * `[from, state.end)`, or -1. The state remembers, under `needles`, where
420
+ * each one next occurs (`[from, at, end]` at `3 * i`), so successive
421
+ * searches forwards over one input search each needle once per occurrence
422
+ * rather than once per call (as `nextStop` does for `takeTill`).
423
+ *
424
+ * A remembered answer is used only where it still holds: an occurrence, when
425
+ * the search starts between where it was searched from and where it was
426
+ * found, and it still fits before the end; "not found", when the search
427
+ * starts no earlier and the end is no further than the end that search ran
428
+ * to (a parser may narrow `state.end` for a while, and `reset` widens it
429
+ * again, so a miss within a narrower end says nothing about a wider one).
430
+ */
431
+ export const indexOfAny = (state: State<Input>, needles: ReadonlyArray<Input>, from: number): number => {
432
+ const searches = (state.searches ??= new Map())
433
+ let memo = searches.get(needles)
434
+ if (memo === undefined) {
435
+ memo = needles.flatMap(() => [-1, -1, -1])
436
+ searches.set(needles, memo)
437
+ }
438
+ let best = -1
439
+ for (let i = 0; i < needles.length; i++) {
440
+ const known = memo[3 * i] ?? -1
441
+ let at = memo[3 * i + 1] ?? -1
442
+ const searchedTo = memo[3 * i + 2] ?? -1
443
+ const needle = needles[i] ?? ""
444
+ const stale =
445
+ known < 0 || from < known || (at >= 0 ? at < from || at > state.end - needle.length : state.end > searchedTo)
446
+ if (stale) {
447
+ at = indexOf(state, needle, from)
448
+ memo[3 * i] = from
449
+ memo[3 * i + 1] = at
450
+ memo[3 * i + 2] = state.end
451
+ }
452
+ if (at >= 0 && (best < 0 || at < best)) best = at
453
+ }
454
+ return best
455
+ }
456
+
376
457
  /** Finds `needle` within `[from, state.end)`; returns -1 if absent. */
377
458
  export const indexOf = (state: State<Input>, needle: Input, from: number): number => {
378
459
  // The whole needle must lie before `state.end`, which may narrow the input
@@ -380,11 +461,14 @@ export const indexOf = (state: State<Input>, needle: Input, from: number): numbe
380
461
  if (from > last) return -1
381
462
  switch (state.inputKind) {
382
463
  case 0: {
464
+ // SAFETY: `inputKind` 0 is set only for a string input, and the callers' types give the needle the input's kind
383
465
  const i = (state.input as string).indexOf(needle as string, from)
384
466
  return i <= last ? i : -1
385
467
  }
386
468
  case 1: {
469
+ // SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
387
470
  const hay = state.input as Uint8Array
471
+ // SAFETY: the callers' types give the needle the input's kind (`Text` encodes it for byte input)
388
472
  const n = needle as Uint8Array
389
473
  if (n.byteLength === 0) return from
390
474
  const first = n[0]
@@ -398,7 +482,9 @@ export const indexOf = (state: State<Input>, needle: Input, from: number): numbe
398
482
  return -1
399
483
  }
400
484
  default: {
485
+ // SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
401
486
  const hay = state.input as ReadonlyArray<unknown>
487
+ // SAFETY: the callers' types give the needle the input's kind, an array of the same element type
402
488
  const n = needle as ReadonlyArray<unknown>
403
489
  outer: for (let i = from; i <= last; i++) {
404
490
  for (let j = 0; j < n.length; j++) if (hay[i + j] !== n[j]) continue outer
@@ -428,7 +514,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
428
514
  const n = Math.min(len, avail)
429
515
  switch (state.inputKind) {
430
516
  case 0: {
517
+ // SAFETY: `inputKind` 0 is set only for a string input
431
518
  const s = state.input as string
519
+ // SAFETY: the callers' types give the literal the input's kind (`Token.literal` encodes a string literal for byte input)
432
520
  const l = lit as string
433
521
  if (!caseless) {
434
522
  if (avail >= len) return s.startsWith(l, pos) ? len : -1
@@ -440,7 +528,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
440
528
  return n === len ? len : -2
441
529
  }
442
530
  case 1: {
531
+ // SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
443
532
  const b = state.input as Uint8Array
533
+ // SAFETY: the callers' types give the literal the input's kind (`Token.literal` and `Text` encode string literals for byte input)
444
534
  const l = lit as Uint8Array
445
535
  for (let i = 0; i < n; i++) {
446
536
  const x = b[pos + i]
@@ -450,7 +540,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
450
540
  return n === len ? len : -2
451
541
  }
452
542
  default: {
543
+ // SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
453
544
  const a = state.input as ReadonlyArray<unknown>
545
+ // SAFETY: the callers' types give the literal the input's kind, an array of the same element type
454
546
  const l = lit as ReadonlyArray<unknown>
455
547
  for (let i = 0; i < n; i++) if (a[pos + i] !== l[i]) return -1
456
548
  return n === len ? len : -2
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Tuple steps: run steps in sequence and collect their outputs in an array.
3
+ *
4
+ * V8 pretenures by allocation site, and only array and object literals have
5
+ * one: an array from `new Array(n)` starts young, so tuples that a parse keeps
6
+ * (a `repeat(seq(...))`, a tree) are copied by a scavenge or two before they
7
+ * are promoted. A literal's site learns that its arrays survive, and V8 then
8
+ * allocates them straight into the old generation. So arities up to
9
+ * {@link maxLiteral} build their tuple with a literal of that arity, each its
10
+ * own site; longer tuples fall back to `new Array(n)` (#86).
11
+ */
12
+ import { Fail } from "../Input.js"
13
+ import type { State } from "../Input.js"
14
+
15
+ type Step = (state: State<any>) => unknown
16
+
17
+ /** The longest tuple built with a literal. */
18
+ const maxLiteral = 8
19
+
20
+ /** A step that runs `steps` in order and returns their outputs as an array, or {@link Fail}. */
21
+ export const tupleStep = (steps: ReadonlyArray<Step>): ((state: State<any>) => Array<unknown> | Fail) => {
22
+ const n = steps.length
23
+ if (n <= maxLiteral) return literals[n](steps)
24
+ return (state) => {
25
+ const out = new Array<unknown>(n)
26
+ for (let i = 0; i < n; i++) {
27
+ const a = steps[i](state)
28
+ if (a === Fail) return Fail
29
+ out[i] = a
30
+ }
31
+ return out
32
+ }
33
+ }
34
+
35
+ const literals: ReadonlyArray<(steps: ReadonlyArray<Step>) => (state: State<any>) => Array<unknown> | Fail> = [
36
+ () => () => [],
37
+ ([s0]) =>
38
+ (state) => {
39
+ const a0 = s0(state)
40
+ if (a0 === Fail) return Fail
41
+ return [a0]
42
+ },
43
+ ([s0, s1]) =>
44
+ (state) => {
45
+ const a0 = s0(state)
46
+ if (a0 === Fail) return Fail
47
+ const a1 = s1(state)
48
+ if (a1 === Fail) return Fail
49
+ return [a0, a1]
50
+ },
51
+ ([s0, s1, s2]) =>
52
+ (state) => {
53
+ const a0 = s0(state)
54
+ if (a0 === Fail) return Fail
55
+ const a1 = s1(state)
56
+ if (a1 === Fail) return Fail
57
+ const a2 = s2(state)
58
+ if (a2 === Fail) return Fail
59
+ return [a0, a1, a2]
60
+ },
61
+ ([s0, s1, s2, s3]) =>
62
+ (state) => {
63
+ const a0 = s0(state)
64
+ if (a0 === Fail) return Fail
65
+ const a1 = s1(state)
66
+ if (a1 === Fail) return Fail
67
+ const a2 = s2(state)
68
+ if (a2 === Fail) return Fail
69
+ const a3 = s3(state)
70
+ if (a3 === Fail) return Fail
71
+ return [a0, a1, a2, a3]
72
+ },
73
+ ([s0, s1, s2, s3, s4]) =>
74
+ (state) => {
75
+ const a0 = s0(state)
76
+ if (a0 === Fail) return Fail
77
+ const a1 = s1(state)
78
+ if (a1 === Fail) return Fail
79
+ const a2 = s2(state)
80
+ if (a2 === Fail) return Fail
81
+ const a3 = s3(state)
82
+ if (a3 === Fail) return Fail
83
+ const a4 = s4(state)
84
+ if (a4 === Fail) return Fail
85
+ return [a0, a1, a2, a3, a4]
86
+ },
87
+ ([s0, s1, s2, s3, s4, s5]) =>
88
+ (state) => {
89
+ const a0 = s0(state)
90
+ if (a0 === Fail) return Fail
91
+ const a1 = s1(state)
92
+ if (a1 === Fail) return Fail
93
+ const a2 = s2(state)
94
+ if (a2 === Fail) return Fail
95
+ const a3 = s3(state)
96
+ if (a3 === Fail) return Fail
97
+ const a4 = s4(state)
98
+ if (a4 === Fail) return Fail
99
+ const a5 = s5(state)
100
+ if (a5 === Fail) return Fail
101
+ return [a0, a1, a2, a3, a4, a5]
102
+ },
103
+ ([s0, s1, s2, s3, s4, s5, s6]) =>
104
+ (state) => {
105
+ const a0 = s0(state)
106
+ if (a0 === Fail) return Fail
107
+ const a1 = s1(state)
108
+ if (a1 === Fail) return Fail
109
+ const a2 = s2(state)
110
+ if (a2 === Fail) return Fail
111
+ const a3 = s3(state)
112
+ if (a3 === Fail) return Fail
113
+ const a4 = s4(state)
114
+ if (a4 === Fail) return Fail
115
+ const a5 = s5(state)
116
+ if (a5 === Fail) return Fail
117
+ const a6 = s6(state)
118
+ if (a6 === Fail) return Fail
119
+ return [a0, a1, a2, a3, a4, a5, a6]
120
+ },
121
+ ([s0, s1, s2, s3, s4, s5, s6, s7]) =>
122
+ (state) => {
123
+ const a0 = s0(state)
124
+ if (a0 === Fail) return Fail
125
+ const a1 = s1(state)
126
+ if (a1 === Fail) return Fail
127
+ const a2 = s2(state)
128
+ if (a2 === Fail) return Fail
129
+ const a3 = s3(state)
130
+ if (a3 === Fail) return Fail
131
+ const a4 = s4(state)
132
+ if (a4 === Fail) return Fail
133
+ const a5 = s5(state)
134
+ if (a5 === Fail) return Fail
135
+ const a6 = s6(state)
136
+ if (a6 === Fail) return Fail
137
+ const a7 = s7(state)
138
+ if (a7 === Fail) return Fail
139
+ return [a0, a1, a2, a3, a4, a5, a6, a7]
140
+ }
141
+ ]