@thomasfosterau/effect-parse 0.4.0 → 0.5.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/README.md +40 -14
- package/dist/Ascii.d.ts +11 -4
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +7 -1
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +64 -35
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +7 -4
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +74 -2
- package/dist/Compiler.d.ts.map +1 -1
- package/dist/Compiler.js +78 -1
- package/dist/Compiler.js.map +1 -1
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +86 -74
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +37 -4
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +42 -0
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +5 -15
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts +2 -2
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +1 -1
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +370 -28
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +390 -19
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.js +2 -2
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +3 -2
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +15 -10
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +23 -5
- package/dist/Syntax.d.ts.map +1 -1
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +9 -9
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +55 -10
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +59 -21
- package/dist/Token.js.map +1 -1
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +257 -97
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +3 -1
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +3 -2
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +7 -1
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/graph.d.ts +17 -0
- package/dist/internal/compiler/graph.d.ts.map +1 -0
- package/dist/internal/compiler/graph.js +56 -0
- package/dist/internal/compiler/graph.js.map +1 -0
- package/dist/internal/compiler/jit.d.ts +5 -0
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +185 -96
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +10 -2
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/search.d.ts +32 -0
- package/dist/internal/compiler/search.d.ts.map +1 -0
- package/dist/internal/compiler/search.js +68 -0
- package/dist/internal/compiler/search.js.map +1 -0
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +1 -1
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/compiler/wasm.d.ts +8 -0
- package/dist/internal/compiler/wasm.d.ts.map +1 -0
- package/dist/internal/compiler/wasm.js +2769 -0
- package/dist/internal/compiler/wasm.js.map +1 -0
- package/dist/internal/compiler/wasmAsm.d.ts +199 -0
- package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
- package/dist/internal/compiler/wasmAsm.js +427 -0
- package/dist/internal/compiler/wasmAsm.js.map +1 -0
- package/dist/internal/entries.d.ts +3 -1
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +1 -2
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +58 -2
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +13 -3
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/run.d.ts +12 -0
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +51 -11
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +6 -0
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +8 -2
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/syntax.d.ts +4 -4
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +18 -10
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/text.d.ts +32 -0
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +68 -14
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/tokens.d.ts +13 -0
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +59 -39
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +5 -4
- package/src/Ascii.ts +42 -9
- package/src/Binary.ts +151 -71
- package/src/Bits.ts +12 -6
- package/src/Compiler.ts +101 -10
- package/src/Expression.ts +94 -81
- package/src/Input.ts +65 -6
- package/src/Nom.ts +15 -5
- package/src/ParseError.ts +9 -4
- package/src/Parser.ts +682 -129
- package/src/ParserSchema.ts +1 -1
- package/src/ParserStream.ts +40 -19
- package/src/Syntax.ts +60 -36
- package/src/Text.ts +16 -15
- package/src/Token.ts +138 -29
- package/src/internal/compiler/closure.ts +362 -135
- package/src/internal/compiler/entries.ts +4 -2
- package/src/internal/compiler/first.ts +10 -0
- package/src/internal/compiler/graph.ts +68 -0
- package/src/internal/compiler/jit.ts +284 -154
- package/src/internal/compiler/registry.ts +10 -2
- package/src/internal/compiler/search.ts +68 -0
- package/src/internal/compiler/sets.ts +7 -3
- package/src/internal/compiler/wasm.ts +3203 -0
- package/src/internal/compiler/wasmAsm.ts +484 -0
- package/src/internal/entries.ts +10 -4
- package/src/internal/node.ts +54 -4
- package/src/internal/numbers.ts +3 -1
- package/src/internal/run.ts +71 -12
- package/src/internal/stack.ts +8 -1
- package/src/internal/syntax.ts +39 -18
- package/src/internal/text.ts +97 -9
- package/src/internal/tokens.ts +96 -14
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import * as Predicate from "effect/Predicate"
|
|
11
11
|
import type { Encoding } from "../../Encoding.js"
|
|
12
|
-
import type { ErrMode, State } from "../../Input.js"
|
|
12
|
+
import type { ErrMode, Input, State } from "../../Input.js"
|
|
13
13
|
import { Fail } from "../../Input.js"
|
|
14
14
|
import * as Option from "effect/Option"
|
|
15
15
|
import * as Result from "effect/Result"
|
|
16
16
|
import type { Node, NodeOf, ParserLike } from "../node.js"
|
|
17
17
|
import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
|
|
18
|
-
import { nodeOf, scansFloat } from "../node.js"
|
|
18
|
+
import { Defer, nodeOf, scansFloat } from "../node.js"
|
|
19
19
|
import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js"
|
|
20
|
-
import { charOf, fastLimit, firstCodePoint, fold, utf16be, utf16le, validate } from "../text.js"
|
|
21
|
-
import { countTokens, indexOf } from "../tokens.js"
|
|
20
|
+
import { charOf, fastLimit, firstCodePoint, fold, textIn, utf16be, utf16le, utf8, utf8Text, validate } from "../text.js"
|
|
21
|
+
import { countTokens, indexOf, indexOfAny } from "../tokens.js"
|
|
22
22
|
import type { AltPlan } from "./first.js"
|
|
23
23
|
import { Key } from "../key.js"
|
|
24
24
|
import * as Resume from "../resume.js"
|
|
@@ -26,6 +26,7 @@ import type { Backend } from "./registry.js"
|
|
|
26
26
|
import type { EntryPair } from "./entries.js"
|
|
27
27
|
import { buildsEntries, pairOf } from "./entries.js"
|
|
28
28
|
import { FirstSets, planAlt } from "./first.js"
|
|
29
|
+
import { nextStop, SearchHead, StopCache, stopChars } from "./search.js"
|
|
29
30
|
import { acceptTable, kindOfEncoding, TableSize, textSet, tokenSet } from "./sets.js"
|
|
30
31
|
|
|
31
32
|
export type Step = (state: State<any>) => any
|
|
@@ -137,6 +138,11 @@ class Compiler {
|
|
|
137
138
|
switch (node._tag) {
|
|
138
139
|
case "Opaque":
|
|
139
140
|
return this.fallback(p, node, "not describable (Parser.make, gen, ...)")
|
|
141
|
+
case "Fixed":
|
|
142
|
+
// `Binary`'s numbers: the step is already what a closure would be
|
|
143
|
+
return p.step
|
|
144
|
+
case "Combinator":
|
|
145
|
+
return node.build(node.parsers.map((q) => this.compile(q, true)))
|
|
140
146
|
case "Succeed": {
|
|
141
147
|
const value = node.value
|
|
142
148
|
return () => value
|
|
@@ -158,10 +164,8 @@ class Compiler {
|
|
|
158
164
|
s.mode = BT
|
|
159
165
|
return Fail
|
|
160
166
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return Fail
|
|
164
|
-
}
|
|
167
|
+
// `eof`'s own failure: `Incomplete`, needing one more unit
|
|
168
|
+
if (s.partial) return s.incomplete(1)
|
|
165
169
|
return undefined
|
|
166
170
|
}
|
|
167
171
|
case "Location":
|
|
@@ -176,14 +180,13 @@ class Compiler {
|
|
|
176
180
|
case "Take":
|
|
177
181
|
case "TakeWhile":
|
|
178
182
|
case "TakeUntil":
|
|
183
|
+
case "TakeUntilAny":
|
|
179
184
|
case "Rest":
|
|
180
185
|
if (!this.native(node.encoding)) return this.fallback(p, node, "token parser on array input")
|
|
181
186
|
return this.partialSafe(
|
|
182
187
|
p,
|
|
183
188
|
node.encoding === undefined ? this.token(p, node, used) : this.text(p, node, node.encoding, used),
|
|
184
|
-
|
|
185
|
-
? node.literal.length * 4
|
|
186
|
-
: Margin
|
|
189
|
+
marginOf(node)
|
|
187
190
|
)
|
|
188
191
|
case "TillLineEnding":
|
|
189
192
|
case "Identifier":
|
|
@@ -199,7 +202,7 @@ class Compiler {
|
|
|
199
202
|
if (step !== undefined) return step
|
|
200
203
|
}
|
|
201
204
|
// Streaming builds keep the array, which their checkpoints hold
|
|
202
|
-
if (node.f === fromEntries &&
|
|
205
|
+
if (node.f === fromEntries && buildsEntries(node.self)) return this.entries(node.self)
|
|
203
206
|
const c = this.compile(node.self, true)
|
|
204
207
|
const f = node.f
|
|
205
208
|
return (s) => {
|
|
@@ -246,6 +249,28 @@ class Compiler {
|
|
|
246
249
|
return Fail
|
|
247
250
|
}
|
|
248
251
|
}
|
|
252
|
+
case "FastPath": {
|
|
253
|
+
const c = this.compile(node.self, used)
|
|
254
|
+
const fast = node.fast
|
|
255
|
+
return (s) => {
|
|
256
|
+
const start = s.pos
|
|
257
|
+
const a = fast(s)
|
|
258
|
+
if (a !== Defer) return a
|
|
259
|
+
s.pos = start
|
|
260
|
+
return c(s)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
case "MapOrFail": {
|
|
264
|
+
const c = this.compile(node.self, true)
|
|
265
|
+
const f = node.f
|
|
266
|
+
return (s) => {
|
|
267
|
+
const a = c(s)
|
|
268
|
+
if (a === Fail) return Fail
|
|
269
|
+
const b = f(a)
|
|
270
|
+
if (b === Fail) s.mode = BT
|
|
271
|
+
return b
|
|
272
|
+
}
|
|
273
|
+
}
|
|
249
274
|
case "Filter": {
|
|
250
275
|
const c = this.compile(node.self, true)
|
|
251
276
|
const predicate = node.predicate
|
|
@@ -280,6 +305,16 @@ class Compiler {
|
|
|
280
305
|
return a === Fail ? Fail : [a, s.slice(start, s.pos)]
|
|
281
306
|
}
|
|
282
307
|
}
|
|
308
|
+
case "MapSpan": {
|
|
309
|
+
const c = this.compile(node.self, used)
|
|
310
|
+
if (!used) return c
|
|
311
|
+
const f = node.f
|
|
312
|
+
return (s) => {
|
|
313
|
+
const start = s.pos
|
|
314
|
+
const a = c(s)
|
|
315
|
+
return a === Fail ? Fail : f(a, start, s.pos)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
283
318
|
case "Span": {
|
|
284
319
|
const c = this.compile(node.self, false)
|
|
285
320
|
if (!used) return c
|
|
@@ -308,6 +343,7 @@ class Compiler {
|
|
|
308
343
|
}
|
|
309
344
|
case "Context":
|
|
310
345
|
case "Expected":
|
|
346
|
+
case "Quiet":
|
|
311
347
|
case "MapError":
|
|
312
348
|
case "OrFail":
|
|
313
349
|
case "Trace":
|
|
@@ -377,6 +413,11 @@ class Compiler {
|
|
|
377
413
|
return out
|
|
378
414
|
}
|
|
379
415
|
}
|
|
416
|
+
case "SeqWith": {
|
|
417
|
+
const steps = node.parsers.map((q) => this.compile(q, used))
|
|
418
|
+
if (!used) return this.chain(steps)
|
|
419
|
+
return this.seqWith(steps, node.f)
|
|
420
|
+
}
|
|
380
421
|
case "ZipWith": {
|
|
381
422
|
const c1 = this.compile(node.self, used)
|
|
382
423
|
const c2 = this.compile(node.that, used)
|
|
@@ -394,8 +435,26 @@ class Compiler {
|
|
|
394
435
|
case "Dispatch":
|
|
395
436
|
return this.dispatch(p, node, used)
|
|
396
437
|
case "Repeat":
|
|
397
|
-
case "Fold":
|
|
438
|
+
case "Fold": {
|
|
439
|
+
// A run of a character set whose characters are not kept scans as a `takeWhile` (#79)
|
|
440
|
+
const element = Predicate.isTagged(node, "Repeat") && !used ? nodeOf(node.self) : undefined
|
|
441
|
+
if (
|
|
442
|
+
element !== undefined &&
|
|
443
|
+
Predicate.isTagged(element, "OneOf") &&
|
|
444
|
+
this.kind !== 2 &&
|
|
445
|
+
this.native(element.encoding)
|
|
446
|
+
) {
|
|
447
|
+
const { encoding, negate, set } = element
|
|
448
|
+
const run: NodeOf<"TakeWhile"> = { _tag: "TakeWhile", set, negate, encoding, min: node.min, max: node.max }
|
|
449
|
+
// The repetition's own step is the exact fallback near the end of a partial input
|
|
450
|
+
return this.partialSafe(
|
|
451
|
+
p,
|
|
452
|
+
encoding === undefined ? this.token(p, run, false) : this.text(p, run, encoding, false),
|
|
453
|
+
Margin
|
|
454
|
+
)
|
|
455
|
+
}
|
|
398
456
|
return this.repeat(node, used)
|
|
457
|
+
}
|
|
399
458
|
case "RepeatTill":
|
|
400
459
|
return this.repeatTill(node)
|
|
401
460
|
case "Separated":
|
|
@@ -408,13 +467,51 @@ class Compiler {
|
|
|
408
467
|
}
|
|
409
468
|
}
|
|
410
469
|
|
|
470
|
+
/** `seqWith`: up to three parsers without an array of values. */
|
|
471
|
+
seqWith(steps: ReadonlyArray<Step>, f: NodeOf<"SeqWith">["f"]): Step {
|
|
472
|
+
const n = steps.length
|
|
473
|
+
if (n === 0) return () => f()
|
|
474
|
+
const [c1, c2, c3] = steps
|
|
475
|
+
if (n === 1)
|
|
476
|
+
return (s) => {
|
|
477
|
+
const a = c1(s)
|
|
478
|
+
return a === Fail ? Fail : f(a)
|
|
479
|
+
}
|
|
480
|
+
if (n === 2)
|
|
481
|
+
return (s) => {
|
|
482
|
+
const a = c1(s)
|
|
483
|
+
if (a === Fail) return Fail
|
|
484
|
+
const b = c2(s)
|
|
485
|
+
return b === Fail ? Fail : f(a, b)
|
|
486
|
+
}
|
|
487
|
+
if (n === 3)
|
|
488
|
+
return (s) => {
|
|
489
|
+
const a = c1(s)
|
|
490
|
+
if (a === Fail) return Fail
|
|
491
|
+
const b = c2(s)
|
|
492
|
+
if (b === Fail) return Fail
|
|
493
|
+
const c = c3(s)
|
|
494
|
+
return c === Fail ? Fail : f(a, b, c)
|
|
495
|
+
}
|
|
496
|
+
return (s) => {
|
|
497
|
+
const values = new Array(n)
|
|
498
|
+
for (let i = 0; i < n; i++) {
|
|
499
|
+
const a = steps[i](s)
|
|
500
|
+
if (a === Fail) return Fail
|
|
501
|
+
values[i] = a
|
|
502
|
+
}
|
|
503
|
+
return f(...values)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
411
507
|
/** Runs steps in order, for their effect on the cursor only. */
|
|
412
508
|
chain(steps: ReadonlyArray<Step>): Step {
|
|
413
509
|
const n = steps.length
|
|
414
510
|
if (n === 0) return () => undefined
|
|
415
511
|
if (n === 1) return steps[0]
|
|
416
512
|
if (n === 2) {
|
|
417
|
-
const
|
|
513
|
+
const a = steps[0]
|
|
514
|
+
const b = steps[1]
|
|
418
515
|
return (s) => (a(s) === Fail ? Fail : b(s))
|
|
419
516
|
}
|
|
420
517
|
return (s) => {
|
|
@@ -443,7 +540,8 @@ class Compiler {
|
|
|
443
540
|
}
|
|
444
541
|
if (Predicate.isNumber(output)) {
|
|
445
542
|
if (n === 2) {
|
|
446
|
-
const
|
|
543
|
+
const a = steps[0]
|
|
544
|
+
const b = steps[1]
|
|
447
545
|
return output === 0
|
|
448
546
|
? (s) => {
|
|
449
547
|
const x = a(s)
|
|
@@ -479,8 +577,8 @@ class Compiler {
|
|
|
479
577
|
/** The unit at the cursor, or -1 at the end of the input. */
|
|
480
578
|
private unitReader(): (s: State<any>) => number {
|
|
481
579
|
return this.kind === 0
|
|
482
|
-
? (s) => (s.pos < s.end ?
|
|
483
|
-
: (s) => (s.pos < s.end ?
|
|
580
|
+
? (s: State<string>) => (s.pos < s.end ? s.input.charCodeAt(s.pos) : -1)
|
|
581
|
+
: (s: State<Uint8Array>) => (s.pos < s.end ? s.input[s.pos] : -1)
|
|
484
582
|
}
|
|
485
583
|
|
|
486
584
|
alt(node: NodeOf<"Alt">, used: boolean): Step {
|
|
@@ -533,7 +631,7 @@ class Compiler {
|
|
|
533
631
|
const compiled = new Map<ParserLike, Step>()
|
|
534
632
|
if (node.cases !== undefined) {
|
|
535
633
|
for (const key of Reflect.ownKeys(node.cases)) {
|
|
536
|
-
const q = node.cases[key
|
|
634
|
+
const q = node.cases[key]
|
|
537
635
|
if (!compiled.has(q)) compiled.set(q, this.compile(q, used))
|
|
538
636
|
}
|
|
539
637
|
}
|
|
@@ -557,9 +655,9 @@ class Compiler {
|
|
|
557
655
|
return general
|
|
558
656
|
}
|
|
559
657
|
// Switch on the next unit when the selector is `any` or `peek(any)`
|
|
560
|
-
|
|
561
|
-
const peek = Predicate.isTagged(
|
|
562
|
-
|
|
658
|
+
const top = nodeOf(node.selector)
|
|
659
|
+
const peek = Predicate.isTagged(top, "Peek")
|
|
660
|
+
const sel = peek ? nodeOf(top.self) : top
|
|
563
661
|
if (!Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) return general
|
|
564
662
|
const enc = sel.encoding
|
|
565
663
|
// Units below `limit` are whole tokens, so the key is known in advance
|
|
@@ -597,17 +695,17 @@ class Compiler {
|
|
|
597
695
|
const { max, min } = node
|
|
598
696
|
const checkLoop = max === Infinity
|
|
599
697
|
const isFold = Predicate.isTagged(node, "Fold")
|
|
600
|
-
const cond = isFold ?
|
|
698
|
+
const cond = isFold ? node.while : undefined
|
|
601
699
|
const c = this.compile(node.self, used)
|
|
602
700
|
const resumable = this.resumable
|
|
603
701
|
const key = new Key()
|
|
604
702
|
// The accumulator: an array, the fold's value, or nothing when unused (and unneeded by `while`)
|
|
605
703
|
const tracked = used || cond !== undefined
|
|
606
|
-
const init: () => unknown = !tracked ? () => undefined : isFold ?
|
|
704
|
+
const init: () => unknown = !tracked ? () => undefined : isFold ? node.init : () => []
|
|
607
705
|
const add: (acc: any, a: unknown) => unknown = !tracked
|
|
608
706
|
? (acc) => acc
|
|
609
707
|
: isFold
|
|
610
|
-
?
|
|
708
|
+
? node.f
|
|
611
709
|
: (acc, a) => {
|
|
612
710
|
acc.push(a)
|
|
613
711
|
return acc
|
|
@@ -618,6 +716,7 @@ class Compiler {
|
|
|
618
716
|
let count = 0
|
|
619
717
|
let resumed = false
|
|
620
718
|
if (resumable && s.resume !== undefined) {
|
|
719
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
|
|
621
720
|
const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; count: number; pos: number } | undefined
|
|
622
721
|
if (cp instanceof Resume.Done) {
|
|
623
722
|
s.pos = cp.pos
|
|
@@ -657,44 +756,43 @@ class Compiler {
|
|
|
657
756
|
*/
|
|
658
757
|
private entries(p: ParserLike): Step {
|
|
659
758
|
const node = nodeOf(p)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return a
|
|
669
|
-
}
|
|
759
|
+
if (Predicate.isTagged(node, "Mode")) {
|
|
760
|
+
const c = this.entries(node.self)
|
|
761
|
+
const from = node.from
|
|
762
|
+
const to = node.to
|
|
763
|
+
return (s) => {
|
|
764
|
+
const a = c(s)
|
|
765
|
+
if (a === Fail && s.mode === from) s.mode = to
|
|
766
|
+
return a
|
|
670
767
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
768
|
+
}
|
|
769
|
+
if (Predicate.isTagged(node, "Seq")) {
|
|
770
|
+
// SAFETY: `buildsEntries` passes through a sequence only when it keeps one output, by index
|
|
771
|
+
const i = node.output as number
|
|
772
|
+
const steps = node.parsers.map((q, j) => (j === i ? this.entries(q) : this.compile(q, false)))
|
|
773
|
+
return (s) => {
|
|
774
|
+
let out: unknown
|
|
775
|
+
for (let j = 0; j < steps.length; j++) {
|
|
776
|
+
const a = steps[j](s)
|
|
777
|
+
if (a === Fail) return Fail
|
|
778
|
+
if (j === i) out = a
|
|
682
779
|
}
|
|
780
|
+
return out
|
|
683
781
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
782
|
+
}
|
|
783
|
+
if (Predicate.isTagged(node, "Repeat") || Predicate.isTagged(node, "Separated")) {
|
|
784
|
+
const pair = pairOf(node.self)
|
|
785
|
+
if (pair !== undefined) return this.entriesLoop(node, pair)
|
|
786
|
+
// Not reached (`buildsEntries` found the pair): build the pairs, then the object
|
|
787
|
+
const c = this.compile(p, true)
|
|
788
|
+
return (s) => {
|
|
789
|
+
const a = c(s)
|
|
790
|
+
// SAFETY: a used `repeat` or `separated` returns the array of its elements, here the pairs
|
|
791
|
+
return a === Fail ? Fail : fromEntries(a as Iterable<readonly [PropertyKey, unknown]>)
|
|
694
792
|
}
|
|
695
|
-
default:
|
|
696
|
-
return this.entries((node as { readonly self: ParserLike }).self)
|
|
697
793
|
}
|
|
794
|
+
// SAFETY: the other nodes `buildsEntries` passes through are diagnostics, which all wrap a `self`
|
|
795
|
+
return this.entries((node as { readonly self: ParserLike }).self)
|
|
698
796
|
}
|
|
699
797
|
|
|
700
798
|
private entriesLoop(node: NodeOf<"Repeat"> | NodeOf<"Separated">, pair: EntryPair): Step {
|
|
@@ -715,10 +813,17 @@ class Compiler {
|
|
|
715
813
|
}
|
|
716
814
|
const { max, min } = node
|
|
717
815
|
const checkLoop = max === Infinity
|
|
816
|
+
// A streaming build's checkpoint holds the object built so far (see `repeat`)
|
|
817
|
+
const resumable = this.resumable
|
|
818
|
+
const site = new Key()
|
|
718
819
|
if (Predicate.isTagged(node, "Repeat")) {
|
|
719
820
|
return (s) => {
|
|
720
|
-
const
|
|
721
|
-
|
|
821
|
+
const at = s.pos
|
|
822
|
+
const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
|
|
823
|
+
if (cp instanceof Resume.Done) return cp.value
|
|
824
|
+
const resumed = cp !== undefined
|
|
825
|
+
const acc = cp?.acc ?? new EntriesAcc()
|
|
826
|
+
for (let count = cp?.count ?? 0; count < max; count++) {
|
|
722
827
|
const start = s.pos
|
|
723
828
|
let key: unknown
|
|
724
829
|
let value: unknown
|
|
@@ -733,9 +838,7 @@ class Compiler {
|
|
|
733
838
|
else if (j === vi) value = a
|
|
734
839
|
}
|
|
735
840
|
if (!ok) {
|
|
736
|
-
|
|
737
|
-
s.pos = start
|
|
738
|
-
return acc.result()
|
|
841
|
+
return stopEntries(s, site, at, resumed, acc, count, start, min)
|
|
739
842
|
}
|
|
740
843
|
if (checkLoop && s.pos === start) {
|
|
741
844
|
s.mode = CUT
|
|
@@ -743,40 +846,42 @@ class Compiler {
|
|
|
743
846
|
}
|
|
744
847
|
acc.add(key, value)
|
|
745
848
|
}
|
|
746
|
-
return acc
|
|
849
|
+
return entriesDone(s, site, at, resumed, acc)
|
|
747
850
|
}
|
|
748
851
|
}
|
|
749
852
|
const sep = this.compile(node.separator, false)
|
|
750
853
|
return (s) => {
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
if (
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
return acc.result()
|
|
854
|
+
const at = s.pos
|
|
855
|
+
const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
|
|
856
|
+
if (cp instanceof Resume.Done) return cp.value
|
|
857
|
+
const resumed = cp !== undefined
|
|
858
|
+
const acc = cp?.acc ?? new EntriesAcc()
|
|
859
|
+
let count = cp?.count ?? 0
|
|
860
|
+
if (!resumed) {
|
|
861
|
+
if (max === 0) return acc.result()
|
|
862
|
+
const start = s.pos
|
|
863
|
+
if (!entry(s, acc)) {
|
|
864
|
+
if (s.mode !== BT || min > 0) return Fail
|
|
865
|
+
s.pos = start
|
|
866
|
+
return acc.result()
|
|
867
|
+
}
|
|
868
|
+
count++
|
|
759
869
|
}
|
|
760
|
-
count++
|
|
761
870
|
while (count < max) {
|
|
762
|
-
start = s.pos
|
|
871
|
+
const start = s.pos
|
|
763
872
|
if (sep(s) === Fail) {
|
|
764
|
-
|
|
765
|
-
s.pos = start
|
|
766
|
-
return acc.result()
|
|
873
|
+
return stopEntries(s, site, at, resumed, acc, count, start, min)
|
|
767
874
|
}
|
|
768
875
|
if (checkLoop && s.pos === start) {
|
|
769
876
|
s.mode = CUT
|
|
770
877
|
return Fail
|
|
771
878
|
}
|
|
772
879
|
if (!entry(s, acc)) {
|
|
773
|
-
|
|
774
|
-
s.pos = start
|
|
775
|
-
return acc.result()
|
|
880
|
+
return stopEntries(s, site, at, resumed, acc, count, start, min)
|
|
776
881
|
}
|
|
777
882
|
count++
|
|
778
883
|
}
|
|
779
|
-
return acc
|
|
884
|
+
return entriesDone(s, site, at, resumed, acc)
|
|
780
885
|
}
|
|
781
886
|
}
|
|
782
887
|
|
|
@@ -819,6 +924,7 @@ class Compiler {
|
|
|
819
924
|
let count = 0
|
|
820
925
|
let resumed = false
|
|
821
926
|
if (resumable && s.resume !== undefined) {
|
|
927
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
|
|
822
928
|
const cp = Resume.take(s, key, entry) as
|
|
823
929
|
| Resume.Done
|
|
824
930
|
| { acc: Array<unknown>; count: number; pos: number }
|
|
@@ -885,6 +991,7 @@ class Compiler {
|
|
|
885
991
|
let acc: unknown
|
|
886
992
|
let resumed = false
|
|
887
993
|
if (resumable && s.resume !== undefined) {
|
|
994
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ acc, pos }` when it runs out, a `Done` when it completes
|
|
888
995
|
const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; pos: number } | undefined
|
|
889
996
|
if (cp instanceof Resume.Done) {
|
|
890
997
|
s.pos = cp.pos
|
|
@@ -961,21 +1068,22 @@ class Compiler {
|
|
|
961
1068
|
// ---------------------------------------------------------------------------
|
|
962
1069
|
|
|
963
1070
|
token(p: ParserLike, node: TokenNode, used: boolean): Step {
|
|
1071
|
+
// SAFETY: `build` compiles a token node without an encoding only when `native` holds, that is when `kind` is not 2
|
|
964
1072
|
const kind = this.kind as 0 | 1
|
|
965
1073
|
switch (node._tag) {
|
|
966
1074
|
case "Any":
|
|
967
1075
|
if (kind === 1) {
|
|
968
|
-
return (s) => {
|
|
1076
|
+
return (s: State<Uint8Array>) => {
|
|
969
1077
|
const pos = s.pos
|
|
970
1078
|
if (pos >= s.end) return fail(s)
|
|
971
1079
|
s.pos = pos + 1
|
|
972
|
-
return
|
|
1080
|
+
return s.input[pos]
|
|
973
1081
|
}
|
|
974
1082
|
}
|
|
975
1083
|
return (s) => {
|
|
976
1084
|
const pos = s.pos
|
|
977
1085
|
if (pos >= s.end) return fail(s)
|
|
978
|
-
const str = s.input
|
|
1086
|
+
const str: string = s.input
|
|
979
1087
|
const c = str.charCodeAt(pos)
|
|
980
1088
|
if (c >= 0xd800 && c <= 0xdbff && pos + 1 < str.length && (str.charCodeAt(pos + 1) & 0xfc00) === 0xdc00) {
|
|
981
1089
|
s.pos = pos + 2
|
|
@@ -992,10 +1100,10 @@ class Compiler {
|
|
|
992
1100
|
const accept = acceptTable(set, negate)
|
|
993
1101
|
const test = set.test
|
|
994
1102
|
if (kind === 1) {
|
|
995
|
-
return (s) => {
|
|
1103
|
+
return (s: State<Uint8Array>) => {
|
|
996
1104
|
const pos = s.pos
|
|
997
1105
|
if (pos >= s.end) return fail(s)
|
|
998
|
-
const b =
|
|
1106
|
+
const b = s.input[pos]
|
|
999
1107
|
if (accept[b] === 0) return fail(s)
|
|
1000
1108
|
s.pos = pos + 1
|
|
1001
1109
|
return b
|
|
@@ -1004,7 +1112,7 @@ class Compiler {
|
|
|
1004
1112
|
return (s) => {
|
|
1005
1113
|
const pos = s.pos
|
|
1006
1114
|
if (pos >= s.end) return fail(s)
|
|
1007
|
-
const str = s.input
|
|
1115
|
+
const str: string = s.input
|
|
1008
1116
|
const c = str.charCodeAt(pos)
|
|
1009
1117
|
if (c < TableSize) {
|
|
1010
1118
|
if (accept[c] === 0) return fail(s)
|
|
@@ -1025,15 +1133,15 @@ class Compiler {
|
|
|
1025
1133
|
case "Take": {
|
|
1026
1134
|
const count = node.count
|
|
1027
1135
|
if (kind === 1) {
|
|
1028
|
-
return (s) => {
|
|
1136
|
+
return (s: State<Uint8Array>) => {
|
|
1029
1137
|
const start = s.pos
|
|
1030
1138
|
if (s.end - start < count) return failAt(s, s.end)
|
|
1031
1139
|
s.pos = start + count
|
|
1032
|
-
return used ?
|
|
1140
|
+
return used ? s.input.subarray(start, start + count) : undefined
|
|
1033
1141
|
}
|
|
1034
1142
|
}
|
|
1035
1143
|
return (s) => {
|
|
1036
|
-
const str = s.input
|
|
1144
|
+
const str: string = s.input
|
|
1037
1145
|
const start = s.pos
|
|
1038
1146
|
const end = s.end
|
|
1039
1147
|
let i = start
|
|
@@ -1050,12 +1158,16 @@ class Compiler {
|
|
|
1050
1158
|
}
|
|
1051
1159
|
case "TakeWhile":
|
|
1052
1160
|
return this.tokenTakeWhile(node, used)
|
|
1053
|
-
case "TakeUntil":
|
|
1054
|
-
|
|
1161
|
+
case "TakeUntil":
|
|
1162
|
+
case "TakeUntilAny": {
|
|
1163
|
+
const { max, min } = node
|
|
1055
1164
|
const bounded = min > 0 || max !== Infinity
|
|
1165
|
+
const search: (s: State<Input>, from: number) => number = Predicate.isTagged(node, "TakeUntil")
|
|
1166
|
+
? (s, from) => indexOf(s, node.literal, from)
|
|
1167
|
+
: (s, from) => indexOfAny(s, node.literals, from)
|
|
1056
1168
|
return (s) => {
|
|
1057
1169
|
const start = s.pos
|
|
1058
|
-
const idx =
|
|
1170
|
+
const idx = search(s, start)
|
|
1059
1171
|
if (idx < 0) return failAt(s, s.end)
|
|
1060
1172
|
if (bounded) {
|
|
1061
1173
|
const count = countTokens(s, start, idx)
|
|
@@ -1078,15 +1190,15 @@ class Compiler {
|
|
|
1078
1190
|
const lit = node.literal
|
|
1079
1191
|
const literalKind = Predicate.isString(lit) ? 0 : lit instanceof Uint8Array ? 1 : 2
|
|
1080
1192
|
if (this.kind === 0) {
|
|
1081
|
-
if (
|
|
1082
|
-
const str = lit
|
|
1193
|
+
if (!Predicate.isString(lit)) return this.fallback(p, node, "byte or array literal on string input")
|
|
1194
|
+
const str = lit
|
|
1083
1195
|
const len = str.length
|
|
1084
1196
|
if (node.caseless) {
|
|
1085
1197
|
const lowered = Array.from({ length: len }, (_, i) => lowerAscii(str.charCodeAt(i)))
|
|
1086
1198
|
return (s) => {
|
|
1087
1199
|
const pos = s.pos
|
|
1088
1200
|
if (s.end - pos < len) return fail(s)
|
|
1089
|
-
const input = s.input
|
|
1201
|
+
const input: string = s.input
|
|
1090
1202
|
for (let i = 0; i < len; i++) {
|
|
1091
1203
|
if (lowerAscii(input.charCodeAt(pos + i)) !== lowered[i]) return fail(s)
|
|
1092
1204
|
}
|
|
@@ -1096,8 +1208,8 @@ class Compiler {
|
|
|
1096
1208
|
}
|
|
1097
1209
|
return stringLiteral(str, str)
|
|
1098
1210
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1211
|
+
const bytes = Predicate.isString(lit) ? new TextEncoder().encode(lit) : lit instanceof Uint8Array ? lit : undefined
|
|
1212
|
+
if (bytes === undefined) return this.fallback(p, node, "array literal on byte input")
|
|
1101
1213
|
const len = bytes.length
|
|
1102
1214
|
const sameKind = literalKind === 1 && !node.caseless
|
|
1103
1215
|
const expect = node.caseless ? Array.from(bytes, lowerAscii) : Array.from(bytes)
|
|
@@ -1105,7 +1217,7 @@ class Compiler {
|
|
|
1105
1217
|
return (s) => {
|
|
1106
1218
|
const pos = s.pos
|
|
1107
1219
|
if (s.end - pos < len) return fail(s)
|
|
1108
|
-
const input = s.input
|
|
1220
|
+
const input: Uint8Array = s.input
|
|
1109
1221
|
for (let i = 0; i < len; i++) {
|
|
1110
1222
|
const b = input[pos + i]
|
|
1111
1223
|
if ((caseless ? lowerAscii(b) : b) !== expect[i]) return fail(s)
|
|
@@ -1116,13 +1228,14 @@ class Compiler {
|
|
|
1116
1228
|
}
|
|
1117
1229
|
|
|
1118
1230
|
tokenTakeWhile(node: NodeOf<"TakeWhile">, used: boolean): Step {
|
|
1231
|
+
// SAFETY: called from `token` only, whose `kind` is not 2
|
|
1119
1232
|
const kind = this.kind as 0 | 1
|
|
1120
1233
|
const { max, min, negate } = node
|
|
1121
1234
|
const set = tokenSet(node.set, kind)
|
|
1122
1235
|
const accept = acceptTable(set, negate)
|
|
1123
1236
|
if (kind === 1) {
|
|
1124
1237
|
return (s) => {
|
|
1125
|
-
const bytes = s.input
|
|
1238
|
+
const bytes: Uint8Array = s.input
|
|
1126
1239
|
const start = s.pos
|
|
1127
1240
|
const limit = max === Infinity ? s.end : Math.min(s.end, start + max)
|
|
1128
1241
|
let i = start
|
|
@@ -1132,9 +1245,11 @@ class Compiler {
|
|
|
1132
1245
|
return used ? bytes.subarray(start, i) : undefined
|
|
1133
1246
|
}
|
|
1134
1247
|
}
|
|
1248
|
+
const stops = negate && min <= 1 && max === Infinity ? stopChars(set) : undefined
|
|
1249
|
+
if (stops !== undefined) return stopTakeTill(stops, min, used)
|
|
1135
1250
|
const test = set.test
|
|
1136
1251
|
return (s) => {
|
|
1137
|
-
const str = s.input
|
|
1252
|
+
const str: string = s.input
|
|
1138
1253
|
const start = s.pos
|
|
1139
1254
|
const end = s.end
|
|
1140
1255
|
let i = start
|
|
@@ -1172,7 +1287,9 @@ class Compiler {
|
|
|
1172
1287
|
const { fast, unitAt, width: unitWidth } = unitReader(this.kind, enc)
|
|
1173
1288
|
const decode = enc.decode
|
|
1174
1289
|
const width = enc.width
|
|
1175
|
-
|
|
1290
|
+
// UTF-8 decodes through the state's ASCII run (see `utf8Text`)
|
|
1291
|
+
const toText: (s: State<any>, start: number, end: number) => string =
|
|
1292
|
+
enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
|
|
1176
1293
|
switch (node._tag) {
|
|
1177
1294
|
case "Any":
|
|
1178
1295
|
return (s) => {
|
|
@@ -1190,6 +1307,7 @@ class Compiler {
|
|
|
1190
1307
|
return used ? charOf(cp) : undefined
|
|
1191
1308
|
}
|
|
1192
1309
|
case "Literal": {
|
|
1310
|
+
// SAFETY: a `Literal` with an encoding is a `Text` literal, which records the string it encodes
|
|
1193
1311
|
const lit = node.literal as string
|
|
1194
1312
|
if (node.caseless) {
|
|
1195
1313
|
const folded = Array.from(lit, (c) => fold(firstCodePoint(c)))
|
|
@@ -1214,18 +1332,18 @@ class Compiler {
|
|
|
1214
1332
|
if (fold(cp) !== folded[i]) return failAt(s, pos)
|
|
1215
1333
|
}
|
|
1216
1334
|
s.pos = pos
|
|
1217
|
-
return used ? toText(
|
|
1335
|
+
return used ? toText(s, start, pos) : undefined
|
|
1218
1336
|
}
|
|
1219
1337
|
}
|
|
1220
1338
|
const encoded = enc.encode(lit)
|
|
1221
1339
|
if (Predicate.isString(encoded)) return stringLiteral(encoded, lit)
|
|
1222
|
-
const bytes = encoded
|
|
1340
|
+
const bytes: Uint8Array = encoded
|
|
1223
1341
|
const len = bytes.length
|
|
1224
1342
|
if (len === 1) {
|
|
1225
1343
|
const b0 = bytes[0]
|
|
1226
|
-
return (s) => {
|
|
1344
|
+
return (s: State<Uint8Array>) => {
|
|
1227
1345
|
const pos = s.pos
|
|
1228
|
-
if (pos < s.end &&
|
|
1346
|
+
if (pos < s.end && s.input[pos] === b0) {
|
|
1229
1347
|
s.pos = pos + 1
|
|
1230
1348
|
return lit
|
|
1231
1349
|
}
|
|
@@ -1235,7 +1353,7 @@ class Compiler {
|
|
|
1235
1353
|
return (s) => {
|
|
1236
1354
|
const pos = s.pos
|
|
1237
1355
|
if (s.end - pos < len) return fail(s)
|
|
1238
|
-
const input = s.input
|
|
1356
|
+
const input: Uint8Array = s.input
|
|
1239
1357
|
for (let i = 0; i < len; i++) if (input[pos + i] !== bytes[i]) return fail(s)
|
|
1240
1358
|
s.pos = pos + len
|
|
1241
1359
|
return lit
|
|
@@ -1280,7 +1398,7 @@ class Compiler {
|
|
|
1280
1398
|
}
|
|
1281
1399
|
}
|
|
1282
1400
|
s.pos = pos
|
|
1283
|
-
return used ? toText(
|
|
1401
|
+
return used ? toText(s, start, pos) : undefined
|
|
1284
1402
|
}
|
|
1285
1403
|
}
|
|
1286
1404
|
case "TakeWhile": {
|
|
@@ -1296,6 +1414,7 @@ class Compiler {
|
|
|
1296
1414
|
return this.kind === 0 ? takeWhileString(loop, enc) : takeWhileBytes(loop, enc)
|
|
1297
1415
|
}
|
|
1298
1416
|
case "TakeUntil":
|
|
1417
|
+
case "TakeUntilAny":
|
|
1299
1418
|
case "Rest":
|
|
1300
1419
|
return this.fallback(p, node, "Text parser without a compiled form")
|
|
1301
1420
|
}
|
|
@@ -1307,9 +1426,9 @@ class Compiler {
|
|
|
1307
1426
|
if (enc === undefined) {
|
|
1308
1427
|
if (!used) return c
|
|
1309
1428
|
if (this.kind === 0) {
|
|
1310
|
-
return (s) => {
|
|
1429
|
+
return (s: State<string>) => {
|
|
1311
1430
|
const start = s.pos
|
|
1312
|
-
return c(s) === Fail ? Fail :
|
|
1431
|
+
return c(s) === Fail ? Fail : s.input.slice(start, s.pos)
|
|
1313
1432
|
}
|
|
1314
1433
|
}
|
|
1315
1434
|
return (s) => {
|
|
@@ -1319,7 +1438,9 @@ class Compiler {
|
|
|
1319
1438
|
}
|
|
1320
1439
|
// `Text.taken` checks that the text is well formed, then decodes it
|
|
1321
1440
|
const { fast, unitAt, width } = unitReader(this.kind, enc)
|
|
1322
|
-
|
|
1441
|
+
// UTF-8 decodes through the state's ASCII run (see `utf8Text`)
|
|
1442
|
+
const toText: (s: State<any>, start: number, end: number) => string =
|
|
1443
|
+
enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
|
|
1323
1444
|
return (s) => {
|
|
1324
1445
|
const start = s.pos
|
|
1325
1446
|
if (c(s) === Fail) return Fail
|
|
@@ -1328,7 +1449,7 @@ class Compiler {
|
|
|
1328
1449
|
let i = start
|
|
1329
1450
|
while (i < end && unitAt(input, i, end) < fast) i += width
|
|
1330
1451
|
if (i < end && validate(enc, s, i, end) < 0) return fail(s)
|
|
1331
|
-
return used ? toText(
|
|
1452
|
+
return used ? toText(s, start, end) : undefined
|
|
1332
1453
|
}
|
|
1333
1454
|
}
|
|
1334
1455
|
|
|
@@ -1341,11 +1462,7 @@ class Compiler {
|
|
|
1341
1462
|
if (!floatTexts.has(node.self) || this.kind === 2) return undefined
|
|
1342
1463
|
if (!scansFloat(node.self, (enc) => this.native(enc))) return undefined
|
|
1343
1464
|
const c = this.compile(node.self, true)
|
|
1344
|
-
const scan =
|
|
1345
|
-
input: unknown,
|
|
1346
|
-
i: number,
|
|
1347
|
-
end: number
|
|
1348
|
-
) => unknown
|
|
1465
|
+
const scan = this.kind === 0 ? scanFloatString : scanFloatBytes
|
|
1349
1466
|
const resumable = this.resumable
|
|
1350
1467
|
return (s) => {
|
|
1351
1468
|
const v = scan(s.input, s.pos, s.end)
|
|
@@ -1372,15 +1489,15 @@ class Compiler {
|
|
|
1372
1489
|
if (enc === undefined || (ctrl < fastLimit(enc) && ctrl < TableSize)) {
|
|
1373
1490
|
control =
|
|
1374
1491
|
kind === 0
|
|
1375
|
-
? (s) => {
|
|
1376
|
-
if (s.pos < s.end &&
|
|
1492
|
+
? (s: State<string>) => {
|
|
1493
|
+
if (s.pos < s.end && s.input.charCodeAt(s.pos) === ctrl) {
|
|
1377
1494
|
s.pos++
|
|
1378
1495
|
return true
|
|
1379
1496
|
}
|
|
1380
1497
|
return false
|
|
1381
1498
|
}
|
|
1382
|
-
: (s) => {
|
|
1383
|
-
if (s.pos < s.end &&
|
|
1499
|
+
: (s: State<Uint8Array>) => {
|
|
1500
|
+
if (s.pos < s.end && s.input[s.pos] === ctrl) {
|
|
1384
1501
|
s.pos++
|
|
1385
1502
|
return true
|
|
1386
1503
|
}
|
|
@@ -1407,19 +1524,20 @@ class Compiler {
|
|
|
1407
1524
|
const resumable = this.resumable
|
|
1408
1525
|
const key = new Key()
|
|
1409
1526
|
/** Fails as incomplete, keeping a checkpoint in a streaming parse. */
|
|
1410
|
-
const incomplete = (s: State<any>, start: number, checkpoint:
|
|
1527
|
+
const incomplete = (s: State<any>, start: number, checkpoint: EscapedCheckpoint): typeof Fail => {
|
|
1411
1528
|
if (s.resume !== undefined) Resume.save(s, key, start, checkpoint)
|
|
1412
1529
|
s.mode = INC
|
|
1413
1530
|
return Fail
|
|
1414
1531
|
}
|
|
1415
1532
|
if (raw) {
|
|
1416
|
-
const slice: (s: State<any>, start: number) =>
|
|
1417
|
-
enc === undefined ? (s, start) => s.slice(start, s.pos) : (s, start) => enc
|
|
1533
|
+
const slice: (s: State<any>, start: number) => Input =
|
|
1534
|
+
enc === undefined ? (s, start) => s.slice(start, s.pos) : (s, start) => textIn(enc, s, start, s.pos)
|
|
1418
1535
|
return (s) => {
|
|
1419
1536
|
const start = s.pos
|
|
1420
1537
|
let resumed = false
|
|
1421
1538
|
if (resumable && s.resume !== undefined) {
|
|
1422
|
-
|
|
1539
|
+
// SAFETY: only this raw loop saves checkpoints under `key`: the position it stopped at, or a `Done` when it completes
|
|
1540
|
+
const cp = Resume.take(s, key, start) as Resume.Done | Extract<EscapedCheckpoint, number> | undefined
|
|
1423
1541
|
if (cp instanceof Resume.Done) {
|
|
1424
1542
|
s.pos = cp.pos
|
|
1425
1543
|
return cp.value
|
|
@@ -1447,7 +1565,11 @@ class Compiler {
|
|
|
1447
1565
|
return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
|
|
1448
1566
|
}
|
|
1449
1567
|
}
|
|
1450
|
-
if (s.partial)
|
|
1568
|
+
if (s.partial) {
|
|
1569
|
+
// The text ran out of input: `escaped`'s own failure, needing more
|
|
1570
|
+
s.incomplete(0)
|
|
1571
|
+
return incomplete(s, start, s.pos)
|
|
1572
|
+
}
|
|
1451
1573
|
return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
|
|
1452
1574
|
}
|
|
1453
1575
|
}
|
|
@@ -1456,7 +1578,8 @@ class Compiler {
|
|
|
1456
1578
|
let out = ""
|
|
1457
1579
|
let resumed = false
|
|
1458
1580
|
if (resumable && s.resume !== undefined) {
|
|
1459
|
-
|
|
1581
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ out, pos }` when it runs out, a `Done` when it completes
|
|
1582
|
+
const cp = Resume.take(s, key, start) as Resume.Done | Exclude<EscapedCheckpoint, number> | undefined
|
|
1460
1583
|
if (cp instanceof Resume.Done) {
|
|
1461
1584
|
s.pos = cp.pos
|
|
1462
1585
|
return cp.value
|
|
@@ -1488,13 +1611,31 @@ class Compiler {
|
|
|
1488
1611
|
return resumed ? Resume.done(s, key, start, true, out) : out
|
|
1489
1612
|
}
|
|
1490
1613
|
}
|
|
1491
|
-
if (s.partial)
|
|
1614
|
+
if (s.partial) {
|
|
1615
|
+
// The text ran out of input: `escaped`'s own failure, needing more
|
|
1616
|
+
s.incomplete(0)
|
|
1617
|
+
return incomplete(s, start, { out, pos: s.pos })
|
|
1618
|
+
}
|
|
1492
1619
|
return resumed ? Resume.done(s, key, start, true, out) : out
|
|
1493
1620
|
}
|
|
1494
1621
|
}
|
|
1495
1622
|
}
|
|
1496
1623
|
|
|
1497
|
-
|
|
1624
|
+
/** How far past the cursor a token parser may read: its literals' length in UTF-8 at most, or `Margin`. */
|
|
1625
|
+
const marginOf = (node: TokenNode): number => {
|
|
1626
|
+
if (Predicate.isTagged(node, "Literal") || Predicate.isTagged(node, "TakeUntil")) return node.literal.length * 4
|
|
1627
|
+
if (Predicate.isTagged(node, "TakeUntilAny")) return Math.max(0, ...node.literals.map((lit) => lit.length)) * 4
|
|
1628
|
+
return Margin
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
type TokenNode = NodeOf<"Any" | "Literal" | "OneOf" | "Take" | "TakeWhile" | "TakeUntil" | "TakeUntilAny" | "Rest">
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Where a compiled `escaped` that ran out of input resumes: the position
|
|
1635
|
+
* (`takeEscaped`, which slices the input), or the text unescaped so far and
|
|
1636
|
+
* the position after it.
|
|
1637
|
+
*/
|
|
1638
|
+
type EscapedCheckpoint = number | { readonly out: string; readonly pos: number }
|
|
1498
1639
|
|
|
1499
1640
|
const fail = (s: State<any>): typeof Fail => {
|
|
1500
1641
|
s.mode = BT
|
|
@@ -1651,18 +1792,18 @@ const stringLiteral = (str: string, value: unknown): Step => {
|
|
|
1651
1792
|
if (len === 0) return () => value
|
|
1652
1793
|
if (len === 1) {
|
|
1653
1794
|
const c0 = str.charCodeAt(0)
|
|
1654
|
-
return (s) => {
|
|
1795
|
+
return (s: State<string>) => {
|
|
1655
1796
|
const pos = s.pos
|
|
1656
|
-
if (pos < s.end &&
|
|
1797
|
+
if (pos < s.end && s.input.charCodeAt(pos) === c0) {
|
|
1657
1798
|
s.pos = pos + 1
|
|
1658
1799
|
return value
|
|
1659
1800
|
}
|
|
1660
1801
|
return fail(s)
|
|
1661
1802
|
}
|
|
1662
1803
|
}
|
|
1663
|
-
return (s) => {
|
|
1804
|
+
return (s: State<string>) => {
|
|
1664
1805
|
const pos = s.pos
|
|
1665
|
-
if (s.end - pos >= len &&
|
|
1806
|
+
if (s.end - pos >= len && s.input.startsWith(str, pos)) {
|
|
1666
1807
|
s.pos = pos + len
|
|
1667
1808
|
return value
|
|
1668
1809
|
}
|
|
@@ -1670,7 +1811,81 @@ const stringLiteral = (str: string, value: unknown): Step => {
|
|
|
1670
1811
|
}
|
|
1671
1812
|
}
|
|
1672
1813
|
|
|
1673
|
-
/**
|
|
1814
|
+
/**
|
|
1815
|
+
* `Token.takeTill` on a string, of one to three stop characters that are
|
|
1816
|
+
* whole code points (see `search.ts`): every other unit, surrogates
|
|
1817
|
+
* included, is accepted, so the run ends at the first stop character, or
|
|
1818
|
+
* at the state's end.
|
|
1819
|
+
*/
|
|
1820
|
+
const stopTakeTill = (stops: ReadonlyArray<number>, min: number, used: boolean): Step => {
|
|
1821
|
+
const head = stops.length === 1 ? SearchHead : 2 * SearchHead
|
|
1822
|
+
const c0 = stops[0]
|
|
1823
|
+
const c1 = stops.length > 1 ? stops[1] : -1
|
|
1824
|
+
const c2 = stops.length > 2 ? stops[2] : -1
|
|
1825
|
+
const one = String.fromCharCode(c0)
|
|
1826
|
+
const site = new StopCache(stops.map((u) => String.fromCharCode(u)))
|
|
1827
|
+
return (s: State<string>) => {
|
|
1828
|
+
const str = s.input
|
|
1829
|
+
const start = s.pos
|
|
1830
|
+
const end = s.end
|
|
1831
|
+
const h = start + head < end ? start + head : end
|
|
1832
|
+
let i = start
|
|
1833
|
+
let c = -1
|
|
1834
|
+
while (i < h && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) i++
|
|
1835
|
+
// A run that ends right after the head needs no search
|
|
1836
|
+
if (i === h && i < end && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) {
|
|
1837
|
+
if (stops.length === 1) {
|
|
1838
|
+
const at = str.indexOf(one, i)
|
|
1839
|
+
i = at < 0 || at > end ? end : at
|
|
1840
|
+
} else i = nextStop(site, s, i)
|
|
1841
|
+
}
|
|
1842
|
+
if (i - start < min) return failAt(s, i)
|
|
1843
|
+
s.pos = i
|
|
1844
|
+
return used ? str.slice(start, i) : undefined
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
/** A checkpoint of `entriesLoop`: the object so far, the entries parsed and where the next one starts. */
|
|
1849
|
+
interface EntriesCheckpoint {
|
|
1850
|
+
readonly acc: EntriesAcc
|
|
1851
|
+
readonly count: number
|
|
1852
|
+
readonly pos: number
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/** Takes the checkpoint `entriesLoop` saved at `at`, restoring the position of a completed one. */
|
|
1856
|
+
const resumeEntries = (s: State<any>, key: Key, at: number): Resume.Done | EntriesCheckpoint | undefined => {
|
|
1857
|
+
// SAFETY: only `entriesLoop` saves checkpoints under its `key`: an `EntriesCheckpoint` when it runs out, a `Done` when it completes
|
|
1858
|
+
const cp = Resume.take(s, key, at) as Resume.Done | EntriesCheckpoint | undefined
|
|
1859
|
+
if (cp instanceof Resume.Done) s.pos = cp.pos
|
|
1860
|
+
else if (cp !== undefined) s.pos = cp.pos
|
|
1861
|
+
return cp
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
/** The object `entriesLoop` built, recorded as done when the loop resumed. */
|
|
1865
|
+
const entriesDone = (s: State<any>, key: Key, at: number, resumed: boolean, acc: EntriesAcc): object =>
|
|
1866
|
+
resumed ? Resume.done(s, key, at, true, acc.result()) : acc.result()
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Where `entriesLoop` stops at an entry (or separator) that failed at
|
|
1870
|
+
* `start`: a checkpoint when the input ran out, `Fail` unless the failure
|
|
1871
|
+
* backtracks after `min` entries, otherwise the object.
|
|
1872
|
+
*/
|
|
1873
|
+
const stopEntries = (
|
|
1874
|
+
s: State<any>,
|
|
1875
|
+
site: Key,
|
|
1876
|
+
at: number,
|
|
1877
|
+
resumed: boolean,
|
|
1878
|
+
acc: EntriesAcc,
|
|
1879
|
+
count: number,
|
|
1880
|
+
start: number,
|
|
1881
|
+
min: number
|
|
1882
|
+
): object | typeof Fail => {
|
|
1883
|
+
if (s.mode === INC && s.resume !== undefined) Resume.save(s, site, at, { acc, count, pos: start })
|
|
1884
|
+
if (s.mode !== BT || count < min) return Fail
|
|
1885
|
+
s.pos = start
|
|
1886
|
+
return entriesDone(s, site, at, resumed, acc)
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1674
1889
|
/** The object of `Object.fromEntries`, built an entry at a time (see `internal/entries.ts`). */
|
|
1675
1890
|
class EntriesAcc {
|
|
1676
1891
|
readonly o: Record<string, unknown> = {}
|
|
@@ -1721,8 +1936,20 @@ export const backend: Backend = {
|
|
|
1721
1936
|
Result.match(
|
|
1722
1937
|
Result.try(() => compileClosure(p, kind, resumable)),
|
|
1723
1938
|
{
|
|
1724
|
-
onSuccess: ({ fallbacks, step }) => ({
|
|
1725
|
-
|
|
1939
|
+
onSuccess: ({ fallbacks, step }) => ({
|
|
1940
|
+
backend: "closure" as const,
|
|
1941
|
+
step,
|
|
1942
|
+
guarded: false,
|
|
1943
|
+
fallbacks,
|
|
1944
|
+
error: undefined
|
|
1945
|
+
}),
|
|
1946
|
+
onFailure: (error) => ({
|
|
1947
|
+
backend: "interpreted" as const,
|
|
1948
|
+
step: undefined,
|
|
1949
|
+
guarded: false,
|
|
1950
|
+
fallbacks: [],
|
|
1951
|
+
error
|
|
1952
|
+
})
|
|
1726
1953
|
}
|
|
1727
1954
|
)
|
|
1728
1955
|
}
|