@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,409 @@
1
+ import type { Input, State } from "../Input.js"
2
+
3
+ /** A compiled token set. */
4
+ export interface Matcher {
5
+ /** Tests a code point (string input) or a byte (Uint8Array input). */
6
+ readonly code: (code: number) => boolean
7
+ /** Tests an element of an array input. */
8
+ readonly token: (token: unknown) => boolean
9
+ /** Human readable description, used in error messages. */
10
+ readonly description: string
11
+ }
12
+
13
+ /** @internal */
14
+ export interface TokenRangeLike {
15
+ readonly _tag: "TokenRange"
16
+ readonly from: string | number
17
+ readonly to: string | number
18
+ }
19
+
20
+ const isRange = (u: unknown): u is TokenRangeLike =>
21
+ typeof u === "object" && u !== null && (u as any)._tag === "TokenRange"
22
+
23
+ const toCode = (u: string | number): number => typeof u === "number" ? u : u.codePointAt(0)!
24
+
25
+ /** @internal */
26
+ export const describeChar = (s: string): string => JSON.stringify(s)
27
+
28
+ const describeSet = (u: unknown): string => {
29
+ if (typeof u === "string") {
30
+ const chars = Array.from(u)
31
+ return chars.length === 1 ? describeChar(u) : `one of ${JSON.stringify(u)}`
32
+ }
33
+ if (typeof u === "number") return `0x${u.toString(16).padStart(2, "0")}`
34
+ if (typeof u === "function") return u.name ? u.name : "a matching token"
35
+ if (u instanceof Uint8Array) return `one of ${describeBytes(u)}`
36
+ if (isRange(u)) return `${describeSet(u.from)}..=${describeSet(u.to)}`
37
+ if (Array.isArray(u)) return u.map(describeSet).join(" or ")
38
+ return JSON.stringify(u)
39
+ }
40
+
41
+ /** Renders bytes as a Rust-style byte string literal: b"GET\r\n". */
42
+ export const describeBytes = (bytes: Uint8Array): string => {
43
+ let out = "b\""
44
+ for (const b of bytes) {
45
+ if (b === 0x22) out += "\\\""
46
+ else if (b === 0x5c) out += "\\\\"
47
+ else if (b === 0x0a) out += "\\n"
48
+ else if (b === 0x0d) out += "\\r"
49
+ else if (b === 0x09) out += "\\t"
50
+ else if (b >= 0x20 && b < 0x7f) out += String.fromCharCode(b)
51
+ else out += `\\x${b.toString(16).padStart(2, "0")}`
52
+ }
53
+ return out + "\""
54
+ }
55
+
56
+ const compileCode = (u: unknown): (code: number) => boolean => {
57
+ if (typeof u === "string") {
58
+ const codes = Array.from(u, (c) => c.codePointAt(0)!)
59
+ if (codes.length === 1) {
60
+ const c = codes[0]!
61
+ return (x) => x === c
62
+ }
63
+ return bitmap(codes)
64
+ }
65
+ if (typeof u === "number") return (x) => x === u
66
+ if (typeof u === "function") return u as (code: number) => boolean
67
+ if (u instanceof Uint8Array) return bitmap(Array.from(u))
68
+ if (isRange(u)) {
69
+ const from = toCode(u.from)
70
+ const to = toCode(u.to)
71
+ return (x) => x >= from && x <= to
72
+ }
73
+ if (Array.isArray(u)) {
74
+ const tests = u.map(compileCode)
75
+ if (tests.length === 1) return tests[0]!
76
+ return (x) => {
77
+ for (let i = 0; i < tests.length; i++) if (tests[i]!(x)) return true
78
+ return false
79
+ }
80
+ }
81
+ throw new TypeError(`invalid token set: ${String(u)}`)
82
+ }
83
+
84
+ const bitmap = (codes: ReadonlyArray<number>): (code: number) => boolean => {
85
+ const ascii = new Uint8Array(256)
86
+ let other: Set<number> | undefined
87
+ for (const c of codes) {
88
+ if (c < 256) ascii[c] = 1
89
+ else (other ??= new Set()).add(c)
90
+ }
91
+ if (other === undefined) return (x) => x < 256 && ascii[x] === 1
92
+ const set = other
93
+ return (x) => x < 256 ? ascii[x] === 1 : set.has(x)
94
+ }
95
+
96
+ const compileToken = (u: unknown): (token: unknown) => boolean => {
97
+ if (typeof u === "function") return u as (token: unknown) => boolean
98
+ if (Array.isArray(u)) {
99
+ const set = new Set(u)
100
+ return (t) => set.has(t)
101
+ }
102
+ return (t) => t === u
103
+ }
104
+
105
+ /**
106
+ * Marks a predicate over characters that also accepts a code point (a
107
+ * number), so string inputs can call it without allocating a string per
108
+ * character.
109
+ */
110
+ export const CodePointPredicate: unique symbol = Symbol.for("effect-parse/CodePointPredicate")
111
+
112
+ /**
113
+ * The code points accepted by a predicate, as sorted, disjoint, inclusive
114
+ * ranges, when they are known. The compiler turns such predicates into
115
+ * comparisons or tables.
116
+ */
117
+ export const PredicateRanges: unique symbol = Symbol.for("effect-parse/PredicateRanges")
118
+
119
+ /**
120
+ * Marks `f` as a {@link CodePointPredicate} named `name` (used in error
121
+ * messages), optionally recording the code point ranges it accepts.
122
+ */
123
+ export const codePointPredicate = <F extends (c: never) => boolean>(
124
+ name: string,
125
+ f: F,
126
+ ranges?: ReadonlyArray<readonly [number, number]>
127
+ ): F => {
128
+ Object.defineProperty(f, "name", { value: name })
129
+ ;(f as any)[CodePointPredicate] = true
130
+ if (ranges !== undefined) (f as any)[PredicateRanges] = ranges
131
+ return f
132
+ }
133
+
134
+ /**
135
+ * Compiles a token set. Predicates for string inputs receive the token as a
136
+ * string, so they are wrapped here.
137
+ */
138
+ export const compile = (set: unknown, kind: "string" | "code" = "code"): Matcher => {
139
+ const description = describeSet(set)
140
+ let code: (code: number) => boolean
141
+ if (typeof set === "function" && kind === "string" && !(CodePointPredicate in set)) {
142
+ const pred = set as (c: string) => boolean
143
+ code = (x) => pred(String.fromCodePoint(x))
144
+ } else {
145
+ code = compileCodeSafe(set, kind)
146
+ }
147
+ return { code, token: compileToken(set), description }
148
+ }
149
+
150
+ const compileCodeSafe = (set: unknown, kind: "string" | "code"): (code: number) => boolean => {
151
+ if (Array.isArray(set) && kind === "string") {
152
+ // Predicates nested in arrays also take strings
153
+ const tests = set.map((s) => compile(s, kind).code)
154
+ return (x) => {
155
+ for (let i = 0; i < tests.length; i++) if (tests[i]!(x)) return true
156
+ return false
157
+ }
158
+ }
159
+ try {
160
+ return compileCode(set)
161
+ } catch {
162
+ return () => false
163
+ }
164
+ }
165
+
166
+ /**
167
+ * A matcher that works for every input kind. String predicates receive the
168
+ * token as a string, byte predicates receive numbers.
169
+ */
170
+ export interface AnyMatcher {
171
+ readonly forString: Matcher
172
+ readonly forCode: Matcher
173
+ readonly description: string
174
+ }
175
+
176
+ export const compileAny = (set: unknown): AnyMatcher => {
177
+ const forCode = compile(set, "code")
178
+ const needsString = typeof set === "function" || Array.isArray(set)
179
+ return {
180
+ forString: needsString ? compile(set, "string") : forCode,
181
+ forCode,
182
+ description: forCode.description
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Whether the string token at `i` is a high surrogate at the very end of a
188
+ * partial input, i.e. the first half of a code point whose second half has
189
+ * not arrived yet.
190
+ */
191
+ export const splitSurrogate = (state: State<Input>, i: number): boolean => {
192
+ if (!state.partial || i !== state.end - 1) return false
193
+ const c = (state.input as string).charCodeAt(i)
194
+ return c >= 0xd800 && c <= 0xdbff
195
+ }
196
+
197
+ /** Reads the code point at `i` of a string. */
198
+ export const codePointAt = (s: string, i: number): number => {
199
+ const c = s.charCodeAt(i)
200
+ if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {
201
+ const d = s.charCodeAt(i + 1)
202
+ if (d >= 0xdc00 && d <= 0xdfff) return ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000
203
+ }
204
+ return c
205
+ }
206
+
207
+ /**
208
+ * Tests the token at the current position. Returns the token (as the input's
209
+ * token type) and advances, or returns `NoMatch`/`End`.
210
+ */
211
+ export const NoMatch: unique symbol = Symbol("NoMatch")
212
+ export const End: unique symbol = Symbol("End")
213
+
214
+ export const nextIf = (
215
+ state: State<Input>,
216
+ m: AnyMatcher,
217
+ negate: boolean
218
+ ): unknown => {
219
+ const pos = state.pos
220
+ if (pos >= state.end) return End
221
+ switch (state.inputKind) {
222
+ case 0: {
223
+ const s = state.input as string
224
+ if (splitSurrogate(state, pos)) return End
225
+ const cp = codePointAt(s, pos)
226
+ if (m.forString.code(cp) === negate) return NoMatch
227
+ const len = cp > 0xffff ? 2 : 1
228
+ state.pos = pos + len
229
+ return len === 1 ? s[pos] : s.slice(pos, pos + 2)
230
+ }
231
+ case 1: {
232
+ const b = (state.input as Uint8Array)[pos]!
233
+ if (m.forCode.code(b) === negate) return NoMatch
234
+ state.pos = pos + 1
235
+ return b
236
+ }
237
+ default: {
238
+ const t = (state.input as ReadonlyArray<unknown>)[pos]
239
+ if (m.forCode.token(t) === negate) return NoMatch
240
+ state.pos = pos + 1
241
+ return t
242
+ }
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Scans forward while tokens satisfy the matcher (xor `negate`), up to `max`
248
+ * tokens. Returns the offset reached, the number of tokens matched, and
249
+ * whether the end of input was hit before `max` tokens.
250
+ */
251
+ export const scan = (
252
+ state: State<Input>,
253
+ m: AnyMatcher,
254
+ negate: boolean,
255
+ max: number
256
+ ): { readonly offset: number; readonly count: number; readonly hitEnd: boolean } => {
257
+ let i = state.pos
258
+ const end = state.end
259
+ let count = 0
260
+ switch (state.inputKind) {
261
+ case 0: {
262
+ const s = state.input as string
263
+ const test = m.forString.code
264
+ while (count < max) {
265
+ if (i >= end) return { offset: i, count, hitEnd: true }
266
+ const c = s.charCodeAt(i)
267
+ if (c < 0xd800 || c > 0xdbff) {
268
+ if (test(c) === negate) break
269
+ i++
270
+ } else {
271
+ if (splitSurrogate(state, i)) return { offset: i, count, hitEnd: true }
272
+ const cp = codePointAt(s, i)
273
+ if (test(cp) === negate) break
274
+ i += cp > 0xffff ? 2 : 1
275
+ }
276
+ count++
277
+ }
278
+ return { offset: i, count, hitEnd: false }
279
+ }
280
+ case 1: {
281
+ const bytes = state.input as Uint8Array
282
+ const test = m.forCode.code
283
+ while (count < max) {
284
+ if (i >= end) return { offset: i, count, hitEnd: true }
285
+ if (test(bytes[i]!) === negate) break
286
+ i++
287
+ count++
288
+ }
289
+ return { offset: i, count, hitEnd: false }
290
+ }
291
+ default: {
292
+ const arr = state.input as ReadonlyArray<unknown>
293
+ const test = m.forCode.token
294
+ while (count < max) {
295
+ if (i >= end) return { offset: i, count, hitEnd: true }
296
+ if (test(arr[i]) === negate) break
297
+ i++
298
+ count++
299
+ }
300
+ return { offset: i, count, hitEnd: false }
301
+ }
302
+ }
303
+ }
304
+
305
+ /** Advances `n` tokens from `start`, returning the offset reached and the count. */
306
+ export const advance = (state: State<Input>, start: number, n: number): { offset: number; count: number } => {
307
+ if (state.inputKind !== 0) {
308
+ const offset = Math.min(state.end, start + n)
309
+ return { offset, count: offset - start }
310
+ }
311
+ const s = state.input as string
312
+ let i = start
313
+ let count = 0
314
+ while (count < n && i < state.end) {
315
+ if (splitSurrogate(state, i)) break
316
+ const c = s.charCodeAt(i)
317
+ i += c >= 0xd800 && c <= 0xdbff && i + 1 < s.length && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00 ? 2 : 1
318
+ count++
319
+ }
320
+ return { offset: i, count }
321
+ }
322
+
323
+ /** Counts tokens between two offsets. */
324
+ export const countTokens = (state: State<Input>, start: number, end: number): number => {
325
+ if (state.inputKind !== 0) return end - start
326
+ const s = state.input as string
327
+ let count = 0
328
+ for (let i = start; i < end; i++) {
329
+ const c = s.charCodeAt(i)
330
+ if (c >= 0xd800 && c <= 0xdbff && i + 1 < end && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) i++
331
+ count++
332
+ }
333
+ return count
334
+ }
335
+
336
+ /** Finds `needle` in the input at or after `from`; returns -1 if absent. */
337
+ export const indexOf = (state: State<Input>, needle: Input, from: number): number => {
338
+ switch (state.inputKind) {
339
+ case 0:
340
+ return (state.input as string).indexOf(needle as string, from)
341
+ case 1: {
342
+ const hay = state.input as Uint8Array
343
+ const n = needle as Uint8Array
344
+ if (n.length === 0) return from <= hay.length ? from : -1
345
+ const first = n[0]!
346
+ let i = hay.indexOf(first, from)
347
+ while (i !== -1 && i + n.length <= hay.length) {
348
+ let j = 1
349
+ while (j < n.length && hay[i + j] === n[j]) j++
350
+ if (j === n.length) return i
351
+ i = hay.indexOf(first, i + 1)
352
+ }
353
+ return -1
354
+ }
355
+ default: {
356
+ const hay = state.input as ReadonlyArray<unknown>
357
+ const n = needle as ReadonlyArray<unknown>
358
+ outer: for (let i = from; i + n.length <= hay.length; i++) {
359
+ for (let j = 0; j < n.length; j++) if (hay[i + j] !== n[j]) continue outer
360
+ return i
361
+ }
362
+ return -1
363
+ }
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Compares the input at the current position with a literal. Returns the
369
+ * number of matching units if the literal matches completely (its length),
370
+ * -1 on mismatch, or -2 when the input ends while matching a prefix.
371
+ */
372
+ export const matchLiteral = (state: State<Input>, lit: Input, caseless: boolean): number => {
373
+ const pos = state.pos
374
+ const len = lit.length
375
+ const avail = state.end - pos
376
+ const n = Math.min(len, avail)
377
+ switch (state.inputKind) {
378
+ case 0: {
379
+ const s = state.input as string
380
+ const l = lit as string
381
+ if (!caseless) {
382
+ if (avail >= len) return s.startsWith(l, pos) ? len : -1
383
+ return l.startsWith(s.slice(pos)) ? -2 : -1
384
+ }
385
+ for (let i = 0; i < n; i++) {
386
+ if (lowerAscii(s.charCodeAt(pos + i)) !== lowerAscii(l.charCodeAt(i))) return -1
387
+ }
388
+ return n === len ? len : -2
389
+ }
390
+ case 1: {
391
+ const b = state.input as Uint8Array
392
+ const l = lit as Uint8Array
393
+ for (let i = 0; i < n; i++) {
394
+ const x = b[pos + i]!
395
+ const y = l[i]!
396
+ if (caseless ? lowerAscii(x) !== lowerAscii(y) : x !== y) return -1
397
+ }
398
+ return n === len ? len : -2
399
+ }
400
+ default: {
401
+ const a = state.input as ReadonlyArray<unknown>
402
+ const l = lit as ReadonlyArray<unknown>
403
+ for (let i = 0; i < n; i++) if (a[pos + i] !== l[i]) return -1
404
+ return n === len ? len : -2
405
+ }
406
+ }
407
+ }
408
+
409
+ const lowerAscii = (c: number): number => c >= 65 && c <= 90 ? c + 32 : c