@thomasfosterau/effect-parse 0.3.0 → 0.4.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/dist/Ascii.d.ts +0 -2
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +6 -6
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +0 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +1 -2
- package/dist/Bits.d.ts +0 -2
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +2 -3
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +0 -2
- package/dist/Compiler.d.ts.map +1 -1
- package/dist/Compiler.js +1 -2
- package/dist/Encoding.d.ts +0 -2
- package/dist/Encoding.d.ts.map +1 -1
- package/dist/Encoding.js +1 -2
- package/dist/Expression.d.ts +0 -2
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +44 -8
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +32 -4
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +47 -8
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +0 -2
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js +1 -2
- package/dist/ParseError.d.ts +0 -2
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +6 -9
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +2 -2
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +26 -24
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +0 -2
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +2 -3
- package/dist/ParserStream.d.ts +0 -2
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +15 -9
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +0 -2
- package/dist/Syntax.d.ts.map +1 -1
- package/dist/Syntax.js +13 -11
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts +0 -2
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +1 -2
- package/dist/Token.d.ts +0 -2
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +1 -2
- package/dist/internal/compiler/closure.d.ts +5 -2
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +160 -92
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +0 -2
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +6 -7
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts +0 -2
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +10 -10
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts +0 -2
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +52 -51
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +0 -2
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js +1 -2
- package/dist/internal/compiler/sets.d.ts +2 -2
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +8 -3
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/entries.d.ts +0 -2
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +1 -2
- package/dist/internal/key.d.ts +10 -0
- package/dist/internal/key.d.ts.map +1 -0
- package/dist/internal/key.js +11 -0
- package/dist/internal/key.js.map +1 -0
- package/dist/internal/node.d.ts +11 -4
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +15 -3
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts +0 -2
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js +1 -2
- package/dist/internal/resume.d.ts +5 -6
- package/dist/internal/resume.d.ts.map +1 -1
- package/dist/internal/resume.js +1 -2
- package/dist/internal/resume.js.map +1 -1
- package/dist/internal/run.d.ts +0 -2
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +2 -3
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +0 -2
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +1 -2
- package/dist/internal/syntax.d.ts +30 -20
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +73 -66
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/text.d.ts +5 -2
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +14 -5
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/ties.d.ts +0 -2
- package/dist/internal/ties.d.ts.map +1 -1
- package/dist/internal/ties.js +1 -2
- package/dist/internal/tokens.d.ts +10 -9
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +4 -8
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +6 -6
- package/src/Ascii.ts +6 -5
- package/src/Bits.ts +1 -1
- package/src/Expression.ts +7 -6
- package/src/Input.ts +50 -11
- package/src/ParseError.ts +9 -7
- package/src/Parser.ts +26 -23
- package/src/ParserStream.ts +20 -8
- package/src/Syntax.ts +12 -9
- package/src/internal/compiler/closure.ts +202 -90
- package/src/internal/compiler/entries.ts +14 -11
- package/src/internal/compiler/first.ts +9 -8
- package/src/internal/compiler/jit.ts +95 -74
- package/src/internal/compiler/sets.ts +7 -0
- package/src/internal/key.ts +11 -0
- package/src/internal/node.ts +19 -2
- package/src/internal/resume.ts +5 -4
- package/src/internal/run.ts +1 -1
- package/src/internal/syntax.ts +78 -87
- package/src/internal/text.ts +24 -4
- package/src/internal/tokens.ts +20 -13
|
@@ -16,26 +16,29 @@
|
|
|
16
16
|
* the interpreter for what they can't compile) on the shared state, with
|
|
17
17
|
* `state.pos` kept in sync.
|
|
18
18
|
*/
|
|
19
|
+
import * as Arr from "effect/Array"
|
|
19
20
|
import * as Predicate from "effect/Predicate"
|
|
20
21
|
import * as Option from "effect/Option"
|
|
22
|
+
import * as Result from "effect/Result"
|
|
21
23
|
import type { Encoding } from "../../Encoding.js"
|
|
22
24
|
import type { State } from "../../Input.js"
|
|
23
25
|
import { Fail } from "../../Input.js"
|
|
24
26
|
import type { Node, NodeOf, ParserLike } from "../node.js"
|
|
25
27
|
import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
|
|
26
|
-
import { children, nodeOf } from "../node.js"
|
|
28
|
+
import { children, nodeOf, scansFloat } from "../node.js"
|
|
27
29
|
import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js"
|
|
28
30
|
import { compileAny } from "../tokens.js"
|
|
31
|
+
import { Key } from "../key.js"
|
|
29
32
|
import * as Resume from "../resume.js"
|
|
30
33
|
import { charOf, fastLimit, latin1, string, utf16be, utf16le, utf8, validate } from "../text.js"
|
|
31
|
-
import type { ClosureCompiler, Fallback, Step } from "./closure.js"
|
|
34
|
+
import type { ClosureCompiler, CompiledRoot, Fallback, Step } from "./closure.js"
|
|
32
35
|
import { backend as closureBackend, createClosureCompiler } from "./closure.js"
|
|
33
36
|
import { buildsEntries, pairOf } from "./entries.js"
|
|
34
37
|
import type { Backend } from "./registry.js"
|
|
35
38
|
import type { First } from "./first.js"
|
|
36
39
|
import { planAlt, Strict } from "./first.js"
|
|
37
40
|
import type { CharSet } from "./sets.js"
|
|
38
|
-
import { kindOfEncoding, reachesAbove, TableSize, textSet, tokenSet } from "./sets.js"
|
|
41
|
+
import { acceptTable, kindOfEncoding, reachesAbove, TableSize, textSet, tokenSet } from "./sets.js"
|
|
39
42
|
|
|
40
43
|
/** Helpers available to generated code as `R`. */
|
|
41
44
|
const runtime = {
|
|
@@ -43,7 +46,7 @@ const runtime = {
|
|
|
43
46
|
save: Resume.save,
|
|
44
47
|
Done: Resume.Done,
|
|
45
48
|
/** Records the result of a resumed repetition (see `Resume.done`). */
|
|
46
|
-
done: (state: State<any>, key:
|
|
49
|
+
done: (state: State<any>, key: Key, entry: number, value: unknown, pos: number): void =>
|
|
47
50
|
Resume.save(state, key, entry, new Resume.Done(value, pos)),
|
|
48
51
|
some: Option.some,
|
|
49
52
|
none: Option.none,
|
|
@@ -77,11 +80,11 @@ let supported = false
|
|
|
77
80
|
const jitSupported = (): boolean => {
|
|
78
81
|
if (checked !== globalThis.Function) {
|
|
79
82
|
checked = globalThis.Function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const fn = checked
|
|
84
|
+
supported = Result.getOrElse(
|
|
85
|
+
Result.try(() => fn("return true")() === true),
|
|
86
|
+
() => false
|
|
87
|
+
)
|
|
85
88
|
}
|
|
86
89
|
return supported
|
|
87
90
|
}
|
|
@@ -141,7 +144,7 @@ const runsOf = (table: Uint8Array): Array<[number, number]> => {
|
|
|
141
144
|
|
|
142
145
|
/** A JavaScript expression testing `v` against ranges, or `undefined` if too long. */
|
|
143
146
|
const rangeTest = (runs: ReadonlyArray<readonly [number, number]>, v: string): string | undefined => {
|
|
144
|
-
if (runs
|
|
147
|
+
if (Arr.isReadonlyArrayEmpty(runs)) return "false"
|
|
145
148
|
if (runs.length > 4) return undefined
|
|
146
149
|
return runs.map(([a, b]) => (a === b ? `${v}===${a}` : `(${v}>=${a}&&${v}<=${b})`)).join("||")
|
|
147
150
|
}
|
|
@@ -172,8 +175,8 @@ const runCost = ([a, b]: readonly [number, number], size: number): number =>
|
|
|
172
175
|
const tableTest = (table: Uint8Array, v: string, size: number): string | undefined => {
|
|
173
176
|
const accept = runsWhere(table, size, 1)
|
|
174
177
|
const reject = runsWhere(table, size, 0)
|
|
175
|
-
if (accept
|
|
176
|
-
if (reject
|
|
178
|
+
if (Arr.isReadonlyArrayEmpty(accept)) return "false"
|
|
179
|
+
if (Arr.isReadonlyArrayEmpty(reject)) return "true"
|
|
177
180
|
const cost = (runs: ReadonlyArray<readonly [number, number]>) => runs.reduce((n, r) => n + runCost(r, size), 0)
|
|
178
181
|
const acceptCost = cost(accept)
|
|
179
182
|
const rejectCost = cost(reject)
|
|
@@ -309,7 +312,7 @@ class Emitter {
|
|
|
309
312
|
const done = this.label()
|
|
310
313
|
const body = this.inline(p, used, out, `break ${done};`, fn, 0)
|
|
311
314
|
this.functions.push(
|
|
312
|
-
`function ${name}(p){const I=$I,E=$E;${fn.vars
|
|
315
|
+
`function ${name}(p){const I=$I,E=$E;${Arr.isArrayNonEmpty(fn.vars) ? `let ${fn.vars.join(",")};` : ""}` +
|
|
313
316
|
`${done}:{${body}P=p;return ${out ?? "void 0"}}return F}`
|
|
314
317
|
)
|
|
315
318
|
return name
|
|
@@ -321,7 +324,7 @@ class Emitter {
|
|
|
321
324
|
*/
|
|
322
325
|
gen(p: ParserLike, used: boolean, out: string | null, fail: string, fn: Fn, depth: number): string {
|
|
323
326
|
const node = nodeOf(p)
|
|
324
|
-
if (node
|
|
327
|
+
if (Predicate.isTagged(node, "Suspend")) return this.call(this.rule(node.resolve(), used), out, fail)
|
|
325
328
|
if (!isLeaf(node) && (((this.refs.get(p) ?? 0) > 1 && !this.small(p)) || depth > 32)) {
|
|
326
329
|
return this.call(this.rule(p, used), out, fail)
|
|
327
330
|
}
|
|
@@ -333,7 +336,13 @@ class Emitter {
|
|
|
333
336
|
let budget = 6
|
|
334
337
|
const visit = (q: ParserLike): boolean => {
|
|
335
338
|
const node = nodeOf(q)
|
|
336
|
-
if (
|
|
339
|
+
if (
|
|
340
|
+
--budget < 0 ||
|
|
341
|
+
Predicate.isTagged(node, "Suspend") ||
|
|
342
|
+
Predicate.isTagged(node, "Escaped") ||
|
|
343
|
+
Predicate.isTagged(node, "Dispatch")
|
|
344
|
+
)
|
|
345
|
+
return false
|
|
337
346
|
return children(node).every(visit)
|
|
338
347
|
}
|
|
339
348
|
return visit(p)
|
|
@@ -477,8 +486,7 @@ class Emitter {
|
|
|
477
486
|
case "Literal":
|
|
478
487
|
return this.leaf(p, node, used, out, fail, fn, node.literal.length * 4)
|
|
479
488
|
case "Escaped": {
|
|
480
|
-
|
|
481
|
-
if (!this.native(node.encoding) || this.resumable) return this.delegate(p, used, out, fail, fn)
|
|
489
|
+
if (!this.native(node.encoding)) return this.delegate(p, used, out, fail, fn)
|
|
482
490
|
return this.escaped(p, node, out, fail, fn, d)
|
|
483
491
|
}
|
|
484
492
|
case "Map": {
|
|
@@ -504,7 +512,7 @@ class Emitter {
|
|
|
504
512
|
case "FilterMap": {
|
|
505
513
|
const t = this.v(fn)
|
|
506
514
|
const r = this.v(fn)
|
|
507
|
-
const [tag, field] = node
|
|
515
|
+
const [tag, field] = Predicate.isTagged(node, "TryMap") ? ["Success", "success"] : ["Some", "value"]
|
|
508
516
|
return (
|
|
509
517
|
this.gen(node.self, true, t, fail, fn, d) +
|
|
510
518
|
`${r}=${this.const(node.f)}(${t});if(${r}._tag!=="${tag}"){${BT}${fail}}${set(`${r}.${field}`)}`
|
|
@@ -542,7 +550,7 @@ class Emitter {
|
|
|
542
550
|
case "WithSpan": {
|
|
543
551
|
const st = this.v(fn)
|
|
544
552
|
const t = this.v(fn)
|
|
545
|
-
const extra = node
|
|
553
|
+
const extra = Predicate.isTagged(node, "WithSpan") ? `{start:${st},end:p}` : `$S.slice(${st},p)`
|
|
546
554
|
return `${st}=p;` + this.gen(node.self, true, t, fail, fn, d) + set(`[${t},${extra}]`)
|
|
547
555
|
}
|
|
548
556
|
case "Span": {
|
|
@@ -576,8 +584,8 @@ class Emitter {
|
|
|
576
584
|
const ok = this.label()
|
|
577
585
|
const bad = this.label()
|
|
578
586
|
const t = out === null ? null : this.v(fn)
|
|
579
|
-
const some = node
|
|
580
|
-
const orElse = node
|
|
587
|
+
const some = Predicate.isTagged(node, "Opt") ? `R.some(${t})` : `${t}`
|
|
588
|
+
const orElse = Predicate.isTagged(node, "Opt") ? "R.none()" : `${this.const(node.value)}()`
|
|
581
589
|
return (
|
|
582
590
|
`${st}=p;${ok}:{${bad}:{${this.gen(node.self, used, t, `break ${bad};`, fn, d)}${set(some)}break ${ok};}` +
|
|
583
591
|
`if($S.mode!=="Backtrack"){${fail}}p=${st};${set(orElse)}}`
|
|
@@ -641,12 +649,7 @@ class Emitter {
|
|
|
641
649
|
*/
|
|
642
650
|
private float(node: NodeOf<"Map">, out: string, fail: string, fn: Fn, d: number): string | undefined {
|
|
643
651
|
if (!floatTexts.has(node.self) || this.kind === 2) return undefined
|
|
644
|
-
|
|
645
|
-
while (taken._tag === "Expected") taken = nodeOf(taken.self)
|
|
646
|
-
if (taken._tag !== "Taken") return undefined
|
|
647
|
-
const enc = taken.encoding
|
|
648
|
-
// Decimal notation is ASCII, which these encodings read unit by unit
|
|
649
|
-
if (enc !== undefined && !(this.native(enc) && enc.unit === 1 && enc.asciiCompatible)) return undefined
|
|
652
|
+
if (!scansFloat(node.self, (enc) => this.native(enc))) return undefined
|
|
650
653
|
const t = this.v(fn)
|
|
651
654
|
const scan = this.const(floatScan)
|
|
652
655
|
// Near the end of a partial input, the parser may have to report `Incomplete`
|
|
@@ -681,7 +684,7 @@ class Emitter {
|
|
|
681
684
|
|
|
682
685
|
alt(node: NodeOf<"Alt">, used: boolean, out: string | null, fail: string, fn: Fn, d: number): string {
|
|
683
686
|
const parsers = node.parsers
|
|
684
|
-
if (parsers
|
|
687
|
+
if (Arr.isReadonlyArrayEmpty(parsers)) return `${BT}${fail}`
|
|
685
688
|
if (parsers.length === 1) return this.gen(parsers[0], used, out, fail, fn, d)
|
|
686
689
|
const firsts = this.kind === 2 ? undefined : parsers.map((q) => this.closure.firsts.of(q))
|
|
687
690
|
const prune = firsts !== undefined && planAlt(firsts) !== undefined
|
|
@@ -755,9 +758,9 @@ class Emitter {
|
|
|
755
758
|
return code + `default:{$S.pos=p;${r}=${q}.step($S);if(${r}===F){${fail}}p=$S.pos;}}`
|
|
756
759
|
}
|
|
757
760
|
let sel = nodeOf(node.selector)
|
|
758
|
-
const peek = sel
|
|
761
|
+
const peek = Predicate.isTagged(sel, "Peek")
|
|
759
762
|
if (peek) sel = nodeOf((sel as NodeOf<"Peek">).self)
|
|
760
|
-
if (sel
|
|
763
|
+
if (!Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) return general(true)
|
|
761
764
|
const enc = sel.encoding
|
|
762
765
|
const r = enc === undefined ? undefined : this.reader(enc)
|
|
763
766
|
const limit = r === undefined ? (this.kind === 0 ? 0xd800 : TableSize) : Math.min(r.fast, 0xd800)
|
|
@@ -802,7 +805,7 @@ class Emitter {
|
|
|
802
805
|
const bad = this.label()
|
|
803
806
|
const n = this.v(fn)
|
|
804
807
|
const st = this.v(fn)
|
|
805
|
-
const isFold = node
|
|
808
|
+
const isFold = Predicate.isTagged(node, "Fold")
|
|
806
809
|
const cond = isFold ? (node as NodeOf<"Fold">).while : undefined
|
|
807
810
|
const keep = out !== null && used && collect === undefined
|
|
808
811
|
// `while` needs the accumulator even when the output is unused
|
|
@@ -848,7 +851,7 @@ class Emitter {
|
|
|
848
851
|
* of a `Done` one; a resumed repetition records its result when it ends.
|
|
849
852
|
*/
|
|
850
853
|
private resumePoint(fn: Fn) {
|
|
851
|
-
const key = this.const(
|
|
854
|
+
const key = this.const(new Key())
|
|
852
855
|
const entry = this.v(fn)
|
|
853
856
|
const resumed = this.v(fn)
|
|
854
857
|
const done = this.label()
|
|
@@ -970,7 +973,7 @@ class Emitter {
|
|
|
970
973
|
`else ${deferred}=R.addEntry(${out},${deferred},${key},${value});`,
|
|
971
974
|
finish: `if(${deferred}!==void 0)R.flushEntries(${out},${deferred});`
|
|
972
975
|
}
|
|
973
|
-
return node
|
|
976
|
+
return Predicate.isTagged(node, "Repeat")
|
|
974
977
|
? this.repeat(node, true, out, fail, fn, d, collect)
|
|
975
978
|
: this.separated(node, true, out, fail, fn, d, collect)
|
|
976
979
|
}
|
|
@@ -1190,21 +1193,32 @@ class Emitter {
|
|
|
1190
1193
|
return `${head}if(${c}<${small}){if(!(${member}))break;p++}else ${rest}${count}}${finish}`
|
|
1191
1194
|
}
|
|
1192
1195
|
|
|
1196
|
+
/**
|
|
1197
|
+
* `escaped`. In a resumable build it keeps the closure compiler's
|
|
1198
|
+
* checkpoints: where the next segment starts and, unless raw, the text
|
|
1199
|
+
* so far, saved when a segment or an escape runs out of input.
|
|
1200
|
+
*/
|
|
1193
1201
|
escaped(p: ParserLike, node: NodeOf<"Escaped">, out: string | null, fail: string, fn: Fn, d: number): string {
|
|
1194
1202
|
const enc = node.encoding
|
|
1195
1203
|
const ctrl = node.control
|
|
1196
|
-
const
|
|
1197
|
-
if (
|
|
1204
|
+
const rd = enc === undefined ? undefined : this.reader(enc)
|
|
1205
|
+
if (rd !== undefined && !(ctrl < rd.fast)) return this.delegate(p, !node.raw, out, fail, fn)
|
|
1198
1206
|
const raw = node.raw
|
|
1207
|
+
const r = this.resumable ? this.resumePoint(fn) : undefined
|
|
1199
1208
|
const acc = raw ? null : (out ?? this.v(fn))
|
|
1200
1209
|
const x = raw ? null : this.v(fn)
|
|
1201
1210
|
const b = this.v(fn)
|
|
1202
|
-
const st = this.v(fn)
|
|
1211
|
+
const st = r?.entry ?? this.v(fn)
|
|
1203
1212
|
const okv = this.v(fn)
|
|
1204
1213
|
const loop = this.label()
|
|
1205
1214
|
const bad = this.label()
|
|
1215
|
+
/** Saves the checkpoint of a segment starting at `pos`, when the parse ran out of input. */
|
|
1216
|
+
const save = (pos: string) =>
|
|
1217
|
+
r === undefined
|
|
1218
|
+
? ""
|
|
1219
|
+
: `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},${raw ? pos : `{out:${acc},pos:${pos}}`});`
|
|
1206
1220
|
const normal = this.gen(node.normal, !raw, x, `break ${bad};`, fn, d)
|
|
1207
|
-
const escape = this.gen(node.escape, !raw, x, fail
|
|
1221
|
+
const escape = this.gen(node.escape, !raw, x, `${save(b)}${fail}`, fn, d)
|
|
1208
1222
|
const onNormal = raw
|
|
1209
1223
|
? `if(p===${b}){$S.mode="Cut";${fail}}continue ${loop};`
|
|
1210
1224
|
: `if(p!==${b}){${acc}+=${x};continue ${loop}}`
|
|
@@ -1214,37 +1228,40 @@ class Emitter {
|
|
|
1214
1228
|
: this.kind === 0
|
|
1215
1229
|
? `I.slice(${st},p)`
|
|
1216
1230
|
: `I.subarray(${st},p)`
|
|
1217
|
-
const ctrlTest =
|
|
1218
|
-
|
|
1219
|
-
|
|
1231
|
+
const ctrlTest =
|
|
1232
|
+
rd === undefined ? `p<E&&${this.unit("p")}===${ctrl}` : `${rd.guard("p")}&&${rd.unit("p")}===${ctrl}`
|
|
1233
|
+
// In a resumable build, running out of a partial input (not stopping
|
|
1234
|
+
// before a non-control character) leaves the text incomplete
|
|
1235
|
+
const stop = r === undefined ? loop : this.label()
|
|
1236
|
+
const loopCode =
|
|
1237
|
+
`${loop}:while(p<E){${b}=p;${okv}=false;` +
|
|
1220
1238
|
`${bad}:{${normal}${okv}=true;}` +
|
|
1221
|
-
`if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${fail}}p=${b}}` +
|
|
1222
|
-
`if(${ctrlTest}){p+=${
|
|
1223
|
-
|
|
1224
|
-
|
|
1239
|
+
`if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${save(b)}${fail}}p=${b}}` +
|
|
1240
|
+
`if(${ctrlTest}){p+=${rd?.width ?? 1};${escape}${raw ? "" : `${acc}+=${x};`}}else break ${stop};}`
|
|
1241
|
+
const body =
|
|
1242
|
+
r === undefined ? loopCode : `${stop}:{${loopCode}if($S.partial){$S.mode="Incomplete";${save("p")}${fail}}}`
|
|
1243
|
+
if (r === undefined) {
|
|
1244
|
+
return `${raw ? `${st}=p;` : `${acc}="";`}${body}${raw && out !== null ? `${out}=${slice};` : ""}`
|
|
1245
|
+
}
|
|
1246
|
+
// `acc` is set unless raw
|
|
1247
|
+
if (acc !== null) return r.wrap(acc, `${acc}=cp.out;p=cp.pos;`, `${acc}="";`, body)
|
|
1248
|
+
const value = out ?? this.v(fn)
|
|
1249
|
+
return r.wrap(value, "p=cp;", "", `${body}${value}=${out === null ? "void 0" : slice};`)
|
|
1225
1250
|
}
|
|
1226
1251
|
}
|
|
1227
1252
|
|
|
1228
1253
|
/** The UTF-16 code units of a string. */
|
|
1229
1254
|
const unitsOf = (s: string): Array<number> => Array.from({ length: s.length }, (_, i) => s.charCodeAt(i))
|
|
1230
1255
|
|
|
1231
|
-
/**
|
|
1232
|
-
|
|
1233
|
-
const t = new Uint8Array(TableSize)
|
|
1234
|
-
for (let c = 0; c < TableSize; c++) t[c] = (set.table[c] === 1) !== negate ? 1 : 0
|
|
1235
|
-
return t
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
/** Generates the source of a compiled parser, without building it. */
|
|
1239
|
-
const generate = (
|
|
1240
|
-
root: ParserLike,
|
|
1241
|
-
kind: 0 | 1 | 2,
|
|
1242
|
-
resumable = false
|
|
1243
|
-
): {
|
|
1256
|
+
/** The source of a compiled parser, with the values it reads as `C`. */
|
|
1257
|
+
interface Generated {
|
|
1244
1258
|
readonly source: string
|
|
1245
1259
|
readonly constants: ReadonlyArray<unknown>
|
|
1246
1260
|
readonly fallbacks: ReadonlyArray<Fallback>
|
|
1247
|
-
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/** Generates the source of a compiled parser, without building it. */
|
|
1264
|
+
const generate = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): Generated => {
|
|
1248
1265
|
const e = new Emitter(root, kind, resumable)
|
|
1249
1266
|
const main = e.rule(root, true)
|
|
1250
1267
|
const source =
|
|
@@ -1254,16 +1271,13 @@ const generate = (
|
|
|
1254
1271
|
return { source, constants: e.constants, fallbacks: e.fallbacks }
|
|
1255
1272
|
}
|
|
1256
1273
|
|
|
1257
|
-
/**
|
|
1258
|
-
|
|
1259
|
-
root: ParserLike,
|
|
1260
|
-
kind: 0 | 1 | 2,
|
|
1261
|
-
resumable = false
|
|
1262
|
-
): {
|
|
1263
|
-
readonly step: Step
|
|
1264
|
-
readonly fallbacks: ReadonlyArray<Fallback>
|
|
1274
|
+
/** A root parser compiled with `new Function`, with its generated source. */
|
|
1275
|
+
interface JitCompiled extends CompiledRoot {
|
|
1265
1276
|
readonly source: string
|
|
1266
|
-
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/** Compiles a root parser for one input kind with `new Function`. */
|
|
1280
|
+
const compileJit = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): JitCompiled => {
|
|
1267
1281
|
const { constants, fallbacks, source } = generate(root, kind, resumable)
|
|
1268
1282
|
const run = globalThis.Function("C", "F", "R", source)(constants, Fail, runtime) as Step
|
|
1269
1283
|
// Generated code reads past `end` expecting nothing to match there, which
|
|
@@ -1283,12 +1297,19 @@ const compileJit = (
|
|
|
1283
1297
|
export const backend: Backend = {
|
|
1284
1298
|
build: (p, kind, resumable) => {
|
|
1285
1299
|
if (!jitSupported()) return closureBackend.build(p, kind, resumable)
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1300
|
+
return Result.match(
|
|
1301
|
+
Result.try(() => compileJit(p, kind, resumable)),
|
|
1302
|
+
{
|
|
1303
|
+
onSuccess: ({ fallbacks, source, step }) => ({
|
|
1304
|
+
backend: "jit" as const,
|
|
1305
|
+
step,
|
|
1306
|
+
fallbacks,
|
|
1307
|
+
error: undefined,
|
|
1308
|
+
source
|
|
1309
|
+
}),
|
|
1310
|
+
// Fall back to the closure compiler, remembering why
|
|
1311
|
+
onFailure: (error) => ({ ...closureBackend.build(p, kind, resumable), jitError: error })
|
|
1312
|
+
}
|
|
1313
|
+
)
|
|
1293
1314
|
}
|
|
1294
1315
|
}
|
|
@@ -21,6 +21,13 @@ export interface CharSet {
|
|
|
21
21
|
readonly ranges: ReadonlyArray<readonly [number, number]> | undefined
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** 1 where a small code is accepted (taking `negate` into account). */
|
|
25
|
+
export const acceptTable = (set: CharSet, negate: boolean): Uint8Array => {
|
|
26
|
+
const t = new Uint8Array(TableSize)
|
|
27
|
+
for (let c = 0; c < TableSize; c++) t[c] = (set.table[c] === 1) !== negate ? 1 : 0
|
|
28
|
+
return t
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
const isRange = (u: unknown): u is { readonly from: string | number; readonly to: string | number } =>
|
|
25
32
|
Predicate.isObjectOrArray(u) && (u as any)._tag === "TokenRange"
|
|
26
33
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity keys: a parser that keeps state across parses (resume
|
|
3
|
+
* checkpoints, reusable syntax nodes) creates one `Key` when it is built and
|
|
4
|
+
* looks its state up by that key's reference.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A per-parser identity, compared by reference. */
|
|
8
|
+
export class Key {
|
|
9
|
+
// Nominal: an empty class would accept any object
|
|
10
|
+
declare private readonly brand: void
|
|
11
|
+
}
|
package/src/internal/node.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* `Suspend` is just a reference.
|
|
8
8
|
*/
|
|
9
9
|
import * as Predicate from "effect/Predicate"
|
|
10
|
+
import type * as Option from "effect/Option"
|
|
11
|
+
import type * as Result from "effect/Result"
|
|
10
12
|
import type { Encoding } from "../Encoding.js"
|
|
11
13
|
import type { ErrMode, Input, State } from "../Input.js"
|
|
12
14
|
|
|
@@ -67,8 +69,8 @@ export type Node =
|
|
|
67
69
|
| { readonly _tag: "Map"; readonly self: P; readonly f: (a: any) => unknown }
|
|
68
70
|
| { readonly _tag: "As"; readonly self: P; readonly value: unknown }
|
|
69
71
|
| { readonly _tag: "Tap"; readonly self: P; readonly f: (a: any) => void }
|
|
70
|
-
| { readonly _tag: "TryMap"; readonly self: P; readonly f: (a: any) => unknown }
|
|
71
|
-
| { readonly _tag: "FilterMap"; readonly self: P; readonly f: (a: any) => unknown }
|
|
72
|
+
| { readonly _tag: "TryMap"; readonly self: P; readonly f: (a: any) => Result.Result<unknown, unknown> }
|
|
73
|
+
| { readonly _tag: "FilterMap"; readonly self: P; readonly f: (a: any) => Option.Option<unknown> }
|
|
72
74
|
| { readonly _tag: "Filter"; readonly self: P; readonly predicate: (a: any) => boolean }
|
|
73
75
|
| { readonly _tag: "FlatMap"; readonly self: P; readonly f: (a: any) => P }
|
|
74
76
|
// Consumed input
|
|
@@ -160,6 +162,21 @@ export const bounds = (
|
|
|
160
162
|
return { min: range.min ?? 0, max: range.max ?? Infinity }
|
|
161
163
|
}
|
|
162
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Whether the compilers' one-pass float scan applies to `p`, the text
|
|
167
|
+
* parser of `Ascii.float` / `Text.float`: a `taken` (under any `expected`
|
|
168
|
+
* labels) in an encoding that `native` accepts, reading one ASCII-compatible
|
|
169
|
+
* unit at a time, or in no encoding at all.
|
|
170
|
+
*/
|
|
171
|
+
export const scansFloat = (p: ParserLike, native: (enc: Encoding<any>) => boolean): boolean => {
|
|
172
|
+
let taken = nodeOf(p)
|
|
173
|
+
while (Predicate.isTagged(taken, "Expected")) taken = nodeOf(taken.self)
|
|
174
|
+
if (!Predicate.isTagged(taken, "Taken")) return false
|
|
175
|
+
const enc = taken.encoding
|
|
176
|
+
// Decimal notation is ASCII, which these encodings read unit by unit
|
|
177
|
+
return enc === undefined || (native(enc) && enc.unit === 1 && enc.asciiCompatible)
|
|
178
|
+
}
|
|
179
|
+
|
|
163
180
|
/** The child parsers of a node, in evaluation order. */
|
|
164
181
|
export const children = (node: Node): ReadonlyArray<P> => {
|
|
165
182
|
switch (node._tag) {
|
package/src/internal/resume.ts
CHANGED
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
* it all again. `Done` checkpoints stay in place when taken.
|
|
20
20
|
*/
|
|
21
21
|
import type { State } from "../Input.js"
|
|
22
|
+
import type { Key } from "./key.js"
|
|
22
23
|
|
|
23
24
|
/** The checkpoints of a resumable parse. */
|
|
24
|
-
export type Checkpoints = Map<
|
|
25
|
+
export type Checkpoints = Map<Key, Map<number, unknown>>
|
|
25
26
|
|
|
26
27
|
/** Takes (and removes) the checkpoint of `key` entered at `offset`, if any. */
|
|
27
|
-
export const take = (state: State<any>, key:
|
|
28
|
+
export const take = (state: State<any>, key: Key, offset: number): unknown => {
|
|
28
29
|
const byKey = state.resume?.get(key)
|
|
29
30
|
if (byKey === undefined) return undefined
|
|
30
31
|
const checkpoint = byKey.get(offset)
|
|
@@ -44,13 +45,13 @@ export class Done {
|
|
|
44
45
|
* Returns `value`, the result of a repetition entered at `entry`; when the
|
|
45
46
|
* repetition was resumed from a checkpoint, records it as {@link Done}.
|
|
46
47
|
*/
|
|
47
|
-
export const done = <A>(state: State<any>, key:
|
|
48
|
+
export const done = <A>(state: State<any>, key: Key, entry: number, resumed: boolean, value: A): A => {
|
|
48
49
|
if (resumed && state.resume !== undefined) save(state, key, entry, new Done(value, state.pos))
|
|
49
50
|
return value
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/** Records the checkpoint of `key` entered at `offset`. */
|
|
53
|
-
export const save = (state: State<any>, key:
|
|
54
|
+
export const save = (state: State<any>, key: Key, offset: number, checkpoint: unknown): void => {
|
|
54
55
|
const resume = state.resume
|
|
55
56
|
if (resume === undefined) return
|
|
56
57
|
let byKey = resume.get(key)
|