@thomasfosterau/effect-parse 0.0.0-stage → 0.1.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +550 -2
  3. package/dist/Ascii.d.ts +392 -0
  4. package/dist/Ascii.d.ts.map +1 -0
  5. package/dist/Ascii.js +610 -0
  6. package/dist/Ascii.js.map +1 -0
  7. package/dist/Binary.d.ts +153 -0
  8. package/dist/Binary.d.ts.map +1 -0
  9. package/dist/Binary.js +171 -0
  10. package/dist/Binary.js.map +1 -0
  11. package/dist/Bits.d.ts +60 -0
  12. package/dist/Bits.d.ts.map +1 -0
  13. package/dist/Bits.js +144 -0
  14. package/dist/Bits.js.map +1 -0
  15. package/dist/Encoding.d.ts +116 -0
  16. package/dist/Encoding.d.ts.map +1 -0
  17. package/dist/Encoding.js +85 -0
  18. package/dist/Encoding.js.map +1 -0
  19. package/dist/Expression.d.ts +124 -0
  20. package/dist/Expression.d.ts.map +1 -0
  21. package/dist/Expression.js +139 -0
  22. package/dist/Expression.js.map +1 -0
  23. package/dist/Input.d.ts +301 -0
  24. package/dist/Input.d.ts.map +1 -0
  25. package/dist/Input.js +312 -0
  26. package/dist/Input.js.map +1 -0
  27. package/dist/Nom.d.ts +518 -0
  28. package/dist/Nom.d.ts.map +1 -0
  29. package/dist/Nom.js +519 -0
  30. package/dist/Nom.js.map +1 -0
  31. package/dist/ParseError.d.ts +120 -0
  32. package/dist/ParseError.d.ts.map +1 -0
  33. package/dist/ParseError.js +301 -0
  34. package/dist/ParseError.js.map +1 -0
  35. package/dist/Parser.d.ts +1312 -0
  36. package/dist/Parser.d.ts.map +1 -0
  37. package/dist/Parser.js +1936 -0
  38. package/dist/Parser.js.map +1 -0
  39. package/dist/ParserSchema.d.ts +31 -0
  40. package/dist/ParserSchema.d.ts.map +1 -0
  41. package/dist/ParserSchema.js +56 -0
  42. package/dist/ParserSchema.js.map +1 -0
  43. package/dist/ParserStream.d.ts +127 -0
  44. package/dist/ParserStream.d.ts.map +1 -0
  45. package/dist/ParserStream.js +270 -0
  46. package/dist/ParserStream.js.map +1 -0
  47. package/dist/Text.d.ts +330 -0
  48. package/dist/Text.d.ts.map +1 -0
  49. package/dist/Text.js +529 -0
  50. package/dist/Text.js.map +1 -0
  51. package/dist/Token.d.ts +225 -0
  52. package/dist/Token.d.ts.map +1 -0
  53. package/dist/Token.js +294 -0
  54. package/dist/Token.js.map +1 -0
  55. package/dist/index.d.ts +17 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +17 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/internal/compiler/closure.d.ts +25 -0
  60. package/dist/internal/compiler/closure.d.ts.map +1 -0
  61. package/dist/internal/compiler/closure.js +1668 -0
  62. package/dist/internal/compiler/closure.js.map +1 -0
  63. package/dist/internal/compiler/entries.d.ts +19 -0
  64. package/dist/internal/compiler/entries.d.ts.map +1 -0
  65. package/dist/internal/compiler/entries.js +32 -0
  66. package/dist/internal/compiler/entries.js.map +1 -0
  67. package/dist/internal/compiler/first.d.ts +33 -0
  68. package/dist/internal/compiler/first.d.ts.map +1 -0
  69. package/dist/internal/compiler/first.js +235 -0
  70. package/dist/internal/compiler/first.js.map +1 -0
  71. package/dist/internal/compiler/jit.d.ts +20 -0
  72. package/dist/internal/compiler/jit.d.ts.map +1 -0
  73. package/dist/internal/compiler/jit.js +1030 -0
  74. package/dist/internal/compiler/jit.js.map +1 -0
  75. package/dist/internal/compiler/registry.d.ts +34 -0
  76. package/dist/internal/compiler/registry.d.ts.map +1 -0
  77. package/dist/internal/compiler/registry.js +46 -0
  78. package/dist/internal/compiler/registry.js.map +1 -0
  79. package/dist/internal/compiler/sets.d.ts +29 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -0
  81. package/dist/internal/compiler/sets.js +85 -0
  82. package/dist/internal/compiler/sets.js.map +1 -0
  83. package/dist/internal/entries.d.ts +27 -0
  84. package/dist/internal/entries.d.ts.map +1 -0
  85. package/dist/internal/entries.js +42 -0
  86. package/dist/internal/entries.js.map +1 -0
  87. package/dist/internal/node.d.ts +251 -0
  88. package/dist/internal/node.d.ts.map +1 -0
  89. package/dist/internal/node.js +73 -0
  90. package/dist/internal/node.js.map +1 -0
  91. package/dist/internal/numbers.d.ts +41 -0
  92. package/dist/internal/numbers.d.ts.map +1 -0
  93. package/dist/internal/numbers.js +302 -0
  94. package/dist/internal/numbers.js.map +1 -0
  95. package/dist/internal/resume.d.ts +39 -0
  96. package/dist/internal/resume.d.ts.map +1 -0
  97. package/dist/internal/resume.js +37 -0
  98. package/dist/internal/resume.js.map +1 -0
  99. package/dist/internal/text.d.ts +72 -0
  100. package/dist/internal/text.d.ts.map +1 -0
  101. package/dist/internal/text.js +391 -0
  102. package/dist/internal/text.js.map +1 -0
  103. package/dist/internal/tokens.d.ts +93 -0
  104. package/dist/internal/tokens.d.ts.map +1 -0
  105. package/dist/internal/tokens.js +400 -0
  106. package/dist/internal/tokens.js.map +1 -0
  107. package/package.json +115 -3
  108. package/src/Ascii.ts +741 -0
  109. package/src/Binary.ts +228 -0
  110. package/src/Bits.ts +192 -0
  111. package/src/Encoding.ts +128 -0
  112. package/src/Expression.ts +245 -0
  113. package/src/Input.ts +429 -0
  114. package/src/Nom.ts +642 -0
  115. package/src/ParseError.ts +378 -0
  116. package/src/Parser.ts +2763 -0
  117. package/src/ParserSchema.ts +87 -0
  118. package/src/ParserStream.ts +390 -0
  119. package/src/Text.ts +757 -0
  120. package/src/Token.ts +416 -0
  121. package/src/index.ts +16 -0
  122. package/src/internal/compiler/closure.ts +1583 -0
  123. package/src/internal/compiler/entries.ts +36 -0
  124. package/src/internal/compiler/first.ts +252 -0
  125. package/src/internal/compiler/jit.ts +1091 -0
  126. package/src/internal/compiler/registry.ts +79 -0
  127. package/src/internal/compiler/sets.ts +100 -0
  128. package/src/internal/entries.ts +51 -0
  129. package/src/internal/node.ts +207 -0
  130. package/src/internal/numbers.ts +291 -0
  131. package/src/internal/resume.ts +55 -0
  132. package/src/internal/text.ts +411 -0
  133. package/src/internal/tokens.ts +409 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Number conversions shared by the `Ascii` and `Text` parsers and the
3
+ * compilers.
4
+ */
5
+ import type { Input } from "../Input.js"
6
+ import { Fail } from "../Input.js"
7
+ import { latin1 } from "./text.js"
8
+
9
+ const toText = (s: Input): string =>
10
+ typeof s === "string" ? s : s instanceof Uint8Array ? latin1.text(s, 0, s.length) : s.join("")
11
+
12
+ /** The value of the text matched by `Ascii.float` and `Text.float`. */
13
+ export const toFloat = (s: Input): number => {
14
+ if (typeof s === "string") {
15
+ // Plain decimal notation (as opposed to `inf` or `nan`) ends in a digit or "."
16
+ const last = s.charCodeAt(s.length - 1)
17
+ if ((last >= 48 && last <= 57) || last === 46) return Number(s)
18
+ }
19
+ const t = toText(s).toLowerCase()
20
+ const negative = t.startsWith("-")
21
+ const body = t.startsWith("-") || t.startsWith("+") ? t.slice(1) : t
22
+ if (body === "nan") return NaN
23
+ if (body.startsWith("inf")) return negative ? -Infinity : Infinity
24
+ return Number(t)
25
+ }
26
+
27
+ /**
28
+ * The parsers that `Ascii.float` and `Text.float` map with `toFloat`: the
29
+ * compilers replace them by {@link scanFloatString} and
30
+ * {@link scanFloatBytes} when the text is in decimal notation.
31
+ */
32
+ export const floatTexts = new WeakSet<object>()
33
+
34
+ /** Adds a parser to {@link floatTexts}. */
35
+ export const floatText = <P extends object>(p: P): P => {
36
+ floatTexts.add(p)
37
+ return p
38
+ }
39
+
40
+ /** The powers of ten that doubles represent exactly. */
41
+ const exact = Array.from({ length: 23 }, (_, i) => Number(`1e${i}`))
42
+
43
+ /**
44
+ * Where the latest scan ended, when it succeeded, and the furthest offset it
45
+ * read (when it succeeded or failed): streaming builds re-run the parser
46
+ * when that is near the end of a partial input.
47
+ */
48
+ export const floatScan = { end: 0, read: 0 }
49
+
50
+ const isDigit = (c: number): boolean => c >= 48 && c <= 57
51
+
52
+ // Exact products of doubles (Dekker), without allocating: `a * b` is
53
+ // `productHi + productLo` exactly, barring overflow and underflow
54
+ const Split = 134217729 // 2^27 + 1
55
+ let productHi = 0
56
+ let productLo = 0
57
+ const twoProduct = (a: number, b: number): void => {
58
+ const p = a * b
59
+ let t = Split * a
60
+ const ah = t - (t - a)
61
+ const al = a - ah
62
+ t = Split * b
63
+ const bh = t - (t - b)
64
+ const bl = b - bh
65
+ productHi = p
66
+ productLo = ((ah * bh - p) + ah * bl + al * bh) + al * bl
67
+ }
68
+
69
+ /** 2^-96: a bound on the relative error of {@link decimal}'s 19-digit path. */
70
+ const Error19 = 1 / 2 ** 96
71
+
72
+ /**
73
+ * The double nearest to `±(high * 10^low digits + low) * 10^e10`, where
74
+ * `high` holds the first 15 significant digits, and `low` the next `lowDigits`
75
+ * (at most 4). Returns `undefined` when this can't tell it apart from the
76
+ * neighbouring doubles, for the caller to use `Number`.
77
+ *
78
+ * Up to 15 digits, the digits and a power of ten up to 10^22 are exact
79
+ * doubles, so one multiplication or division rounds correctly (Clinger's
80
+ * fast path). Up to 19 digits, the digits are an exact sum of two doubles,
81
+ * and multiplying or dividing that by an exact power of ten gives the value
82
+ * to within a relative 2^-96 (double-double arithmetic). It is then rounded
83
+ * once, and kept only if the bounds of that error round to the same double:
84
+ * this is never the case for an exact tie, so ties go to `Number` too.
85
+ */
86
+ const decimal = (negative: boolean, high: number, low: number, lowDigits: number, e10: number): number | undefined => {
87
+ let v: number
88
+ if (lowDigits === 0) {
89
+ if (e10 > 22 || e10 < -22) return undefined
90
+ v = e10 === 0 ? high : e10 > 0 ? high * exact[e10]! : high / exact[-e10]!
91
+ } else {
92
+ if (e10 > 22 || e10 < -22) return undefined
93
+ // The digits: high * 10^lowDigits + low = wh + wl, exactly
94
+ twoProduct(high, exact[lowDigits]!)
95
+ const wh = productHi + low
96
+ const wl = productLo + (low - (wh - productHi))
97
+ let xh: number
98
+ let xl: number
99
+ if (e10 >= 0) {
100
+ const p = exact[e10]!
101
+ twoProduct(wh, p)
102
+ xh = productHi
103
+ xl = productLo + wl * p
104
+ } else {
105
+ const p = exact[-e10]!
106
+ xh = wh / p
107
+ twoProduct(xh, p)
108
+ xl = ((wh - productHi) - productLo + wl) / p
109
+ }
110
+ v = xh + xl
111
+ const error = (xh < 0 ? -xh : xh) * Error19
112
+ if (xh + (xl + error) !== v || xh + (xl - error) !== v) return undefined
113
+ }
114
+ return negative ? -v : v
115
+ }
116
+
117
+ /**
118
+ * Matches and converts what `Ascii.float` matches at `i`, when it is in
119
+ * decimal notation: an optional sign, then digits or "." (the other forms,
120
+ * `nan` and `inf`, start with a letter). Returns the value, setting
121
+ * `floatScan.end`; `Fail` if the float parser fails there (always with
122
+ * `Backtrack`); or `undefined` if the text does not start like a decimal
123
+ * number, for the caller to run the parser.
124
+ *
125
+ * Numbers with up to 19 significant digits and a decimal exponent up to 22
126
+ * are converted by {@link decimal}, the rest by `Number`, so the value is
127
+ * always what `Number` gives for the text.
128
+ */
129
+ export const scanFloatString = (s: string, i: number, end: number): number | Fail | undefined => {
130
+ const start = i
131
+ let c = i < end ? s.charCodeAt(i) : -1
132
+ if (c === 45 || c === 43) {
133
+ c = i + 1 < end ? s.charCodeAt(i + 1) : -1
134
+ if (!isDigit(c) && c !== 46) return undefined
135
+ i++
136
+ } else if (!isDigit(c) && c !== 46) {
137
+ return undefined
138
+ }
139
+ // The first 15 significant digits, the next 4, how many there are in all,
140
+ // and the decimal exponent of the last one kept
141
+ let high = 0
142
+ let low = 0
143
+ let digits = 0
144
+ let e10 = 0
145
+ if (c !== 46) {
146
+ do {
147
+ if (digits < 15) {
148
+ if (high !== 0 || c !== 48) {
149
+ high = high * 10 + (c - 48)
150
+ digits++
151
+ }
152
+ } else {
153
+ if (digits < 19) low = low * 10 + (c - 48)
154
+ else e10++
155
+ digits++
156
+ }
157
+ c = ++i < end ? s.charCodeAt(i) : -1
158
+ } while (isDigit(c))
159
+ if (c === 46) c = ++i < end ? s.charCodeAt(i) : -1
160
+ } else {
161
+ // "." must be followed by a digit
162
+ c = ++i < end ? s.charCodeAt(i) : -1
163
+ if (!isDigit(c)) {
164
+ floatScan.read = i
165
+ return Fail
166
+ }
167
+ }
168
+ while (isDigit(c)) {
169
+ if (digits < 15) {
170
+ if (high !== 0 || c !== 48) {
171
+ high = high * 10 + (c - 48)
172
+ digits++
173
+ }
174
+ e10--
175
+ } else {
176
+ if (digits < 19) {
177
+ low = low * 10 + (c - 48)
178
+ e10--
179
+ }
180
+ digits++
181
+ }
182
+ c = ++i < end ? s.charCodeAt(i) : -1
183
+ }
184
+ let read = i
185
+ if (c === 101 || c === 69) {
186
+ // The exponent is optional: without digits, it is not consumed
187
+ let j = i + 1
188
+ c = j < end ? s.charCodeAt(j) : -1
189
+ const negative = c === 45
190
+ if (negative || c === 43) c = ++j < end ? s.charCodeAt(j) : -1
191
+ if (isDigit(c)) {
192
+ let e = 0
193
+ do {
194
+ // Past this, the number can only overflow or underflow
195
+ if (e < 1e6) e = e * 10 + (c - 48)
196
+ c = ++j < end ? s.charCodeAt(j) : -1
197
+ } while (isDigit(c))
198
+ e10 += negative ? -e : e
199
+ i = j
200
+ }
201
+ read = j
202
+ }
203
+ floatScan.end = i
204
+ floatScan.read = read
205
+ const negative = s.charCodeAt(start) === 45
206
+ if (high === 0) return negative ? -0 : 0
207
+ const v = digits > 19 ? undefined : decimal(negative, high, low, digits > 15 ? digits - 15 : 0, e10)
208
+ return v === undefined ? Number(s.slice(start, i)) : v
209
+ }
210
+
211
+ /** {@link scanFloatString} on bytes, or the units of an ASCII compatible encoding. */
212
+ export const scanFloatBytes = (b: Uint8Array, i: number, end: number): number | Fail | undefined => {
213
+ const start = i
214
+ let c = i < end ? b[i]! : -1
215
+ if (c === 45 || c === 43) {
216
+ c = i + 1 < end ? b[i + 1]! : -1
217
+ if (!isDigit(c) && c !== 46) return undefined
218
+ i++
219
+ } else if (!isDigit(c) && c !== 46) {
220
+ return undefined
221
+ }
222
+ let high = 0
223
+ let low = 0
224
+ let digits = 0
225
+ let e10 = 0
226
+ if (c !== 46) {
227
+ do {
228
+ if (digits < 15) {
229
+ if (high !== 0 || c !== 48) {
230
+ high = high * 10 + (c - 48)
231
+ digits++
232
+ }
233
+ } else {
234
+ if (digits < 19) low = low * 10 + (c - 48)
235
+ else e10++
236
+ digits++
237
+ }
238
+ c = ++i < end ? b[i]! : -1
239
+ } while (isDigit(c))
240
+ if (c === 46) c = ++i < end ? b[i]! : -1
241
+ } else {
242
+ c = ++i < end ? b[i]! : -1
243
+ if (!isDigit(c)) {
244
+ floatScan.read = i
245
+ return Fail
246
+ }
247
+ }
248
+ while (isDigit(c)) {
249
+ if (digits < 15) {
250
+ if (high !== 0 || c !== 48) {
251
+ high = high * 10 + (c - 48)
252
+ digits++
253
+ }
254
+ e10--
255
+ } else {
256
+ if (digits < 19) {
257
+ low = low * 10 + (c - 48)
258
+ e10--
259
+ }
260
+ digits++
261
+ }
262
+ c = ++i < end ? b[i]! : -1
263
+ }
264
+ let read = i
265
+ if (c === 101 || c === 69) {
266
+ let j = i + 1
267
+ c = j < end ? b[j]! : -1
268
+ const negative = c === 45
269
+ if (negative || c === 43) c = ++j < end ? b[j]! : -1
270
+ if (isDigit(c)) {
271
+ let e = 0
272
+ do {
273
+ if (e < 1e6) e = e * 10 + (c - 48)
274
+ c = ++j < end ? b[j]! : -1
275
+ } while (isDigit(c))
276
+ e10 += negative ? -e : e
277
+ i = j
278
+ }
279
+ read = j
280
+ }
281
+ floatScan.end = i
282
+ floatScan.read = read
283
+ const negative = b[start] === 45
284
+ if (high === 0) return negative ? -0 : 0
285
+ const v = digits > 19 ? undefined : decimal(negative, high, low, digits > 15 ? digits - 15 : 0, e10)
286
+ return v === undefined ? Number(asciiText(b, start, i)) : v
287
+ }
288
+
289
+ /** The text of ASCII bytes. */
290
+ const asciiText = (b: Uint8Array, start: number, end: number): string =>
291
+ String.fromCharCode.apply(null, b.subarray(start, end) as unknown as Array<number>)
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Checkpoints for resumable (streaming) parses.
3
+ *
4
+ * In partial mode a parser only succeeds on what it has seen, so a value
5
+ * parsed from a prefix of the input stays the same when more input
6
+ * arrives. A repetition that runs out of input mid-way can therefore record
7
+ * how far it got (its accumulator and position) before the failure
8
+ * propagates. When the parse is attempted again with more input, the same
9
+ * repetition, entered at the same offset, takes its checkpoint and carries
10
+ * on from there instead of re-parsing every element.
11
+ *
12
+ * Checkpoints are keyed by a per-parser key and the offset the repetition
13
+ * started at. Taking a checkpoint removes it, so its accumulator has a
14
+ * single owner; the repetition saves a new one if it runs out again.
15
+ *
16
+ * A resumed repetition that then completes leaves a {@link Done}
17
+ * checkpoint with its result, since the input may still run out after it
18
+ * (in trailing whitespace, say) and the next attempt would otherwise parse
19
+ * it all again. `Done` checkpoints stay in place when taken.
20
+ */
21
+ import type { State } from "../Input.js"
22
+
23
+ /** The checkpoints of a resumable parse. */
24
+ export type Checkpoints = Map<object, Map<number, unknown>>
25
+
26
+ /** Takes (and removes) the checkpoint of `key` entered at `offset`, if any. */
27
+ export const take = (state: State<any>, key: object, offset: number): unknown => {
28
+ const byKey = state.resume!.get(key)
29
+ if (byKey === undefined) return undefined
30
+ const checkpoint = byKey.get(offset)
31
+ if (checkpoint !== undefined && !(checkpoint instanceof Done)) byKey.delete(offset)
32
+ return checkpoint
33
+ }
34
+
35
+ /** The result of a repetition that completed, and where it ended. */
36
+ export class Done {
37
+ constructor(readonly value: unknown, readonly pos: number) {}
38
+ }
39
+
40
+ /**
41
+ * Returns `value`, the result of a repetition entered at `entry`; when the
42
+ * repetition was resumed from a checkpoint, records it as {@link Done}.
43
+ */
44
+ export const done = <A>(state: State<any>, key: object, entry: number, resumed: boolean, value: A): A => {
45
+ if (resumed && state.resume !== undefined) save(state, key, entry, new Done(value, state.pos))
46
+ return value
47
+ }
48
+
49
+ /** Records the checkpoint of `key` entered at `offset`. */
50
+ export const save = (state: State<any>, key: object, offset: number, checkpoint: unknown): void => {
51
+ const resume = state.resume!
52
+ let byKey = resume.get(key)
53
+ if (byKey === undefined) resume.set(key, byKey = new Map())
54
+ byKey.set(offset, checkpoint)
55
+ }