@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.
- package/LICENSE +21 -0
- package/README.md +550 -2
- package/dist/Ascii.d.ts +392 -0
- package/dist/Ascii.d.ts.map +1 -0
- package/dist/Ascii.js +610 -0
- package/dist/Ascii.js.map +1 -0
- package/dist/Binary.d.ts +153 -0
- package/dist/Binary.d.ts.map +1 -0
- package/dist/Binary.js +171 -0
- package/dist/Binary.js.map +1 -0
- package/dist/Bits.d.ts +60 -0
- package/dist/Bits.d.ts.map +1 -0
- package/dist/Bits.js +144 -0
- package/dist/Bits.js.map +1 -0
- package/dist/Encoding.d.ts +116 -0
- package/dist/Encoding.d.ts.map +1 -0
- package/dist/Encoding.js +85 -0
- package/dist/Encoding.js.map +1 -0
- package/dist/Expression.d.ts +124 -0
- package/dist/Expression.d.ts.map +1 -0
- package/dist/Expression.js +139 -0
- package/dist/Expression.js.map +1 -0
- package/dist/Input.d.ts +301 -0
- package/dist/Input.d.ts.map +1 -0
- package/dist/Input.js +312 -0
- package/dist/Input.js.map +1 -0
- package/dist/Nom.d.ts +518 -0
- package/dist/Nom.d.ts.map +1 -0
- package/dist/Nom.js +519 -0
- package/dist/Nom.js.map +1 -0
- package/dist/ParseError.d.ts +120 -0
- package/dist/ParseError.d.ts.map +1 -0
- package/dist/ParseError.js +301 -0
- package/dist/ParseError.js.map +1 -0
- package/dist/Parser.d.ts +1312 -0
- package/dist/Parser.d.ts.map +1 -0
- package/dist/Parser.js +1936 -0
- package/dist/Parser.js.map +1 -0
- package/dist/ParserSchema.d.ts +31 -0
- package/dist/ParserSchema.d.ts.map +1 -0
- package/dist/ParserSchema.js +56 -0
- package/dist/ParserSchema.js.map +1 -0
- package/dist/ParserStream.d.ts +127 -0
- package/dist/ParserStream.d.ts.map +1 -0
- package/dist/ParserStream.js +270 -0
- package/dist/ParserStream.js.map +1 -0
- package/dist/Text.d.ts +330 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/Text.js +529 -0
- package/dist/Text.js.map +1 -0
- package/dist/Token.d.ts +225 -0
- package/dist/Token.d.ts.map +1 -0
- package/dist/Token.js +294 -0
- package/dist/Token.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/compiler/closure.d.ts +25 -0
- package/dist/internal/compiler/closure.d.ts.map +1 -0
- package/dist/internal/compiler/closure.js +1668 -0
- package/dist/internal/compiler/closure.js.map +1 -0
- package/dist/internal/compiler/entries.d.ts +19 -0
- package/dist/internal/compiler/entries.d.ts.map +1 -0
- package/dist/internal/compiler/entries.js +32 -0
- package/dist/internal/compiler/entries.js.map +1 -0
- package/dist/internal/compiler/first.d.ts +33 -0
- package/dist/internal/compiler/first.d.ts.map +1 -0
- package/dist/internal/compiler/first.js +235 -0
- package/dist/internal/compiler/first.js.map +1 -0
- package/dist/internal/compiler/jit.d.ts +20 -0
- package/dist/internal/compiler/jit.d.ts.map +1 -0
- package/dist/internal/compiler/jit.js +1030 -0
- package/dist/internal/compiler/jit.js.map +1 -0
- package/dist/internal/compiler/registry.d.ts +34 -0
- package/dist/internal/compiler/registry.d.ts.map +1 -0
- package/dist/internal/compiler/registry.js +46 -0
- package/dist/internal/compiler/registry.js.map +1 -0
- package/dist/internal/compiler/sets.d.ts +29 -0
- package/dist/internal/compiler/sets.d.ts.map +1 -0
- package/dist/internal/compiler/sets.js +85 -0
- package/dist/internal/compiler/sets.js.map +1 -0
- package/dist/internal/entries.d.ts +27 -0
- package/dist/internal/entries.d.ts.map +1 -0
- package/dist/internal/entries.js +42 -0
- package/dist/internal/entries.js.map +1 -0
- package/dist/internal/node.d.ts +251 -0
- package/dist/internal/node.d.ts.map +1 -0
- package/dist/internal/node.js +73 -0
- package/dist/internal/node.js.map +1 -0
- package/dist/internal/numbers.d.ts +41 -0
- package/dist/internal/numbers.d.ts.map +1 -0
- package/dist/internal/numbers.js +302 -0
- package/dist/internal/numbers.js.map +1 -0
- package/dist/internal/resume.d.ts +39 -0
- package/dist/internal/resume.d.ts.map +1 -0
- package/dist/internal/resume.js +37 -0
- package/dist/internal/resume.js.map +1 -0
- package/dist/internal/text.d.ts +72 -0
- package/dist/internal/text.d.ts.map +1 -0
- package/dist/internal/text.js +391 -0
- package/dist/internal/text.js.map +1 -0
- package/dist/internal/tokens.d.ts +93 -0
- package/dist/internal/tokens.d.ts.map +1 -0
- package/dist/internal/tokens.js +400 -0
- package/dist/internal/tokens.js.map +1 -0
- package/package.json +115 -3
- package/src/Ascii.ts +741 -0
- package/src/Binary.ts +228 -0
- package/src/Bits.ts +192 -0
- package/src/Encoding.ts +128 -0
- package/src/Expression.ts +245 -0
- package/src/Input.ts +429 -0
- package/src/Nom.ts +642 -0
- package/src/ParseError.ts +378 -0
- package/src/Parser.ts +2763 -0
- package/src/ParserSchema.ts +87 -0
- package/src/ParserStream.ts +390 -0
- package/src/Text.ts +757 -0
- package/src/Token.ts +416 -0
- package/src/index.ts +16 -0
- package/src/internal/compiler/closure.ts +1583 -0
- package/src/internal/compiler/entries.ts +36 -0
- package/src/internal/compiler/first.ts +252 -0
- package/src/internal/compiler/jit.ts +1091 -0
- package/src/internal/compiler/registry.ts +79 -0
- package/src/internal/compiler/sets.ts +100 -0
- package/src/internal/entries.ts +51 -0
- package/src/internal/node.ts +207 -0
- package/src/internal/numbers.ts +291 -0
- package/src/internal/resume.ts +55 -0
- package/src/internal/text.ts +411 -0
- 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
|
+
})()
|