@thomasfosterau/effect-parse 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +37 -13
  2. package/dist/Ascii.d.ts +11 -4
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +7 -1
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts.map +1 -1
  7. package/dist/Binary.js +64 -35
  8. package/dist/Binary.js.map +1 -1
  9. package/dist/Bits.d.ts.map +1 -1
  10. package/dist/Bits.js +7 -4
  11. package/dist/Bits.js.map +1 -1
  12. package/dist/Compiler.d.ts +74 -2
  13. package/dist/Compiler.d.ts.map +1 -1
  14. package/dist/Compiler.js +78 -1
  15. package/dist/Compiler.js.map +1 -1
  16. package/dist/Expression.d.ts.map +1 -1
  17. package/dist/Expression.js +86 -74
  18. package/dist/Expression.js.map +1 -1
  19. package/dist/Input.d.ts +37 -4
  20. package/dist/Input.d.ts.map +1 -1
  21. package/dist/Input.js +42 -0
  22. package/dist/Input.js.map +1 -1
  23. package/dist/Nom.d.ts +5 -15
  24. package/dist/Nom.d.ts.map +1 -1
  25. package/dist/Nom.js.map +1 -1
  26. package/dist/ParseError.d.ts.map +1 -1
  27. package/dist/ParseError.js.map +1 -1
  28. package/dist/Parser.d.ts +366 -26
  29. package/dist/Parser.d.ts.map +1 -1
  30. package/dist/Parser.js +390 -19
  31. package/dist/Parser.js.map +1 -1
  32. package/dist/ParserSchema.js +1 -1
  33. package/dist/ParserStream.d.ts +3 -2
  34. package/dist/ParserStream.d.ts.map +1 -1
  35. package/dist/ParserStream.js +13 -9
  36. package/dist/ParserStream.js.map +1 -1
  37. package/dist/Syntax.d.ts +23 -5
  38. package/dist/Syntax.d.ts.map +1 -1
  39. package/dist/Syntax.js.map +1 -1
  40. package/dist/Text.d.ts.map +1 -1
  41. package/dist/Text.js +9 -9
  42. package/dist/Text.js.map +1 -1
  43. package/dist/Token.d.ts +55 -10
  44. package/dist/Token.d.ts.map +1 -1
  45. package/dist/Token.js +59 -21
  46. package/dist/Token.js.map +1 -1
  47. package/dist/internal/compiler/closure.d.ts.map +1 -1
  48. package/dist/internal/compiler/closure.js +257 -97
  49. package/dist/internal/compiler/closure.js.map +1 -1
  50. package/dist/internal/compiler/entries.d.ts +3 -1
  51. package/dist/internal/compiler/entries.d.ts.map +1 -1
  52. package/dist/internal/compiler/entries.js +3 -2
  53. package/dist/internal/compiler/entries.js.map +1 -1
  54. package/dist/internal/compiler/first.d.ts.map +1 -1
  55. package/dist/internal/compiler/first.js +7 -1
  56. package/dist/internal/compiler/first.js.map +1 -1
  57. package/dist/internal/compiler/graph.d.ts +17 -0
  58. package/dist/internal/compiler/graph.d.ts.map +1 -0
  59. package/dist/internal/compiler/graph.js +56 -0
  60. package/dist/internal/compiler/graph.js.map +1 -0
  61. package/dist/internal/compiler/jit.d.ts +5 -0
  62. package/dist/internal/compiler/jit.d.ts.map +1 -1
  63. package/dist/internal/compiler/jit.js +185 -96
  64. package/dist/internal/compiler/jit.js.map +1 -1
  65. package/dist/internal/compiler/registry.d.ts +10 -2
  66. package/dist/internal/compiler/registry.d.ts.map +1 -1
  67. package/dist/internal/compiler/registry.js.map +1 -1
  68. package/dist/internal/compiler/search.d.ts +32 -0
  69. package/dist/internal/compiler/search.d.ts.map +1 -0
  70. package/dist/internal/compiler/search.js +68 -0
  71. package/dist/internal/compiler/search.js.map +1 -0
  72. package/dist/internal/compiler/sets.d.ts.map +1 -1
  73. package/dist/internal/compiler/sets.js +1 -1
  74. package/dist/internal/compiler/sets.js.map +1 -1
  75. package/dist/internal/compiler/wasm.d.ts +8 -0
  76. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  77. package/dist/internal/compiler/wasm.js +2769 -0
  78. package/dist/internal/compiler/wasm.js.map +1 -0
  79. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  80. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  81. package/dist/internal/compiler/wasmAsm.js +427 -0
  82. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  83. package/dist/internal/entries.d.ts +3 -1
  84. package/dist/internal/entries.d.ts.map +1 -1
  85. package/dist/internal/entries.js.map +1 -1
  86. package/dist/internal/node.d.ts +58 -2
  87. package/dist/internal/node.d.ts.map +1 -1
  88. package/dist/internal/node.js +13 -3
  89. package/dist/internal/node.js.map +1 -1
  90. package/dist/internal/numbers.d.ts.map +1 -1
  91. package/dist/internal/numbers.js.map +1 -1
  92. package/dist/internal/run.d.ts +12 -0
  93. package/dist/internal/run.d.ts.map +1 -1
  94. package/dist/internal/run.js +51 -11
  95. package/dist/internal/run.js.map +1 -1
  96. package/dist/internal/stack.d.ts +6 -0
  97. package/dist/internal/stack.d.ts.map +1 -1
  98. package/dist/internal/stack.js +8 -2
  99. package/dist/internal/stack.js.map +1 -1
  100. package/dist/internal/syntax.d.ts +4 -4
  101. package/dist/internal/syntax.d.ts.map +1 -1
  102. package/dist/internal/syntax.js +18 -10
  103. package/dist/internal/syntax.js.map +1 -1
  104. package/dist/internal/text.d.ts +32 -0
  105. package/dist/internal/text.d.ts.map +1 -1
  106. package/dist/internal/text.js +68 -14
  107. package/dist/internal/text.js.map +1 -1
  108. package/dist/internal/tokens.d.ts +13 -0
  109. package/dist/internal/tokens.d.ts.map +1 -1
  110. package/dist/internal/tokens.js +59 -39
  111. package/dist/internal/tokens.js.map +1 -1
  112. package/package.json +5 -4
  113. package/src/Ascii.ts +42 -9
  114. package/src/Binary.ts +151 -71
  115. package/src/Bits.ts +12 -6
  116. package/src/Compiler.ts +101 -10
  117. package/src/Expression.ts +94 -81
  118. package/src/Input.ts +65 -6
  119. package/src/Nom.ts +15 -5
  120. package/src/ParseError.ts +3 -0
  121. package/src/Parser.ts +678 -127
  122. package/src/ParserStream.ts +37 -18
  123. package/src/Syntax.ts +60 -36
  124. package/src/Text.ts +16 -15
  125. package/src/Token.ts +138 -29
  126. package/src/internal/compiler/closure.ts +362 -135
  127. package/src/internal/compiler/entries.ts +4 -2
  128. package/src/internal/compiler/first.ts +10 -0
  129. package/src/internal/compiler/graph.ts +68 -0
  130. package/src/internal/compiler/jit.ts +284 -154
  131. package/src/internal/compiler/registry.ts +10 -2
  132. package/src/internal/compiler/search.ts +68 -0
  133. package/src/internal/compiler/sets.ts +7 -3
  134. package/src/internal/compiler/wasm.ts +3203 -0
  135. package/src/internal/compiler/wasmAsm.ts +484 -0
  136. package/src/internal/entries.ts +8 -2
  137. package/src/internal/node.ts +54 -4
  138. package/src/internal/numbers.ts +3 -1
  139. package/src/internal/run.ts +71 -12
  140. package/src/internal/stack.ts +8 -1
  141. package/src/internal/syntax.ts +39 -18
  142. package/src/internal/text.ts +97 -9
  143. package/src/internal/tokens.ts +96 -14
@@ -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,36 @@ 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]` at `2 * i`), so successive searches
421
+ * forwards over one input search each needle once per occurrence rather
422
+ * than once per call (as `nextStop` does for `takeTill`).
423
+ */
424
+ export const indexOfAny = (state: State<Input>, needles: ReadonlyArray<Input>, from: number): number => {
425
+ const searches = (state.searches ??= new Map())
426
+ let memo = searches.get(needles)
427
+ if (memo === undefined) {
428
+ memo = needles.flatMap(() => [-1, -1])
429
+ searches.set(needles, memo)
430
+ }
431
+ let best = -1
432
+ for (let i = 0; i < needles.length; i++) {
433
+ const known = memo[2 * i] ?? -1
434
+ let at = memo[2 * i + 1] ?? -1
435
+ const needle = needles[i] ?? ""
436
+ // Searched again when the input's end moved before a remembered occurrence
437
+ if (known < 0 || from < known || (at >= 0 && (at < from || at > state.end - needle.length))) {
438
+ at = indexOf(state, needle, from)
439
+ memo[2 * i] = from
440
+ memo[2 * i + 1] = at
441
+ }
442
+ if (at >= 0 && (best < 0 || at < best)) best = at
443
+ }
444
+ return best
445
+ }
446
+
376
447
  /** Finds `needle` within `[from, state.end)`; returns -1 if absent. */
377
448
  export const indexOf = (state: State<Input>, needle: Input, from: number): number => {
378
449
  // The whole needle must lie before `state.end`, which may narrow the input
@@ -380,11 +451,14 @@ export const indexOf = (state: State<Input>, needle: Input, from: number): numbe
380
451
  if (from > last) return -1
381
452
  switch (state.inputKind) {
382
453
  case 0: {
454
+ // SAFETY: `inputKind` 0 is set only for a string input, and the callers' types give the needle the input's kind
383
455
  const i = (state.input as string).indexOf(needle as string, from)
384
456
  return i <= last ? i : -1
385
457
  }
386
458
  case 1: {
459
+ // SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
387
460
  const hay = state.input as Uint8Array
461
+ // SAFETY: the callers' types give the needle the input's kind (`Text` encodes it for byte input)
388
462
  const n = needle as Uint8Array
389
463
  if (n.byteLength === 0) return from
390
464
  const first = n[0]
@@ -398,7 +472,9 @@ export const indexOf = (state: State<Input>, needle: Input, from: number): numbe
398
472
  return -1
399
473
  }
400
474
  default: {
475
+ // SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
401
476
  const hay = state.input as ReadonlyArray<unknown>
477
+ // SAFETY: the callers' types give the needle the input's kind, an array of the same element type
402
478
  const n = needle as ReadonlyArray<unknown>
403
479
  outer: for (let i = from; i <= last; i++) {
404
480
  for (let j = 0; j < n.length; j++) if (hay[i + j] !== n[j]) continue outer
@@ -428,7 +504,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
428
504
  const n = Math.min(len, avail)
429
505
  switch (state.inputKind) {
430
506
  case 0: {
507
+ // SAFETY: `inputKind` 0 is set only for a string input
431
508
  const s = state.input as string
509
+ // SAFETY: the callers' types give the literal the input's kind (`Token.literal` encodes a string literal for byte input)
432
510
  const l = lit as string
433
511
  if (!caseless) {
434
512
  if (avail >= len) return s.startsWith(l, pos) ? len : -1
@@ -440,7 +518,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
440
518
  return n === len ? len : -2
441
519
  }
442
520
  case 1: {
521
+ // SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
443
522
  const b = state.input as Uint8Array
523
+ // SAFETY: the callers' types give the literal the input's kind (`Token.literal` and `Text` encode string literals for byte input)
444
524
  const l = lit as Uint8Array
445
525
  for (let i = 0; i < n; i++) {
446
526
  const x = b[pos + i]
@@ -450,7 +530,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
450
530
  return n === len ? len : -2
451
531
  }
452
532
  default: {
533
+ // SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
453
534
  const a = state.input as ReadonlyArray<unknown>
535
+ // SAFETY: the callers' types give the literal the input's kind, an array of the same element type
454
536
  const l = lit as ReadonlyArray<unknown>
455
537
  for (let i = 0; i < n; i++) if (a[pos + i] !== l[i]) return -1
456
538
  return n === len ? len : -2