@thomasfosterau/effect-parse 0.0.0-stage → 0.0.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,378 @@
1
+ /**
2
+ * The error type produced when running a parser.
3
+ *
4
+ * `ParseError` merges winnow's `ContextError`/`ParseError` and nom's
5
+ * `VerboseError`. It records where parsing failed, what was expected there,
6
+ * the context labels that were active, and an optional typed `cause`
7
+ * produced by `Parser.fail`, `Parser.tryMap` and friends. Its `message`
8
+ * shows the failing position in the input:
9
+ *
10
+ * ```text
11
+ * Parse error at line 1, column 4:
12
+ * 1 | [1,,2]
13
+ * | ^
14
+ * expected digit
15
+ * ```
16
+ *
17
+ * `ParseError` is a `Data.TaggedError`, so it can be yielded directly inside
18
+ * `Effect.gen`.
19
+ *
20
+ * @since 0.1.0
21
+ */
22
+ import * as Data from "effect/Data"
23
+ import * as Option from "effect/Option"
24
+ import type { Encoding } from "./Encoding.js"
25
+ import type { ErrMode, Failure, Input } from "./Input.js"
26
+ import * as text from "./internal/text.js"
27
+
28
+ /**
29
+ * The kind of parser that produced an error, in the spirit of nom's
30
+ * `ErrorKind`.
31
+ *
32
+ * @since 0.1.0
33
+ * @category models
34
+ */
35
+ export type ErrorKind =
36
+ | "Alpha"
37
+ | "AlphaNumeric"
38
+ | "Alt"
39
+ | "Assert"
40
+ | "Bits"
41
+ | "Complete"
42
+ | "CrLf"
43
+ | "Digit"
44
+ | "Dispatch"
45
+ | "Eof"
46
+ | "Escaped"
47
+ | "Fail"
48
+ | "Float"
49
+ | "HexDigit"
50
+ | "Incomplete"
51
+ | "LineEnding"
52
+ | "Literal"
53
+ | "Many"
54
+ | "MultiSpace"
55
+ | "NoneOf"
56
+ | "Not"
57
+ | "OctDigit"
58
+ | "OneOf"
59
+ | "Permutation"
60
+ | "Separated"
61
+ | "Slice"
62
+ | "Space"
63
+ | "Take"
64
+ | "TakeUntil"
65
+ | "TakeWhile"
66
+ | "Token"
67
+ | "Verify"
68
+
69
+ /**
70
+ * @since 0.1.0
71
+ * @category type ids
72
+ */
73
+ export const TypeId: "~effect-parse/ParseError" = "~effect-parse/ParseError"
74
+
75
+ /**
76
+ * An error produced by running a parser.
77
+ *
78
+ * @since 0.1.0
79
+ * @category models
80
+ */
81
+ export class ParseError<out E = never> extends Data.TaggedError("ParseError")<{
82
+ /** The input that was being parsed. */
83
+ readonly input: Input
84
+ /** The offset of the failure in the input. */
85
+ readonly offset: number
86
+ /** The kind of parser that failed. */
87
+ readonly kind: ErrorKind
88
+ /** How the error propagated before parsing stopped. */
89
+ readonly mode: ErrMode
90
+ /** Descriptions of what was expected at `offset`. */
91
+ readonly expected: ReadonlyArray<string>
92
+ /** Context labels (`Parser.context`), innermost first. */
93
+ readonly context: ReadonlyArray<string>
94
+ /** The user supplied cause, if any. */
95
+ readonly cause: E | undefined
96
+ /** An optional free-form description. */
97
+ readonly description: string | undefined
98
+ /**
99
+ * For byte inputs parsed by `Text` parsers, the text encoding. Errors
100
+ * then show the input as text, with line and column numbers.
101
+ */
102
+ readonly encoding?: Encoding<Uint8Array> | undefined
103
+ }> {
104
+ /**
105
+ * @since 0.1.0
106
+ */
107
+ readonly [TypeId]: typeof TypeId = TypeId
108
+
109
+ /**
110
+ * The 1-based line and column of the failure (for strings and encoded
111
+ * text; for other inputs the line is 1 and the column is `offset + 1`).
112
+ *
113
+ * @since 0.1.0
114
+ */
115
+ get position(): LineColumn {
116
+ return lineColumn(this.input, this.offset, this.encoding)
117
+ }
118
+
119
+ /**
120
+ * The rendered, human-readable error message.
121
+ *
122
+ * @since 0.1.0
123
+ */
124
+ override get message(): string {
125
+ return render(this)
126
+ }
127
+
128
+ /**
129
+ * @since 0.1.0
130
+ */
131
+ override toString(): string {
132
+ return `ParseError: ${this.message}`
133
+ }
134
+ }
135
+
136
+ /**
137
+ * @since 0.1.0
138
+ * @category guards
139
+ */
140
+ export const isParseError = (u: unknown): u is ParseError<unknown> =>
141
+ typeof u === "object" && u !== null && TypeId in u
142
+
143
+ /**
144
+ * Builds a `ParseError` from the failure details recorded while parsing.
145
+ *
146
+ * @since 0.1.0
147
+ * @category constructors
148
+ */
149
+ export const fromFailure = <E = never>(
150
+ input: Input,
151
+ failure: Failure,
152
+ mode: ErrMode,
153
+ encoding?: Encoding<any> | undefined
154
+ ): ParseError<E> =>
155
+ new ParseError<E>({
156
+ input,
157
+ offset: failure.offset,
158
+ kind: failure.kind,
159
+ mode,
160
+ expected: failure.expected,
161
+ context: failure.context,
162
+ cause: failure.hasCause ? failure.cause as E : undefined,
163
+ description: failure.message,
164
+ encoding: input instanceof Uint8Array ? encoding : undefined
165
+ })
166
+
167
+ /**
168
+ * The user supplied cause of an error, if any.
169
+ *
170
+ * @since 0.1.0
171
+ * @category getters
172
+ */
173
+ export const getCause = <E>(self: ParseError<E>): Option.Option<E> =>
174
+ self.cause === undefined ? Option.none() : Option.some(self.cause)
175
+
176
+ /**
177
+ * A 1-based line/column position.
178
+ *
179
+ * @since 0.1.0
180
+ * @category models
181
+ */
182
+ export interface LineColumn {
183
+ readonly line: number
184
+ readonly column: number
185
+ }
186
+
187
+ /**
188
+ * Computes the 1-based line and column (in code points) of an offset. Lines
189
+ * are separated by `\n`. Byte inputs are read as text when an `encoding`
190
+ * is given. For other inputs the line is always 1 and the column is
191
+ * `offset + 1`.
192
+ *
193
+ * @since 0.1.0
194
+ * @category utils
195
+ */
196
+ export const lineColumn = (input: Input, offset: number, encoding?: Encoding<any> | undefined): LineColumn => {
197
+ if (encoding !== undefined && input instanceof Uint8Array) {
198
+ let line = 1
199
+ let column = 1
200
+ text.walk(encoding, input, 0, Math.min(offset, input.length), (cp) => {
201
+ if (cp === 10) {
202
+ line++
203
+ column = 1
204
+ } else {
205
+ column++
206
+ }
207
+ })
208
+ return { line, column }
209
+ }
210
+ if (typeof input !== "string") return { line: 1, column: offset + 1 }
211
+ let line = 1
212
+ let lineStart = 0
213
+ for (let i = 0; i < offset && i < input.length; i++) {
214
+ if (input.charCodeAt(i) === 10) {
215
+ line++
216
+ lineStart = i + 1
217
+ }
218
+ }
219
+ return { line, column: codePointCount(input, lineStart, Math.min(offset, input.length)) + 1 }
220
+ }
221
+
222
+ const codePointCount = (s: string, start: number, end: number): number => {
223
+ let n = 0
224
+ for (let i = start; i < end; i++) {
225
+ const c = s.charCodeAt(i)
226
+ if (c >= 0xd800 && c <= 0xdbff && i + 1 < end) {
227
+ const d = s.charCodeAt(i + 1)
228
+ if (d >= 0xdc00 && d <= 0xdfff) i++
229
+ }
230
+ n++
231
+ }
232
+ return n
233
+ }
234
+
235
+ /**
236
+ * Formats a list of expectations: `a`, `a or b`, `a, b or c`.
237
+ *
238
+ * @since 0.1.0
239
+ * @category utils
240
+ */
241
+ export const formatExpected = (expected: ReadonlyArray<string>): string => {
242
+ if (expected.length === 0) return ""
243
+ if (expected.length === 1) return expected[0]!
244
+ return `${expected.slice(0, -1).join(", ")} or ${expected[expected.length - 1]}`
245
+ }
246
+
247
+ const hexBytes = (bytes: Uint8Array): string =>
248
+ Array.from(bytes, (b) => `0x${b.toString(16).padStart(2, "0")}`).join(" ")
249
+
250
+ const describeFound = (input: Input, offset: number, encoding: Encoding<any> | undefined): string => {
251
+ if (offset >= input.length) return "end of input"
252
+ if (encoding !== undefined && input instanceof Uint8Array) {
253
+ const cp = encoding.decode(input, offset, input.length)
254
+ if (cp >= 0) return JSON.stringify(String.fromCodePoint(cp))
255
+ const bad = input.subarray(offset, Math.min(input.length, offset + Math.max(encoding.unit, 1)))
256
+ return cp === text.Truncated
257
+ ? `incomplete ${encoding.name} sequence ${hexBytes(input.subarray(offset))}`
258
+ : `invalid ${encoding.name} ${hexBytes(bad)}`
259
+ }
260
+ if (typeof input === "string") {
261
+ const cp = input.codePointAt(offset)!
262
+ return JSON.stringify(String.fromCodePoint(cp))
263
+ }
264
+ if (input instanceof Uint8Array) {
265
+ return `0x${input[offset]!.toString(16).padStart(2, "0")}`
266
+ }
267
+ return inspectToken(input[offset])
268
+ }
269
+
270
+ /** @internal */
271
+ export const inspectToken = (u: unknown): string => {
272
+ if (typeof u === "string") return JSON.stringify(u)
273
+ if (typeof u === "object" && u !== null) {
274
+ try {
275
+ return JSON.stringify(u)
276
+ } catch {
277
+ return String(u)
278
+ }
279
+ }
280
+ return String(u)
281
+ }
282
+
283
+ const causeMessage = (cause: unknown): string | undefined => {
284
+ if (cause === undefined) return undefined
285
+ if (typeof cause === "string") return cause
286
+ if (cause instanceof Error) return cause.message
287
+ if (typeof cause === "object" && cause !== null && "message" in cause && typeof cause.message === "string") {
288
+ return cause.message
289
+ }
290
+ return inspectToken(cause)
291
+ }
292
+
293
+ const renderSnippet = (input: Input, offset: number, encoding: Encoding<any> | undefined): Array<string> => {
294
+ if (encoding !== undefined && input instanceof Uint8Array) {
295
+ // Find the line containing `offset`, then decode it
296
+ let lineStart = 0
297
+ let end = input.length
298
+ let column = 1
299
+ text.walk(encoding, input, 0, input.length, (cp, i, w) => {
300
+ if (i >= offset && cp === 10) {
301
+ end = i
302
+ return false
303
+ }
304
+ if (i < offset) {
305
+ if (cp === 10) {
306
+ lineStart = i + w
307
+ column = 1
308
+ } else {
309
+ column++
310
+ }
311
+ }
312
+ })
313
+ let line = text.lenientText(encoding, input, lineStart, end)
314
+ if (line.endsWith("\r")) line = line.slice(0, -1)
315
+ const gutter = String(lineColumn(input, offset, encoding).line)
316
+ const pad = " ".repeat(gutter.length)
317
+ return [
318
+ `${gutter} | ${line}`,
319
+ `${pad} | ${" ".repeat(column - 1)}^`
320
+ ]
321
+ }
322
+ if (typeof input === "string") {
323
+ const { column, line } = lineColumn(input, offset)
324
+ let start = offset
325
+ while (start > 0 && input.charCodeAt(start - 1) !== 10) start--
326
+ let end = offset
327
+ while (end < input.length && input.charCodeAt(end) !== 10) end++
328
+ let text = input.slice(start, end)
329
+ if (text.endsWith("\r")) text = text.slice(0, -1)
330
+ const gutter = String(line)
331
+ const pad = " ".repeat(gutter.length)
332
+ return [
333
+ `${gutter} | ${text}`,
334
+ `${pad} | ${" ".repeat(column - 1)}^`
335
+ ]
336
+ }
337
+ if (input instanceof Uint8Array) {
338
+ const start = Math.max(0, offset - 8)
339
+ const end = Math.min(input.length, offset + 8)
340
+ const bytes: Array<string> = []
341
+ for (let i = start; i < end; i++) bytes.push(input[i]!.toString(16).padStart(2, "0"))
342
+ const caret = (offset - start) * 3
343
+ return [
344
+ `${start.toString(16).padStart(8, "0")} | ${bytes.join(" ")}`,
345
+ `${" ".repeat(8)} | ${" ".repeat(caret)}^`
346
+ ]
347
+ }
348
+ return []
349
+ }
350
+
351
+ const render = (error: ParseError<unknown>): string => {
352
+ const { encoding, input, offset } = error
353
+ const lines: Array<string> = []
354
+ if (typeof input === "string") {
355
+ const { column, line } = lineColumn(input, offset)
356
+ lines.push(`Parse error at line ${line}, column ${column}:`)
357
+ } else if (encoding !== undefined) {
358
+ const { column, line } = lineColumn(input, offset, encoding)
359
+ lines.push(`Parse error at line ${line}, column ${column} (byte ${offset}):`)
360
+ } else {
361
+ lines.push(`Parse error at offset ${offset}:`)
362
+ }
363
+ lines.push(...renderSnippet(input, offset, encoding))
364
+ for (let i = error.context.length - 1; i >= 0; i--) {
365
+ lines.push(`while parsing ${error.context[i]}`)
366
+ }
367
+ const cause = causeMessage(error.cause)
368
+ if (cause !== undefined) lines.push(cause)
369
+ if (error.description !== undefined) lines.push(error.description)
370
+ if (error.mode === "Incomplete") {
371
+ lines.push("unexpected end of input (incomplete)")
372
+ } else if (error.expected.length > 0) {
373
+ lines.push(`expected ${formatExpected(error.expected)}, found ${describeFound(input, offset, encoding)}`)
374
+ } else if (cause === undefined && error.description === undefined) {
375
+ lines.push(`unexpected ${describeFound(input, offset, encoding)} (${error.kind})`)
376
+ }
377
+ return lines.join("\n")
378
+ }