@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.
- 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
|
@@ -0,0 +1,1583 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closure compiler: turns a parser's description into specialized
|
|
3
|
+
* closures with the step signature. Compiled steps return a value, or
|
|
4
|
+
* `Fail` with only `state.mode` set: no diagnostics are recorded.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here generates code, so it works under a Content Security
|
|
7
|
+
* Policy. Parsers that can't be compiled run their own (interpreted) steps
|
|
8
|
+
* on the same state.
|
|
9
|
+
*/
|
|
10
|
+
import type { Encoding } from "../../Encoding.js"
|
|
11
|
+
import type { ErrMode, State } from "../../Input.js"
|
|
12
|
+
import { Fail } from "../../Input.js"
|
|
13
|
+
import * as Option from "effect/Option"
|
|
14
|
+
import type { Node, NodeOf, ParserLike } from "../node.js"
|
|
15
|
+
import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
|
|
16
|
+
import { nodeOf } from "../node.js"
|
|
17
|
+
import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js"
|
|
18
|
+
import { charOf, fastLimit, fold, validate } from "../text.js"
|
|
19
|
+
import { countTokens, indexOf } from "../tokens.js"
|
|
20
|
+
import type { AltPlan } from "./first.js"
|
|
21
|
+
import * as Resume from "../resume.js"
|
|
22
|
+
import { entriesShape, pairOf } from "./entries.js"
|
|
23
|
+
import { FirstSets, planAlt } from "./first.js"
|
|
24
|
+
import type { CharSet } from "./sets.js"
|
|
25
|
+
import { kindOfEncoding, TableSize, textSet, tokenSet } from "./sets.js"
|
|
26
|
+
|
|
27
|
+
export type Step = (state: State<any>) => any
|
|
28
|
+
|
|
29
|
+
/** A parser that the compiler left to its interpreted step. */
|
|
30
|
+
export interface Fallback {
|
|
31
|
+
readonly name: string | undefined
|
|
32
|
+
readonly tag: Node["_tag"]
|
|
33
|
+
readonly reason: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The maximum number of distinct parsers compiled for one root. */
|
|
37
|
+
export const maxNodes = 2048
|
|
38
|
+
|
|
39
|
+
const BT: ErrMode = "Backtrack"
|
|
40
|
+
const CUT: ErrMode = "Cut"
|
|
41
|
+
const INC: ErrMode = "Incomplete"
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* How many units past the cursor a single token can depend on (a code
|
|
45
|
+
* point is at most 4 units). In a partial parse, a token parser that read
|
|
46
|
+
* no closer than this to the end of the input behaves exactly as on
|
|
47
|
+
* complete input.
|
|
48
|
+
*/
|
|
49
|
+
const Margin = 4
|
|
50
|
+
|
|
51
|
+
const lowerAscii = (c: number): number => c >= 65 && c <= 90 ? c + 32 : c
|
|
52
|
+
|
|
53
|
+
const isSuccess = (r: any): boolean => r._tag === "Success"
|
|
54
|
+
const isSome = (o: any): boolean => o._tag === "Some"
|
|
55
|
+
|
|
56
|
+
interface Slot {
|
|
57
|
+
fn: Step | undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class Compiler {
|
|
61
|
+
readonly used = new Map<ParserLike, Slot>()
|
|
62
|
+
readonly unused = new Map<ParserLike, Slot>()
|
|
63
|
+
readonly firsts: FirstSets
|
|
64
|
+
readonly fallbacks: Array<Fallback> = []
|
|
65
|
+
private readonly fellBack = new Set<ParserLike>()
|
|
66
|
+
private budget = maxNodes
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `resumable`: compile for streaming parses, which may be partial (see
|
|
70
|
+
* `state.partial`) and keep checkpoints (see `internal/resume.ts`).
|
|
71
|
+
*/
|
|
72
|
+
constructor(readonly kind: 0 | 1 | 2, readonly resumable = false) {
|
|
73
|
+
this.firsts = new FirstSets(kind)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* In a resumable build, runs the compiled token parser `step`, and falls
|
|
78
|
+
* back to the interpreted one when it read within `lookahead` units of
|
|
79
|
+
* the end of a partial input, where it might have to report `Incomplete`.
|
|
80
|
+
* Compiled token parsers leave `state.pos` where they stopped reading,
|
|
81
|
+
* even when they fail.
|
|
82
|
+
*/
|
|
83
|
+
private partialSafe(p: ParserLike, step: Step, lookahead: number): Step {
|
|
84
|
+
if (!this.resumable) return step
|
|
85
|
+
const interpreted = p.step
|
|
86
|
+
const ahead = Math.max(lookahead, Margin)
|
|
87
|
+
return (s) => {
|
|
88
|
+
if (!s.partial) return step(s)
|
|
89
|
+
const start = s.pos
|
|
90
|
+
const r = step(s)
|
|
91
|
+
if (s.pos + ahead <= s.end) return r
|
|
92
|
+
s.pos = start
|
|
93
|
+
return interpreted(s)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Compiles `p`; when `used` is false its output is not needed. */
|
|
98
|
+
compile(p: ParserLike, used: boolean): Step {
|
|
99
|
+
const memo = used ? this.used : this.unused
|
|
100
|
+
const slot = memo.get(p)
|
|
101
|
+
if (slot !== undefined) {
|
|
102
|
+
if (slot.fn !== undefined) return slot.fn
|
|
103
|
+
return (s) => slot.fn!(s) // a cycle through `suspend`
|
|
104
|
+
}
|
|
105
|
+
const fresh: Slot = { fn: undefined }
|
|
106
|
+
memo.set(p, fresh)
|
|
107
|
+
const node = nodeOf(p)
|
|
108
|
+
const fn = --this.budget < 0 ? this.fallback(p, node, "node budget exceeded") : this.build(p, node, used)
|
|
109
|
+
fresh.fn = fn
|
|
110
|
+
return fn
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fallback(p: ParserLike, node: Node, reason: string): Step {
|
|
114
|
+
this.note(p, node, reason)
|
|
115
|
+
return p.step
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Records that (part of) `p` runs interpreted. */
|
|
119
|
+
note(p: ParserLike, node: Node, reason: string): void {
|
|
120
|
+
if (!this.fellBack.has(p)) {
|
|
121
|
+
this.fellBack.add(p)
|
|
122
|
+
this.fallbacks.push({ name: p.name, tag: node._tag, reason })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Whether a token node reads this input kind natively. */
|
|
127
|
+
private native(enc: Encoding<any> | undefined): boolean {
|
|
128
|
+
return enc === undefined ? this.kind !== 2 : kindOfEncoding(enc) === this.kind
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
build(p: ParserLike, node: Node, used: boolean): Step {
|
|
132
|
+
switch (node._tag) {
|
|
133
|
+
case "Opaque":
|
|
134
|
+
return this.fallback(p, node, "not describable (Parser.make, gen, ...)")
|
|
135
|
+
case "Succeed": {
|
|
136
|
+
const value = node.value
|
|
137
|
+
return () => value
|
|
138
|
+
}
|
|
139
|
+
case "Sync": {
|
|
140
|
+
const evaluate = node.evaluate
|
|
141
|
+
return used ? () => evaluate() : () => undefined
|
|
142
|
+
}
|
|
143
|
+
case "Fail": {
|
|
144
|
+
const mode = node.mode
|
|
145
|
+
return (s) => {
|
|
146
|
+
s.mode = mode
|
|
147
|
+
return Fail
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
case "Eof":
|
|
151
|
+
return (s) => {
|
|
152
|
+
if (s.pos < s.end) {
|
|
153
|
+
s.mode = BT
|
|
154
|
+
return Fail
|
|
155
|
+
}
|
|
156
|
+
if (s.partial) {
|
|
157
|
+
s.mode = INC
|
|
158
|
+
return Fail
|
|
159
|
+
}
|
|
160
|
+
return undefined
|
|
161
|
+
}
|
|
162
|
+
case "Location":
|
|
163
|
+
return (s) => s.pos
|
|
164
|
+
case "RestLen":
|
|
165
|
+
return (s) => s.end - s.pos
|
|
166
|
+
case "Suspend":
|
|
167
|
+
return this.compile(node.resolve(), used)
|
|
168
|
+
case "Any":
|
|
169
|
+
case "Literal":
|
|
170
|
+
case "OneOf":
|
|
171
|
+
case "Take":
|
|
172
|
+
case "TakeWhile":
|
|
173
|
+
case "TakeUntil":
|
|
174
|
+
case "Rest":
|
|
175
|
+
if (!this.native(node.encoding)) return this.fallback(p, node, "token parser on array input")
|
|
176
|
+
return this.partialSafe(
|
|
177
|
+
p,
|
|
178
|
+
node.encoding === undefined ? this.token(p, node, used) : this.text(p, node, node.encoding, used),
|
|
179
|
+
node._tag === "Literal" || node._tag === "TakeUntil" ? node.literal.length * 4 : Margin
|
|
180
|
+
)
|
|
181
|
+
case "TillLineEnding":
|
|
182
|
+
case "Identifier":
|
|
183
|
+
case "Bom":
|
|
184
|
+
return this.fallback(p, node, "leaf parser without a compiled form")
|
|
185
|
+
case "Escaped":
|
|
186
|
+
if (!this.native(node.encoding)) return this.fallback(p, node, "token parser on array input")
|
|
187
|
+
return this.escaped(node)
|
|
188
|
+
case "Map": {
|
|
189
|
+
if (!used) return this.compile(node.self, false)
|
|
190
|
+
if (node.f === toFloat) {
|
|
191
|
+
const step = this.float(node)
|
|
192
|
+
if (step !== undefined) return step
|
|
193
|
+
}
|
|
194
|
+
// Streaming builds keep the array, which their checkpoints hold
|
|
195
|
+
if (node.f === fromEntries && !this.resumable && entriesShape(node.self)) return this.entries(node.self)
|
|
196
|
+
const c = this.compile(node.self, true)
|
|
197
|
+
const f = node.f
|
|
198
|
+
return (s) => {
|
|
199
|
+
const a = c(s)
|
|
200
|
+
return a === Fail ? Fail : f(a)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
case "As": {
|
|
204
|
+
const c = this.compile(node.self, false)
|
|
205
|
+
if (!used) return c
|
|
206
|
+
const value = node.value
|
|
207
|
+
return (s) => c(s) === Fail ? Fail : value
|
|
208
|
+
}
|
|
209
|
+
case "Tap": {
|
|
210
|
+
const c = this.compile(node.self, true)
|
|
211
|
+
const f = node.f
|
|
212
|
+
return (s) => {
|
|
213
|
+
const a = c(s)
|
|
214
|
+
if (a !== Fail) f(a)
|
|
215
|
+
return a
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
case "TryMap": {
|
|
219
|
+
const c = this.compile(node.self, true)
|
|
220
|
+
const f = node.f
|
|
221
|
+
return (s) => {
|
|
222
|
+
const a = c(s)
|
|
223
|
+
if (a === Fail) return Fail
|
|
224
|
+
const r = f(a) as any
|
|
225
|
+
if (isSuccess(r)) return r.success
|
|
226
|
+
s.mode = BT
|
|
227
|
+
return Fail
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
case "FilterMap": {
|
|
231
|
+
const c = this.compile(node.self, true)
|
|
232
|
+
const f = node.f
|
|
233
|
+
return (s) => {
|
|
234
|
+
const a = c(s)
|
|
235
|
+
if (a === Fail) return Fail
|
|
236
|
+
const o = f(a) as any
|
|
237
|
+
if (isSome(o)) return o.value
|
|
238
|
+
s.mode = BT
|
|
239
|
+
return Fail
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
case "Filter": {
|
|
243
|
+
const c = this.compile(node.self, true)
|
|
244
|
+
const predicate = node.predicate
|
|
245
|
+
return (s) => {
|
|
246
|
+
const a = c(s)
|
|
247
|
+
if (a === Fail) return Fail
|
|
248
|
+
if (predicate(a)) return a
|
|
249
|
+
s.mode = BT
|
|
250
|
+
return Fail
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
case "FlatMap": {
|
|
254
|
+
// The parser returned by `f` runs interpreted
|
|
255
|
+
const c = this.compile(node.self, true)
|
|
256
|
+
const f = node.f
|
|
257
|
+
this.note(p, node, "the parser returned by the function runs interpreted")
|
|
258
|
+
return (s) => {
|
|
259
|
+
const a = c(s)
|
|
260
|
+
return a === Fail ? Fail : f(a).step(s)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
case "Taken":
|
|
264
|
+
if (node.encoding !== undefined && !this.native(node.encoding)) {
|
|
265
|
+
return this.fallback(p, node, "Text parser on another input kind")
|
|
266
|
+
}
|
|
267
|
+
return this.taken(node, used)
|
|
268
|
+
case "WithTaken": {
|
|
269
|
+
const c = this.compile(node.self, true)
|
|
270
|
+
return (s) => {
|
|
271
|
+
const start = s.pos
|
|
272
|
+
const a = c(s)
|
|
273
|
+
return a === Fail ? Fail : [a, s.slice(start, s.pos)]
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
case "Span": {
|
|
277
|
+
const c = this.compile(node.self, false)
|
|
278
|
+
if (!used) return c
|
|
279
|
+
return (s) => {
|
|
280
|
+
const start = s.pos
|
|
281
|
+
return c(s) === Fail ? Fail : { start, end: s.pos }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
case "WithSpan": {
|
|
285
|
+
const c = this.compile(node.self, true)
|
|
286
|
+
return (s) => {
|
|
287
|
+
const start = s.pos
|
|
288
|
+
const a = c(s)
|
|
289
|
+
return a === Fail ? Fail : [a, { start, end: s.pos }]
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
case "Mode": {
|
|
293
|
+
const c = this.compile(node.self, used)
|
|
294
|
+
const from = node.from
|
|
295
|
+
const to = node.to
|
|
296
|
+
return (s) => {
|
|
297
|
+
const a = c(s)
|
|
298
|
+
if (a === Fail && s.mode === from) s.mode = to
|
|
299
|
+
return a
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
case "Context":
|
|
303
|
+
case "Expected":
|
|
304
|
+
case "MapError":
|
|
305
|
+
case "OrFail":
|
|
306
|
+
case "Trace":
|
|
307
|
+
case "RetryAfter":
|
|
308
|
+
// Diagnostics only, or inactive outside parseRecoverable / tracing
|
|
309
|
+
return this.compile(node.self, used)
|
|
310
|
+
case "ResumeAfter": {
|
|
311
|
+
const c = this.compile(node.self, used)
|
|
312
|
+
if (!used) return c
|
|
313
|
+
return (s) => {
|
|
314
|
+
const a = c(s)
|
|
315
|
+
return a === Fail ? Fail : Option.some(a)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
case "Opt":
|
|
319
|
+
case "OrElseSucceed": {
|
|
320
|
+
const c = this.compile(node.self, used)
|
|
321
|
+
const orElse = !used ? undefined : node._tag === "Opt" ? Option.none : node.value
|
|
322
|
+
const some = used && node._tag === "Opt"
|
|
323
|
+
return (s) => {
|
|
324
|
+
const start = s.pos
|
|
325
|
+
const a = c(s)
|
|
326
|
+
if (a !== Fail) return some ? Option.some(a) : a
|
|
327
|
+
if (s.mode !== BT) return Fail
|
|
328
|
+
s.pos = start
|
|
329
|
+
return orElse === undefined ? undefined : orElse()
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
case "Peek": {
|
|
333
|
+
const c = this.compile(node.self, used)
|
|
334
|
+
return (s) => {
|
|
335
|
+
const start = s.pos
|
|
336
|
+
const a = c(s)
|
|
337
|
+
if (a !== Fail) s.pos = start
|
|
338
|
+
return a
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
case "Not": {
|
|
342
|
+
const c = this.compile(node.self, false)
|
|
343
|
+
return (s) => {
|
|
344
|
+
const start = s.pos
|
|
345
|
+
if (c(s) !== Fail) {
|
|
346
|
+
s.pos = start
|
|
347
|
+
s.mode = BT
|
|
348
|
+
return Fail
|
|
349
|
+
}
|
|
350
|
+
if (s.mode !== BT) return Fail
|
|
351
|
+
s.pos = start
|
|
352
|
+
return undefined
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
case "Seq":
|
|
356
|
+
return this.seq(node, used)
|
|
357
|
+
case "Struct": {
|
|
358
|
+
const keys = node.keys
|
|
359
|
+
const steps = node.parsers.map((q) => this.compile(q, used))
|
|
360
|
+
const n = steps.length
|
|
361
|
+
if (!used) return this.chain(steps)
|
|
362
|
+
return (s) => {
|
|
363
|
+
const out: Record<string, unknown> = {}
|
|
364
|
+
for (let i = 0; i < n; i++) {
|
|
365
|
+
const a = steps[i]!(s)
|
|
366
|
+
if (a === Fail) return Fail
|
|
367
|
+
out[keys[i]!] = a
|
|
368
|
+
}
|
|
369
|
+
return out
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
case "ZipWith": {
|
|
373
|
+
const c1 = this.compile(node.self, used)
|
|
374
|
+
const c2 = this.compile(node.that, used)
|
|
375
|
+
if (!used) return this.chain([c1, c2])
|
|
376
|
+
const f = node.f
|
|
377
|
+
return (s) => {
|
|
378
|
+
const a = c1(s)
|
|
379
|
+
if (a === Fail) return Fail
|
|
380
|
+
const b = c2(s)
|
|
381
|
+
return b === Fail ? Fail : f(a, b)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
case "Alt":
|
|
385
|
+
return this.alt(node, used)
|
|
386
|
+
case "Dispatch":
|
|
387
|
+
return this.dispatch(p, node, used)
|
|
388
|
+
case "Repeat":
|
|
389
|
+
case "Fold":
|
|
390
|
+
return this.repeat(node, used)
|
|
391
|
+
case "RepeatTill":
|
|
392
|
+
return this.repeatTill(node)
|
|
393
|
+
case "Separated":
|
|
394
|
+
return this.separated(node, used)
|
|
395
|
+
case "SeparatedFold":
|
|
396
|
+
return this.separatedFold(node)
|
|
397
|
+
case "Service":
|
|
398
|
+
case "Provide":
|
|
399
|
+
return this.fallback(p, node, "services stay interpreted")
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Runs steps in order, for their effect on the cursor only. */
|
|
404
|
+
chain(steps: ReadonlyArray<Step>): Step {
|
|
405
|
+
const n = steps.length
|
|
406
|
+
if (n === 0) return () => undefined
|
|
407
|
+
if (n === 1) return steps[0]!
|
|
408
|
+
if (n === 2) {
|
|
409
|
+
const [a, b] = steps as [Step, Step]
|
|
410
|
+
return (s) => a(s) === Fail ? Fail : b(s)
|
|
411
|
+
}
|
|
412
|
+
return (s) => {
|
|
413
|
+
for (let i = 0; i < n; i++) if (steps[i]!(s) === Fail) return Fail
|
|
414
|
+
return undefined
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
seq(node: NodeOf<"Seq">, used: boolean): Step {
|
|
419
|
+
const output = node.output
|
|
420
|
+
const keep = (i: number) =>
|
|
421
|
+
used && (output === "tuple" || output === i || (typeof output === "object" && output.includes(i)))
|
|
422
|
+
const steps = node.parsers.map((q, i) => this.compile(q, keep(i)))
|
|
423
|
+
const n = steps.length
|
|
424
|
+
if (!used) return this.chain(steps)
|
|
425
|
+
if (output === "tuple") {
|
|
426
|
+
return (s) => {
|
|
427
|
+
const out = new Array(n)
|
|
428
|
+
for (let i = 0; i < n; i++) {
|
|
429
|
+
const a = steps[i]!(s)
|
|
430
|
+
if (a === Fail) return Fail
|
|
431
|
+
out[i] = a
|
|
432
|
+
}
|
|
433
|
+
return out
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (typeof output === "number") {
|
|
437
|
+
if (n === 2) {
|
|
438
|
+
const [a, b] = steps as [Step, Step]
|
|
439
|
+
return output === 0
|
|
440
|
+
? (s) => {
|
|
441
|
+
const x = a(s)
|
|
442
|
+
if (x === Fail) return Fail
|
|
443
|
+
return b(s) === Fail ? Fail : x
|
|
444
|
+
}
|
|
445
|
+
: (s) => a(s) === Fail ? Fail : b(s)
|
|
446
|
+
}
|
|
447
|
+
return (s) => {
|
|
448
|
+
let out: unknown
|
|
449
|
+
for (let i = 0; i < n; i++) {
|
|
450
|
+
const a = steps[i]!(s)
|
|
451
|
+
if (a === Fail) return Fail
|
|
452
|
+
if (i === output) out = a
|
|
453
|
+
}
|
|
454
|
+
return out
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const [i0, i1] = output
|
|
458
|
+
return (s) => {
|
|
459
|
+
let x: unknown
|
|
460
|
+
let y: unknown
|
|
461
|
+
for (let i = 0; i < n; i++) {
|
|
462
|
+
const a = steps[i]!(s)
|
|
463
|
+
if (a === Fail) return Fail
|
|
464
|
+
if (i === i0) x = a
|
|
465
|
+
else if (i === i1) y = a
|
|
466
|
+
}
|
|
467
|
+
return [x, y]
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** The unit at the cursor, or -1 at the end of the input. */
|
|
472
|
+
private unitReader(): (s: State<any>) => number {
|
|
473
|
+
return this.kind === 0
|
|
474
|
+
? (s) => s.pos < s.end ? (s.input as string).charCodeAt(s.pos) : -1
|
|
475
|
+
: (s) => s.pos < s.end ? (s.input as Uint8Array)[s.pos]! : -1
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
alt(node: NodeOf<"Alt">, used: boolean): Step {
|
|
479
|
+
const steps = node.parsers.map((q) => this.compile(q, used))
|
|
480
|
+
const n = steps.length
|
|
481
|
+
if (n === 0) {
|
|
482
|
+
return (s) => {
|
|
483
|
+
s.mode = BT
|
|
484
|
+
return Fail
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (n === 1) return steps[0]!
|
|
488
|
+
const plan: AltPlan | undefined = this.kind === 2
|
|
489
|
+
? undefined
|
|
490
|
+
: planAlt(node.parsers.map((q) => this.firsts.of(q)))
|
|
491
|
+
if (plan === undefined) {
|
|
492
|
+
return (s) => {
|
|
493
|
+
const start = s.pos
|
|
494
|
+
for (let i = 0; i < n; i++) {
|
|
495
|
+
const a = steps[i]!(s)
|
|
496
|
+
if (a !== Fail) return a
|
|
497
|
+
if (s.mode !== BT) return Fail
|
|
498
|
+
s.pos = start
|
|
499
|
+
}
|
|
500
|
+
s.mode = BT
|
|
501
|
+
return Fail
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const lists = plan.lists.map((l) => l.map((i) => steps[i]!))
|
|
505
|
+
const { byUnit, end, high } = plan
|
|
506
|
+
const unit = this.unitReader()
|
|
507
|
+
const resumable = this.resumable
|
|
508
|
+
return (s) => {
|
|
509
|
+
// Near the end of a partial input, branches may be incomplete rather than fail
|
|
510
|
+
const u = resumable && s.partial && s.pos + Margin > s.end ? -2 : unit(s)
|
|
511
|
+
const candidates = u === -2 ? steps : lists[u < 0 ? end : u < TableSize ? byUnit[u]! : high]!
|
|
512
|
+
const start = s.pos
|
|
513
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
514
|
+
const a = candidates[i]!(s)
|
|
515
|
+
if (a !== Fail) return a
|
|
516
|
+
if (s.mode !== BT) return Fail
|
|
517
|
+
s.pos = start
|
|
518
|
+
}
|
|
519
|
+
s.mode = BT
|
|
520
|
+
return Fail
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
dispatch(p: ParserLike, node: NodeOf<"Dispatch">, used: boolean): Step {
|
|
525
|
+
const selector = this.compile(node.selector, true)
|
|
526
|
+
const choose = node.choose
|
|
527
|
+
const compiled = new Map<ParserLike, Step>()
|
|
528
|
+
if (node.cases !== undefined) {
|
|
529
|
+
for (const key of Reflect.ownKeys(node.cases)) {
|
|
530
|
+
const q = node.cases[key as any]!
|
|
531
|
+
if (!compiled.has(q)) compiled.set(q, this.compile(q, used))
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
if (node.otherwise !== undefined && !compiled.has(node.otherwise)) {
|
|
535
|
+
compiled.set(node.otherwise, this.compile(node.otherwise, used))
|
|
536
|
+
}
|
|
537
|
+
const general: Step = (s) => {
|
|
538
|
+
const start = s.pos
|
|
539
|
+
const key = selector(s)
|
|
540
|
+
if (key === Fail) return Fail
|
|
541
|
+
const q = choose(key)
|
|
542
|
+
if (q === undefined) {
|
|
543
|
+
s.pos = start
|
|
544
|
+
s.mode = BT
|
|
545
|
+
return Fail
|
|
546
|
+
}
|
|
547
|
+
return (compiled.get(q) ?? q.step)(s)
|
|
548
|
+
}
|
|
549
|
+
if (node.cases === undefined) {
|
|
550
|
+
this.note(p, node, "the parser returned by the function of cases runs interpreted")
|
|
551
|
+
return general
|
|
552
|
+
}
|
|
553
|
+
// Switch on the next unit when the selector is `any` or `peek(any)`
|
|
554
|
+
let sel = nodeOf(node.selector)
|
|
555
|
+
const peek = sel._tag === "Peek"
|
|
556
|
+
if (peek) sel = nodeOf((sel as NodeOf<"Peek">).self)
|
|
557
|
+
if (sel._tag !== "Any" || !this.native(sel.encoding)) return general
|
|
558
|
+
const enc = sel.encoding
|
|
559
|
+
// Units below `limit` are whole tokens, so the key is known in advance
|
|
560
|
+
const limit = enc === undefined ? (this.kind === 0 ? 0xd800 : TableSize) : Math.min(fastLimit(enc), 0xd800)
|
|
561
|
+
const size = Math.min(limit, TableSize)
|
|
562
|
+
if (size === 0) return general
|
|
563
|
+
const table: Array<Step | null> = []
|
|
564
|
+
for (let u = 0; u < size; u++) {
|
|
565
|
+
const key = enc === undefined && this.kind === 1 ? u : charOf(u)
|
|
566
|
+
const q = choose(key)
|
|
567
|
+
table.push(q === undefined ? null : compiled.get(q) ?? q.step)
|
|
568
|
+
}
|
|
569
|
+
const unit = this.unitReader()
|
|
570
|
+
const advance = peek ? 0 : 1
|
|
571
|
+
const resumable = this.resumable
|
|
572
|
+
return (s) => {
|
|
573
|
+
if (resumable && s.partial && s.pos + Margin > s.end) return general(s)
|
|
574
|
+
const u = unit(s)
|
|
575
|
+
if (u < 0) {
|
|
576
|
+
s.mode = BT
|
|
577
|
+
return Fail
|
|
578
|
+
}
|
|
579
|
+
if (u >= size) return general(s)
|
|
580
|
+
const q = table[u]!
|
|
581
|
+
if (q === null) {
|
|
582
|
+
s.mode = BT
|
|
583
|
+
return Fail
|
|
584
|
+
}
|
|
585
|
+
s.pos += advance
|
|
586
|
+
return q(s)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
repeat(node: NodeOf<"Repeat"> | NodeOf<"Fold">, used: boolean): Step {
|
|
591
|
+
const { max, min } = node
|
|
592
|
+
const checkLoop = max === Infinity
|
|
593
|
+
const isFold = node._tag === "Fold"
|
|
594
|
+
const c = this.compile(node.self, used)
|
|
595
|
+
const resumable = this.resumable
|
|
596
|
+
const key = {}
|
|
597
|
+
// The accumulator: an array, the fold's value, or nothing when unused
|
|
598
|
+
const init: () => unknown = !used ? () => undefined : isFold ? (node as NodeOf<"Fold">).init : () => []
|
|
599
|
+
const add: (acc: any, a: unknown) => unknown = !used
|
|
600
|
+
? (acc) => acc
|
|
601
|
+
: isFold
|
|
602
|
+
? (node as NodeOf<"Fold">).f
|
|
603
|
+
: (acc, a) => {
|
|
604
|
+
acc.push(a)
|
|
605
|
+
return acc
|
|
606
|
+
}
|
|
607
|
+
return (s) => {
|
|
608
|
+
const entry = s.pos
|
|
609
|
+
let acc: unknown
|
|
610
|
+
let count = 0
|
|
611
|
+
let resumed = false
|
|
612
|
+
if (resumable && s.resume !== undefined) {
|
|
613
|
+
const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; count: number; pos: number } | undefined
|
|
614
|
+
if (cp instanceof Resume.Done) {
|
|
615
|
+
s.pos = cp.pos
|
|
616
|
+
return cp.value
|
|
617
|
+
}
|
|
618
|
+
if (cp !== undefined) {
|
|
619
|
+
acc = cp.acc
|
|
620
|
+
count = cp.count
|
|
621
|
+
s.pos = cp.pos
|
|
622
|
+
resumed = true
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (!resumed) acc = init()
|
|
626
|
+
for (; count < max; count++) {
|
|
627
|
+
const start = s.pos
|
|
628
|
+
const a = c(s)
|
|
629
|
+
if (a === Fail) {
|
|
630
|
+
if (s.mode === INC && s.resume !== undefined) Resume.save(s, key, entry, { acc, count, pos: start })
|
|
631
|
+
if (s.mode !== BT || count < min) return Fail
|
|
632
|
+
s.pos = start
|
|
633
|
+
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
634
|
+
}
|
|
635
|
+
if (checkLoop && s.pos === start) {
|
|
636
|
+
s.mode = CUT
|
|
637
|
+
return Fail
|
|
638
|
+
}
|
|
639
|
+
acc = add(acc, a)
|
|
640
|
+
}
|
|
641
|
+
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* `map(self, Object.fromEntries)`, where `entriesShape(self)`: builds the
|
|
647
|
+
* object directly (see the JIT).
|
|
648
|
+
*/
|
|
649
|
+
private entries(p: ParserLike): Step {
|
|
650
|
+
const node = nodeOf(p)
|
|
651
|
+
switch (node._tag) {
|
|
652
|
+
case "Mode": {
|
|
653
|
+
const c = this.entries(node.self)
|
|
654
|
+
const from = node.from
|
|
655
|
+
const to = node.to
|
|
656
|
+
return (s) => {
|
|
657
|
+
const a = c(s)
|
|
658
|
+
if (a === Fail && s.mode === from) s.mode = to
|
|
659
|
+
return a
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
case "Seq": {
|
|
663
|
+
const i = node.output as number
|
|
664
|
+
const steps = node.parsers.map((q, j) => j === i ? this.entries(q) : this.compile(q, false))
|
|
665
|
+
return (s) => {
|
|
666
|
+
let out: unknown
|
|
667
|
+
for (let j = 0; j < steps.length; j++) {
|
|
668
|
+
const a = steps[j]!(s)
|
|
669
|
+
if (a === Fail) return Fail
|
|
670
|
+
if (j === i) out = a
|
|
671
|
+
}
|
|
672
|
+
return out
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
case "Repeat":
|
|
676
|
+
case "Separated":
|
|
677
|
+
return this.entriesLoop(node)
|
|
678
|
+
default:
|
|
679
|
+
return this.entries((node as { readonly self: ParserLike }).self)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private entriesLoop(node: NodeOf<"Repeat"> | NodeOf<"Separated">): Step {
|
|
684
|
+
const pair = pairOf(node.self)!
|
|
685
|
+
const parts = pair.parsers.map((q, j) => this.compile(q, j === pair.key || j === pair.value))
|
|
686
|
+
const { key: ki, value: vi } = pair
|
|
687
|
+
// Parses an entry and adds it to `acc`
|
|
688
|
+
const entry = (s: State<any>, acc: EntriesAcc): boolean => {
|
|
689
|
+
let key: unknown
|
|
690
|
+
let value: unknown
|
|
691
|
+
for (let j = 0; j < parts.length; j++) {
|
|
692
|
+
const a = parts[j]!(s)
|
|
693
|
+
if (a === Fail) return false
|
|
694
|
+
if (j === ki) key = a
|
|
695
|
+
else if (j === vi) value = a
|
|
696
|
+
}
|
|
697
|
+
acc.add(key, value)
|
|
698
|
+
return true
|
|
699
|
+
}
|
|
700
|
+
const { max, min } = node
|
|
701
|
+
const checkLoop = max === Infinity
|
|
702
|
+
if (node._tag === "Repeat") {
|
|
703
|
+
return (s) => {
|
|
704
|
+
const acc = new EntriesAcc()
|
|
705
|
+
for (let count = 0; count < max; count++) {
|
|
706
|
+
const start = s.pos
|
|
707
|
+
let key: unknown
|
|
708
|
+
let value: unknown
|
|
709
|
+
let ok = true
|
|
710
|
+
for (let j = 0; j < parts.length; j++) {
|
|
711
|
+
const a = parts[j]!(s)
|
|
712
|
+
if (a === Fail) {
|
|
713
|
+
ok = false
|
|
714
|
+
break
|
|
715
|
+
}
|
|
716
|
+
if (j === ki) key = a
|
|
717
|
+
else if (j === vi) value = a
|
|
718
|
+
}
|
|
719
|
+
if (!ok) {
|
|
720
|
+
if (s.mode !== BT || count < min) return Fail
|
|
721
|
+
s.pos = start
|
|
722
|
+
return acc.result()
|
|
723
|
+
}
|
|
724
|
+
if (checkLoop && s.pos === start) {
|
|
725
|
+
s.mode = CUT
|
|
726
|
+
return Fail
|
|
727
|
+
}
|
|
728
|
+
acc.add(key, value)
|
|
729
|
+
}
|
|
730
|
+
return acc.result()
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const sep = this.compile(node.separator, false)
|
|
734
|
+
return (s) => {
|
|
735
|
+
const acc = new EntriesAcc()
|
|
736
|
+
let count = 0
|
|
737
|
+
if (max === 0) return acc.result()
|
|
738
|
+
let start = s.pos
|
|
739
|
+
if (!entry(s, acc)) {
|
|
740
|
+
if (s.mode !== BT || min > 0) return Fail
|
|
741
|
+
s.pos = start
|
|
742
|
+
return acc.result()
|
|
743
|
+
}
|
|
744
|
+
count++
|
|
745
|
+
while (count < max) {
|
|
746
|
+
start = s.pos
|
|
747
|
+
if (sep(s) === Fail) {
|
|
748
|
+
if (s.mode !== BT || count < min) return Fail
|
|
749
|
+
s.pos = start
|
|
750
|
+
return acc.result()
|
|
751
|
+
}
|
|
752
|
+
if (checkLoop && s.pos === start) {
|
|
753
|
+
s.mode = CUT
|
|
754
|
+
return Fail
|
|
755
|
+
}
|
|
756
|
+
if (!entry(s, acc)) {
|
|
757
|
+
if (s.mode !== BT || count < min) return Fail
|
|
758
|
+
s.pos = start
|
|
759
|
+
return acc.result()
|
|
760
|
+
}
|
|
761
|
+
count++
|
|
762
|
+
}
|
|
763
|
+
return acc.result()
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
repeatTill(node: NodeOf<"RepeatTill">): Step {
|
|
768
|
+
const { max, min } = node
|
|
769
|
+
const c = this.compile(node.self, true)
|
|
770
|
+
const end = this.compile(node.end, true)
|
|
771
|
+
return (s) => {
|
|
772
|
+
const acc: Array<unknown> = []
|
|
773
|
+
while (true) {
|
|
774
|
+
const start = s.pos
|
|
775
|
+
if (acc.length >= min) {
|
|
776
|
+
const b = end(s)
|
|
777
|
+
if (b !== Fail) return [acc, b]
|
|
778
|
+
if (s.mode !== BT) return Fail
|
|
779
|
+
s.pos = start
|
|
780
|
+
if (acc.length >= max) return Fail
|
|
781
|
+
}
|
|
782
|
+
const a = c(s)
|
|
783
|
+
if (a === Fail) return Fail
|
|
784
|
+
if (s.pos === start) {
|
|
785
|
+
s.mode = CUT
|
|
786
|
+
return Fail
|
|
787
|
+
}
|
|
788
|
+
acc.push(a)
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
separated(node: NodeOf<"Separated">, used: boolean): Step {
|
|
794
|
+
const { max, min } = node
|
|
795
|
+
const checkLoop = max === Infinity
|
|
796
|
+
const c = this.compile(node.self, used)
|
|
797
|
+
const sep = this.compile(node.separator, false)
|
|
798
|
+
const resumable = this.resumable
|
|
799
|
+
const key = {}
|
|
800
|
+
return (s) => {
|
|
801
|
+
const entry = s.pos
|
|
802
|
+
let acc: Array<unknown> | undefined
|
|
803
|
+
let count = 0
|
|
804
|
+
let resumed = false
|
|
805
|
+
if (resumable && s.resume !== undefined) {
|
|
806
|
+
const cp = Resume.take(s, key, entry) as Resume.Done | { acc: Array<unknown>; count: number; pos: number } | undefined
|
|
807
|
+
if (cp instanceof Resume.Done) {
|
|
808
|
+
s.pos = cp.pos
|
|
809
|
+
return cp.value
|
|
810
|
+
}
|
|
811
|
+
if (cp !== undefined) {
|
|
812
|
+
acc = cp.acc
|
|
813
|
+
count = cp.count
|
|
814
|
+
s.pos = cp.pos
|
|
815
|
+
resumed = true
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (!resumed) {
|
|
819
|
+
acc = used ? [] : undefined
|
|
820
|
+
if (max === 0) return acc
|
|
821
|
+
const start = s.pos
|
|
822
|
+
const first = c(s)
|
|
823
|
+
if (first === Fail) {
|
|
824
|
+
if (s.mode !== BT || min > 0) return Fail
|
|
825
|
+
s.pos = start
|
|
826
|
+
return acc
|
|
827
|
+
}
|
|
828
|
+
acc?.push(first)
|
|
829
|
+
count = 1
|
|
830
|
+
}
|
|
831
|
+
while (count < max) {
|
|
832
|
+
const start = s.pos
|
|
833
|
+
if (sep(s) === Fail) {
|
|
834
|
+
if (s.mode === INC && s.resume !== undefined) Resume.save(s, key, entry, { acc, count, pos: start })
|
|
835
|
+
if (s.mode !== BT || count < min) return Fail
|
|
836
|
+
s.pos = start
|
|
837
|
+
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
838
|
+
}
|
|
839
|
+
if (checkLoop && s.pos === start) {
|
|
840
|
+
s.mode = CUT
|
|
841
|
+
return Fail
|
|
842
|
+
}
|
|
843
|
+
const a = c(s)
|
|
844
|
+
if (a === Fail) {
|
|
845
|
+
if (s.mode === INC && s.resume !== undefined) Resume.save(s, key, entry, { acc, count, pos: start })
|
|
846
|
+
if (s.mode !== BT || count < min) return Fail
|
|
847
|
+
s.pos = start
|
|
848
|
+
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
849
|
+
}
|
|
850
|
+
acc?.push(a)
|
|
851
|
+
count++
|
|
852
|
+
}
|
|
853
|
+
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
separatedFold(node: NodeOf<"SeparatedFold">): Step {
|
|
858
|
+
const c = this.compile(node.self, true)
|
|
859
|
+
const sep = this.compile(node.separator, true)
|
|
860
|
+
const op = node.op
|
|
861
|
+
const resumable = this.resumable
|
|
862
|
+
const key = {}
|
|
863
|
+
if (!node.right) {
|
|
864
|
+
return (s) => {
|
|
865
|
+
const entry = s.pos
|
|
866
|
+
let acc: unknown
|
|
867
|
+
let resumed = false
|
|
868
|
+
if (resumable && s.resume !== undefined) {
|
|
869
|
+
const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; pos: number } | undefined
|
|
870
|
+
if (cp instanceof Resume.Done) {
|
|
871
|
+
s.pos = cp.pos
|
|
872
|
+
return cp.value
|
|
873
|
+
}
|
|
874
|
+
if (cp !== undefined) {
|
|
875
|
+
acc = cp.acc
|
|
876
|
+
s.pos = cp.pos
|
|
877
|
+
resumed = true
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (!resumed) {
|
|
881
|
+
acc = c(s)
|
|
882
|
+
if (acc === Fail) return Fail
|
|
883
|
+
}
|
|
884
|
+
while (true) {
|
|
885
|
+
const start = s.pos
|
|
886
|
+
const x = sep(s)
|
|
887
|
+
if (x === Fail) {
|
|
888
|
+
if (s.mode === INC && s.resume !== undefined) Resume.save(s, key, entry, { acc, pos: start })
|
|
889
|
+
if (s.mode !== BT) return Fail
|
|
890
|
+
s.pos = start
|
|
891
|
+
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
892
|
+
}
|
|
893
|
+
if (s.pos === start) {
|
|
894
|
+
s.mode = CUT
|
|
895
|
+
return Fail
|
|
896
|
+
}
|
|
897
|
+
const a = c(s)
|
|
898
|
+
if (a === Fail) {
|
|
899
|
+
if (s.mode === INC && s.resume !== undefined) Resume.save(s, key, entry, { acc, pos: start })
|
|
900
|
+
if (s.mode !== BT) return Fail
|
|
901
|
+
s.pos = start
|
|
902
|
+
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
903
|
+
}
|
|
904
|
+
acc = op(acc, x, a)
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return (s) => {
|
|
909
|
+
const first = c(s)
|
|
910
|
+
if (first === Fail) return Fail
|
|
911
|
+
const values: Array<unknown> = [first]
|
|
912
|
+
const seps: Array<unknown> = []
|
|
913
|
+
while (true) {
|
|
914
|
+
const start = s.pos
|
|
915
|
+
const x = sep(s)
|
|
916
|
+
if (x === Fail) {
|
|
917
|
+
if (s.mode !== BT) return Fail
|
|
918
|
+
s.pos = start
|
|
919
|
+
break
|
|
920
|
+
}
|
|
921
|
+
if (s.pos === start) {
|
|
922
|
+
s.mode = CUT
|
|
923
|
+
return Fail
|
|
924
|
+
}
|
|
925
|
+
const a = c(s)
|
|
926
|
+
if (a === Fail) {
|
|
927
|
+
if (s.mode !== BT) return Fail
|
|
928
|
+
s.pos = start
|
|
929
|
+
break
|
|
930
|
+
}
|
|
931
|
+
seps.push(x)
|
|
932
|
+
values.push(a)
|
|
933
|
+
}
|
|
934
|
+
let acc = values[values.length - 1]
|
|
935
|
+
for (let i = seps.length - 1; i >= 0; i--) acc = op(values[i], seps[i], acc)
|
|
936
|
+
return acc
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// ---------------------------------------------------------------------------
|
|
941
|
+
// Token parsers (`Token`, `Ascii`)
|
|
942
|
+
// ---------------------------------------------------------------------------
|
|
943
|
+
|
|
944
|
+
token(p: ParserLike, node: TokenNode, used: boolean): Step {
|
|
945
|
+
const kind = this.kind as 0 | 1
|
|
946
|
+
switch (node._tag) {
|
|
947
|
+
case "Any":
|
|
948
|
+
if (kind === 1) {
|
|
949
|
+
return (s) => {
|
|
950
|
+
const pos = s.pos
|
|
951
|
+
if (pos >= s.end) return fail(s)
|
|
952
|
+
s.pos = pos + 1
|
|
953
|
+
return (s.input as Uint8Array)[pos]
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return (s) => {
|
|
957
|
+
const pos = s.pos
|
|
958
|
+
if (pos >= s.end) return fail(s)
|
|
959
|
+
const str = s.input as string
|
|
960
|
+
const c = str.charCodeAt(pos)
|
|
961
|
+
if (c >= 0xd800 && c <= 0xdbff && pos + 1 < str.length && (str.charCodeAt(pos + 1) & 0xfc00) === 0xdc00) {
|
|
962
|
+
s.pos = pos + 2
|
|
963
|
+
return used ? str.slice(pos, pos + 2) : undefined
|
|
964
|
+
}
|
|
965
|
+
s.pos = pos + 1
|
|
966
|
+
return used ? str[pos] : undefined
|
|
967
|
+
}
|
|
968
|
+
case "Literal":
|
|
969
|
+
return this.tokenLiteral(p, node)
|
|
970
|
+
case "OneOf": {
|
|
971
|
+
const set = tokenSet(node.set, kind)
|
|
972
|
+
const negate = node.negate
|
|
973
|
+
const accept = acceptTable(set, negate)
|
|
974
|
+
const test = set.test
|
|
975
|
+
if (kind === 1) {
|
|
976
|
+
return (s) => {
|
|
977
|
+
const pos = s.pos
|
|
978
|
+
if (pos >= s.end) return fail(s)
|
|
979
|
+
const b = (s.input as Uint8Array)[pos]!
|
|
980
|
+
if (accept[b] === 0) return fail(s)
|
|
981
|
+
s.pos = pos + 1
|
|
982
|
+
return b
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return (s) => {
|
|
986
|
+
const pos = s.pos
|
|
987
|
+
if (pos >= s.end) return fail(s)
|
|
988
|
+
const str = s.input as string
|
|
989
|
+
const c = str.charCodeAt(pos)
|
|
990
|
+
if (c < TableSize) {
|
|
991
|
+
if (accept[c] === 0) return fail(s)
|
|
992
|
+
s.pos = pos + 1
|
|
993
|
+
return used ? str[pos] : undefined
|
|
994
|
+
}
|
|
995
|
+
let cp = c
|
|
996
|
+
if (c >= 0xd800 && c <= 0xdbff && pos + 1 < str.length) {
|
|
997
|
+
const d = str.charCodeAt(pos + 1)
|
|
998
|
+
if ((d & 0xfc00) === 0xdc00) cp = ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000
|
|
999
|
+
}
|
|
1000
|
+
if (test(cp) === negate) return fail(s)
|
|
1001
|
+
const len = cp > 0xffff ? 2 : 1
|
|
1002
|
+
s.pos = pos + len
|
|
1003
|
+
return !used ? undefined : len === 1 ? str[pos] : str.slice(pos, pos + 2)
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
case "Take": {
|
|
1007
|
+
const count = node.count
|
|
1008
|
+
if (kind === 1) {
|
|
1009
|
+
return (s) => {
|
|
1010
|
+
const start = s.pos
|
|
1011
|
+
if (s.end - start < count) return failAt(s, s.end)
|
|
1012
|
+
s.pos = start + count
|
|
1013
|
+
return used ? (s.input as Uint8Array).subarray(start, start + count) : undefined
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return (s) => {
|
|
1017
|
+
const str = s.input as string
|
|
1018
|
+
const start = s.pos
|
|
1019
|
+
const end = s.end
|
|
1020
|
+
let i = start
|
|
1021
|
+
let got = 0
|
|
1022
|
+
while (got < count && i < end) {
|
|
1023
|
+
const c = str.charCodeAt(i)
|
|
1024
|
+
i += c >= 0xd800 && c <= 0xdbff && i + 1 < str.length && (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00 ? 2 : 1
|
|
1025
|
+
got++
|
|
1026
|
+
}
|
|
1027
|
+
if (got < count) return failAt(s, i)
|
|
1028
|
+
s.pos = i
|
|
1029
|
+
return used ? str.slice(start, i) : undefined
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
case "TakeWhile":
|
|
1033
|
+
return this.tokenTakeWhile(node, used)
|
|
1034
|
+
case "TakeUntil": {
|
|
1035
|
+
const { literal, max, min } = node
|
|
1036
|
+
const bounded = min > 0 || max !== Infinity
|
|
1037
|
+
return (s) => {
|
|
1038
|
+
const start = s.pos
|
|
1039
|
+
const idx = indexOf(s, literal, start)
|
|
1040
|
+
if (idx < 0) return failAt(s, s.end)
|
|
1041
|
+
if (bounded) {
|
|
1042
|
+
const count = countTokens(s, start, idx)
|
|
1043
|
+
if (count < min || count > max) return failAt(s, idx)
|
|
1044
|
+
}
|
|
1045
|
+
s.pos = idx
|
|
1046
|
+
return used ? s.slice(start, idx) : undefined
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
case "Rest":
|
|
1050
|
+
return (s) => {
|
|
1051
|
+
const start = s.pos
|
|
1052
|
+
s.pos = s.end
|
|
1053
|
+
return used ? s.slice(start, s.end) : undefined
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
tokenLiteral(p: ParserLike, node: NodeOf<"Literal">): Step {
|
|
1059
|
+
const lit = node.literal
|
|
1060
|
+
const literalKind = typeof lit === "string" ? 0 : lit instanceof Uint8Array ? 1 : 2
|
|
1061
|
+
if (this.kind === 0) {
|
|
1062
|
+
if (literalKind !== 0) return this.fallback(p, node, "byte or array literal on string input")
|
|
1063
|
+
const str = lit as string
|
|
1064
|
+
const len = str.length
|
|
1065
|
+
if (node.caseless) {
|
|
1066
|
+
const lowered = Array.from({ length: len }, (_, i) => lowerAscii(str.charCodeAt(i)))
|
|
1067
|
+
return (s) => {
|
|
1068
|
+
const pos = s.pos
|
|
1069
|
+
if (s.end - pos < len) return fail(s)
|
|
1070
|
+
const input = s.input as string
|
|
1071
|
+
for (let i = 0; i < len; i++) {
|
|
1072
|
+
if (lowerAscii(input.charCodeAt(pos + i)) !== lowered[i]) return fail(s)
|
|
1073
|
+
}
|
|
1074
|
+
s.pos = pos + len
|
|
1075
|
+
return input.slice(pos, pos + len)
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return stringLiteral(str, str)
|
|
1079
|
+
}
|
|
1080
|
+
if (literalKind === 2) return this.fallback(p, node, "array literal on byte input")
|
|
1081
|
+
const bytes = literalKind === 0 ? new TextEncoder().encode(lit as string) : lit as Uint8Array
|
|
1082
|
+
const len = bytes.length
|
|
1083
|
+
const sameKind = literalKind === 1 && !node.caseless
|
|
1084
|
+
const expect = node.caseless ? Array.from(bytes, lowerAscii) : Array.from(bytes)
|
|
1085
|
+
const caseless = node.caseless
|
|
1086
|
+
return (s) => {
|
|
1087
|
+
const pos = s.pos
|
|
1088
|
+
if (s.end - pos < len) return fail(s)
|
|
1089
|
+
const input = s.input as Uint8Array
|
|
1090
|
+
for (let i = 0; i < len; i++) {
|
|
1091
|
+
const b = input[pos + i]!
|
|
1092
|
+
if ((caseless ? lowerAscii(b) : b) !== expect[i]) return fail(s)
|
|
1093
|
+
}
|
|
1094
|
+
s.pos = pos + len
|
|
1095
|
+
return sameKind ? lit : input.subarray(pos, pos + len)
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
tokenTakeWhile(node: NodeOf<"TakeWhile">, used: boolean): Step {
|
|
1100
|
+
const kind = this.kind as 0 | 1
|
|
1101
|
+
const { max, min, negate } = node
|
|
1102
|
+
const set = tokenSet(node.set, kind)
|
|
1103
|
+
const accept = acceptTable(set, negate)
|
|
1104
|
+
if (kind === 1) {
|
|
1105
|
+
return (s) => {
|
|
1106
|
+
const bytes = s.input as Uint8Array
|
|
1107
|
+
const start = s.pos
|
|
1108
|
+
const limit = max === Infinity ? s.end : Math.min(s.end, start + max)
|
|
1109
|
+
let i = start
|
|
1110
|
+
while (i < limit && accept[bytes[i]!] === 1) i++
|
|
1111
|
+
if (i - start < min) return failAt(s, i)
|
|
1112
|
+
s.pos = i
|
|
1113
|
+
return used ? bytes.subarray(start, i) : undefined
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
const test = set.test
|
|
1117
|
+
return (s) => {
|
|
1118
|
+
const str = s.input as string
|
|
1119
|
+
const start = s.pos
|
|
1120
|
+
const end = s.end
|
|
1121
|
+
let i = start
|
|
1122
|
+
let count = 0
|
|
1123
|
+
while (count < max && i < end) {
|
|
1124
|
+
const c = str.charCodeAt(i)
|
|
1125
|
+
if (c < TableSize) {
|
|
1126
|
+
if (accept[c] === 0) break
|
|
1127
|
+
i++
|
|
1128
|
+
} else if (c < 0xd800 || c > 0xdbff) {
|
|
1129
|
+
if (test(c) === negate) break
|
|
1130
|
+
i++
|
|
1131
|
+
} else {
|
|
1132
|
+
let cp = c
|
|
1133
|
+
if (i + 1 < str.length) {
|
|
1134
|
+
const d = str.charCodeAt(i + 1)
|
|
1135
|
+
if ((d & 0xfc00) === 0xdc00) cp = ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000
|
|
1136
|
+
}
|
|
1137
|
+
if (test(cp) === negate) break
|
|
1138
|
+
i += cp > 0xffff ? 2 : 1
|
|
1139
|
+
}
|
|
1140
|
+
count++
|
|
1141
|
+
}
|
|
1142
|
+
if (count < min) return failAt(s, i)
|
|
1143
|
+
s.pos = i
|
|
1144
|
+
return used ? str.slice(start, i) : undefined
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// ---------------------------------------------------------------------------
|
|
1149
|
+
// Text parsers, specialized per encoding
|
|
1150
|
+
// ---------------------------------------------------------------------------
|
|
1151
|
+
|
|
1152
|
+
text(p: ParserLike, node: TokenNode, enc: Encoding<any>, used: boolean): Step {
|
|
1153
|
+
const kind = this.kind as 0 | 1
|
|
1154
|
+
const fast = fastLimit(enc)
|
|
1155
|
+
const decode = enc.decode
|
|
1156
|
+
const width = enc.width
|
|
1157
|
+
const toText = enc.text
|
|
1158
|
+
const unitAt: (input: any, i: number) => number = kind === 0
|
|
1159
|
+
? (input: string, i) => input.charCodeAt(i)
|
|
1160
|
+
: (input: Uint8Array, i) => input[i]!
|
|
1161
|
+
switch (node._tag) {
|
|
1162
|
+
case "Any":
|
|
1163
|
+
return (s) => {
|
|
1164
|
+
const pos = s.pos
|
|
1165
|
+
const end = s.end
|
|
1166
|
+
if (pos >= end) return fail(s)
|
|
1167
|
+
const c = unitAt(s.input, pos)
|
|
1168
|
+
if (c < fast) {
|
|
1169
|
+
s.pos = pos + 1
|
|
1170
|
+
return used ? charOf(c) : undefined
|
|
1171
|
+
}
|
|
1172
|
+
const cp = decode(s.input, pos, end)
|
|
1173
|
+
if (cp < 0) return fail(s)
|
|
1174
|
+
s.pos = pos + width(cp)
|
|
1175
|
+
return used ? charOf(cp) : undefined
|
|
1176
|
+
}
|
|
1177
|
+
case "Literal": {
|
|
1178
|
+
const lit = node.literal as string
|
|
1179
|
+
if (node.caseless) {
|
|
1180
|
+
const folded = Array.from(lit, (c) => fold(c.codePointAt(0)!))
|
|
1181
|
+
const n = folded.length
|
|
1182
|
+
return (s) => {
|
|
1183
|
+
const start = s.pos
|
|
1184
|
+
const end = s.end
|
|
1185
|
+
const input = s.input
|
|
1186
|
+
let pos = start
|
|
1187
|
+
for (let i = 0; i < n; i++) {
|
|
1188
|
+
if (pos >= end) return failAt(s, pos)
|
|
1189
|
+
const c = unitAt(input, pos)
|
|
1190
|
+
let cp: number
|
|
1191
|
+
if (c < fast) {
|
|
1192
|
+
cp = c
|
|
1193
|
+
pos++
|
|
1194
|
+
} else {
|
|
1195
|
+
cp = decode(input, pos, end)
|
|
1196
|
+
if (cp < 0) return failAt(s, pos)
|
|
1197
|
+
pos += width(cp)
|
|
1198
|
+
}
|
|
1199
|
+
if (fold(cp) !== folded[i]) return failAt(s, pos)
|
|
1200
|
+
}
|
|
1201
|
+
s.pos = pos
|
|
1202
|
+
return used ? toText(input, start, pos) : undefined
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const encoded = enc.encode(lit)
|
|
1206
|
+
if (typeof encoded === "string") return stringLiteral(encoded, lit)
|
|
1207
|
+
const bytes = encoded as Uint8Array
|
|
1208
|
+
const len = bytes.length
|
|
1209
|
+
if (len === 1) {
|
|
1210
|
+
const b0 = bytes[0]!
|
|
1211
|
+
return (s) => {
|
|
1212
|
+
const pos = s.pos
|
|
1213
|
+
if (pos < s.end && (s.input as Uint8Array)[pos] === b0) {
|
|
1214
|
+
s.pos = pos + 1
|
|
1215
|
+
return lit
|
|
1216
|
+
}
|
|
1217
|
+
return fail(s)
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
return (s) => {
|
|
1221
|
+
const pos = s.pos
|
|
1222
|
+
if (s.end - pos < len) return fail(s)
|
|
1223
|
+
const input = s.input as Uint8Array
|
|
1224
|
+
for (let i = 0; i < len; i++) if (input[pos + i] !== bytes[i]) return fail(s)
|
|
1225
|
+
s.pos = pos + len
|
|
1226
|
+
return lit
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
case "OneOf": {
|
|
1230
|
+
const set = textSet(node.set)
|
|
1231
|
+
const negate = node.negate
|
|
1232
|
+
const accept = acceptTable(set, negate)
|
|
1233
|
+
const test = set.test
|
|
1234
|
+
return (s) => {
|
|
1235
|
+
const pos = s.pos
|
|
1236
|
+
const end = s.end
|
|
1237
|
+
if (pos >= end) return fail(s)
|
|
1238
|
+
const c = unitAt(s.input, pos)
|
|
1239
|
+
if (c < fast && c < TableSize) {
|
|
1240
|
+
if (accept[c] === 0) return fail(s)
|
|
1241
|
+
s.pos = pos + 1
|
|
1242
|
+
return used ? charOf(c) : undefined
|
|
1243
|
+
}
|
|
1244
|
+
const cp = c < fast ? c : decode(s.input, pos, end)
|
|
1245
|
+
if (cp < 0 || test(cp) === negate) return fail(s)
|
|
1246
|
+
s.pos = pos + (c < fast ? 1 : width(cp))
|
|
1247
|
+
return used ? charOf(cp) : undefined
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
case "Take": {
|
|
1251
|
+
const count = node.count
|
|
1252
|
+
return (s) => {
|
|
1253
|
+
const start = s.pos
|
|
1254
|
+
const end = s.end
|
|
1255
|
+
const input = s.input
|
|
1256
|
+
let pos = start
|
|
1257
|
+
for (let got = 0; got < count; got++) {
|
|
1258
|
+
if (pos >= end) return failAt(s, pos)
|
|
1259
|
+
const c = unitAt(input, pos)
|
|
1260
|
+
if (c < fast) pos++
|
|
1261
|
+
else {
|
|
1262
|
+
const cp = decode(input, pos, end)
|
|
1263
|
+
if (cp < 0) return failAt(s, pos)
|
|
1264
|
+
pos += width(cp)
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
s.pos = pos
|
|
1268
|
+
return used ? toText(input, start, pos) : undefined
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
case "TakeWhile": {
|
|
1272
|
+
const { max, min, negate } = node
|
|
1273
|
+
const set = textSet(node.set)
|
|
1274
|
+
const accept = acceptTable(set, negate)
|
|
1275
|
+
const test = set.test
|
|
1276
|
+
const small = Math.min(fast, TableSize)
|
|
1277
|
+
return (s) => {
|
|
1278
|
+
const input = s.input
|
|
1279
|
+
const start = s.pos
|
|
1280
|
+
const end = s.end
|
|
1281
|
+
let i = start
|
|
1282
|
+
let count = 0
|
|
1283
|
+
while (count < max && i < end) {
|
|
1284
|
+
const c = unitAt(input, i)
|
|
1285
|
+
if (c < small) {
|
|
1286
|
+
if (accept[c] === 0) break
|
|
1287
|
+
i++
|
|
1288
|
+
} else if (c < fast) {
|
|
1289
|
+
if (test(c) === negate) break
|
|
1290
|
+
i++
|
|
1291
|
+
} else {
|
|
1292
|
+
const cp = decode(input, i, end)
|
|
1293
|
+
if (cp < 0 || test(cp) === negate) break
|
|
1294
|
+
i += width(cp)
|
|
1295
|
+
}
|
|
1296
|
+
count++
|
|
1297
|
+
}
|
|
1298
|
+
if (count < min) return failAt(s, i)
|
|
1299
|
+
s.pos = i
|
|
1300
|
+
return used ? toText(input, start, i) : undefined
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
case "TakeUntil":
|
|
1304
|
+
case "Rest":
|
|
1305
|
+
return this.fallback(p, node, "Text parser without a compiled form")
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
taken(node: NodeOf<"Taken">, used: boolean): Step {
|
|
1310
|
+
const c = this.compile(node.self, false)
|
|
1311
|
+
const enc = node.encoding
|
|
1312
|
+
if (enc === undefined) {
|
|
1313
|
+
if (!used) return c
|
|
1314
|
+
if (this.kind === 0) {
|
|
1315
|
+
return (s) => {
|
|
1316
|
+
const start = s.pos
|
|
1317
|
+
return c(s) === Fail ? Fail : (s.input as string).slice(start, s.pos)
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return (s) => {
|
|
1321
|
+
const start = s.pos
|
|
1322
|
+
return c(s) === Fail ? Fail : s.slice(start, s.pos)
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
// `Text.taken` checks that the text is well formed, then decodes it
|
|
1326
|
+
const fast = fastLimit(enc)
|
|
1327
|
+
const toText = enc.text
|
|
1328
|
+
const unitAt: (input: any, i: number) => number = this.kind === 0
|
|
1329
|
+
? (input: string, i) => input.charCodeAt(i)
|
|
1330
|
+
: (input: Uint8Array, i) => input[i]!
|
|
1331
|
+
return (s) => {
|
|
1332
|
+
const start = s.pos
|
|
1333
|
+
if (c(s) === Fail) return Fail
|
|
1334
|
+
const end = s.pos
|
|
1335
|
+
const input = s.input
|
|
1336
|
+
let i = start
|
|
1337
|
+
while (i < end && unitAt(input, i) < fast) i++
|
|
1338
|
+
if (i < end && validate(enc, s, i, end) < 0) return fail(s)
|
|
1339
|
+
return used ? toText(input, start, end) : undefined
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* `Ascii.float` and `Text.float` (`map(floatText, toFloat)`): numbers in
|
|
1345
|
+
* decimal notation are matched and converted in one pass, without
|
|
1346
|
+
* slicing the input; the rest runs the compiled parser (see the JIT).
|
|
1347
|
+
*/
|
|
1348
|
+
private float(node: NodeOf<"Map">): Step | undefined {
|
|
1349
|
+
if (!floatTexts.has(node.self) || this.kind === 2) return undefined
|
|
1350
|
+
let taken = nodeOf(node.self)
|
|
1351
|
+
while (taken._tag === "Expected") taken = nodeOf(taken.self)
|
|
1352
|
+
if (taken._tag !== "Taken") return undefined
|
|
1353
|
+
const enc = taken.encoding
|
|
1354
|
+
if (enc !== undefined && !(this.native(enc) && enc.unit === 1 && enc.asciiCompatible)) return undefined
|
|
1355
|
+
const c = this.compile(node.self, true)
|
|
1356
|
+
const scan = (this.kind === 0 ? scanFloatString : scanFloatBytes) as (input: unknown, i: number, end: number) => unknown
|
|
1357
|
+
const resumable = this.resumable
|
|
1358
|
+
return (s) => {
|
|
1359
|
+
const v = scan(s.input, s.pos, s.end)
|
|
1360
|
+
// Near the end of a partial input, the parser may have to report `Incomplete`
|
|
1361
|
+
if (v === undefined || (resumable && s.partial && floatScan.read + Margin > s.end)) {
|
|
1362
|
+
const t = c(s)
|
|
1363
|
+
return t === Fail ? Fail : toFloat(t)
|
|
1364
|
+
}
|
|
1365
|
+
if (v === Fail) return fail(s)
|
|
1366
|
+
s.pos = floatScan.end
|
|
1367
|
+
return v
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
escaped(node: NodeOf<"Escaped">): Step {
|
|
1372
|
+
const raw = node.raw
|
|
1373
|
+
const normal = this.compile(node.normal, !raw)
|
|
1374
|
+
const escape = this.compile(node.escape, !raw)
|
|
1375
|
+
const enc = node.encoding
|
|
1376
|
+
const ctrl = node.control
|
|
1377
|
+
const kind = this.kind
|
|
1378
|
+
// Matches the control character, advancing past it
|
|
1379
|
+
let control: (s: State<any>) => boolean
|
|
1380
|
+
if (enc === undefined || (ctrl < fastLimit(enc) && ctrl < TableSize)) {
|
|
1381
|
+
control = kind === 0
|
|
1382
|
+
? (s) => {
|
|
1383
|
+
if (s.pos < s.end && (s.input as string).charCodeAt(s.pos) === ctrl) {
|
|
1384
|
+
s.pos++
|
|
1385
|
+
return true
|
|
1386
|
+
}
|
|
1387
|
+
return false
|
|
1388
|
+
}
|
|
1389
|
+
: (s) => {
|
|
1390
|
+
if (s.pos < s.end && (s.input as Uint8Array)[s.pos] === ctrl) {
|
|
1391
|
+
s.pos++
|
|
1392
|
+
return true
|
|
1393
|
+
}
|
|
1394
|
+
return false
|
|
1395
|
+
}
|
|
1396
|
+
} else {
|
|
1397
|
+
control = (s) => {
|
|
1398
|
+
if (s.pos >= s.end) return false
|
|
1399
|
+
const cp = enc.decode(s.input, s.pos, s.end)
|
|
1400
|
+
if (cp !== ctrl) return false
|
|
1401
|
+
s.pos += enc.width(cp)
|
|
1402
|
+
return true
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
const resumable = this.resumable
|
|
1406
|
+
const key = {}
|
|
1407
|
+
/** Fails as incomplete, keeping a checkpoint in a streaming parse. */
|
|
1408
|
+
const incomplete = (s: State<any>, start: number, checkpoint: unknown): typeof Fail => {
|
|
1409
|
+
if (s.resume !== undefined) Resume.save(s, key, start, checkpoint)
|
|
1410
|
+
s.mode = INC
|
|
1411
|
+
return Fail
|
|
1412
|
+
}
|
|
1413
|
+
if (raw) {
|
|
1414
|
+
const slice: (s: State<any>, start: number) => unknown = enc === undefined
|
|
1415
|
+
? (s, start) => s.slice(start, s.pos)
|
|
1416
|
+
: (s, start) => enc.text(s.input, start, s.pos)
|
|
1417
|
+
return (s) => {
|
|
1418
|
+
const start = s.pos
|
|
1419
|
+
let resumed = false
|
|
1420
|
+
if (resumable && s.resume !== undefined) {
|
|
1421
|
+
const cp = Resume.take(s, key, start) as Resume.Done | number | undefined
|
|
1422
|
+
if (cp instanceof Resume.Done) {
|
|
1423
|
+
s.pos = cp.pos
|
|
1424
|
+
return cp.value
|
|
1425
|
+
}
|
|
1426
|
+
if (cp !== undefined) {
|
|
1427
|
+
s.pos = cp
|
|
1428
|
+
resumed = true
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
while (s.pos < s.end) {
|
|
1432
|
+
const before = s.pos
|
|
1433
|
+
if (normal(s) !== Fail) {
|
|
1434
|
+
if (s.pos === before) {
|
|
1435
|
+
s.mode = CUT
|
|
1436
|
+
return Fail
|
|
1437
|
+
}
|
|
1438
|
+
continue
|
|
1439
|
+
}
|
|
1440
|
+
if (s.mode === INC) return incomplete(s, start, before)
|
|
1441
|
+
if (s.mode !== BT) return Fail
|
|
1442
|
+
s.pos = before
|
|
1443
|
+
if (control(s)) {
|
|
1444
|
+
if (escape(s) === Fail) return s.mode === INC ? incomplete(s, start, before) : Fail
|
|
1445
|
+
} else {
|
|
1446
|
+
return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (s.partial) return incomplete(s, start, s.pos)
|
|
1450
|
+
return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return (s) => {
|
|
1454
|
+
const start = s.pos
|
|
1455
|
+
let out = ""
|
|
1456
|
+
let resumed = false
|
|
1457
|
+
if (resumable && s.resume !== undefined) {
|
|
1458
|
+
const cp = Resume.take(s, key, start) as Resume.Done | { out: string; pos: number } | undefined
|
|
1459
|
+
if (cp instanceof Resume.Done) {
|
|
1460
|
+
s.pos = cp.pos
|
|
1461
|
+
return cp.value
|
|
1462
|
+
}
|
|
1463
|
+
if (cp !== undefined) {
|
|
1464
|
+
out = cp.out
|
|
1465
|
+
s.pos = cp.pos
|
|
1466
|
+
resumed = true
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
while (s.pos < s.end) {
|
|
1470
|
+
const before = s.pos
|
|
1471
|
+
const a = normal(s)
|
|
1472
|
+
if (a !== Fail) {
|
|
1473
|
+
if (s.pos !== before) {
|
|
1474
|
+
out += a
|
|
1475
|
+
continue
|
|
1476
|
+
}
|
|
1477
|
+
} else {
|
|
1478
|
+
if (s.mode === INC) return incomplete(s, start, { out, pos: before })
|
|
1479
|
+
if (s.mode !== BT) return Fail
|
|
1480
|
+
s.pos = before
|
|
1481
|
+
}
|
|
1482
|
+
if (control(s)) {
|
|
1483
|
+
const b = escape(s)
|
|
1484
|
+
if (b === Fail) return s.mode === INC ? incomplete(s, start, { out, pos: before }) : Fail
|
|
1485
|
+
out += b
|
|
1486
|
+
} else {
|
|
1487
|
+
return resumed ? Resume.done(s, key, start, true, out) : out
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
if (s.partial) return incomplete(s, start, { out, pos: s.pos })
|
|
1491
|
+
return resumed ? Resume.done(s, key, start, true, out) : out
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
type TokenNode = NodeOf<"Any" | "Literal" | "OneOf" | "Take" | "TakeWhile" | "TakeUntil" | "Rest">
|
|
1497
|
+
|
|
1498
|
+
const fail = (s: State<any>): typeof Fail => {
|
|
1499
|
+
s.mode = BT
|
|
1500
|
+
return Fail
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/** Fails, leaving the cursor at `pos`: how far a token parser read (see `partialSafe`). */
|
|
1504
|
+
const failAt = (s: State<any>, pos: number): typeof Fail => {
|
|
1505
|
+
s.pos = pos
|
|
1506
|
+
s.mode = BT
|
|
1507
|
+
return Fail
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
/** 1 where a small code is accepted (taking `negate` into account). */
|
|
1511
|
+
const acceptTable = (set: CharSet, negate: boolean): Uint8Array => {
|
|
1512
|
+
const t = new Uint8Array(TableSize)
|
|
1513
|
+
for (let c = 0; c < TableSize; c++) t[c] = (set.table[c] === 1) !== negate ? 1 : 0
|
|
1514
|
+
return t
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/** A literal matched against a string input, returning `value`. */
|
|
1518
|
+
const stringLiteral = (str: string, value: unknown): Step => {
|
|
1519
|
+
const len = str.length
|
|
1520
|
+
if (len === 0) return () => value
|
|
1521
|
+
if (len === 1) {
|
|
1522
|
+
const c0 = str.charCodeAt(0)
|
|
1523
|
+
return (s) => {
|
|
1524
|
+
const pos = s.pos
|
|
1525
|
+
if (pos < s.end && (s.input as string).charCodeAt(pos) === c0) {
|
|
1526
|
+
s.pos = pos + 1
|
|
1527
|
+
return value
|
|
1528
|
+
}
|
|
1529
|
+
return fail(s)
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return (s) => {
|
|
1533
|
+
const pos = s.pos
|
|
1534
|
+
if (s.end - pos >= len && (s.input as string).startsWith(str, pos)) {
|
|
1535
|
+
s.pos = pos + len
|
|
1536
|
+
return value
|
|
1537
|
+
}
|
|
1538
|
+
return fail(s)
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/** A closure compiler for one input kind; it memoizes what it compiles. */
|
|
1543
|
+
/** The object of `Object.fromEntries`, built an entry at a time (see `internal/entries.ts`). */
|
|
1544
|
+
class EntriesAcc {
|
|
1545
|
+
readonly o: Record<string, unknown> = {}
|
|
1546
|
+
deferred: Array<unknown> | undefined = undefined
|
|
1547
|
+
add(key: unknown, value: unknown): void {
|
|
1548
|
+
if (this.deferred === undefined && isPlainKey(key)) this.o[key] = value
|
|
1549
|
+
else this.deferred = addEntry(this.o, this.deferred, key, value)
|
|
1550
|
+
}
|
|
1551
|
+
result(): object {
|
|
1552
|
+
if (this.deferred !== undefined) flushEntries(this.o, this.deferred)
|
|
1553
|
+
return this.o
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
export interface ClosureCompiler {
|
|
1558
|
+
readonly compile: (p: ParserLike, used: boolean) => Step
|
|
1559
|
+
readonly note: (p: ParserLike, node: Node, reason: string) => void
|
|
1560
|
+
readonly firsts: FirstSets
|
|
1561
|
+
readonly fallbacks: ReadonlyArray<Fallback>
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
export const createClosureCompiler = (kind: 0 | 1 | 2, resumable = false): ClosureCompiler => {
|
|
1565
|
+
const compiler = new Compiler(kind, resumable)
|
|
1566
|
+
return {
|
|
1567
|
+
compile: (p, used) => compiler.compile(p, used),
|
|
1568
|
+
note: (p, node, reason) => compiler.note(p, node, reason),
|
|
1569
|
+
firsts: compiler.firsts,
|
|
1570
|
+
fallbacks: compiler.fallbacks
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/** Compiles a root parser for one input kind. */
|
|
1575
|
+
export const compileClosure = (
|
|
1576
|
+
root: ParserLike,
|
|
1577
|
+
kind: 0 | 1 | 2,
|
|
1578
|
+
resumable = false
|
|
1579
|
+
): { readonly step: Step; readonly fallbacks: ReadonlyArray<Fallback> } => {
|
|
1580
|
+
const compiler = new Compiler(kind, resumable)
|
|
1581
|
+
const step = compiler.compile(root, true)
|
|
1582
|
+
return { step, fallbacks: compiler.fallbacks }
|
|
1583
|
+
}
|