@thomasfosterau/effect-parse 0.4.1 → 0.6.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/CHANGELOG.md +202 -0
- package/README.md +56 -14
- package/dist/Ascii.d.ts +11 -4
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +12 -4
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +5 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +83 -37
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +11 -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 +56 -5
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +70 -1
- 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 +69 -11
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +129 -28
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +375 -29
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +394 -37
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +2 -3
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +122 -14
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +293 -62
- 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 +1 -1
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +21 -15
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +65 -11
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +73 -26
- package/dist/Token.js.map +1 -1
- package/dist/internal/compiler/closure.d.ts +1 -1
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +263 -113
- 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 +9 -2
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/graph.d.ts +39 -0
- package/dist/internal/compiler/graph.d.ts.map +1 -0
- package/dist/internal/compiler/graph.js +78 -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 +208 -106
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +11 -3
- 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 +2840 -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.map +1 -1
- package/dist/internal/node.d.ts +77 -3
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +54 -10
- 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 +14 -0
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +53 -11
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +14 -0
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +24 -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 +20 -0
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +72 -39
- package/dist/internal/tokens.js.map +1 -1
- package/dist/internal/tuple.d.ts +6 -0
- package/dist/internal/tuple.d.ts.map +1 -0
- package/dist/internal/tuple.js +170 -0
- package/dist/internal/tuple.js.map +1 -0
- package/package.json +18 -10
- package/src/Ascii.ts +47 -12
- package/src/Binary.ts +174 -73
- package/src/Bits.ts +16 -6
- package/src/Compiler.ts +101 -10
- package/src/Expression.ts +94 -81
- package/src/Input.ts +99 -7
- package/src/Nom.ts +15 -5
- package/src/ParseError.ts +209 -52
- package/src/Parser.ts +684 -143
- package/src/ParserSchema.ts +1 -2
- package/src/ParserStream.ts +361 -68
- package/src/Syntax.ts +60 -36
- package/src/Text.ts +32 -27
- package/src/Token.ts +152 -34
- package/src/internal/compiler/closure.ts +371 -153
- package/src/internal/compiler/entries.ts +4 -2
- package/src/internal/compiler/first.ts +16 -0
- package/src/internal/compiler/graph.ts +96 -0
- package/src/internal/compiler/jit.ts +319 -169
- 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 +3276 -0
- package/src/internal/compiler/wasmAsm.ts +484 -0
- package/src/internal/entries.ts +8 -2
- package/src/internal/node.ts +103 -7
- package/src/internal/numbers.ts +3 -1
- package/src/internal/run.ts +73 -12
- package/src/internal/stack.ts +25 -1
- package/src/internal/syntax.ts +39 -18
- package/src/internal/text.ts +97 -9
- package/src/internal/tokens.ts +106 -14
- package/src/internal/tuple.ts +141 -0
|
@@ -9,16 +9,17 @@
|
|
|
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
|
+
import { tupleStep } from "../tuple.js"
|
|
22
23
|
import type { AltPlan } from "./first.js"
|
|
23
24
|
import { Key } from "../key.js"
|
|
24
25
|
import * as Resume from "../resume.js"
|
|
@@ -26,6 +27,7 @@ import type { Backend } from "./registry.js"
|
|
|
26
27
|
import type { EntryPair } from "./entries.js"
|
|
27
28
|
import { buildsEntries, pairOf } from "./entries.js"
|
|
28
29
|
import { FirstSets, planAlt } from "./first.js"
|
|
30
|
+
import { nextStop, SearchHead, StopCache, stopChars } from "./search.js"
|
|
29
31
|
import { acceptTable, kindOfEncoding, TableSize, textSet, tokenSet } from "./sets.js"
|
|
30
32
|
|
|
31
33
|
export type Step = (state: State<any>) => any
|
|
@@ -137,6 +139,11 @@ class Compiler {
|
|
|
137
139
|
switch (node._tag) {
|
|
138
140
|
case "Opaque":
|
|
139
141
|
return this.fallback(p, node, "not describable (Parser.make, gen, ...)")
|
|
142
|
+
case "Fixed":
|
|
143
|
+
// `Binary`'s numbers: the step is already what a closure would be
|
|
144
|
+
return p.step
|
|
145
|
+
case "Combinator":
|
|
146
|
+
return node.build(node.parsers.map((q) => this.compile(q, true)))
|
|
140
147
|
case "Succeed": {
|
|
141
148
|
const value = node.value
|
|
142
149
|
return () => value
|
|
@@ -158,10 +165,8 @@ class Compiler {
|
|
|
158
165
|
s.mode = BT
|
|
159
166
|
return Fail
|
|
160
167
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return Fail
|
|
164
|
-
}
|
|
168
|
+
// `eof`'s own failure: `Incomplete`, needing one more unit
|
|
169
|
+
if (s.partial) return s.incomplete(1)
|
|
165
170
|
return undefined
|
|
166
171
|
}
|
|
167
172
|
case "Location":
|
|
@@ -176,14 +181,13 @@ class Compiler {
|
|
|
176
181
|
case "Take":
|
|
177
182
|
case "TakeWhile":
|
|
178
183
|
case "TakeUntil":
|
|
184
|
+
case "TakeUntilAny":
|
|
179
185
|
case "Rest":
|
|
180
186
|
if (!this.native(node.encoding)) return this.fallback(p, node, "token parser on array input")
|
|
181
187
|
return this.partialSafe(
|
|
182
188
|
p,
|
|
183
189
|
node.encoding === undefined ? this.token(p, node, used) : this.text(p, node, node.encoding, used),
|
|
184
|
-
|
|
185
|
-
? node.literal.length * 4
|
|
186
|
-
: Margin
|
|
190
|
+
marginOf(node)
|
|
187
191
|
)
|
|
188
192
|
case "TillLineEnding":
|
|
189
193
|
case "Identifier":
|
|
@@ -199,7 +203,7 @@ class Compiler {
|
|
|
199
203
|
if (step !== undefined) return step
|
|
200
204
|
}
|
|
201
205
|
// Streaming builds keep the array, which their checkpoints hold
|
|
202
|
-
if (node.f === fromEntries &&
|
|
206
|
+
if (node.f === fromEntries && buildsEntries(node.self)) return this.entries(node.self)
|
|
203
207
|
const c = this.compile(node.self, true)
|
|
204
208
|
const f = node.f
|
|
205
209
|
return (s) => {
|
|
@@ -246,6 +250,28 @@ class Compiler {
|
|
|
246
250
|
return Fail
|
|
247
251
|
}
|
|
248
252
|
}
|
|
253
|
+
case "FastPath": {
|
|
254
|
+
const c = this.compile(node.self, used)
|
|
255
|
+
const fast = node.fast
|
|
256
|
+
return (s) => {
|
|
257
|
+
const start = s.pos
|
|
258
|
+
const a = fast(s)
|
|
259
|
+
if (a !== Defer) return a
|
|
260
|
+
s.pos = start
|
|
261
|
+
return c(s)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
case "MapOrFail": {
|
|
265
|
+
const c = this.compile(node.self, true)
|
|
266
|
+
const f = node.f
|
|
267
|
+
return (s) => {
|
|
268
|
+
const a = c(s)
|
|
269
|
+
if (a === Fail) return Fail
|
|
270
|
+
const b = f(a)
|
|
271
|
+
if (b === Fail) s.mode = BT
|
|
272
|
+
return b
|
|
273
|
+
}
|
|
274
|
+
}
|
|
249
275
|
case "Filter": {
|
|
250
276
|
const c = this.compile(node.self, true)
|
|
251
277
|
const predicate = node.predicate
|
|
@@ -280,6 +306,16 @@ class Compiler {
|
|
|
280
306
|
return a === Fail ? Fail : [a, s.slice(start, s.pos)]
|
|
281
307
|
}
|
|
282
308
|
}
|
|
309
|
+
case "MapSpan": {
|
|
310
|
+
const c = this.compile(node.self, used)
|
|
311
|
+
if (!used) return c
|
|
312
|
+
const f = node.f
|
|
313
|
+
return (s) => {
|
|
314
|
+
const start = s.pos
|
|
315
|
+
const a = c(s)
|
|
316
|
+
return a === Fail ? Fail : f(a, start, s.pos)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
283
319
|
case "Span": {
|
|
284
320
|
const c = this.compile(node.self, false)
|
|
285
321
|
if (!used) return c
|
|
@@ -308,6 +344,7 @@ class Compiler {
|
|
|
308
344
|
}
|
|
309
345
|
case "Context":
|
|
310
346
|
case "Expected":
|
|
347
|
+
case "Quiet":
|
|
311
348
|
case "MapError":
|
|
312
349
|
case "OrFail":
|
|
313
350
|
case "Trace":
|
|
@@ -377,6 +414,11 @@ class Compiler {
|
|
|
377
414
|
return out
|
|
378
415
|
}
|
|
379
416
|
}
|
|
417
|
+
case "SeqWith": {
|
|
418
|
+
const steps = node.parsers.map((q) => this.compile(q, used))
|
|
419
|
+
if (!used) return this.chain(steps)
|
|
420
|
+
return this.seqWith(steps, node.f)
|
|
421
|
+
}
|
|
380
422
|
case "ZipWith": {
|
|
381
423
|
const c1 = this.compile(node.self, used)
|
|
382
424
|
const c2 = this.compile(node.that, used)
|
|
@@ -394,8 +436,26 @@ class Compiler {
|
|
|
394
436
|
case "Dispatch":
|
|
395
437
|
return this.dispatch(p, node, used)
|
|
396
438
|
case "Repeat":
|
|
397
|
-
case "Fold":
|
|
439
|
+
case "Fold": {
|
|
440
|
+
// A run of a character set whose characters are not kept scans as a `takeWhile` (#79)
|
|
441
|
+
const element = Predicate.isTagged(node, "Repeat") && !used ? nodeOf(node.self) : undefined
|
|
442
|
+
if (
|
|
443
|
+
element !== undefined &&
|
|
444
|
+
Predicate.isTagged(element, "OneOf") &&
|
|
445
|
+
this.kind !== 2 &&
|
|
446
|
+
this.native(element.encoding)
|
|
447
|
+
) {
|
|
448
|
+
const { encoding, negate, set } = element
|
|
449
|
+
const run: NodeOf<"TakeWhile"> = { _tag: "TakeWhile", set, negate, encoding, min: node.min, max: node.max }
|
|
450
|
+
// The repetition's own step is the exact fallback near the end of a partial input
|
|
451
|
+
return this.partialSafe(
|
|
452
|
+
p,
|
|
453
|
+
encoding === undefined ? this.token(p, run, false) : this.text(p, run, encoding, false),
|
|
454
|
+
Margin
|
|
455
|
+
)
|
|
456
|
+
}
|
|
398
457
|
return this.repeat(node, used)
|
|
458
|
+
}
|
|
399
459
|
case "RepeatTill":
|
|
400
460
|
return this.repeatTill(node)
|
|
401
461
|
case "Separated":
|
|
@@ -408,13 +468,51 @@ class Compiler {
|
|
|
408
468
|
}
|
|
409
469
|
}
|
|
410
470
|
|
|
471
|
+
/** `seqWith`: up to three parsers without an array of values. */
|
|
472
|
+
seqWith(steps: ReadonlyArray<Step>, f: NodeOf<"SeqWith">["f"]): Step {
|
|
473
|
+
const n = steps.length
|
|
474
|
+
if (n === 0) return () => f()
|
|
475
|
+
const [c1, c2, c3] = steps
|
|
476
|
+
if (n === 1)
|
|
477
|
+
return (s) => {
|
|
478
|
+
const a = c1(s)
|
|
479
|
+
return a === Fail ? Fail : f(a)
|
|
480
|
+
}
|
|
481
|
+
if (n === 2)
|
|
482
|
+
return (s) => {
|
|
483
|
+
const a = c1(s)
|
|
484
|
+
if (a === Fail) return Fail
|
|
485
|
+
const b = c2(s)
|
|
486
|
+
return b === Fail ? Fail : f(a, b)
|
|
487
|
+
}
|
|
488
|
+
if (n === 3)
|
|
489
|
+
return (s) => {
|
|
490
|
+
const a = c1(s)
|
|
491
|
+
if (a === Fail) return Fail
|
|
492
|
+
const b = c2(s)
|
|
493
|
+
if (b === Fail) return Fail
|
|
494
|
+
const c = c3(s)
|
|
495
|
+
return c === Fail ? Fail : f(a, b, c)
|
|
496
|
+
}
|
|
497
|
+
return (s) => {
|
|
498
|
+
const values = new Array(n)
|
|
499
|
+
for (let i = 0; i < n; i++) {
|
|
500
|
+
const a = steps[i](s)
|
|
501
|
+
if (a === Fail) return Fail
|
|
502
|
+
values[i] = a
|
|
503
|
+
}
|
|
504
|
+
return f(...values)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
411
508
|
/** Runs steps in order, for their effect on the cursor only. */
|
|
412
509
|
chain(steps: ReadonlyArray<Step>): Step {
|
|
413
510
|
const n = steps.length
|
|
414
511
|
if (n === 0) return () => undefined
|
|
415
512
|
if (n === 1) return steps[0]
|
|
416
513
|
if (n === 2) {
|
|
417
|
-
const
|
|
514
|
+
const a = steps[0]
|
|
515
|
+
const b = steps[1]
|
|
418
516
|
return (s) => (a(s) === Fail ? Fail : b(s))
|
|
419
517
|
}
|
|
420
518
|
return (s) => {
|
|
@@ -430,20 +528,11 @@ class Compiler {
|
|
|
430
528
|
const steps = node.parsers.map((q, i) => this.compile(q, keep(i)))
|
|
431
529
|
const n = steps.length
|
|
432
530
|
if (!used) return this.chain(steps)
|
|
433
|
-
if (output === "tuple")
|
|
434
|
-
return (s) => {
|
|
435
|
-
const out = new Array(n)
|
|
436
|
-
for (let i = 0; i < n; i++) {
|
|
437
|
-
const a = steps[i](s)
|
|
438
|
-
if (a === Fail) return Fail
|
|
439
|
-
out[i] = a
|
|
440
|
-
}
|
|
441
|
-
return out
|
|
442
|
-
}
|
|
443
|
-
}
|
|
531
|
+
if (output === "tuple") return tupleStep(steps)
|
|
444
532
|
if (Predicate.isNumber(output)) {
|
|
445
533
|
if (n === 2) {
|
|
446
|
-
const
|
|
534
|
+
const a = steps[0]
|
|
535
|
+
const b = steps[1]
|
|
447
536
|
return output === 0
|
|
448
537
|
? (s) => {
|
|
449
538
|
const x = a(s)
|
|
@@ -479,8 +568,8 @@ class Compiler {
|
|
|
479
568
|
/** The unit at the cursor, or -1 at the end of the input. */
|
|
480
569
|
private unitReader(): (s: State<any>) => number {
|
|
481
570
|
return this.kind === 0
|
|
482
|
-
? (s) => (s.pos < s.end ?
|
|
483
|
-
: (s) => (s.pos < s.end ?
|
|
571
|
+
? (s: State<string>) => (s.pos < s.end ? s.input.charCodeAt(s.pos) : -1)
|
|
572
|
+
: (s: State<Uint8Array>) => (s.pos < s.end ? s.input[s.pos] : -1)
|
|
484
573
|
}
|
|
485
574
|
|
|
486
575
|
alt(node: NodeOf<"Alt">, used: boolean): Step {
|
|
@@ -533,7 +622,7 @@ class Compiler {
|
|
|
533
622
|
const compiled = new Map<ParserLike, Step>()
|
|
534
623
|
if (node.cases !== undefined) {
|
|
535
624
|
for (const key of Reflect.ownKeys(node.cases)) {
|
|
536
|
-
const q = node.cases[key
|
|
625
|
+
const q = node.cases[key]
|
|
537
626
|
if (!compiled.has(q)) compiled.set(q, this.compile(q, used))
|
|
538
627
|
}
|
|
539
628
|
}
|
|
@@ -557,9 +646,9 @@ class Compiler {
|
|
|
557
646
|
return general
|
|
558
647
|
}
|
|
559
648
|
// Switch on the next unit when the selector is `any` or `peek(any)`
|
|
560
|
-
|
|
561
|
-
const peek = Predicate.isTagged(
|
|
562
|
-
|
|
649
|
+
const top = nodeOf(node.selector)
|
|
650
|
+
const peek = Predicate.isTagged(top, "Peek")
|
|
651
|
+
const sel = peek ? nodeOf(top.self) : top
|
|
563
652
|
if (!Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) return general
|
|
564
653
|
const enc = sel.encoding
|
|
565
654
|
// Units below `limit` are whole tokens, so the key is known in advance
|
|
@@ -595,19 +684,19 @@ class Compiler {
|
|
|
595
684
|
|
|
596
685
|
repeat(node: NodeOf<"Repeat"> | NodeOf<"Fold">, used: boolean): Step {
|
|
597
686
|
const { max, min } = node
|
|
598
|
-
|
|
687
|
+
// Bounded or not, the repetition checks progress below (#115)
|
|
599
688
|
const isFold = Predicate.isTagged(node, "Fold")
|
|
600
|
-
const cond = isFold ?
|
|
689
|
+
const cond = isFold ? node.while : undefined
|
|
601
690
|
const c = this.compile(node.self, used)
|
|
602
691
|
const resumable = this.resumable
|
|
603
692
|
const key = new Key()
|
|
604
693
|
// The accumulator: an array, the fold's value, or nothing when unused (and unneeded by `while`)
|
|
605
694
|
const tracked = used || cond !== undefined
|
|
606
|
-
const init: () => unknown = !tracked ? () => undefined : isFold ?
|
|
695
|
+
const init: () => unknown = !tracked ? () => undefined : isFold ? node.init : () => []
|
|
607
696
|
const add: (acc: any, a: unknown) => unknown = !tracked
|
|
608
697
|
? (acc) => acc
|
|
609
698
|
: isFold
|
|
610
|
-
?
|
|
699
|
+
? node.f
|
|
611
700
|
: (acc, a) => {
|
|
612
701
|
acc.push(a)
|
|
613
702
|
return acc
|
|
@@ -618,6 +707,7 @@ class Compiler {
|
|
|
618
707
|
let count = 0
|
|
619
708
|
let resumed = false
|
|
620
709
|
if (resumable && s.resume !== undefined) {
|
|
710
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
|
|
621
711
|
const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; count: number; pos: number } | undefined
|
|
622
712
|
if (cp instanceof Resume.Done) {
|
|
623
713
|
s.pos = cp.pos
|
|
@@ -641,7 +731,7 @@ class Compiler {
|
|
|
641
731
|
s.pos = start
|
|
642
732
|
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
643
733
|
}
|
|
644
|
-
if (
|
|
734
|
+
if (s.pos === start) {
|
|
645
735
|
s.mode = CUT
|
|
646
736
|
return Fail
|
|
647
737
|
}
|
|
@@ -657,44 +747,43 @@ class Compiler {
|
|
|
657
747
|
*/
|
|
658
748
|
private entries(p: ParserLike): Step {
|
|
659
749
|
const node = nodeOf(p)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return a
|
|
669
|
-
}
|
|
750
|
+
if (Predicate.isTagged(node, "Mode")) {
|
|
751
|
+
const c = this.entries(node.self)
|
|
752
|
+
const from = node.from
|
|
753
|
+
const to = node.to
|
|
754
|
+
return (s) => {
|
|
755
|
+
const a = c(s)
|
|
756
|
+
if (a === Fail && s.mode === from) s.mode = to
|
|
757
|
+
return a
|
|
670
758
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
759
|
+
}
|
|
760
|
+
if (Predicate.isTagged(node, "Seq")) {
|
|
761
|
+
// SAFETY: `buildsEntries` passes through a sequence only when it keeps one output, by index
|
|
762
|
+
const i = node.output as number
|
|
763
|
+
const steps = node.parsers.map((q, j) => (j === i ? this.entries(q) : this.compile(q, false)))
|
|
764
|
+
return (s) => {
|
|
765
|
+
let out: unknown
|
|
766
|
+
for (let j = 0; j < steps.length; j++) {
|
|
767
|
+
const a = steps[j](s)
|
|
768
|
+
if (a === Fail) return Fail
|
|
769
|
+
if (j === i) out = a
|
|
682
770
|
}
|
|
771
|
+
return out
|
|
683
772
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
773
|
+
}
|
|
774
|
+
if (Predicate.isTagged(node, "Repeat") || Predicate.isTagged(node, "Separated")) {
|
|
775
|
+
const pair = pairOf(node.self)
|
|
776
|
+
if (pair !== undefined) return this.entriesLoop(node, pair)
|
|
777
|
+
// Not reached (`buildsEntries` found the pair): build the pairs, then the object
|
|
778
|
+
const c = this.compile(p, true)
|
|
779
|
+
return (s) => {
|
|
780
|
+
const a = c(s)
|
|
781
|
+
// SAFETY: a used `repeat` or `separated` returns the array of its elements, here the pairs
|
|
782
|
+
return a === Fail ? Fail : fromEntries(a as Iterable<readonly [PropertyKey, unknown]>)
|
|
694
783
|
}
|
|
695
|
-
default:
|
|
696
|
-
return this.entries((node as { readonly self: ParserLike }).self)
|
|
697
784
|
}
|
|
785
|
+
// SAFETY: the other nodes `buildsEntries` passes through are diagnostics, which all wrap a `self`
|
|
786
|
+
return this.entries((node as { readonly self: ParserLike }).self)
|
|
698
787
|
}
|
|
699
788
|
|
|
700
789
|
private entriesLoop(node: NodeOf<"Repeat"> | NodeOf<"Separated">, pair: EntryPair): Step {
|
|
@@ -714,11 +803,18 @@ class Compiler {
|
|
|
714
803
|
return true
|
|
715
804
|
}
|
|
716
805
|
const { max, min } = node
|
|
717
|
-
|
|
806
|
+
// Bounded or not, the repetition checks progress below (#115)
|
|
807
|
+
// A streaming build's checkpoint holds the object built so far (see `repeat`)
|
|
808
|
+
const resumable = this.resumable
|
|
809
|
+
const site = new Key()
|
|
718
810
|
if (Predicate.isTagged(node, "Repeat")) {
|
|
719
811
|
return (s) => {
|
|
720
|
-
const
|
|
721
|
-
|
|
812
|
+
const at = s.pos
|
|
813
|
+
const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
|
|
814
|
+
if (cp instanceof Resume.Done) return cp.value
|
|
815
|
+
const resumed = cp !== undefined
|
|
816
|
+
const acc = cp?.acc ?? new EntriesAcc()
|
|
817
|
+
for (let count = cp?.count ?? 0; count < max; count++) {
|
|
722
818
|
const start = s.pos
|
|
723
819
|
let key: unknown
|
|
724
820
|
let value: unknown
|
|
@@ -733,50 +829,50 @@ class Compiler {
|
|
|
733
829
|
else if (j === vi) value = a
|
|
734
830
|
}
|
|
735
831
|
if (!ok) {
|
|
736
|
-
|
|
737
|
-
s.pos = start
|
|
738
|
-
return acc.result()
|
|
832
|
+
return stopEntries(s, site, at, resumed, acc, count, start, min)
|
|
739
833
|
}
|
|
740
|
-
if (
|
|
834
|
+
if (s.pos === start) {
|
|
741
835
|
s.mode = CUT
|
|
742
836
|
return Fail
|
|
743
837
|
}
|
|
744
838
|
acc.add(key, value)
|
|
745
839
|
}
|
|
746
|
-
return acc
|
|
840
|
+
return entriesDone(s, site, at, resumed, acc)
|
|
747
841
|
}
|
|
748
842
|
}
|
|
749
843
|
const sep = this.compile(node.separator, false)
|
|
750
844
|
return (s) => {
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
if (
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
return acc.result()
|
|
845
|
+
const at = s.pos
|
|
846
|
+
const cp = resumable && s.resume !== undefined ? resumeEntries(s, site, at) : undefined
|
|
847
|
+
if (cp instanceof Resume.Done) return cp.value
|
|
848
|
+
const resumed = cp !== undefined
|
|
849
|
+
const acc = cp?.acc ?? new EntriesAcc()
|
|
850
|
+
let count = cp?.count ?? 0
|
|
851
|
+
if (!resumed) {
|
|
852
|
+
if (max === 0) return acc.result()
|
|
853
|
+
const start = s.pos
|
|
854
|
+
if (!entry(s, acc)) {
|
|
855
|
+
if (s.mode !== BT || min > 0) return Fail
|
|
856
|
+
s.pos = start
|
|
857
|
+
return acc.result()
|
|
858
|
+
}
|
|
859
|
+
count++
|
|
759
860
|
}
|
|
760
|
-
count++
|
|
761
861
|
while (count < max) {
|
|
762
|
-
start = s.pos
|
|
862
|
+
const start = s.pos
|
|
763
863
|
if (sep(s) === Fail) {
|
|
764
|
-
|
|
765
|
-
s.pos = start
|
|
766
|
-
return acc.result()
|
|
864
|
+
return stopEntries(s, site, at, resumed, acc, count, start, min)
|
|
767
865
|
}
|
|
768
|
-
if (
|
|
866
|
+
if (s.pos === start) {
|
|
769
867
|
s.mode = CUT
|
|
770
868
|
return Fail
|
|
771
869
|
}
|
|
772
870
|
if (!entry(s, acc)) {
|
|
773
|
-
|
|
774
|
-
s.pos = start
|
|
775
|
-
return acc.result()
|
|
871
|
+
return stopEntries(s, site, at, resumed, acc, count, start, min)
|
|
776
872
|
}
|
|
777
873
|
count++
|
|
778
874
|
}
|
|
779
|
-
return acc
|
|
875
|
+
return entriesDone(s, site, at, resumed, acc)
|
|
780
876
|
}
|
|
781
877
|
}
|
|
782
878
|
|
|
@@ -808,7 +904,7 @@ class Compiler {
|
|
|
808
904
|
|
|
809
905
|
separated(node: NodeOf<"Separated">, used: boolean): Step {
|
|
810
906
|
const { max, min } = node
|
|
811
|
-
|
|
907
|
+
// Bounded or not, the repetition checks progress below (#115)
|
|
812
908
|
const c = this.compile(node.self, used)
|
|
813
909
|
const sep = this.compile(node.separator, false)
|
|
814
910
|
const resumable = this.resumable
|
|
@@ -819,6 +915,7 @@ class Compiler {
|
|
|
819
915
|
let count = 0
|
|
820
916
|
let resumed = false
|
|
821
917
|
if (resumable && s.resume !== undefined) {
|
|
918
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ acc, count, pos }` when it runs out, a `Done` when it completes
|
|
822
919
|
const cp = Resume.take(s, key, entry) as
|
|
823
920
|
| Resume.Done
|
|
824
921
|
| { acc: Array<unknown>; count: number; pos: number }
|
|
@@ -855,7 +952,7 @@ class Compiler {
|
|
|
855
952
|
s.pos = start
|
|
856
953
|
return resumed ? Resume.done(s, key, entry, true, acc) : acc
|
|
857
954
|
}
|
|
858
|
-
if (
|
|
955
|
+
if (s.pos === start) {
|
|
859
956
|
s.mode = CUT
|
|
860
957
|
return Fail
|
|
861
958
|
}
|
|
@@ -885,6 +982,7 @@ class Compiler {
|
|
|
885
982
|
let acc: unknown
|
|
886
983
|
let resumed = false
|
|
887
984
|
if (resumable && s.resume !== undefined) {
|
|
985
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ acc, pos }` when it runs out, a `Done` when it completes
|
|
888
986
|
const cp = Resume.take(s, key, entry) as Resume.Done | { acc: unknown; pos: number } | undefined
|
|
889
987
|
if (cp instanceof Resume.Done) {
|
|
890
988
|
s.pos = cp.pos
|
|
@@ -961,21 +1059,22 @@ class Compiler {
|
|
|
961
1059
|
// ---------------------------------------------------------------------------
|
|
962
1060
|
|
|
963
1061
|
token(p: ParserLike, node: TokenNode, used: boolean): Step {
|
|
1062
|
+
// SAFETY: `build` compiles a token node without an encoding only when `native` holds, that is when `kind` is not 2
|
|
964
1063
|
const kind = this.kind as 0 | 1
|
|
965
1064
|
switch (node._tag) {
|
|
966
1065
|
case "Any":
|
|
967
1066
|
if (kind === 1) {
|
|
968
|
-
return (s) => {
|
|
1067
|
+
return (s: State<Uint8Array>) => {
|
|
969
1068
|
const pos = s.pos
|
|
970
1069
|
if (pos >= s.end) return fail(s)
|
|
971
1070
|
s.pos = pos + 1
|
|
972
|
-
return
|
|
1071
|
+
return s.input[pos]
|
|
973
1072
|
}
|
|
974
1073
|
}
|
|
975
1074
|
return (s) => {
|
|
976
1075
|
const pos = s.pos
|
|
977
1076
|
if (pos >= s.end) return fail(s)
|
|
978
|
-
const str = s.input
|
|
1077
|
+
const str: string = s.input
|
|
979
1078
|
const c = str.charCodeAt(pos)
|
|
980
1079
|
if (c >= 0xd800 && c <= 0xdbff && pos + 1 < str.length && (str.charCodeAt(pos + 1) & 0xfc00) === 0xdc00) {
|
|
981
1080
|
s.pos = pos + 2
|
|
@@ -992,10 +1091,10 @@ class Compiler {
|
|
|
992
1091
|
const accept = acceptTable(set, negate)
|
|
993
1092
|
const test = set.test
|
|
994
1093
|
if (kind === 1) {
|
|
995
|
-
return (s) => {
|
|
1094
|
+
return (s: State<Uint8Array>) => {
|
|
996
1095
|
const pos = s.pos
|
|
997
1096
|
if (pos >= s.end) return fail(s)
|
|
998
|
-
const b =
|
|
1097
|
+
const b = s.input[pos]
|
|
999
1098
|
if (accept[b] === 0) return fail(s)
|
|
1000
1099
|
s.pos = pos + 1
|
|
1001
1100
|
return b
|
|
@@ -1004,7 +1103,7 @@ class Compiler {
|
|
|
1004
1103
|
return (s) => {
|
|
1005
1104
|
const pos = s.pos
|
|
1006
1105
|
if (pos >= s.end) return fail(s)
|
|
1007
|
-
const str = s.input
|
|
1106
|
+
const str: string = s.input
|
|
1008
1107
|
const c = str.charCodeAt(pos)
|
|
1009
1108
|
if (c < TableSize) {
|
|
1010
1109
|
if (accept[c] === 0) return fail(s)
|
|
@@ -1025,15 +1124,15 @@ class Compiler {
|
|
|
1025
1124
|
case "Take": {
|
|
1026
1125
|
const count = node.count
|
|
1027
1126
|
if (kind === 1) {
|
|
1028
|
-
return (s) => {
|
|
1127
|
+
return (s: State<Uint8Array>) => {
|
|
1029
1128
|
const start = s.pos
|
|
1030
1129
|
if (s.end - start < count) return failAt(s, s.end)
|
|
1031
1130
|
s.pos = start + count
|
|
1032
|
-
return used ?
|
|
1131
|
+
return used ? s.input.subarray(start, start + count) : undefined
|
|
1033
1132
|
}
|
|
1034
1133
|
}
|
|
1035
1134
|
return (s) => {
|
|
1036
|
-
const str = s.input
|
|
1135
|
+
const str: string = s.input
|
|
1037
1136
|
const start = s.pos
|
|
1038
1137
|
const end = s.end
|
|
1039
1138
|
let i = start
|
|
@@ -1050,12 +1149,16 @@ class Compiler {
|
|
|
1050
1149
|
}
|
|
1051
1150
|
case "TakeWhile":
|
|
1052
1151
|
return this.tokenTakeWhile(node, used)
|
|
1053
|
-
case "TakeUntil":
|
|
1054
|
-
|
|
1152
|
+
case "TakeUntil":
|
|
1153
|
+
case "TakeUntilAny": {
|
|
1154
|
+
const { max, min } = node
|
|
1055
1155
|
const bounded = min > 0 || max !== Infinity
|
|
1156
|
+
const search: (s: State<Input>, from: number) => number = Predicate.isTagged(node, "TakeUntil")
|
|
1157
|
+
? (s, from) => indexOf(s, node.literal, from)
|
|
1158
|
+
: (s, from) => indexOfAny(s, node.literals, from)
|
|
1056
1159
|
return (s) => {
|
|
1057
1160
|
const start = s.pos
|
|
1058
|
-
const idx =
|
|
1161
|
+
const idx = search(s, start)
|
|
1059
1162
|
if (idx < 0) return failAt(s, s.end)
|
|
1060
1163
|
if (bounded) {
|
|
1061
1164
|
const count = countTokens(s, start, idx)
|
|
@@ -1078,15 +1181,15 @@ class Compiler {
|
|
|
1078
1181
|
const lit = node.literal
|
|
1079
1182
|
const literalKind = Predicate.isString(lit) ? 0 : lit instanceof Uint8Array ? 1 : 2
|
|
1080
1183
|
if (this.kind === 0) {
|
|
1081
|
-
if (
|
|
1082
|
-
const str = lit
|
|
1184
|
+
if (!Predicate.isString(lit)) return this.fallback(p, node, "byte or array literal on string input")
|
|
1185
|
+
const str = lit
|
|
1083
1186
|
const len = str.length
|
|
1084
1187
|
if (node.caseless) {
|
|
1085
1188
|
const lowered = Array.from({ length: len }, (_, i) => lowerAscii(str.charCodeAt(i)))
|
|
1086
1189
|
return (s) => {
|
|
1087
1190
|
const pos = s.pos
|
|
1088
1191
|
if (s.end - pos < len) return fail(s)
|
|
1089
|
-
const input = s.input
|
|
1192
|
+
const input: string = s.input
|
|
1090
1193
|
for (let i = 0; i < len; i++) {
|
|
1091
1194
|
if (lowerAscii(input.charCodeAt(pos + i)) !== lowered[i]) return fail(s)
|
|
1092
1195
|
}
|
|
@@ -1096,8 +1199,8 @@ class Compiler {
|
|
|
1096
1199
|
}
|
|
1097
1200
|
return stringLiteral(str, str)
|
|
1098
1201
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1202
|
+
const bytes = Predicate.isString(lit) ? new TextEncoder().encode(lit) : lit instanceof Uint8Array ? lit : undefined
|
|
1203
|
+
if (bytes === undefined) return this.fallback(p, node, "array literal on byte input")
|
|
1101
1204
|
const len = bytes.length
|
|
1102
1205
|
const sameKind = literalKind === 1 && !node.caseless
|
|
1103
1206
|
const expect = node.caseless ? Array.from(bytes, lowerAscii) : Array.from(bytes)
|
|
@@ -1105,7 +1208,7 @@ class Compiler {
|
|
|
1105
1208
|
return (s) => {
|
|
1106
1209
|
const pos = s.pos
|
|
1107
1210
|
if (s.end - pos < len) return fail(s)
|
|
1108
|
-
const input = s.input
|
|
1211
|
+
const input: Uint8Array = s.input
|
|
1109
1212
|
for (let i = 0; i < len; i++) {
|
|
1110
1213
|
const b = input[pos + i]
|
|
1111
1214
|
if ((caseless ? lowerAscii(b) : b) !== expect[i]) return fail(s)
|
|
@@ -1116,13 +1219,14 @@ class Compiler {
|
|
|
1116
1219
|
}
|
|
1117
1220
|
|
|
1118
1221
|
tokenTakeWhile(node: NodeOf<"TakeWhile">, used: boolean): Step {
|
|
1222
|
+
// SAFETY: called from `token` only, whose `kind` is not 2
|
|
1119
1223
|
const kind = this.kind as 0 | 1
|
|
1120
1224
|
const { max, min, negate } = node
|
|
1121
1225
|
const set = tokenSet(node.set, kind)
|
|
1122
1226
|
const accept = acceptTable(set, negate)
|
|
1123
1227
|
if (kind === 1) {
|
|
1124
1228
|
return (s) => {
|
|
1125
|
-
const bytes = s.input
|
|
1229
|
+
const bytes: Uint8Array = s.input
|
|
1126
1230
|
const start = s.pos
|
|
1127
1231
|
const limit = max === Infinity ? s.end : Math.min(s.end, start + max)
|
|
1128
1232
|
let i = start
|
|
@@ -1132,9 +1236,11 @@ class Compiler {
|
|
|
1132
1236
|
return used ? bytes.subarray(start, i) : undefined
|
|
1133
1237
|
}
|
|
1134
1238
|
}
|
|
1239
|
+
const stops = negate && min <= 1 && max === Infinity ? stopChars(set) : undefined
|
|
1240
|
+
if (stops !== undefined) return stopTakeTill(stops, min, used)
|
|
1135
1241
|
const test = set.test
|
|
1136
1242
|
return (s) => {
|
|
1137
|
-
const str = s.input
|
|
1243
|
+
const str: string = s.input
|
|
1138
1244
|
const start = s.pos
|
|
1139
1245
|
const end = s.end
|
|
1140
1246
|
let i = start
|
|
@@ -1172,7 +1278,9 @@ class Compiler {
|
|
|
1172
1278
|
const { fast, unitAt, width: unitWidth } = unitReader(this.kind, enc)
|
|
1173
1279
|
const decode = enc.decode
|
|
1174
1280
|
const width = enc.width
|
|
1175
|
-
|
|
1281
|
+
// UTF-8 decodes through the state's ASCII run (see `utf8Text`)
|
|
1282
|
+
const toText: (s: State<any>, start: number, end: number) => string =
|
|
1283
|
+
enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
|
|
1176
1284
|
switch (node._tag) {
|
|
1177
1285
|
case "Any":
|
|
1178
1286
|
return (s) => {
|
|
@@ -1190,6 +1298,7 @@ class Compiler {
|
|
|
1190
1298
|
return used ? charOf(cp) : undefined
|
|
1191
1299
|
}
|
|
1192
1300
|
case "Literal": {
|
|
1301
|
+
// SAFETY: a `Literal` with an encoding is a `Text` literal, which records the string it encodes
|
|
1193
1302
|
const lit = node.literal as string
|
|
1194
1303
|
if (node.caseless) {
|
|
1195
1304
|
const folded = Array.from(lit, (c) => fold(firstCodePoint(c)))
|
|
@@ -1214,18 +1323,18 @@ class Compiler {
|
|
|
1214
1323
|
if (fold(cp) !== folded[i]) return failAt(s, pos)
|
|
1215
1324
|
}
|
|
1216
1325
|
s.pos = pos
|
|
1217
|
-
return used ? toText(
|
|
1326
|
+
return used ? toText(s, start, pos) : undefined
|
|
1218
1327
|
}
|
|
1219
1328
|
}
|
|
1220
1329
|
const encoded = enc.encode(lit)
|
|
1221
1330
|
if (Predicate.isString(encoded)) return stringLiteral(encoded, lit)
|
|
1222
|
-
const bytes = encoded
|
|
1331
|
+
const bytes: Uint8Array = encoded
|
|
1223
1332
|
const len = bytes.length
|
|
1224
1333
|
if (len === 1) {
|
|
1225
1334
|
const b0 = bytes[0]
|
|
1226
|
-
return (s) => {
|
|
1335
|
+
return (s: State<Uint8Array>) => {
|
|
1227
1336
|
const pos = s.pos
|
|
1228
|
-
if (pos < s.end &&
|
|
1337
|
+
if (pos < s.end && s.input[pos] === b0) {
|
|
1229
1338
|
s.pos = pos + 1
|
|
1230
1339
|
return lit
|
|
1231
1340
|
}
|
|
@@ -1235,7 +1344,7 @@ class Compiler {
|
|
|
1235
1344
|
return (s) => {
|
|
1236
1345
|
const pos = s.pos
|
|
1237
1346
|
if (s.end - pos < len) return fail(s)
|
|
1238
|
-
const input = s.input
|
|
1347
|
+
const input: Uint8Array = s.input
|
|
1239
1348
|
for (let i = 0; i < len; i++) if (input[pos + i] !== bytes[i]) return fail(s)
|
|
1240
1349
|
s.pos = pos + len
|
|
1241
1350
|
return lit
|
|
@@ -1280,7 +1389,7 @@ class Compiler {
|
|
|
1280
1389
|
}
|
|
1281
1390
|
}
|
|
1282
1391
|
s.pos = pos
|
|
1283
|
-
return used ? toText(
|
|
1392
|
+
return used ? toText(s, start, pos) : undefined
|
|
1284
1393
|
}
|
|
1285
1394
|
}
|
|
1286
1395
|
case "TakeWhile": {
|
|
@@ -1296,6 +1405,7 @@ class Compiler {
|
|
|
1296
1405
|
return this.kind === 0 ? takeWhileString(loop, enc) : takeWhileBytes(loop, enc)
|
|
1297
1406
|
}
|
|
1298
1407
|
case "TakeUntil":
|
|
1408
|
+
case "TakeUntilAny":
|
|
1299
1409
|
case "Rest":
|
|
1300
1410
|
return this.fallback(p, node, "Text parser without a compiled form")
|
|
1301
1411
|
}
|
|
@@ -1307,9 +1417,9 @@ class Compiler {
|
|
|
1307
1417
|
if (enc === undefined) {
|
|
1308
1418
|
if (!used) return c
|
|
1309
1419
|
if (this.kind === 0) {
|
|
1310
|
-
return (s) => {
|
|
1420
|
+
return (s: State<string>) => {
|
|
1311
1421
|
const start = s.pos
|
|
1312
|
-
return c(s) === Fail ? Fail :
|
|
1422
|
+
return c(s) === Fail ? Fail : s.input.slice(start, s.pos)
|
|
1313
1423
|
}
|
|
1314
1424
|
}
|
|
1315
1425
|
return (s) => {
|
|
@@ -1319,7 +1429,9 @@ class Compiler {
|
|
|
1319
1429
|
}
|
|
1320
1430
|
// `Text.taken` checks that the text is well formed, then decodes it
|
|
1321
1431
|
const { fast, unitAt, width } = unitReader(this.kind, enc)
|
|
1322
|
-
|
|
1432
|
+
// UTF-8 decodes through the state's ASCII run (see `utf8Text`)
|
|
1433
|
+
const toText: (s: State<any>, start: number, end: number) => string =
|
|
1434
|
+
enc === utf8 ? utf8Text : (s, start, end) => enc.text(s.input, start, end)
|
|
1323
1435
|
return (s) => {
|
|
1324
1436
|
const start = s.pos
|
|
1325
1437
|
if (c(s) === Fail) return Fail
|
|
@@ -1328,7 +1440,7 @@ class Compiler {
|
|
|
1328
1440
|
let i = start
|
|
1329
1441
|
while (i < end && unitAt(input, i, end) < fast) i += width
|
|
1330
1442
|
if (i < end && validate(enc, s, i, end) < 0) return fail(s)
|
|
1331
|
-
return used ? toText(
|
|
1443
|
+
return used ? toText(s, start, end) : undefined
|
|
1332
1444
|
}
|
|
1333
1445
|
}
|
|
1334
1446
|
|
|
@@ -1341,11 +1453,7 @@ class Compiler {
|
|
|
1341
1453
|
if (!floatTexts.has(node.self) || this.kind === 2) return undefined
|
|
1342
1454
|
if (!scansFloat(node.self, (enc) => this.native(enc))) return undefined
|
|
1343
1455
|
const c = this.compile(node.self, true)
|
|
1344
|
-
const scan =
|
|
1345
|
-
input: unknown,
|
|
1346
|
-
i: number,
|
|
1347
|
-
end: number
|
|
1348
|
-
) => unknown
|
|
1456
|
+
const scan = this.kind === 0 ? scanFloatString : scanFloatBytes
|
|
1349
1457
|
const resumable = this.resumable
|
|
1350
1458
|
return (s) => {
|
|
1351
1459
|
const v = scan(s.input, s.pos, s.end)
|
|
@@ -1372,15 +1480,15 @@ class Compiler {
|
|
|
1372
1480
|
if (enc === undefined || (ctrl < fastLimit(enc) && ctrl < TableSize)) {
|
|
1373
1481
|
control =
|
|
1374
1482
|
kind === 0
|
|
1375
|
-
? (s) => {
|
|
1376
|
-
if (s.pos < s.end &&
|
|
1483
|
+
? (s: State<string>) => {
|
|
1484
|
+
if (s.pos < s.end && s.input.charCodeAt(s.pos) === ctrl) {
|
|
1377
1485
|
s.pos++
|
|
1378
1486
|
return true
|
|
1379
1487
|
}
|
|
1380
1488
|
return false
|
|
1381
1489
|
}
|
|
1382
|
-
: (s) => {
|
|
1383
|
-
if (s.pos < s.end &&
|
|
1490
|
+
: (s: State<Uint8Array>) => {
|
|
1491
|
+
if (s.pos < s.end && s.input[s.pos] === ctrl) {
|
|
1384
1492
|
s.pos++
|
|
1385
1493
|
return true
|
|
1386
1494
|
}
|
|
@@ -1407,19 +1515,20 @@ class Compiler {
|
|
|
1407
1515
|
const resumable = this.resumable
|
|
1408
1516
|
const key = new Key()
|
|
1409
1517
|
/** Fails as incomplete, keeping a checkpoint in a streaming parse. */
|
|
1410
|
-
const incomplete = (s: State<any>, start: number, checkpoint:
|
|
1518
|
+
const incomplete = (s: State<any>, start: number, checkpoint: EscapedCheckpoint): typeof Fail => {
|
|
1411
1519
|
if (s.resume !== undefined) Resume.save(s, key, start, checkpoint)
|
|
1412
1520
|
s.mode = INC
|
|
1413
1521
|
return Fail
|
|
1414
1522
|
}
|
|
1415
1523
|
if (raw) {
|
|
1416
|
-
const slice: (s: State<any>, start: number) =>
|
|
1417
|
-
enc === undefined ? (s, start) => s.slice(start, s.pos) : (s, start) => enc
|
|
1524
|
+
const slice: (s: State<any>, start: number) => Input =
|
|
1525
|
+
enc === undefined ? (s, start) => s.slice(start, s.pos) : (s, start) => textIn(enc, s, start, s.pos)
|
|
1418
1526
|
return (s) => {
|
|
1419
1527
|
const start = s.pos
|
|
1420
1528
|
let resumed = false
|
|
1421
1529
|
if (resumable && s.resume !== undefined) {
|
|
1422
|
-
|
|
1530
|
+
// SAFETY: only this raw loop saves checkpoints under `key`: the position it stopped at, or a `Done` when it completes
|
|
1531
|
+
const cp = Resume.take(s, key, start) as Resume.Done | Extract<EscapedCheckpoint, number> | undefined
|
|
1423
1532
|
if (cp instanceof Resume.Done) {
|
|
1424
1533
|
s.pos = cp.pos
|
|
1425
1534
|
return cp.value
|
|
@@ -1447,7 +1556,11 @@ class Compiler {
|
|
|
1447
1556
|
return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
|
|
1448
1557
|
}
|
|
1449
1558
|
}
|
|
1450
|
-
if (s.partial)
|
|
1559
|
+
if (s.partial) {
|
|
1560
|
+
// The text ran out of input: `escaped`'s own failure, needing more
|
|
1561
|
+
s.incomplete(0)
|
|
1562
|
+
return incomplete(s, start, s.pos)
|
|
1563
|
+
}
|
|
1451
1564
|
return resumed ? Resume.done(s, key, start, true, slice(s, start)) : slice(s, start)
|
|
1452
1565
|
}
|
|
1453
1566
|
}
|
|
@@ -1456,7 +1569,8 @@ class Compiler {
|
|
|
1456
1569
|
let out = ""
|
|
1457
1570
|
let resumed = false
|
|
1458
1571
|
if (resumable && s.resume !== undefined) {
|
|
1459
|
-
|
|
1572
|
+
// SAFETY: only this loop saves checkpoints under `key`: `{ out, pos }` when it runs out, a `Done` when it completes
|
|
1573
|
+
const cp = Resume.take(s, key, start) as Resume.Done | Exclude<EscapedCheckpoint, number> | undefined
|
|
1460
1574
|
if (cp instanceof Resume.Done) {
|
|
1461
1575
|
s.pos = cp.pos
|
|
1462
1576
|
return cp.value
|
|
@@ -1488,13 +1602,31 @@ class Compiler {
|
|
|
1488
1602
|
return resumed ? Resume.done(s, key, start, true, out) : out
|
|
1489
1603
|
}
|
|
1490
1604
|
}
|
|
1491
|
-
if (s.partial)
|
|
1605
|
+
if (s.partial) {
|
|
1606
|
+
// The text ran out of input: `escaped`'s own failure, needing more
|
|
1607
|
+
s.incomplete(0)
|
|
1608
|
+
return incomplete(s, start, { out, pos: s.pos })
|
|
1609
|
+
}
|
|
1492
1610
|
return resumed ? Resume.done(s, key, start, true, out) : out
|
|
1493
1611
|
}
|
|
1494
1612
|
}
|
|
1495
1613
|
}
|
|
1496
1614
|
|
|
1497
|
-
|
|
1615
|
+
/** How far past the cursor a token parser may read: its literals' length in UTF-8 at most, or `Margin`. */
|
|
1616
|
+
const marginOf = (node: TokenNode): number => {
|
|
1617
|
+
if (Predicate.isTagged(node, "Literal") || Predicate.isTagged(node, "TakeUntil")) return node.literal.length * 4
|
|
1618
|
+
if (Predicate.isTagged(node, "TakeUntilAny")) return Math.max(0, ...node.literals.map((lit) => lit.length)) * 4
|
|
1619
|
+
return Margin
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
type TokenNode = NodeOf<"Any" | "Literal" | "OneOf" | "Take" | "TakeWhile" | "TakeUntil" | "TakeUntilAny" | "Rest">
|
|
1623
|
+
|
|
1624
|
+
/**
|
|
1625
|
+
* Where a compiled `escaped` that ran out of input resumes: the position
|
|
1626
|
+
* (`takeEscaped`, which slices the input), or the text unescaped so far and
|
|
1627
|
+
* the position after it.
|
|
1628
|
+
*/
|
|
1629
|
+
type EscapedCheckpoint = number | { readonly out: string; readonly pos: number }
|
|
1498
1630
|
|
|
1499
1631
|
const fail = (s: State<any>): typeof Fail => {
|
|
1500
1632
|
s.mode = BT
|
|
@@ -1651,18 +1783,18 @@ const stringLiteral = (str: string, value: unknown): Step => {
|
|
|
1651
1783
|
if (len === 0) return () => value
|
|
1652
1784
|
if (len === 1) {
|
|
1653
1785
|
const c0 = str.charCodeAt(0)
|
|
1654
|
-
return (s) => {
|
|
1786
|
+
return (s: State<string>) => {
|
|
1655
1787
|
const pos = s.pos
|
|
1656
|
-
if (pos < s.end &&
|
|
1788
|
+
if (pos < s.end && s.input.charCodeAt(pos) === c0) {
|
|
1657
1789
|
s.pos = pos + 1
|
|
1658
1790
|
return value
|
|
1659
1791
|
}
|
|
1660
1792
|
return fail(s)
|
|
1661
1793
|
}
|
|
1662
1794
|
}
|
|
1663
|
-
return (s) => {
|
|
1795
|
+
return (s: State<string>) => {
|
|
1664
1796
|
const pos = s.pos
|
|
1665
|
-
if (s.end - pos >= len &&
|
|
1797
|
+
if (s.end - pos >= len && s.input.startsWith(str, pos)) {
|
|
1666
1798
|
s.pos = pos + len
|
|
1667
1799
|
return value
|
|
1668
1800
|
}
|
|
@@ -1670,7 +1802,81 @@ const stringLiteral = (str: string, value: unknown): Step => {
|
|
|
1670
1802
|
}
|
|
1671
1803
|
}
|
|
1672
1804
|
|
|
1673
|
-
/**
|
|
1805
|
+
/**
|
|
1806
|
+
* `Token.takeTill` on a string, of one to three stop characters that are
|
|
1807
|
+
* whole code points (see `search.ts`): every other unit, surrogates
|
|
1808
|
+
* included, is accepted, so the run ends at the first stop character, or
|
|
1809
|
+
* at the state's end.
|
|
1810
|
+
*/
|
|
1811
|
+
const stopTakeTill = (stops: ReadonlyArray<number>, min: number, used: boolean): Step => {
|
|
1812
|
+
const head = stops.length === 1 ? SearchHead : 2 * SearchHead
|
|
1813
|
+
const c0 = stops[0]
|
|
1814
|
+
const c1 = stops.length > 1 ? stops[1] : -1
|
|
1815
|
+
const c2 = stops.length > 2 ? stops[2] : -1
|
|
1816
|
+
const one = String.fromCharCode(c0)
|
|
1817
|
+
const site = new StopCache(stops.map((u) => String.fromCharCode(u)))
|
|
1818
|
+
return (s: State<string>) => {
|
|
1819
|
+
const str = s.input
|
|
1820
|
+
const start = s.pos
|
|
1821
|
+
const end = s.end
|
|
1822
|
+
const h = start + head < end ? start + head : end
|
|
1823
|
+
let i = start
|
|
1824
|
+
let c = -1
|
|
1825
|
+
while (i < h && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) i++
|
|
1826
|
+
// A run that ends right after the head needs no search
|
|
1827
|
+
if (i === h && i < end && (c = str.charCodeAt(i)) !== c0 && c !== c1 && c !== c2) {
|
|
1828
|
+
if (stops.length === 1) {
|
|
1829
|
+
const at = str.indexOf(one, i)
|
|
1830
|
+
i = at < 0 || at > end ? end : at
|
|
1831
|
+
} else i = nextStop(site, s, i)
|
|
1832
|
+
}
|
|
1833
|
+
if (i - start < min) return failAt(s, i)
|
|
1834
|
+
s.pos = i
|
|
1835
|
+
return used ? str.slice(start, i) : undefined
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/** A checkpoint of `entriesLoop`: the object so far, the entries parsed and where the next one starts. */
|
|
1840
|
+
interface EntriesCheckpoint {
|
|
1841
|
+
readonly acc: EntriesAcc
|
|
1842
|
+
readonly count: number
|
|
1843
|
+
readonly pos: number
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/** Takes the checkpoint `entriesLoop` saved at `at`, restoring the position of a completed one. */
|
|
1847
|
+
const resumeEntries = (s: State<any>, key: Key, at: number): Resume.Done | EntriesCheckpoint | undefined => {
|
|
1848
|
+
// SAFETY: only `entriesLoop` saves checkpoints under its `key`: an `EntriesCheckpoint` when it runs out, a `Done` when it completes
|
|
1849
|
+
const cp = Resume.take(s, key, at) as Resume.Done | EntriesCheckpoint | undefined
|
|
1850
|
+
if (cp instanceof Resume.Done) s.pos = cp.pos
|
|
1851
|
+
else if (cp !== undefined) s.pos = cp.pos
|
|
1852
|
+
return cp
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/** The object `entriesLoop` built, recorded as done when the loop resumed. */
|
|
1856
|
+
const entriesDone = (s: State<any>, key: Key, at: number, resumed: boolean, acc: EntriesAcc): object =>
|
|
1857
|
+
resumed ? Resume.done(s, key, at, true, acc.result()) : acc.result()
|
|
1858
|
+
|
|
1859
|
+
/**
|
|
1860
|
+
* Where `entriesLoop` stops at an entry (or separator) that failed at
|
|
1861
|
+
* `start`: a checkpoint when the input ran out, `Fail` unless the failure
|
|
1862
|
+
* backtracks after `min` entries, otherwise the object.
|
|
1863
|
+
*/
|
|
1864
|
+
const stopEntries = (
|
|
1865
|
+
s: State<any>,
|
|
1866
|
+
site: Key,
|
|
1867
|
+
at: number,
|
|
1868
|
+
resumed: boolean,
|
|
1869
|
+
acc: EntriesAcc,
|
|
1870
|
+
count: number,
|
|
1871
|
+
start: number,
|
|
1872
|
+
min: number
|
|
1873
|
+
): object | typeof Fail => {
|
|
1874
|
+
if (s.mode === INC && s.resume !== undefined) Resume.save(s, site, at, { acc, count, pos: start })
|
|
1875
|
+
if (s.mode !== BT || count < min) return Fail
|
|
1876
|
+
s.pos = start
|
|
1877
|
+
return entriesDone(s, site, at, resumed, acc)
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1674
1880
|
/** The object of `Object.fromEntries`, built an entry at a time (see `internal/entries.ts`). */
|
|
1675
1881
|
class EntriesAcc {
|
|
1676
1882
|
readonly o: Record<string, unknown> = {}
|
|
@@ -1721,8 +1927,20 @@ export const backend: Backend = {
|
|
|
1721
1927
|
Result.match(
|
|
1722
1928
|
Result.try(() => compileClosure(p, kind, resumable)),
|
|
1723
1929
|
{
|
|
1724
|
-
onSuccess: ({ fallbacks, step }) => ({
|
|
1725
|
-
|
|
1930
|
+
onSuccess: ({ fallbacks, step }) => ({
|
|
1931
|
+
backend: "closure" as const,
|
|
1932
|
+
step,
|
|
1933
|
+
guarded: false,
|
|
1934
|
+
fallbacks,
|
|
1935
|
+
error: undefined
|
|
1936
|
+
}),
|
|
1937
|
+
onFailure: (error) => ({
|
|
1938
|
+
backend: "interpreted" as const,
|
|
1939
|
+
step: undefined,
|
|
1940
|
+
guarded: false,
|
|
1941
|
+
fallbacks: [],
|
|
1942
|
+
error
|
|
1943
|
+
})
|
|
1726
1944
|
}
|
|
1727
1945
|
)
|
|
1728
1946
|
}
|