@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
package/src/Ascii.ts ADDED
@@ -0,0 +1,741 @@
1
+ /**
2
+ * Parsers for ASCII text: character classes, line endings, numbers and
3
+ * escaped strings (winnow's `ascii` module, nom's `character` module).
4
+ *
5
+ * The parsers are typed for `string` input. Each also works on
6
+ * `Uint8Array` input (treating bytes as ASCII), and `Ascii.bytes` gives the
7
+ * same parsers typed for bytes:
8
+ *
9
+ * ```ts
10
+ * import { Ascii, Parser } from "@thomasfosterau/effect-parse"
11
+ *
12
+ * Parser.parse(Ascii.decUint, "42") // 42
13
+ * Parser.parse(Ascii.bytes.decUint, new TextEncoder().encode("42")) // 42
14
+ * ```
15
+ *
16
+ * Partial (streaming) input is supported: for example `digit1` on the
17
+ * partial input `"123"` reports `Incomplete`, since more digits may follow.
18
+ *
19
+ * For Unicode character classes, or for text in other encodings (UTF-8,
20
+ * UTF-16), use the `Text` module.
21
+ *
22
+ * @since 0.1.0
23
+ */
24
+ import * as Result from "effect/Result"
25
+ import type { Encoding } from "./Encoding.js"
26
+ import type { Input, Slice, State } from "./Input.js"
27
+ import { Fail } from "./Input.js"
28
+ import type { ErrorKind } from "./ParseError.js"
29
+ import type { Parser } from "./Parser.js"
30
+ import * as P from "./Parser.js"
31
+ import * as numbers from "./internal/numbers.js"
32
+ import * as Resume from "./internal/resume.js"
33
+ import { latin1 } from "./internal/text.js"
34
+ import { codePointPredicate } from "./internal/tokens.js"
35
+ import { literalImpl, range, takeWhileImpl } from "./Token.js"
36
+ import * as Token from "./Token.js"
37
+
38
+ // -----------------------------------------------------------------------------
39
+ // Character predicates
40
+ // -----------------------------------------------------------------------------
41
+
42
+ const code = (c: string | number): number => typeof c === "number" ? c : c.charCodeAt(0)
43
+
44
+ /**
45
+ * `[a-zA-Z]`
46
+ *
47
+ * @since 0.1.0
48
+ * @category predicates
49
+ */
50
+ export const isAlpha: (c: string | number) => boolean = codePointPredicate("isAlpha", (c: string | number): boolean => {
51
+ const x = code(c)
52
+ return (x >= 65 && x <= 90) || (x >= 97 && x <= 122)
53
+ }, [[65, 90], [97, 122]])
54
+
55
+ /**
56
+ * `[0-9]`
57
+ *
58
+ * @since 0.1.0
59
+ * @category predicates
60
+ */
61
+ export const isDigit: (c: string | number) => boolean = codePointPredicate("isDigit", (c: string | number): boolean => {
62
+ const x = code(c)
63
+ return x >= 48 && x <= 57
64
+ }, [[48, 57]])
65
+
66
+ /**
67
+ * `[0-9a-fA-F]`
68
+ *
69
+ * @since 0.1.0
70
+ * @category predicates
71
+ */
72
+ export const isHexDigit: (c: string | number) => boolean = codePointPredicate("isHexDigit", (c: string | number): boolean => {
73
+ const x = code(c)
74
+ return (x >= 48 && x <= 57) || (x >= 65 && x <= 70) || (x >= 97 && x <= 102)
75
+ }, [[48, 57], [65, 70], [97, 102]])
76
+
77
+ /**
78
+ * `[0-7]`
79
+ *
80
+ * @since 0.1.0
81
+ * @category predicates
82
+ */
83
+ export const isOctDigit: (c: string | number) => boolean = codePointPredicate("isOctDigit", (c: string | number): boolean => {
84
+ const x = code(c)
85
+ return x >= 48 && x <= 55
86
+ }, [[48, 55]])
87
+
88
+ /**
89
+ * `[0-9a-zA-Z]`
90
+ *
91
+ * @since 0.1.0
92
+ * @category predicates
93
+ */
94
+ export const isAlphanumeric: (c: string | number) => boolean = codePointPredicate(
95
+ "isAlphanumeric",
96
+ (c: string | number): boolean => isAlpha(c) || isDigit(c),
97
+ [[48, 57], [65, 90], [97, 122]]
98
+ )
99
+
100
+ /**
101
+ * `[ \t]`
102
+ *
103
+ * @since 0.1.0
104
+ * @category predicates
105
+ */
106
+ export const isSpace: (c: string | number) => boolean = codePointPredicate("isSpace", (c: string | number): boolean => {
107
+ const x = code(c)
108
+ return x === 32 || x === 9
109
+ }, [[9, 9], [32, 32]])
110
+
111
+ /**
112
+ * `[ \t\r\n]`
113
+ *
114
+ * @since 0.1.0
115
+ * @category predicates
116
+ */
117
+ export const isMultispace: (c: string | number) => boolean = codePointPredicate("isMultispace", (c: string | number): boolean => {
118
+ const x = code(c)
119
+ return x === 32 || x === 9 || x === 10 || x === 13
120
+ }, [[9, 10], [13, 13], [32, 32]])
121
+
122
+ /**
123
+ * `\n`
124
+ *
125
+ * @since 0.1.0
126
+ * @category predicates
127
+ */
128
+ export const isNewline: (c: string | number) => boolean = codePointPredicate("isNewline", (c: string | number): boolean => code(c) === 10, [[10, 10]])
129
+
130
+ // Token sets usable for both string and byte input
131
+ const alphaSet = [range("a", "z"), range("A", "Z")]
132
+ const digitSet = range("0", "9")
133
+ const hexSet = [range("0", "9"), range("a", "f"), range("A", "F")]
134
+ const octSet = range("0", "7")
135
+ const alnumSet = [range("a", "z"), range("A", "Z"), range("0", "9")]
136
+ const spaceSet = " \t"
137
+ const multispaceSet = " \t\r\n"
138
+
139
+ const cls = (set: unknown, min: 0 | 1, description: string, kind: ErrorKind, hint = true): Parser<string> =>
140
+ takeWhileImpl<string>(set, { min }, false, `${kind.toLowerCase()}${min}`, description, kind, hint)
141
+
142
+ // -----------------------------------------------------------------------------
143
+ // Character classes
144
+ // -----------------------------------------------------------------------------
145
+
146
+ /**
147
+ * Zero or more ASCII letters, `[a-zA-Z]*`.
148
+ *
149
+ * @since 0.1.0
150
+ * @category character classes
151
+ */
152
+ export const alpha0: Parser<string> = cls(alphaSet, 0, "letter", "Alpha")
153
+
154
+ /**
155
+ * One or more ASCII letters, `[a-zA-Z]+`.
156
+ *
157
+ * @since 0.1.0
158
+ * @category character classes
159
+ */
160
+ export const alpha1: Parser<string> = cls(alphaSet, 1, "letter", "Alpha")
161
+
162
+ /**
163
+ * Zero or more ASCII digits, `[0-9]*`.
164
+ *
165
+ * @since 0.1.0
166
+ * @category character classes
167
+ */
168
+ export const digit0: Parser<string> = cls(digitSet, 0, "digit", "Digit")
169
+
170
+ /**
171
+ * One or more ASCII digits, `[0-9]+`.
172
+ *
173
+ * @since 0.1.0
174
+ * @category character classes
175
+ */
176
+ export const digit1: Parser<string> = cls(digitSet, 1, "digit", "Digit")
177
+
178
+ /**
179
+ * Zero or more hexadecimal digits, `[0-9a-fA-F]*`.
180
+ *
181
+ * @since 0.1.0
182
+ * @category character classes
183
+ */
184
+ export const hexDigit0: Parser<string> = cls(hexSet, 0, "hexadecimal digit", "HexDigit")
185
+
186
+ /**
187
+ * One or more hexadecimal digits, `[0-9a-fA-F]+`.
188
+ *
189
+ * @since 0.1.0
190
+ * @category character classes
191
+ */
192
+ export const hexDigit1: Parser<string> = cls(hexSet, 1, "hexadecimal digit", "HexDigit")
193
+
194
+ /**
195
+ * Zero or more octal digits, `[0-7]*`.
196
+ *
197
+ * @since 0.1.0
198
+ * @category character classes
199
+ */
200
+ export const octDigit0: Parser<string> = cls(octSet, 0, "octal digit", "OctDigit")
201
+
202
+ /**
203
+ * One or more octal digits, `[0-7]+`.
204
+ *
205
+ * @since 0.1.0
206
+ * @category character classes
207
+ */
208
+ export const octDigit1: Parser<string> = cls(octSet, 1, "octal digit", "OctDigit")
209
+
210
+ /**
211
+ * Zero or more ASCII letters or digits, `[0-9a-zA-Z]*`.
212
+ *
213
+ * @since 0.1.0
214
+ * @category character classes
215
+ */
216
+ export const alphanumeric0: Parser<string> = cls(alnumSet, 0, "letter or digit", "AlphaNumeric")
217
+
218
+ /**
219
+ * One or more ASCII letters or digits, `[0-9a-zA-Z]+`.
220
+ *
221
+ * @since 0.1.0
222
+ * @category character classes
223
+ */
224
+ export const alphanumeric1: Parser<string> = cls(alnumSet, 1, "letter or digit", "AlphaNumeric")
225
+
226
+ /**
227
+ * Zero or more spaces and tabs, `[ \t]*`.
228
+ *
229
+ * @since 0.1.0
230
+ * @category character classes
231
+ */
232
+ export const space0: Parser<string> = cls(spaceSet, 0, "space", "Space", false)
233
+
234
+ /**
235
+ * One or more spaces and tabs, `[ \t]+`.
236
+ *
237
+ * @since 0.1.0
238
+ * @category character classes
239
+ */
240
+ export const space1: Parser<string> = cls(spaceSet, 1, "space", "Space", false)
241
+
242
+ /**
243
+ * Zero or more spaces, tabs, carriage returns and line feeds,
244
+ * `[ \t\r\n]*`.
245
+ *
246
+ * @since 0.1.0
247
+ * @category character classes
248
+ */
249
+ export const multispace0: Parser<string> = cls(multispaceSet, 0, "whitespace", "MultiSpace", false)
250
+
251
+ /**
252
+ * One or more spaces, tabs, carriage returns and line feeds,
253
+ * `[ \t\r\n]+`.
254
+ *
255
+ * @since 0.1.0
256
+ * @category character classes
257
+ */
258
+ export const multispace1: Parser<string> = cls(multispaceSet, 1, "whitespace", "MultiSpace", false)
259
+
260
+ // -----------------------------------------------------------------------------
261
+ // Line endings
262
+ // -----------------------------------------------------------------------------
263
+
264
+ /**
265
+ * A carriage return followed by a line feed, `\r\n`.
266
+ *
267
+ * @since 0.1.0
268
+ * @category line endings
269
+ */
270
+ export const crlf: Parser<"\r\n"> = Token.literal("\r\n")
271
+
272
+ /**
273
+ * A line feed, `\n` (nom's `newline`).
274
+ *
275
+ * @since 0.1.0
276
+ * @category line endings
277
+ */
278
+ export const newline: Parser<"\n"> = Token.literal("\n")
279
+
280
+ /**
281
+ * A tab, `\t`.
282
+ *
283
+ * @since 0.1.0
284
+ * @category line endings
285
+ */
286
+ export const tab: Parser<"\t"> = Token.literal("\t")
287
+
288
+ /**
289
+ * A line ending: `\n` or `\r\n`.
290
+ *
291
+ * @since 0.1.0
292
+ * @category line endings
293
+ */
294
+ export const lineEnding: Parser<"\n" | "\r\n"> = P.alt(newline, crlf).pipe(P.expected("line ending"))
295
+
296
+ const expectedLineEnding = ["line ending"]
297
+
298
+ /**
299
+ * Everything up to (not including) a line ending, `[^\r\n]*` (winnow's
300
+ * `till_line_ending`, nom's `not_line_ending`). A carriage return that is
301
+ * not followed by a line feed is an error.
302
+ *
303
+ * @since 0.1.0
304
+ * @category line endings
305
+ */
306
+ export const tillLineEnding: Parser<string> = P.described<string>((state: State<any>) => {
307
+ const start = state.pos
308
+ const input = state.input
309
+ const isString = state.inputKind === 0
310
+ let i = start
311
+ while (i < state.end) {
312
+ const c = isString ? (input as string).charCodeAt(i) : (input as Uint8Array)[i]
313
+ if (c === 10) break
314
+ if (c === 13) {
315
+ if (i + 1 >= state.end) {
316
+ if (state.partial) return state.incomplete(1, expectedLineEnding)
317
+ return state.backtrack("LineEnding", expectedLineEnding, i)
318
+ }
319
+ const d = isString ? (input as string).charCodeAt(i + 1) : (input as Uint8Array)[i + 1]
320
+ if (d === 10) break
321
+ return state.backtrack("LineEnding", expectedLineEnding, i)
322
+ }
323
+ i++
324
+ }
325
+ if (i >= state.end && state.partial) return state.incomplete(1, expectedLineEnding)
326
+ state.pos = i
327
+ return state.slice(start, i) as string
328
+ }, "tillLineEnding", { _tag: "TillLineEnding", encoding: undefined })
329
+
330
+ // -----------------------------------------------------------------------------
331
+ // Numbers
332
+ // -----------------------------------------------------------------------------
333
+
334
+ const toText = (s: Input): string =>
335
+ typeof s === "string" ? s : s instanceof Uint8Array ? latin1.text(s, 0, s.length) : s.join("")
336
+
337
+
338
+ /** @internal */
339
+ export const overflow = (value: number): Result.Result<number, string> =>
340
+ Number.isSafeInteger(value) ? Result.succeed(value) : Result.fail("integer overflow")
341
+
342
+ /**
343
+ * An unsigned decimal integer: one or more digits (winnow's `dec_uint`,
344
+ * nom's `u32`/`u64`). Fails if the value is not a safe integer.
345
+ *
346
+ * @since 0.1.0
347
+ * @category numbers
348
+ */
349
+ export const decUint: Parser<number, string> = digit1.pipe(
350
+ P.tryMap((s) => overflow(Number(toText(s)))),
351
+ P.named("decUint")
352
+ )
353
+
354
+ /**
355
+ * A signed decimal integer: an optional `+` or `-` followed by one or more
356
+ * digits (winnow's `dec_int`, nom's `i32`/`i64`). Fails if the value is not
357
+ * a safe integer.
358
+ *
359
+ * @since 0.1.0
360
+ * @category numbers
361
+ */
362
+ export const decInt: Parser<number, string> = P.taken(P.seq(P.opt(Token.oneOf("+-")), digit1)).pipe(
363
+ P.tryMap((s) => overflow(Number(toText(s)))),
364
+ P.named("decInt")
365
+ )
366
+
367
+ /**
368
+ * An unsigned decimal integer as a `bigint`, of any size.
369
+ *
370
+ * @since 0.1.0
371
+ * @category numbers
372
+ */
373
+ export const decBigUint: Parser<bigint> = digit1.pipe(P.map((s) => BigInt(toText(s))))
374
+
375
+ /**
376
+ * A signed decimal integer as a `bigint`, of any size.
377
+ *
378
+ * @since 0.1.0
379
+ * @category numbers
380
+ */
381
+ export const decBigInt: Parser<bigint> = P.taken(P.seq(P.opt(Token.oneOf("+-")), digit1)).pipe(
382
+ P.map((s) => {
383
+ const t = toText(s)
384
+ return BigInt(t.startsWith("+") ? t.slice(1) : t)
385
+ })
386
+ )
387
+
388
+ /**
389
+ * A hexadecimal unsigned integer, without prefix (winnow's `hex_uint`, nom's
390
+ * `hex_u32`). Fails if the value is not a safe integer.
391
+ *
392
+ * @since 0.1.0
393
+ * @category numbers
394
+ */
395
+ export const hexUint: Parser<number, string> = hexDigit1.pipe(
396
+ P.tryMap((s) => overflow(parseInt(toText(s), 16))),
397
+ P.named("hexUint")
398
+ )
399
+
400
+ const sign = P.opt(Token.oneOf("+-"))
401
+ const exponent = P.opt(P.seq(Token.oneOf("eE"), sign, digit1))
402
+ const unsignedFloat = P.alt(
403
+ P.seq(literalImpl("inf", true), P.opt(literalImpl("inity", true))),
404
+ P.seq(Token.literal("."), digit1, exponent),
405
+ P.seq(digit1, P.opt(P.seq(Token.literal("."), P.opt(digit1))), exponent)
406
+ )
407
+ const floatText = numbers.floatText(P.taken(P.alt(
408
+ literalImpl("nan", true),
409
+ P.seq(sign, unsignedFloat)
410
+ )).pipe(P.expected("number")) as Parser<string>)
411
+
412
+ /** @internal */
413
+ export const toFloat: (s: Input) => number = numbers.toFloat
414
+
415
+ /**
416
+ * A floating point number in decimal or scientific notation, including
417
+ * `inf`, `infinity` and `nan` (case insensitive), as accepted by winnow's
418
+ * `float` and nom's `double`.
419
+ *
420
+ * @example
421
+ * ```ts
422
+ * import { Ascii, Parser } from "@thomasfosterau/effect-parse"
423
+ *
424
+ * Parser.parse(Ascii.float, "-1.5e3") // Result.succeed(-1500)
425
+ * ```
426
+ *
427
+ * @since 0.1.0
428
+ * @category numbers
429
+ */
430
+ export const float: Parser<number> = floatText.pipe(P.map(toFloat), P.named("float"))
431
+
432
+ // -----------------------------------------------------------------------------
433
+ // Literals
434
+ // -----------------------------------------------------------------------------
435
+
436
+ /**
437
+ * Matches a literal ignoring ASCII case (winnow's `Caseless`, nom's
438
+ * `tag_no_case`). Returns the input as written.
439
+ *
440
+ * @since 0.1.0
441
+ * @category literals
442
+ */
443
+ export const caseless: {
444
+ (literal: string): Parser<string>
445
+ (literal: Uint8Array): Parser<Uint8Array, never, never, Uint8Array>
446
+ } = (lit: string | Uint8Array): any => literalImpl(lit, true)
447
+
448
+ // -----------------------------------------------------------------------------
449
+ // Escapes
450
+ // -----------------------------------------------------------------------------
451
+
452
+ const controlCode = (c: string | number): number => typeof c === "number" ? c : c.charCodeAt(0)
453
+
454
+ const atControl = (state: State<Input>, control: number): boolean => {
455
+ if (state.pos >= state.end) return false
456
+ const input = state.input
457
+ const c = state.inputKind === 0 ? (input as string).charCodeAt(state.pos) : (input as Uint8Array)[state.pos]
458
+ return c === control
459
+ }
460
+
461
+ /**
462
+ * Recognizes a string containing escape sequences, returning the raw slice
463
+ * (winnow's `take_escaped`, nom's `escaped`).
464
+ *
465
+ * - `normal` matches runs of ordinary characters (it must consume input)
466
+ * - `control` is the escape character, e.g. `"\\"`
467
+ * - `escapable` matches what may follow the control character
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
472
+ *
473
+ * const raw = Ascii.takeEscaped(Ascii.alpha1, "\\", Token.oneOf("\"n\\"))
474
+ * Parser.parse(raw, "ab\\\"cd") // Result.succeed("ab\\\"cd")
475
+ * ```
476
+ *
477
+ * @since 0.1.0
478
+ * @category escapes
479
+ */
480
+ export const takeEscaped = <A, E, R, I extends Input, B, E2, R2, I2 extends Input>(
481
+ normal: Parser<A, E, R, I>,
482
+ control: string | number,
483
+ escapable: Parser<B, E2, R2, I2>
484
+ ): Parser<Slice<I & I2>, E | E2, R | R2, I & I2> => {
485
+ const ctrl = controlCode(control)
486
+ return takeEscapedImpl(normal, (state) => {
487
+ if (!atControl(state, ctrl)) return false
488
+ state.pos++
489
+ return true
490
+ }, escapable, (state, start) => state.slice(start, state.pos) as Slice<I & I2>, ctrl, undefined)
491
+ }
492
+
493
+ const isIncomplete = (state: State<any>): boolean => state.mode === "Incomplete"
494
+
495
+ /**
496
+ * Fails with the current (`Incomplete`) failure, recording a checkpoint
497
+ * first in a streaming parse.
498
+ */
499
+ const suspendAt = (state: State<any>, key: object, start: number, checkpoint: unknown): Fail => {
500
+ if (state.resume !== undefined) Resume.save(state, key, start, checkpoint)
501
+ return Fail
502
+ }
503
+
504
+ /** @internal */
505
+ export const takeEscapedImpl = <A, E, R, I extends Input, B, E2, R2, I2 extends Input, S>(
506
+ normal: Parser<A, E, R, I>,
507
+ matchControl: (state: State<any>) => boolean,
508
+ escapable: Parser<B, E2, R2, I2>,
509
+ slice: (state: State<any>, start: number) => S,
510
+ control: number,
511
+ encoding: Encoding<any> | undefined
512
+ ): Parser<S, E | E2, R | R2, I & I2> => {
513
+ const normalStep = normal.step
514
+ const escStep = escapable.step
515
+ const key = {}
516
+ return P.described((state: State<any>) => {
517
+ const start = state.pos
518
+ let resumed = false
519
+ if (state.resume !== undefined) {
520
+ const checkpoint = Resume.take(state, key, start) as number | Resume.Done | undefined
521
+ if (checkpoint instanceof Resume.Done) {
522
+ state.pos = checkpoint.pos
523
+ return checkpoint.value as S
524
+ }
525
+ if (checkpoint !== undefined) {
526
+ state.pos = checkpoint
527
+ resumed = true
528
+ }
529
+ }
530
+ while (state.pos < state.end) {
531
+ const before = state.pos
532
+ const a = normalStep(state)
533
+ if (a !== Fail) {
534
+ if (state.pos === before) return state.failMessage("Cut", "Assert", "`takeEscaped` parsers must always consume")
535
+ continue
536
+ }
537
+ if (state.mode === "Incomplete") return suspendAt(state, key, start, before)
538
+ if (state.mode !== "Backtrack") return Fail
539
+ state.pos = before
540
+ if (matchControl(state)) {
541
+ if (escStep(state) === Fail) return isIncomplete(state) ? suspendAt(state, key, start, before) : Fail
542
+ } else {
543
+ return Resume.done(state, key, start, resumed, slice(state, start))
544
+ }
545
+ }
546
+ if (state.partial) {
547
+ state.incomplete(0)
548
+ return suspendAt(state, key, start, state.pos)
549
+ }
550
+ return Resume.done(state, key, start, resumed, slice(state, start))
551
+ }, "takeEscaped", { _tag: "Escaped", normal, control, escape: escapable, raw: true, encoding })
552
+ }
553
+
554
+ /**
555
+ * Parses a string containing escape sequences, returning the *unescaped*
556
+ * string (winnow's `escaped`, nom's `escaped_transform`).
557
+ *
558
+ * - `normal` matches runs of ordinary characters, returning them
559
+ * - `control` is the escape character, e.g. `"\\"`
560
+ * - `escape` parses what follows the control character, returning its
561
+ * replacement
562
+ *
563
+ * @example
564
+ * ```ts
565
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
566
+ *
567
+ * const unescaped = Ascii.escaped(
568
+ * Token.takeTill("\\\""),
569
+ * "\\",
570
+ * Parser.alt(
571
+ * Token.literal("n").pipe(Parser.as("\n")),
572
+ * Token.literal("\\"),
573
+ * Token.literal("\"")
574
+ * )
575
+ * )
576
+ * Parser.parse(unescaped, "a\\nb") // Result.succeed("a\nb")
577
+ * ```
578
+ *
579
+ * @since 0.1.0
580
+ * @category escapes
581
+ */
582
+ export const escaped = <E, R, I extends Input, E2, R2, I2 extends Input>(
583
+ normal: Parser<string, E, R, I>,
584
+ control: string | number,
585
+ escape: Parser<string, E2, R2, I2>
586
+ ): Parser<string, E | E2, R | R2, I & I2> => {
587
+ const ctrl = controlCode(control)
588
+ return escapedImpl(normal, (state) => {
589
+ if (!atControl(state, ctrl)) return false
590
+ state.pos++
591
+ return true
592
+ }, escape, ctrl, undefined)
593
+ }
594
+
595
+ /** How far `escaped` got: the text so far, and where to carry on. */
596
+ interface EscapedCheckpoint {
597
+ readonly out: string
598
+ readonly pos: number
599
+ }
600
+
601
+ /** @internal */
602
+ export const escapedImpl = <E, R, I extends Input, E2, R2, I2 extends Input>(
603
+ normal: Parser<string, E, R, I>,
604
+ matchControl: (state: State<any>) => boolean,
605
+ escape: Parser<string, E2, R2, I2>,
606
+ control: number,
607
+ encoding: Encoding<any> | undefined
608
+ ): Parser<string, E | E2, R | R2, I & I2> => {
609
+ const normalStep = normal.step
610
+ const escStep = escape.step
611
+ const key = {}
612
+ return P.described((state: State<any>) => {
613
+ const start = state.pos
614
+ let out = ""
615
+ let resumed = false
616
+ if (state.resume !== undefined) {
617
+ const checkpoint = Resume.take(state, key, start) as EscapedCheckpoint | Resume.Done | undefined
618
+ if (checkpoint instanceof Resume.Done) {
619
+ state.pos = checkpoint.pos
620
+ return checkpoint.value as string
621
+ }
622
+ if (checkpoint !== undefined) {
623
+ out = checkpoint.out
624
+ state.pos = checkpoint.pos
625
+ resumed = true
626
+ }
627
+ }
628
+ while (state.pos < state.end) {
629
+ const before = state.pos
630
+ const a = normalStep(state)
631
+ if (a !== Fail) {
632
+ if (state.pos !== before) {
633
+ out += a
634
+ continue
635
+ }
636
+ // `normal` matched nothing: fall through to the control character
637
+ } else {
638
+ if (state.mode === "Incomplete") return suspendAt(state, key, start, { out, pos: before })
639
+ if (state.mode !== "Backtrack") return Fail
640
+ state.pos = before
641
+ }
642
+ if (matchControl(state)) {
643
+ const b = escStep(state)
644
+ if (b === Fail) return isIncomplete(state) ? suspendAt(state, key, start, { out, pos: before }) : Fail
645
+ out += b
646
+ } else {
647
+ return Resume.done(state, key, start, resumed, out)
648
+ }
649
+ }
650
+ if (state.partial) {
651
+ state.incomplete(0)
652
+ return suspendAt(state, key, start, { out, pos: state.pos })
653
+ }
654
+ return Resume.done(state, key, start, resumed, out)
655
+ }, "escaped", { _tag: "Escaped", normal, control, escape, raw: false, encoding })
656
+ }
657
+
658
+ // -----------------------------------------------------------------------------
659
+ // Byte views
660
+ // -----------------------------------------------------------------------------
661
+
662
+ type Bytes<P> = P extends Parser<infer A, infer E, infer R, infer _I> ? Parser<A extends string ? Uint8Array : A, E, R, Uint8Array>
663
+ : never
664
+
665
+ /**
666
+ * The parsers of this module, typed for `Uint8Array` input. Slices are
667
+ * returned as `Uint8Array`s.
668
+ *
669
+ * @example
670
+ * ```ts
671
+ * import { Ascii, Parser, Token } from "@thomasfosterau/effect-parse"
672
+ *
673
+ * const encode = (s: string) => new TextEncoder().encode(s)
674
+ * const header = Parser.separatedPair(
675
+ * Ascii.bytes.alpha1,
676
+ * Token.literal(encode(": ")),
677
+ * Ascii.bytes.tillLineEnding
678
+ * )
679
+ * ```
680
+ *
681
+ * @since 0.1.0
682
+ * @category byte views
683
+ */
684
+ export const bytes: {
685
+ readonly alpha0: Bytes<typeof alpha0>
686
+ readonly alpha1: Bytes<typeof alpha1>
687
+ readonly digit0: Bytes<typeof digit0>
688
+ readonly digit1: Bytes<typeof digit1>
689
+ readonly hexDigit0: Bytes<typeof hexDigit0>
690
+ readonly hexDigit1: Bytes<typeof hexDigit1>
691
+ readonly octDigit0: Bytes<typeof octDigit0>
692
+ readonly octDigit1: Bytes<typeof octDigit1>
693
+ readonly alphanumeric0: Bytes<typeof alphanumeric0>
694
+ readonly alphanumeric1: Bytes<typeof alphanumeric1>
695
+ readonly space0: Bytes<typeof space0>
696
+ readonly space1: Bytes<typeof space1>
697
+ readonly multispace0: Bytes<typeof multispace0>
698
+ readonly multispace1: Bytes<typeof multispace1>
699
+ readonly crlf: Parser<Uint8Array, never, never, Uint8Array>
700
+ readonly newline: Parser<Uint8Array, never, never, Uint8Array>
701
+ readonly tab: Parser<Uint8Array, never, never, Uint8Array>
702
+ readonly lineEnding: Parser<Uint8Array, never, never, Uint8Array>
703
+ readonly tillLineEnding: Bytes<typeof tillLineEnding>
704
+ readonly decUint: Parser<number, string, never, Uint8Array>
705
+ readonly decInt: Parser<number, string, never, Uint8Array>
706
+ readonly decBigUint: Parser<bigint, never, never, Uint8Array>
707
+ readonly decBigInt: Parser<bigint, never, never, Uint8Array>
708
+ readonly hexUint: Parser<number, string, never, Uint8Array>
709
+ readonly float: Parser<number, never, never, Uint8Array>
710
+ } = (() => {
711
+ const lit = (s: string) => Token.literal(new TextEncoder().encode(s))
712
+ const crlfB = lit("\r\n")
713
+ const newlineB = lit("\n")
714
+ return {
715
+ alpha0,
716
+ alpha1,
717
+ digit0,
718
+ digit1,
719
+ hexDigit0,
720
+ hexDigit1,
721
+ octDigit0,
722
+ octDigit1,
723
+ alphanumeric0,
724
+ alphanumeric1,
725
+ space0,
726
+ space1,
727
+ multispace0,
728
+ multispace1,
729
+ crlf: crlfB,
730
+ newline: newlineB,
731
+ tab: lit("\t"),
732
+ lineEnding: P.alt(newlineB, crlfB).pipe(P.expected("line ending")),
733
+ tillLineEnding,
734
+ decUint,
735
+ decInt,
736
+ decBigUint,
737
+ decBigInt,
738
+ hexUint,
739
+ float
740
+ } as any
741
+ })()