@thomasfosterau/effect-parse 0.5.0 → 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 (99) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +19 -1
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +5 -3
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +5 -2
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +20 -3
  9. package/dist/Binary.js.map +1 -1
  10. package/dist/Bits.d.ts.map +1 -1
  11. package/dist/Bits.js +4 -0
  12. package/dist/Bits.js.map +1 -1
  13. package/dist/Input.d.ts +21 -3
  14. package/dist/Input.d.ts.map +1 -1
  15. package/dist/Input.js +31 -4
  16. package/dist/Input.js.map +1 -1
  17. package/dist/Nom.d.ts.map +1 -1
  18. package/dist/ParseError.d.ts +69 -11
  19. package/dist/ParseError.d.ts.map +1 -1
  20. package/dist/ParseError.js +129 -28
  21. package/dist/ParseError.js.map +1 -1
  22. package/dist/Parser.d.ts +10 -4
  23. package/dist/Parser.d.ts.map +1 -1
  24. package/dist/Parser.js +10 -24
  25. package/dist/Parser.js.map +1 -1
  26. package/dist/ParserSchema.d.ts.map +1 -1
  27. package/dist/ParserSchema.js +1 -2
  28. package/dist/ParserSchema.js.map +1 -1
  29. package/dist/ParserStream.d.ts +119 -12
  30. package/dist/ParserStream.d.ts.map +1 -1
  31. package/dist/ParserStream.js +282 -55
  32. package/dist/ParserStream.js.map +1 -1
  33. package/dist/Syntax.js +1 -1
  34. package/dist/Text.d.ts.map +1 -1
  35. package/dist/Text.js +13 -7
  36. package/dist/Text.js.map +1 -1
  37. package/dist/Token.d.ts +10 -1
  38. package/dist/Token.d.ts.map +1 -1
  39. package/dist/Token.js +14 -5
  40. package/dist/Token.js.map +1 -1
  41. package/dist/internal/compiler/closure.d.ts +1 -1
  42. package/dist/internal/compiler/closure.d.ts.map +1 -1
  43. package/dist/internal/compiler/closure.js +7 -17
  44. package/dist/internal/compiler/closure.js.map +1 -1
  45. package/dist/internal/compiler/first.d.ts.map +1 -1
  46. package/dist/internal/compiler/first.js +2 -1
  47. package/dist/internal/compiler/first.js.map +1 -1
  48. package/dist/internal/compiler/graph.d.ts +23 -1
  49. package/dist/internal/compiler/graph.d.ts.map +1 -1
  50. package/dist/internal/compiler/graph.js +27 -5
  51. package/dist/internal/compiler/graph.js.map +1 -1
  52. package/dist/internal/compiler/jit.d.ts.map +1 -1
  53. package/dist/internal/compiler/jit.js +26 -13
  54. package/dist/internal/compiler/jit.js.map +1 -1
  55. package/dist/internal/compiler/registry.d.ts +1 -1
  56. package/dist/internal/compiler/wasm.d.ts.map +1 -1
  57. package/dist/internal/compiler/wasm.js +105 -34
  58. package/dist/internal/compiler/wasm.js.map +1 -1
  59. package/dist/internal/node.d.ts +19 -1
  60. package/dist/internal/node.d.ts.map +1 -1
  61. package/dist/internal/node.js +42 -8
  62. package/dist/internal/node.js.map +1 -1
  63. package/dist/internal/run.d.ts +3 -1
  64. package/dist/internal/run.d.ts.map +1 -1
  65. package/dist/internal/run.js +4 -2
  66. package/dist/internal/run.js.map +1 -1
  67. package/dist/internal/stack.d.ts +8 -0
  68. package/dist/internal/stack.d.ts.map +1 -1
  69. package/dist/internal/stack.js +17 -1
  70. package/dist/internal/stack.js.map +1 -1
  71. package/dist/internal/tokens.d.ts +10 -3
  72. package/dist/internal/tokens.d.ts.map +1 -1
  73. package/dist/internal/tokens.js +22 -9
  74. package/dist/internal/tokens.js.map +1 -1
  75. package/dist/internal/tuple.d.ts +6 -0
  76. package/dist/internal/tuple.d.ts.map +1 -0
  77. package/dist/internal/tuple.js +170 -0
  78. package/dist/internal/tuple.js.map +1 -0
  79. package/package.json +17 -10
  80. package/src/Ascii.ts +5 -3
  81. package/src/Binary.ts +24 -3
  82. package/src/Bits.ts +4 -0
  83. package/src/Input.ts +37 -4
  84. package/src/ParseError.ts +206 -52
  85. package/src/Parser.ts +19 -29
  86. package/src/ParserSchema.ts +1 -2
  87. package/src/ParserStream.ts +336 -62
  88. package/src/Text.ts +16 -12
  89. package/src/Token.ts +14 -5
  90. package/src/internal/compiler/closure.ts +9 -18
  91. package/src/internal/compiler/first.ts +6 -0
  92. package/src/internal/compiler/graph.ts +32 -4
  93. package/src/internal/compiler/jit.ts +37 -17
  94. package/src/internal/compiler/wasm.ts +141 -68
  95. package/src/internal/node.ts +49 -3
  96. package/src/internal/run.ts +4 -2
  97. package/src/internal/stack.ts +17 -0
  98. package/src/internal/tokens.ts +20 -10
  99. package/src/internal/tuple.ts +141 -0
@@ -197,13 +197,59 @@ export const describedNode = (p: ParserLike): Node | undefined =>
197
197
  /** The description of a parser: `Opaque` when it has none. */
198
198
  export const nodeOf = (p: ParserLike): Node => describedNode(p) ?? { _tag: "Opaque", step: p.step }
199
199
 
200
- /** Normalizes a range, as `repeat`, `takeWhile`, ... do. */
200
+ /** How a rejected argument reads in an error: never the argument's own text. */
201
+ const describeArgument = (u: unknown): string =>
202
+ Predicate.isNumber(u) || Predicate.isBigInt(u) || u === undefined || u === null
203
+ ? String(u)
204
+ : Predicate.isString(u)
205
+ ? "a string"
206
+ : "a value that is not a number"
207
+
208
+ /**
209
+ * `count` as given, which must be a non-negative safe integer, else a
210
+ * `RangeError`. A count is a `number` only in the types, and each backend
211
+ * read a negative, fractional, `NaN` or non-number count its own way: the
212
+ * interpreter moved the cursor backwards, and the JIT wrote the value into
213
+ * the source it compiles.
214
+ */
215
+ export const checkCount = (count: number, what: string = "count"): number => {
216
+ if (!Number.isSafeInteger(count) || count < 0)
217
+ throw new RangeError(`invalid ${what}: expected a non-negative integer, got ${describeArgument(count)}`)
218
+ return count
219
+ }
220
+
221
+ /**
222
+ * Normalizes a range, as `repeat`, `takeWhile`, ... do. Throws a
223
+ * `RangeError` unless both bounds are non-negative integers (`max` may be
224
+ * `Infinity`) with `min <= max` (see {@link checkCount}).
225
+ */
201
226
  export const bounds = (
202
227
  range: number | { readonly min?: number | undefined; readonly max?: number | undefined } | undefined
203
228
  ): Bounds => {
204
229
  if (range === undefined) return { min: 0, max: Infinity }
205
- if (Predicate.isNumber(range)) return { min: range, max: range }
206
- return { min: range.min ?? 0, max: range.max ?? Infinity }
230
+ if (!Predicate.isObject(range)) {
231
+ const count = checkCount(range)
232
+ return { min: count, max: count }
233
+ }
234
+ const min = checkCount(range.min ?? 0, "range: min")
235
+ const max = range.max ?? Infinity
236
+ if (max !== Infinity) checkCount(max, "range: max")
237
+ if (min > max) throw new RangeError(`invalid range: min (${min}) > max (${max})`)
238
+ return { min, max }
239
+ }
240
+
241
+ /**
242
+ * The code of a control character given as a one-character string or a
243
+ * code: a non-negative integer, else a `RangeError` (it is compared with
244
+ * input units, and the JIT writes it into the source it compiles).
245
+ */
246
+ export const controlCode = (control: string | number, read: (s: string) => number): number => {
247
+ const code = Predicate.isString(control) ? read(control) : control
248
+ if (!Number.isSafeInteger(code) || code < 0)
249
+ throw new RangeError(
250
+ `invalid control character: expected a character or its code, got ${describeArgument(control)}`
251
+ )
252
+ return code
207
253
  }
208
254
 
209
255
  /**
@@ -33,10 +33,12 @@ export const runOn = <A>(
33
33
 
34
34
  /**
35
35
  * Runs a compiled step on `state`: directly when it guards against
36
- * call-stack overflows itself (the JIT's), else under `Stack.run`.
36
+ * call-stack overflows itself (the JIT's), else under `Stack.run`. While it
37
+ * runs, `state.compiledRun` is set, so the interpreted steps it calls skip
38
+ * building the failures it throws away (#82).
37
39
  */
38
40
  export const runCompiled = <A>(step: (state: State<any>) => A, guarded: boolean, state: State<any>): A | Fail =>
39
- guarded ? step(state) : Stack.run(step, state)
41
+ Stack.runCompiled(step, guarded, state)
40
42
 
41
43
  /**
42
44
  * Runs the compiled step of `compiled` (or, if compiling failed, the
@@ -40,6 +40,23 @@ export const run = <S extends State<any>, A>(step: (state: S) => A, state: S): A
40
40
  }
41
41
  }
42
42
 
43
+ /**
44
+ * Runs a compiled step with `state.compiledRun` set, so that the
45
+ * interpreted steps it calls skip building the failures it throws away
46
+ * (#82): directly when the step guards against call-stack overflows itself
47
+ * (the JIT's), else under {@link run}. The flag is restored however the
48
+ * step ends, a throw included, since the state may be the caller's.
49
+ */
50
+ export const runCompiled = <S extends State<any>, A>(step: (state: S) => A, guarded: boolean, state: S): A | Fail => {
51
+ const outer = state.compiledRun
52
+ state.compiledRun = true
53
+ try {
54
+ return guarded ? step(state) : run(step, state)
55
+ } finally {
56
+ state.compiledRun = outer
57
+ }
58
+ }
59
+
43
60
  /**
44
61
  * Records the `TooDeep` failure of a call-stack overflow. The JIT's
45
62
  * generated entry catches overflows itself, with this, so that the runners
@@ -417,27 +417,37 @@ export const countTokens = (state: State<Input>, start: number, end: number): nu
417
417
  /**
418
418
  * The first index at or after `from` of any of `needles` within
419
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`).
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).
423
430
  */
424
431
  export const indexOfAny = (state: State<Input>, needles: ReadonlyArray<Input>, from: number): number => {
425
432
  const searches = (state.searches ??= new Map())
426
433
  let memo = searches.get(needles)
427
434
  if (memo === undefined) {
428
- memo = needles.flatMap(() => [-1, -1])
435
+ memo = needles.flatMap(() => [-1, -1, -1])
429
436
  searches.set(needles, memo)
430
437
  }
431
438
  let best = -1
432
439
  for (let i = 0; i < needles.length; i++) {
433
- const known = memo[2 * i] ?? -1
434
- let at = memo[2 * i + 1] ?? -1
440
+ const known = memo[3 * i] ?? -1
441
+ let at = memo[3 * i + 1] ?? -1
442
+ const searchedTo = memo[3 * i + 2] ?? -1
435
443
  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))) {
444
+ const stale =
445
+ known < 0 || from < known || (at >= 0 ? at < from || at > state.end - needle.length : state.end > searchedTo)
446
+ if (stale) {
438
447
  at = indexOf(state, needle, from)
439
- memo[2 * i] = from
440
- memo[2 * i + 1] = at
448
+ memo[3 * i] = from
449
+ memo[3 * i + 1] = at
450
+ memo[3 * i + 2] = state.end
441
451
  }
442
452
  if (at >= 0 && (best < 0 || at < best)) best = at
443
453
  }
@@ -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
+ ]