@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,245 @@
1
+ /**
2
+ * Operator-precedence expression parsing with a Pratt parser (from
3
+ * `nom-language`'s `precedence`, and winnow's expression parser).
4
+ *
5
+ * Describe the operand parser and a table of prefix, postfix and infix
6
+ * operators with their precedence (higher binds tighter) and associativity.
7
+ * Each operator carries a function that folds its operands into a result.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { Ascii, Expression, Parser, Token } from "@thomasfosterau/effect-parse"
12
+ *
13
+ * const op = (s: string) => Parser.delimited(Ascii.space0, Token.literal(s), Ascii.space0)
14
+ *
15
+ * const arithmetic: Parser.Parser<number, string> = Expression.make({
16
+ * operand: Parser.alt(
17
+ * Ascii.decInt,
18
+ * Parser.delimited(op("("), Parser.suspend(() => arithmetic), op(")"))
19
+ * ),
20
+ * operators: [
21
+ * Expression.prefix(op("-"), 4, (_, x) => -x),
22
+ * Expression.postfix(op("!"), 5, (x) => factorial(x)),
23
+ * Expression.infixRight(op("^"), 3, (l, _, r) => l ** r),
24
+ * Expression.infixLeft(op("*"), 2, (l, _, r) => l * r),
25
+ * Expression.infixLeft(op("/"), 2, (l, _, r) => l / r),
26
+ * Expression.infixLeft(op("+"), 1, (l, _, r) => l + r),
27
+ * Expression.infixLeft(op("-"), 1, (l, _, r) => l - r)
28
+ * ]
29
+ * })
30
+ *
31
+ * declare const factorial: (n: number) => number
32
+ * Parser.parse(arithmetic, "1 + 2 * 3 ^ 2") // Result.succeed(19)
33
+ * ```
34
+ *
35
+ * @since 0.1.0
36
+ */
37
+ import type { Input, State } from "./Input.js"
38
+ import { Fail, mergeFailures } from "./Input.js"
39
+ import type { Failure } from "./Input.js"
40
+ import type { Parser } from "./Parser.js"
41
+ import { make as makeParser } from "./Parser.js"
42
+
43
+ /**
44
+ * An operator in an expression grammar.
45
+ *
46
+ * @since 0.1.0
47
+ * @category models
48
+ */
49
+ export type Operator<A, E = never, R = never, I extends Input = string> =
50
+ | Prefix<A, E, R, I>
51
+ | Postfix<A, E, R, I>
52
+ | Infix<A, E, R, I>
53
+
54
+ interface OperatorBase<E, R, I extends Input> {
55
+ readonly parser: Parser<unknown, E, R, I>
56
+ readonly precedence: number
57
+ }
58
+
59
+ /**
60
+ * @since 0.1.0
61
+ * @category models
62
+ */
63
+ export interface Prefix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {
64
+ readonly _tag: "Prefix"
65
+ readonly fold: (op: any, operand: A) => A
66
+ }
67
+
68
+ /**
69
+ * @since 0.1.0
70
+ * @category models
71
+ */
72
+ export interface Postfix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {
73
+ readonly _tag: "Postfix"
74
+ readonly fold: (operand: A, op: any) => A
75
+ }
76
+
77
+ /**
78
+ * @since 0.1.0
79
+ * @category models
80
+ */
81
+ export interface Infix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {
82
+ readonly _tag: "Infix"
83
+ readonly associativity: "left" | "right"
84
+ readonly fold: (left: A, op: any, right: A) => A
85
+ }
86
+
87
+ /**
88
+ * A prefix operator, e.g. unary minus.
89
+ *
90
+ * @since 0.1.0
91
+ * @category operators
92
+ */
93
+ export const prefix = <O, E, R, I extends Input, A>(
94
+ parser: Parser<O, E, R, I>,
95
+ precedence: number,
96
+ fold: (op: O, operand: A) => A
97
+ ): Prefix<A, E, R, I> => ({ _tag: "Prefix", parser, precedence, fold })
98
+
99
+ /**
100
+ * A postfix operator, e.g. factorial or a function call suffix.
101
+ *
102
+ * @since 0.1.0
103
+ * @category operators
104
+ */
105
+ export const postfix = <O, E, R, I extends Input, A>(
106
+ parser: Parser<O, E, R, I>,
107
+ precedence: number,
108
+ fold: (operand: A, op: O) => A
109
+ ): Postfix<A, E, R, I> => ({ _tag: "Postfix", parser, precedence, fold })
110
+
111
+ /**
112
+ * A left-associative infix operator: `a - b - c` is `(a - b) - c`.
113
+ *
114
+ * @since 0.1.0
115
+ * @category operators
116
+ */
117
+ export const infixLeft = <O, E, R, I extends Input, A>(
118
+ parser: Parser<O, E, R, I>,
119
+ precedence: number,
120
+ fold: (left: A, op: O, right: A) => A
121
+ ): Infix<A, E, R, I> => ({ _tag: "Infix", associativity: "left", parser, precedence, fold })
122
+
123
+ /**
124
+ * A right-associative infix operator: `a ^ b ^ c` is `a ^ (b ^ c)`.
125
+ *
126
+ * @since 0.1.0
127
+ * @category operators
128
+ */
129
+ export const infixRight = <O, E, R, I extends Input, A>(
130
+ parser: Parser<O, E, R, I>,
131
+ precedence: number,
132
+ fold: (left: A, op: O, right: A) => A
133
+ ): Infix<A, E, R, I> => ({ _tag: "Infix", associativity: "right", parser, precedence, fold })
134
+
135
+ type OpError<Op> = Op extends OperatorBase<infer E, infer _R, infer _I> ? E : never
136
+ type OpServices<Op> = Op extends OperatorBase<infer _E, infer R, infer _I> ? R : never
137
+ type OpInput<Op> = [Op] extends [never] ? Input : [Op] extends [OperatorBase<infer _E, infer _R, infer I>] ? I : never
138
+
139
+ /**
140
+ * Builds an expression parser from an operand parser and an operator table.
141
+ *
142
+ * Precedences are compared numerically: higher binds tighter. A prefix
143
+ * operator applies to everything that binds tighter than it, so with `-`
144
+ * at precedence 4 and `^` at 3, `-2 ^ 2` parses as `(-2) ^ 2`.
145
+ *
146
+ * When several operators of the same kind match, the first one in the
147
+ * table wins, so list longer operators (`<=`) before their prefixes (`<`).
148
+ *
149
+ * @since 0.1.0
150
+ * @category constructors
151
+ */
152
+ export const make = <A, E, R, I extends Input, const Ops extends ReadonlyArray<Operator<A, any, any, any>>>(options: {
153
+ readonly operand: Parser<A, E, R, I>
154
+ readonly operators: Ops
155
+ }): Parser<A, E | OpError<Ops[number]>, R | OpServices<Ops[number]>, I & OpInput<Ops[number]>> => {
156
+ const operand = options.operand.step
157
+ const prefixes = options.operators.filter((o): o is Prefix<A, any, any, any> => o._tag === "Prefix")
158
+ const postfixes = options.operators.filter((o): o is Postfix<A, any, any, any> => o._tag === "Postfix")
159
+ const infixes = options.operators.filter((o): o is Infix<A, any, any, any> => o._tag === "Infix")
160
+
161
+ // Attempts each operator in turn. Returns the index of the first match
162
+ // (with its output in `matched`), -1 if none matched, or -2 on a fatal
163
+ // failure. Expectations of failed attempts are accumulated in `failure`.
164
+ let matched: unknown
165
+ let failure: Failure | undefined
166
+ const tryOps = (state: State<any>, ops: ReadonlyArray<OperatorBase<any, any, any>>, mustConsume: boolean): number => {
167
+ const start = state.pos
168
+ for (let i = 0; i < ops.length; i++) {
169
+ const out = ops[i]!.parser.step(state)
170
+ if (out !== Fail) {
171
+ // A prefix or postfix operator matching nothing would apply forever
172
+ if (mustConsume && state.pos === start) {
173
+ state.failMessage("Cut", "Assert", "prefix and postfix operators must always consume")
174
+ return -2
175
+ }
176
+ matched = out
177
+ return i
178
+ }
179
+ if (state.mode !== "Backtrack") return -2
180
+ failure = failure === undefined ? state.failure : mergeFailures(failure, state.failure)
181
+ state.pos = start
182
+ }
183
+ return -1
184
+ }
185
+
186
+ const expr = (state: State<any>, minPower: number): A | Fail => {
187
+ let lhs: A | Fail
188
+ failure = undefined
189
+ const p = tryOps(state, prefixes, true)
190
+ if (p === -2) return Fail
191
+ if (p >= 0) {
192
+ const op = prefixes[p]!
193
+ const opOut = matched
194
+ const operandValue = expr(state, op.precedence * 2)
195
+ if (operandValue === Fail) return Fail
196
+ lhs = op.fold(opOut, operandValue)
197
+ } else {
198
+ const prefixFailure = failure
199
+ lhs = operand(state)
200
+ if (lhs === Fail) {
201
+ if (prefixFailure !== undefined && state.mode === "Backtrack") {
202
+ state.failWith("Backtrack", mergeFailures(prefixFailure, state.failure))
203
+ }
204
+ return Fail
205
+ }
206
+ }
207
+
208
+ while (true) {
209
+ const start = state.pos
210
+ failure = undefined
211
+ const q = tryOps(state, postfixes, true)
212
+ if (q === -2) return Fail
213
+ if (q >= 0) {
214
+ const op = postfixes[q]!
215
+ if (op.precedence * 2 < minPower) {
216
+ state.pos = start
217
+ break
218
+ }
219
+ lhs = op.fold(lhs, matched)
220
+ continue
221
+ }
222
+ const postfixFailure = failure
223
+ const i = tryOps(state, infixes, false)
224
+ if (i === -2) return Fail
225
+ if (i < 0) {
226
+ const f = postfixFailure === undefined ? failure : failure === undefined ? postfixFailure : mergeFailures(postfixFailure, failure)
227
+ if (f !== undefined && f.offset === state.pos) state.setHint(f.offset, f.expected)
228
+ break
229
+ }
230
+ const op = infixes[i]!
231
+ const leftPower = op.precedence * 2
232
+ if (leftPower < minPower) {
233
+ state.pos = start
234
+ break
235
+ }
236
+ const opOut = matched
237
+ const rhs = expr(state, op.associativity === "left" ? leftPower + 1 : leftPower)
238
+ if (rhs === Fail) return Fail
239
+ lhs = op.fold(lhs, opOut, rhs)
240
+ }
241
+ return lhs
242
+ }
243
+
244
+ return makeParser((state: State<any>) => expr(state, 0), "expression")
245
+ }
package/src/Input.ts ADDED
@@ -0,0 +1,429 @@
1
+ /**
2
+ * Input types and the mutable parse {@link State} cursor.
3
+ *
4
+ * Like winnow, effect-parse parsers advance a mutable cursor rather than
5
+ * returning the remaining input. A {@link State} wraps the original input
6
+ * together with the current offset, the streaming (partial) flag, the
7
+ * services available to the parser, and the details of the most recent
8
+ * failure.
9
+ *
10
+ * Most users never touch `State` directly. It is exposed for writing custom
11
+ * low-level parsers with `Parser.make`.
12
+ *
13
+ * @since 0.1.0
14
+ */
15
+ import * as Context from "effect/Context"
16
+ import type { Encoding } from "./Encoding.js"
17
+ import type { ErrorKind } from "./ParseError.js"
18
+
19
+ /**
20
+ * The kinds of input a parser can consume.
21
+ *
22
+ * - `string`: tokens are Unicode code points (returned as strings), offsets
23
+ * are UTF-16 code-unit indices.
24
+ * - `Uint8Array`: tokens are bytes (numbers), slices are `subarray` views.
25
+ * - `ReadonlyArray<T>`: tokens are elements, e.g. the output of a lexer.
26
+ *
27
+ * @since 0.1.0
28
+ * @category models
29
+ */
30
+ export type Input = string | Uint8Array | ReadonlyArray<unknown>
31
+
32
+ /**
33
+ * Binary data that can be passed wherever a `Uint8Array` input is expected:
34
+ * an `ArrayBuffer`, a `SharedArrayBuffer`, a `DataView` or any typed array.
35
+ *
36
+ * It is viewed, not copied, as a `Uint8Array` of its bytes. A typed array
37
+ * with wider elements is viewed in the platform's byte order, which is
38
+ * little endian on all common platforms, so a `Uint16Array` of UTF-16 code
39
+ * units can be parsed with `Text.utf16le`.
40
+ *
41
+ * @since 0.1.0
42
+ * @category models
43
+ */
44
+ export type BinaryInput = ArrayBufferLike | ArrayBufferView
45
+
46
+ /**
47
+ * What the runners accept for a parser over `I`: the input itself, or, for
48
+ * byte parsers, any {@link BinaryInput}.
49
+ *
50
+ * @since 0.1.0
51
+ * @category models
52
+ */
53
+ export type Source<I> = I | ([I] extends [Uint8Array] ? BinaryInput : never)
54
+
55
+ /**
56
+ * Converts a {@link Source} to the input parsers read: binary data becomes a
57
+ * `Uint8Array` view of the same memory, everything else is returned as is.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { Input } from "@thomasfosterau/effect-parse"
62
+ *
63
+ * Input.fromSource(new Uint16Array([0x41]).buffer) // Uint8Array [0x41, 0x00]
64
+ * ```
65
+ *
66
+ * @since 0.1.0
67
+ * @category constructors
68
+ */
69
+ export const fromSource: {
70
+ (source: BinaryInput): Uint8Array
71
+ <I extends Input>(source: Source<I>): I
72
+ } = (source: Input | BinaryInput): any => {
73
+ if (typeof source === "string" || source instanceof Uint8Array || Array.isArray(source)) return source
74
+ if (ArrayBuffer.isView(source)) return new Uint8Array(source.buffer, source.byteOffset, source.byteLength)
75
+ // An explicit length fixes the view's size even for a resizable buffer
76
+ if (isArrayBufferLike(source)) return new Uint8Array(source, 0, source.byteLength)
77
+ return source
78
+ }
79
+
80
+ const isArrayBufferLike = (u: unknown): u is ArrayBufferLike =>
81
+ u instanceof ArrayBuffer || (typeof SharedArrayBuffer !== "undefined" && u instanceof SharedArrayBuffer)
82
+
83
+ /**
84
+ * The type of a single token of an input.
85
+ *
86
+ * @since 0.1.0
87
+ * @category models
88
+ */
89
+ export type Token<I> = I extends string ? string
90
+ : I extends Uint8Array ? number
91
+ : I extends ReadonlyArray<infer T> ? T
92
+ : never
93
+
94
+ /**
95
+ * The type of a contiguous slice of an input.
96
+ *
97
+ * @since 0.1.0
98
+ * @category models
99
+ */
100
+ export type Slice<I> = I extends string ? string
101
+ : I extends Uint8Array ? Uint8Array
102
+ : I extends ReadonlyArray<infer T> ? ReadonlyArray<T>
103
+ : never
104
+
105
+ /**
106
+ * Sentinel returned by a parser step to signal failure. The failure details
107
+ * are stored on the {@link State}.
108
+ *
109
+ * @since 0.1.0
110
+ * @category failure
111
+ */
112
+ export const Fail: unique symbol = Symbol.for("effect-parse/Fail")
113
+
114
+ /**
115
+ * @since 0.1.0
116
+ * @category failure
117
+ */
118
+ export type Fail = typeof Fail
119
+
120
+ /**
121
+ * How a failure propagates (winnow's `ErrMode`, nom's `Err`).
122
+ *
123
+ * - `Backtrack`: recoverable; `alt`, `opt`, `repeat`, ... try something else.
124
+ * - `Cut`: unrecoverable; alternatives are not attempted.
125
+ * - `Incomplete`: a partial (streaming) input ran out; more data is needed.
126
+ *
127
+ * @since 0.1.0
128
+ * @category failure
129
+ */
130
+ export type ErrMode = "Backtrack" | "Cut" | "Incomplete"
131
+
132
+ /**
133
+ * The details of a parse failure.
134
+ *
135
+ * @since 0.1.0
136
+ * @category failure
137
+ */
138
+ export class Failure {
139
+ constructor(
140
+ /** Offset into the input where the failure occurred. */
141
+ readonly offset: number,
142
+ /** The kind of parser that failed. */
143
+ readonly kind: ErrorKind,
144
+ /** Rendered descriptions of what was expected at `offset`. */
145
+ readonly expected: ReadonlyArray<string>,
146
+ /** Context labels added by `Parser.context`, innermost first. */
147
+ readonly context: ReadonlyArray<string>,
148
+ /** Whether a user supplied cause is present. */
149
+ readonly hasCause: boolean,
150
+ /** A user supplied (typed) cause. */
151
+ readonly cause: unknown,
152
+ /** An optional free-form message. */
153
+ readonly message: string | undefined,
154
+ /** For `Incomplete` failures, how many more tokens are needed (0 if unknown). */
155
+ readonly needed: number
156
+ ) {}
157
+ }
158
+
159
+ const empty: ReadonlyArray<string> = []
160
+
161
+ /** @internal */
162
+ export const emptyStrings = empty
163
+
164
+ /**
165
+ * A tracing event emitted by `Parser.trace`.
166
+ *
167
+ * @since 0.1.0
168
+ * @category tracing
169
+ */
170
+ export interface TraceEvent {
171
+ readonly _tag: "Enter" | "Exit"
172
+ readonly name: string
173
+ readonly depth: number
174
+ readonly offset: number
175
+ /** For `Exit` events: the outcome. */
176
+ readonly result?: "Ok" | ErrMode
177
+ /** For `Exit` events: the offset reached. */
178
+ readonly endOffset?: number
179
+ readonly state: State
180
+ }
181
+
182
+ /**
183
+ * The mutable parsing cursor.
184
+ *
185
+ * @since 0.1.0
186
+ * @category models
187
+ */
188
+ export class State<I extends Input = Input> {
189
+ /** The full input being parsed. */
190
+ readonly input: I
191
+ /** The current offset. Advanced by parsers, rewound on backtracking. */
192
+ pos: number
193
+ /** The end offset (the input's length; in bits inside `Bits.bits`). */
194
+ end: number
195
+ /** 0 = string, 1 = Uint8Array, 2 = array. */
196
+ readonly inputKind: 0 | 1 | 2
197
+ /** Whether the input is partial (streaming): running out yields `Incomplete`. */
198
+ partial: boolean
199
+ /** Services available to `Parser.service`. */
200
+ context: Context.Context<never>
201
+ /** Mode of the latest failure. Only meaningful after a step returned {@link Fail}. */
202
+ mode: ErrMode = "Backtrack"
203
+ /** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
204
+ failure: Failure
205
+ /**
206
+ * The offset of a recoverable failure that occurred without consuming
207
+ * input (for example the last attempt of a `repeat`). Its
208
+ * expectations are merged into new failures at the same offset, producing
209
+ * messages like `expected digit or ")"`.
210
+ */
211
+ hintOffset = -1
212
+ /** The expectations of the hint at {@link hintOffset}. */
213
+ hintExpected: ReadonlyArray<string> = empty
214
+ /**
215
+ * The furthest recoverable failure seen so far, including failures that
216
+ * were later swallowed by backtracking (for example a trailing separator
217
+ * followed by an invalid element). It is reported instead of the final
218
+ * failure when it lies further into the input. This is the classic PEG
219
+ * "farthest failure" heuristic.
220
+ */
221
+ deepest: Failure | undefined = undefined
222
+ /** When defined, `Parser.resumeAfter`/`retryAfter` record failures here. */
223
+ recovered: Array<Failure> | undefined = undefined
224
+ /** When defined, `Parser.trace` reports events here. */
225
+ tracer: ((event: TraceEvent) => void) | undefined = undefined
226
+ /** Current tracing depth. */
227
+ depth = 0
228
+ /**
229
+ * The text encoding of a byte input, recorded by `Text` parsers when they
230
+ * fail so that errors can show the input as text.
231
+ */
232
+ encoding: Encoding<any> | undefined = undefined
233
+ /**
234
+ * The checkpoints of a resumable streaming parse (see
235
+ * `ParserStream.parseAll`); `undefined` otherwise.
236
+ *
237
+ * @internal
238
+ */
239
+ resume: Map<object, Map<number, unknown>> | undefined = undefined
240
+
241
+ constructor(source: I | Source<I>, options?: {
242
+ readonly partial?: boolean | undefined
243
+ readonly context?: Context.Context<never> | undefined
244
+ readonly offset?: number | undefined
245
+ }) {
246
+ const input = fromSource(source as Input) as I
247
+ this.input = input
248
+ this.end = input.length
249
+ this.pos = options?.offset ?? 0
250
+ this.partial = options?.partial ?? false
251
+ this.context = options?.context ?? Context.empty()
252
+ this.inputKind = typeof input === "string" ? 0 : input instanceof Uint8Array ? 1 : 2
253
+ this.failure = new Failure(0, "Fail", empty, empty, false, undefined, undefined, 0)
254
+ }
255
+
256
+ /**
257
+ * The number of input units (code units, bytes or elements) remaining.
258
+ */
259
+ remaining(): number {
260
+ return this.end - this.pos
261
+ }
262
+
263
+ /**
264
+ * Whether the cursor is at the end of the input.
265
+ */
266
+ atEnd(): boolean {
267
+ return this.pos >= this.end
268
+ }
269
+
270
+ /**
271
+ * Extracts the slice `[start, end)` of the input.
272
+ */
273
+ slice(start: number, end: number): Slice<I> {
274
+ const input = this.input as any
275
+ switch (this.inputKind) {
276
+ case 0:
277
+ return input.slice(start, end)
278
+ case 1:
279
+ return input.subarray(start, end)
280
+ default:
281
+ return input.slice(start, end)
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Records a recoverable failure at the current offset and returns {@link Fail}.
287
+ */
288
+ backtrack(kind: ErrorKind, expected: ReadonlyArray<string> = empty, offset: number = this.pos): Fail {
289
+ return this.failWith("Backtrack", this.makeFailure(offset, kind, expected))
290
+ }
291
+
292
+ /**
293
+ * Records an unrecoverable failure at the current offset and returns {@link Fail}.
294
+ */
295
+ cut(kind: ErrorKind, expected: ReadonlyArray<string> = empty, offset: number = this.pos): Fail {
296
+ return this.failWith("Cut", this.makeFailure(offset, kind, expected))
297
+ }
298
+
299
+ /**
300
+ * Records an `Incomplete` failure (only meaningful for partial input) and
301
+ * returns {@link Fail}.
302
+ */
303
+ incomplete(needed: number, expected: ReadonlyArray<string> = empty): Fail {
304
+ return this.failWith(
305
+ "Incomplete",
306
+ new Failure(this.pos, "Incomplete", expected, empty, false, undefined, undefined, needed)
307
+ )
308
+ }
309
+
310
+ /**
311
+ * Fails because the input ended: `Incomplete` for partial input, otherwise
312
+ * a `Backtrack` failure of the given kind.
313
+ */
314
+ eoi(kind: ErrorKind, expected: ReadonlyArray<string>, needed: number): Fail {
315
+ return this.partial ? this.incomplete(needed, expected) : this.backtrack(kind, expected)
316
+ }
317
+
318
+ /**
319
+ * Records a failure carrying a user supplied cause.
320
+ */
321
+ failCause(mode: ErrMode, kind: ErrorKind, cause: unknown, offset: number = this.pos): Fail {
322
+ return this.failWith(mode, new Failure(offset, kind, empty, empty, true, cause, undefined, 0))
323
+ }
324
+
325
+ /**
326
+ * Records a failure carrying a message.
327
+ */
328
+ failMessage(mode: ErrMode, kind: ErrorKind, message: string, offset: number = this.pos): Fail {
329
+ return this.failWith(mode, new Failure(offset, kind, empty, empty, false, undefined, message, 0))
330
+ }
331
+
332
+ /**
333
+ * Records the given failure and returns {@link Fail}.
334
+ */
335
+ failWith(mode: ErrMode, failure: Failure): Fail {
336
+ this.mode = mode
337
+ this.failure = failure
338
+ if (mode === "Backtrack" && (this.deepest === undefined || failure.offset >= this.deepest.offset)) {
339
+ this.deepest = failure
340
+ }
341
+ return Fail
342
+ }
343
+
344
+ /**
345
+ * Replaces the details of the current failure (for example to add context),
346
+ * keeping the record of the furthest failure consistent.
347
+ */
348
+ replaceFailure(failure: Failure): void {
349
+ if (this.deepest === this.failure) this.deepest = failure
350
+ this.failure = failure
351
+ }
352
+
353
+ /**
354
+ * The failure to report to the user: the current failure, or the furthest
355
+ * failure if it lies beyond the current one and the current failure is a
356
+ * plain syntax error (no user cause or message).
357
+ */
358
+ reportedFailure(): Failure {
359
+ const f = this.failure
360
+ const d = this.deepest
361
+ if (
362
+ d === undefined || d === f || d.offset <= f.offset || this.mode === "Incomplete" ||
363
+ f.hasCause || f.message !== undefined
364
+ ) {
365
+ return f
366
+ }
367
+ const context = d.context.length === 0 ? f.context : [...d.context, ...f.context.filter((l) => !d.context.includes(l))]
368
+ return new Failure(d.offset, d.kind, d.expected, context, d.hasCause, d.cause, d.message, d.needed)
369
+ }
370
+
371
+ /**
372
+ * Records that, at `offset`, any of `expected` could also have been
373
+ * accepted. Used by parsers that stop without failing, such as `opt`,
374
+ * `repeat` and `Ascii.digit1`.
375
+ */
376
+ setHint(offset: number, expected: ReadonlyArray<string>): void {
377
+ if (this.hintOffset === offset) {
378
+ this.hintExpected = mergeExpected(this.hintExpected, expected)
379
+ } else {
380
+ this.hintOffset = offset
381
+ this.hintExpected = expected
382
+ }
383
+ }
384
+
385
+ /** @internal */
386
+ makeFailure(offset: number, kind: ErrorKind, expected: ReadonlyArray<string>): Failure {
387
+ if (this.hintOffset === offset && this.hintExpected.length > 0) {
388
+ expected = mergeExpected(this.hintExpected, expected)
389
+ }
390
+ return new Failure(offset, kind, expected, empty, false, undefined, undefined, 0)
391
+ }
392
+ }
393
+
394
+ /** @internal */
395
+ export const mergeExpected = (
396
+ a: ReadonlyArray<string>,
397
+ b: ReadonlyArray<string>
398
+ ): ReadonlyArray<string> => {
399
+ if (a.length === 0) return b
400
+ if (b.length === 0) return a
401
+ const out = a.slice()
402
+ for (const e of b) {
403
+ if (!out.includes(e)) out.push(e)
404
+ }
405
+ return out
406
+ }
407
+
408
+ /**
409
+ * Combines the failures of two alternatives: the furthest failure wins, and
410
+ * failures at the same offset have their expectations merged.
411
+ *
412
+ * @internal
413
+ */
414
+ export const mergeFailures = (a: Failure, b: Failure): Failure => {
415
+ if (a.offset > b.offset) return a
416
+ if (b.offset > a.offset) return b
417
+ const expected = mergeExpected(a.expected, b.expected)
418
+ if (expected === b.expected) return b
419
+ return new Failure(
420
+ b.offset,
421
+ b.kind,
422
+ expected,
423
+ b.context.length > 0 ? b.context : a.context,
424
+ b.hasCause || a.hasCause,
425
+ b.hasCause ? b.cause : a.cause,
426
+ b.message ?? a.message,
427
+ b.needed
428
+ )
429
+ }