@thomasfosterau/effect-parse 0.4.1 → 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 +37 -13
- 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.map +1 -1
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +366 -26
- 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 +1 -1
- package/dist/ParserStream.d.ts +3 -2
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +13 -9
- 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.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 +3 -0
- package/src/Parser.ts +678 -127
- package/src/ParserStream.ts +37 -18
- 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 +8 -2
- 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
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// fallow-ignore-file duplicate-export -- every compiler module exports its `backend` under the same name,
|
|
2
|
-
// which the Compiler module imports by path; no barrel re-exports both.
|
|
3
1
|
/**
|
|
4
2
|
* The JIT: emits JavaScript source for a parser's description and builds
|
|
5
3
|
* it with `new Function`.
|
|
@@ -25,29 +23,48 @@ import type { State } from "../../Input.js"
|
|
|
25
23
|
import { Fail } from "../../Input.js"
|
|
26
24
|
import type { Node, NodeOf, ParserLike } from "../node.js"
|
|
27
25
|
import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js"
|
|
28
|
-
import {
|
|
26
|
+
import { Defer, NodeId, nodeOf, scansFloat } from "../node.js"
|
|
29
27
|
import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js"
|
|
30
28
|
import { compileAny } from "../tokens.js"
|
|
31
29
|
import { Key } from "../key.js"
|
|
32
30
|
import * as Resume from "../resume.js"
|
|
33
|
-
import
|
|
31
|
+
import * as Stack from "../stack.js"
|
|
32
|
+
import {
|
|
33
|
+
ShortText,
|
|
34
|
+
charOf,
|
|
35
|
+
fastLimit,
|
|
36
|
+
latin1,
|
|
37
|
+
string,
|
|
38
|
+
textIn,
|
|
39
|
+
utf16be,
|
|
40
|
+
utf16le,
|
|
41
|
+
utf8,
|
|
42
|
+
utf8Text,
|
|
43
|
+
validate
|
|
44
|
+
} from "../text.js"
|
|
34
45
|
import type { ClosureCompiler, CompiledRoot, Fallback, Step } from "./closure.js"
|
|
35
46
|
import { backend as closureBackend, createClosureCompiler } from "./closure.js"
|
|
36
47
|
import { buildsEntries, pairOf } from "./entries.js"
|
|
48
|
+
import { countRefs, isLeaf, isLetter, isSmall, lowerAscii, selectorOf } from "./graph.js"
|
|
37
49
|
import type { Backend } from "./registry.js"
|
|
38
50
|
import type { First } from "./first.js"
|
|
39
51
|
import { planAlt, Strict } from "./first.js"
|
|
40
52
|
import type { CharSet } from "./sets.js"
|
|
41
53
|
import { acceptTable, kindOfEncoding, reachesAbove, TableSize, textSet, tokenSet } from "./sets.js"
|
|
54
|
+
import { nextStop, SearchHead, StopCache, stopChars } from "./search.js"
|
|
42
55
|
|
|
43
56
|
/** Helpers available to generated code as `R`. */
|
|
44
57
|
const runtime = {
|
|
58
|
+
StopCache,
|
|
59
|
+
nextStop,
|
|
45
60
|
take: Resume.take,
|
|
46
61
|
save: Resume.save,
|
|
47
62
|
Done: Resume.Done,
|
|
48
63
|
/** Records the result of a resumed repetition (see `Resume.done`). */
|
|
49
64
|
done: (state: State<any>, key: Key, entry: number, value: unknown, pos: number): void =>
|
|
50
65
|
Resume.save(state, key, entry, new Resume.Done(value, pos)),
|
|
66
|
+
isStackOverflow: Stack.isStackOverflow,
|
|
67
|
+
tooDeep: Stack.tooDeep,
|
|
51
68
|
some: Option.some,
|
|
52
69
|
none: Option.none,
|
|
53
70
|
charOf,
|
|
@@ -57,17 +74,20 @@ const runtime = {
|
|
|
57
74
|
addEntry,
|
|
58
75
|
flushEntries,
|
|
59
76
|
/** `Text.taken`: the text of `[start, end)`, or `Fail` if it is malformed. */
|
|
60
|
-
textOf: (enc: Encoding<any>, fast: number, s: State<any>, start: number, end: number):
|
|
77
|
+
textOf: (enc: Encoding<any>, fast: number, s: State<any>, start: number, end: number): string | typeof Fail => {
|
|
61
78
|
const input = s.input
|
|
62
79
|
let i = start
|
|
80
|
+
// SAFETY: input kind 0 is a string input
|
|
63
81
|
if (s.inputKind === 0) while (i < end && (input as string).charCodeAt(i) < fast) i++
|
|
82
|
+
// SAFETY: `Text` parsers are compiled natively, and so call this, on string (0) and byte (1) inputs only
|
|
64
83
|
else while (i < end && (input as Uint8Array)[i] < fast) i++
|
|
65
84
|
if (i < end && validate(enc, s, i, end) < 0) {
|
|
66
85
|
s.mode = "Backtrack"
|
|
67
86
|
return Fail
|
|
68
87
|
}
|
|
69
|
-
return enc
|
|
70
|
-
}
|
|
88
|
+
return textIn(enc, s, start, end)
|
|
89
|
+
},
|
|
90
|
+
utf8Text
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
let checked: FunctionConstructor | undefined
|
|
@@ -77,7 +97,7 @@ let supported = false
|
|
|
77
97
|
* Whether `new Function` works here (it doesn't under a Content Security
|
|
78
98
|
* Policy). Checked once, and again if `globalThis.Function` is replaced.
|
|
79
99
|
*/
|
|
80
|
-
const jitSupported = (): boolean => {
|
|
100
|
+
export const jitSupported = (): boolean => {
|
|
81
101
|
if (checked !== globalThis.Function) {
|
|
82
102
|
checked = globalThis.Function
|
|
83
103
|
const fn = checked
|
|
@@ -109,26 +129,6 @@ class Fn {
|
|
|
109
129
|
readonly vars: Array<string> = []
|
|
110
130
|
}
|
|
111
131
|
|
|
112
|
-
const isLeaf = (node: Node): boolean => {
|
|
113
|
-
switch (node._tag) {
|
|
114
|
-
case "Succeed":
|
|
115
|
-
case "Fail":
|
|
116
|
-
case "Eof":
|
|
117
|
-
case "Location":
|
|
118
|
-
case "RestLen":
|
|
119
|
-
case "Any":
|
|
120
|
-
case "Literal":
|
|
121
|
-
case "OneOf":
|
|
122
|
-
case "TakeWhile":
|
|
123
|
-
return true
|
|
124
|
-
default:
|
|
125
|
-
return false
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const lowerAscii = (c: number): number => (c >= 65 && c <= 90 ? c + 32 : c)
|
|
130
|
-
const isLetter = (c: number): boolean => (c >= 65 && c <= 90) || (c >= 97 && c <= 122)
|
|
131
|
-
|
|
132
132
|
/** The inclusive ranges of 1s in a table. */
|
|
133
133
|
const runsOf = (table: Uint8Array): Array<[number, number]> => {
|
|
134
134
|
const out: Array<[number, number]> = []
|
|
@@ -211,6 +211,11 @@ interface Collect {
|
|
|
211
211
|
readonly init: string
|
|
212
212
|
readonly element: (fail: string, check: string) => string
|
|
213
213
|
readonly finish: string
|
|
214
|
+
/** The variable holding the result. */
|
|
215
|
+
readonly value: string
|
|
216
|
+
/** In a resumable build: more fields of a checkpoint (`name:variable,`), and the code restoring them from `cp`. */
|
|
217
|
+
readonly saved: string
|
|
218
|
+
readonly restore: string
|
|
214
219
|
}
|
|
215
220
|
|
|
216
221
|
class Emitter {
|
|
@@ -218,7 +223,7 @@ class Emitter {
|
|
|
218
223
|
private readonly constantIndex = new Map<unknown, number>()
|
|
219
224
|
readonly functions: Array<string> = []
|
|
220
225
|
private readonly rules = [new Map<ParserLike, string>(), new Map<ParserLike, string>()]
|
|
221
|
-
private readonly refs
|
|
226
|
+
private readonly refs: WeakMap<ParserLike, number>
|
|
222
227
|
private readonly closure: ClosureCompiler
|
|
223
228
|
private next = 0
|
|
224
229
|
|
|
@@ -228,27 +233,13 @@ class Emitter {
|
|
|
228
233
|
readonly resumable = false
|
|
229
234
|
) {
|
|
230
235
|
this.closure = createClosureCompiler(kind, resumable)
|
|
231
|
-
this.countRefs(root)
|
|
236
|
+
this.refs = countRefs(root)
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
get fallbacks(): ReadonlyArray<Fallback> {
|
|
235
240
|
return this.closure.fallbacks
|
|
236
241
|
}
|
|
237
242
|
|
|
238
|
-
/** Counts the distinct parents of each parser. */
|
|
239
|
-
private countRefs(root: ParserLike): void {
|
|
240
|
-
const seen = new Set<ParserLike>()
|
|
241
|
-
const stack = [root]
|
|
242
|
-
for (let p = stack.pop(); p !== undefined; p = stack.pop()) {
|
|
243
|
-
if (seen.has(p)) continue
|
|
244
|
-
seen.add(p)
|
|
245
|
-
for (const c of new Set(children(nodeOf(p)))) {
|
|
246
|
-
this.refs.set(c, (this.refs.get(c) ?? 0) + 1)
|
|
247
|
-
stack.push(c)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
243
|
const(value: unknown): string {
|
|
253
244
|
let i = this.constantIndex.get(value)
|
|
254
245
|
if (i === undefined) {
|
|
@@ -259,6 +250,13 @@ class Emitter {
|
|
|
259
250
|
return `C[${i}]`
|
|
260
251
|
}
|
|
261
252
|
|
|
253
|
+
/** The text of `[start, p)` in `enc`: UTF-8 decodes through the state's ASCII run (see `utf8Text`). */
|
|
254
|
+
text(enc: Encoding<any>, start: string): string {
|
|
255
|
+
const text = `${this.const(enc.text)}(I,${start},p)`
|
|
256
|
+
// Short strings are built by hand, which no run helps
|
|
257
|
+
return enc === utf8 ? `(p-${start}>${ShortText}?R.utf8Text($S,${start},p):${text})` : text
|
|
258
|
+
}
|
|
259
|
+
|
|
262
260
|
/** A JavaScript literal for simple values, otherwise a constant. */
|
|
263
261
|
lit(value: unknown): string {
|
|
264
262
|
if (value === undefined) return "void 0"
|
|
@@ -325,29 +323,12 @@ class Emitter {
|
|
|
325
323
|
gen(p: ParserLike, used: boolean, out: string | null, fail: string, fn: Fn, depth: number): string {
|
|
326
324
|
const node = nodeOf(p)
|
|
327
325
|
if (Predicate.isTagged(node, "Suspend")) return this.call(this.rule(node.resolve(), used), out, fail)
|
|
328
|
-
if (!isLeaf(node) && (((this.refs.get(p) ?? 0) > 1 && !
|
|
326
|
+
if (!isLeaf(node) && (((this.refs.get(p) ?? 0) > 1 && !isSmall(p)) || depth > 32)) {
|
|
329
327
|
return this.call(this.rule(p, used), out, fail)
|
|
330
328
|
}
|
|
331
329
|
return this.inline(p, used, out, fail, fn, depth)
|
|
332
330
|
}
|
|
333
331
|
|
|
334
|
-
/** Whether a parser is small enough to inline wherever it is used. */
|
|
335
|
-
private small(p: ParserLike): boolean {
|
|
336
|
-
let budget = 6
|
|
337
|
-
const visit = (q: ParserLike): boolean => {
|
|
338
|
-
const node = nodeOf(q)
|
|
339
|
-
if (
|
|
340
|
-
--budget < 0 ||
|
|
341
|
-
Predicate.isTagged(node, "Suspend") ||
|
|
342
|
-
Predicate.isTagged(node, "Escaped") ||
|
|
343
|
-
Predicate.isTagged(node, "Dispatch")
|
|
344
|
-
)
|
|
345
|
-
return false
|
|
346
|
-
return children(node).every(visit)
|
|
347
|
-
}
|
|
348
|
-
return visit(p)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
332
|
/**
|
|
352
333
|
* In a resumable build: token code (which fails by breaking out of `bad`)
|
|
353
334
|
* that read within `lookahead` units of the end of a partial input re-runs
|
|
@@ -464,7 +445,8 @@ class Emitter {
|
|
|
464
445
|
case "Eof":
|
|
465
446
|
return (
|
|
466
447
|
`if(p<E){${BT}${fail}}` +
|
|
467
|
-
|
|
448
|
+
// `eof`'s own failure: `Incomplete`, needing one more unit
|
|
449
|
+
(this.resumable ? `if($S.partial){$S.incomplete(1);${fail}}` : "") +
|
|
468
450
|
set("void 0")
|
|
469
451
|
)
|
|
470
452
|
case "Location":
|
|
@@ -495,8 +477,8 @@ class Emitter {
|
|
|
495
477
|
const code = this.float(node, out, fail, fn, d)
|
|
496
478
|
if (code !== undefined) return code
|
|
497
479
|
}
|
|
498
|
-
// Streaming builds
|
|
499
|
-
if (node.f === fromEntries && out !== null &&
|
|
480
|
+
// Streaming builds too: their checkpoints hold the object built so far
|
|
481
|
+
if (node.f === fromEntries && out !== null && buildsEntries(node.self)) {
|
|
500
482
|
return this.entries(node.self, out, fail, fn, d)
|
|
501
483
|
}
|
|
502
484
|
const t = this.v(fn)
|
|
@@ -518,6 +500,22 @@ class Emitter {
|
|
|
518
500
|
`${r}=${this.const(node.f)}(${t});if(${r}._tag!=="${tag}"){${BT}${fail}}${set(`${r}.${field}`)}`
|
|
519
501
|
)
|
|
520
502
|
}
|
|
503
|
+
case "FastPath": {
|
|
504
|
+
// The fast path runs on the state; when it defers, the grammar runs from where it started
|
|
505
|
+
const st = this.v(fn)
|
|
506
|
+
const t = out ?? this.v(fn)
|
|
507
|
+
const done = this.label()
|
|
508
|
+
return (
|
|
509
|
+
`${st}=p;$S.pos=p;${t}=${this.const(node.fast)}($S);` +
|
|
510
|
+
`${done}:{if(${t}!==${this.const(Defer)}){p=$S.pos;break ${done}}p=${st};` +
|
|
511
|
+
`${this.gen(node.self, used, out, fail, fn, d)}}`
|
|
512
|
+
)
|
|
513
|
+
}
|
|
514
|
+
case "MapOrFail": {
|
|
515
|
+
const t = this.v(fn)
|
|
516
|
+
const r = out ?? this.v(fn)
|
|
517
|
+
return this.gen(node.self, true, t, fail, fn, d) + `${r}=${this.const(node.f)}(${t});if(${r}===F){${BT}${fail}}`
|
|
518
|
+
}
|
|
521
519
|
case "Filter": {
|
|
522
520
|
const t = out ?? this.v(fn)
|
|
523
521
|
return this.gen(node.self, true, t, fail, fn, d) + `if(!${this.const(node.predicate)}(${t})){${BT}${fail}}`
|
|
@@ -553,6 +551,12 @@ class Emitter {
|
|
|
553
551
|
const extra = Predicate.isTagged(node, "WithSpan") ? `{start:${st},end:p}` : `$S.slice(${st},p)`
|
|
554
552
|
return `${st}=p;` + this.gen(node.self, true, t, fail, fn, d) + set(`[${t},${extra}]`)
|
|
555
553
|
}
|
|
554
|
+
case "MapSpan": {
|
|
555
|
+
if (!used) return this.gen(node.self, false, null, fail, fn, d)
|
|
556
|
+
const st = this.v(fn)
|
|
557
|
+
const t = this.v(fn)
|
|
558
|
+
return `${st}=p;` + this.gen(node.self, true, t, fail, fn, d) + set(`${this.const(node.f)}(${t},${st},p)`)
|
|
559
|
+
}
|
|
556
560
|
case "Span": {
|
|
557
561
|
const st = this.v(fn)
|
|
558
562
|
return `${st}=p;` + this.gen(node.self, false, null, fail, fn, d) + set(`{start:${st},end:p}`)
|
|
@@ -567,6 +571,7 @@ class Emitter {
|
|
|
567
571
|
}
|
|
568
572
|
case "Context":
|
|
569
573
|
case "Expected":
|
|
574
|
+
case "Quiet":
|
|
570
575
|
case "MapError":
|
|
571
576
|
case "OrFail":
|
|
572
577
|
case "Trace":
|
|
@@ -615,6 +620,12 @@ class Emitter {
|
|
|
615
620
|
}
|
|
616
621
|
return code + `${out}={${node.keys.map((k, i) => `${JSON.stringify(k)}:${vars[i]}`).join(",")}};`
|
|
617
622
|
}
|
|
623
|
+
case "SeqWith": {
|
|
624
|
+
if (out === null) return node.parsers.map((q) => this.gen(q, false, null, fail, fn, d)).join("")
|
|
625
|
+
const vars = node.parsers.map(() => this.v(fn))
|
|
626
|
+
const code = node.parsers.map((q, i) => this.gen(q, true, vars[i], fail, fn, d)).join("")
|
|
627
|
+
return code + `${out}=${this.const(node.f)}(${vars.join(",")});`
|
|
628
|
+
}
|
|
618
629
|
case "ZipWith": {
|
|
619
630
|
if (out === null)
|
|
620
631
|
return this.gen(node.self, false, null, fail, fn, d) + this.gen(node.that, false, null, fail, fn, d)
|
|
@@ -631,10 +642,20 @@ class Emitter {
|
|
|
631
642
|
case "Dispatch":
|
|
632
643
|
return this.dispatch(p, node, used, out, fail, fn, d)
|
|
633
644
|
case "Repeat":
|
|
634
|
-
case "Fold":
|
|
635
|
-
|
|
645
|
+
case "Fold": {
|
|
646
|
+
// A run of a character set whose characters are not kept is a `takeWhile` (#79)
|
|
647
|
+
const run =
|
|
648
|
+
out === null && Predicate.isTagged(node, "Repeat") ? this.repeatRun(p, node, fail, fn, d) : undefined
|
|
649
|
+
return run ?? this.repeat(node, used, out, fail, fn, d)
|
|
650
|
+
}
|
|
636
651
|
case "Separated":
|
|
637
652
|
return this.separated(node, used, out, fail, fn, d)
|
|
653
|
+
case "Fixed": {
|
|
654
|
+
// On bytes, read inline; at the end of the input, the step reports it
|
|
655
|
+
if (this.kind !== 1) return this.delegate(p, used, out, fail, fn)
|
|
656
|
+
const read = used ? `${out ?? this.v(fn)}=${this.const(node.read)}(I,p,$S);` : ""
|
|
657
|
+
return `if(E-p>=${node.size}){${read}p+=${node.size}}else{${this.delegate(p, used, out, fail, fn)}}`
|
|
658
|
+
}
|
|
638
659
|
default:
|
|
639
660
|
return this.delegate(p, used, out, fail, fn)
|
|
640
661
|
}
|
|
@@ -734,7 +755,7 @@ class Emitter {
|
|
|
734
755
|
// The cases are known: number them, and run the chosen one inline
|
|
735
756
|
const cases = new Map<ParserLike, number>()
|
|
736
757
|
for (const key of Reflect.ownKeys(node.cases)) {
|
|
737
|
-
const q = node.cases[key
|
|
758
|
+
const q = node.cases[key]
|
|
738
759
|
if (!cases.has(q)) cases.set(q, cases.size)
|
|
739
760
|
}
|
|
740
761
|
if (node.otherwise !== undefined && !cases.has(node.otherwise)) cases.set(node.otherwise, cases.size)
|
|
@@ -757,9 +778,7 @@ class Emitter {
|
|
|
757
778
|
const r = out ?? this.v(fn)
|
|
758
779
|
return code + `default:{$S.pos=p;${r}=${q}.step($S);if(${r}===F){${fail}}p=$S.pos;}}`
|
|
759
780
|
}
|
|
760
|
-
|
|
761
|
-
const peek = Predicate.isTagged(sel, "Peek")
|
|
762
|
-
if (peek) sel = nodeOf((sel as NodeOf<"Peek">).self)
|
|
781
|
+
const { node: sel, peek } = selectorOf(node.selector)
|
|
763
782
|
if (!Predicate.isTagged(sel, "Any") || !this.native(sel.encoding)) return general(true)
|
|
764
783
|
const enc = sel.encoding
|
|
765
784
|
const r = enc === undefined ? undefined : this.reader(enc)
|
|
@@ -791,6 +810,55 @@ class Emitter {
|
|
|
791
810
|
return code
|
|
792
811
|
}
|
|
793
812
|
|
|
813
|
+
/**
|
|
814
|
+
* A repetition whose elements are not kept, rewritten to scan a run of a
|
|
815
|
+
* character set in one loop instead of one element at a time (#79):
|
|
816
|
+
*
|
|
817
|
+
* - `repeat(oneOf(S), range)` matches what `takeWhile(S, range)` matches,
|
|
818
|
+
* stopping, failing and reporting `Incomplete` where it does, so it
|
|
819
|
+
* compiles as that `takeWhile`, with the repetition's own step as the
|
|
820
|
+
* fallback near the end of a partial input.
|
|
821
|
+
* - `repeat(alt(oneOf(S), ...rest))` with `min` at most 1 and no `max`
|
|
822
|
+
* matches what `repeat(alt(takeWhile(S, { min: 1 }), ...rest))` matches:
|
|
823
|
+
* a run of `S` is a run of one-character elements, and the counts that
|
|
824
|
+
* differ only matter for other ranges. The first branch is a synthetic
|
|
825
|
+
* `takeWhile` node; outside resumable builds, which never run its step.
|
|
826
|
+
*
|
|
827
|
+
* Returns `undefined` when neither applies.
|
|
828
|
+
*/
|
|
829
|
+
private repeatRun(p: ParserLike, node: NodeOf<"Repeat">, fail: string, fn: Fn, d: number): string | undefined {
|
|
830
|
+
if (this.kind === 2) return undefined
|
|
831
|
+
const element = nodeOf(node.self)
|
|
832
|
+
if (Predicate.isTagged(element, "OneOf") && this.native(element.encoding)) {
|
|
833
|
+
const { encoding, negate, set } = element
|
|
834
|
+
return this.leaf(
|
|
835
|
+
p,
|
|
836
|
+
{ _tag: "TakeWhile", set, negate, encoding, min: node.min, max: node.max },
|
|
837
|
+
false,
|
|
838
|
+
null,
|
|
839
|
+
fail,
|
|
840
|
+
fn
|
|
841
|
+
)
|
|
842
|
+
}
|
|
843
|
+
if (this.resumable || node.min > 1 || node.max !== Infinity || !Predicate.isTagged(element, "Alt")) return undefined
|
|
844
|
+
const [first, ...rest] = element.parsers
|
|
845
|
+
const head = first === undefined ? undefined : nodeOf(first)
|
|
846
|
+
if (
|
|
847
|
+
head === undefined ||
|
|
848
|
+
!Predicate.isTagged(head, "OneOf") ||
|
|
849
|
+
!this.native(head.encoding) ||
|
|
850
|
+
Arr.isReadonlyArrayEmpty(rest)
|
|
851
|
+
) {
|
|
852
|
+
return undefined
|
|
853
|
+
}
|
|
854
|
+
const run = synthetic(
|
|
855
|
+
{ _tag: "TakeWhile", set: head.set, negate: head.negate, encoding: head.encoding, min: 1, max: Infinity },
|
|
856
|
+
node.self.step
|
|
857
|
+
)
|
|
858
|
+
const alt = synthetic({ _tag: "Alt", parsers: [run, ...rest] }, node.self.step)
|
|
859
|
+
return this.repeat({ ...node, self: alt }, false, null, fail, fn, d)
|
|
860
|
+
}
|
|
861
|
+
|
|
794
862
|
repeat(
|
|
795
863
|
node: NodeOf<"Repeat"> | NodeOf<"Fold">,
|
|
796
864
|
used: boolean,
|
|
@@ -806,7 +874,7 @@ class Emitter {
|
|
|
806
874
|
const n = this.v(fn)
|
|
807
875
|
const st = this.v(fn)
|
|
808
876
|
const isFold = Predicate.isTagged(node, "Fold")
|
|
809
|
-
const cond = isFold ?
|
|
877
|
+
const cond = isFold ? node.while : undefined
|
|
810
878
|
const keep = out !== null && used && collect === undefined
|
|
811
879
|
// `while` needs the accumulator even when the output is unused
|
|
812
880
|
const tracked = keep || cond !== undefined
|
|
@@ -816,8 +884,8 @@ class Emitter {
|
|
|
816
884
|
let add = ""
|
|
817
885
|
if (tracked) {
|
|
818
886
|
if (isFold) {
|
|
819
|
-
init = `${acc}=${this.const(
|
|
820
|
-
add = `${acc}=${this.const(
|
|
887
|
+
init = `${acc}=${this.const(node.init)}();`
|
|
888
|
+
add = `${acc}=${this.const(node.f)}(${acc},${x});`
|
|
821
889
|
} else {
|
|
822
890
|
init = `${acc}=[];`
|
|
823
891
|
add = `${acc}.push(${x});`
|
|
@@ -826,11 +894,13 @@ class Emitter {
|
|
|
826
894
|
const check = max === Infinity ? `if(p===${st}){$S.mode="Cut";${fail}}` : ""
|
|
827
895
|
const bound = max === Infinity ? "" : `${n}<${max}`
|
|
828
896
|
const r = this.resumable ? this.resumePoint(fn) : undefined
|
|
897
|
+
// A checkpoint holds the result so far: the accumulator, or what `collect` builds
|
|
898
|
+
const held = collect?.value ?? acc
|
|
829
899
|
const save =
|
|
830
900
|
r === undefined
|
|
831
901
|
? ""
|
|
832
|
-
: `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${
|
|
833
|
-
// With `collect
|
|
902
|
+
: `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${held},${collect?.saved ?? ""}count:${n},pos:${st}});`
|
|
903
|
+
// With `collect`, the element adds itself once the loop check passed
|
|
834
904
|
const element =
|
|
835
905
|
collect === undefined
|
|
836
906
|
? `${this.gen(node.self, tracked, x, `break ${bad};`, fn, d)}${check}${add}`
|
|
@@ -842,7 +912,12 @@ class Emitter {
|
|
|
842
912
|
`${bad}:{${element}continue ${loop};}` +
|
|
843
913
|
`${save}if($S.mode!=="Backtrack"${min > 0 ? `||${n}<${min}` : ""}){${fail}}p=${st};break ${loop};}`
|
|
844
914
|
if (r === undefined) return `${init}${n}=0;${body}${collect?.finish ?? ""}`
|
|
845
|
-
return r.wrap(
|
|
915
|
+
return r.wrap(
|
|
916
|
+
held,
|
|
917
|
+
`${held}=cp.acc;${collect?.restore ?? ""}${n}=cp.count;p=cp.pos;`,
|
|
918
|
+
`${init}${n}=0;`,
|
|
919
|
+
`${body}${collect?.finish ?? ""}`
|
|
920
|
+
)
|
|
846
921
|
}
|
|
847
922
|
|
|
848
923
|
/**
|
|
@@ -887,10 +962,12 @@ class Emitter {
|
|
|
887
962
|
const loop = this.label()
|
|
888
963
|
const push = keep ? `${acc}.push(${x});` : ""
|
|
889
964
|
const r = this.resumable ? this.resumePoint(fn) : undefined
|
|
965
|
+
// A checkpoint holds the result so far: the accumulator, or what `collect` builds
|
|
966
|
+
const held = collect?.value ?? acc
|
|
890
967
|
const save =
|
|
891
968
|
r === undefined
|
|
892
969
|
? ""
|
|
893
|
-
: `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${
|
|
970
|
+
: `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${held},${collect?.saved ?? ""}count:${n},pos:${st}});`
|
|
894
971
|
// Runs `body`; on its failure, stops the repetition (or fails if `fatal`)
|
|
895
972
|
const attempt = (body: (fail: string) => string, success: string, fatal: string, checkpoint: string) => {
|
|
896
973
|
const ok = this.label()
|
|
@@ -915,7 +992,12 @@ class Emitter {
|
|
|
915
992
|
`}`
|
|
916
993
|
if (r === undefined) return `${done}:{${first}${rest}}${collect?.finish ?? ""}`
|
|
917
994
|
// `done` ends the repetition; the checkpoint wrapper records the result after it
|
|
918
|
-
return r.wrap(
|
|
995
|
+
return r.wrap(
|
|
996
|
+
held,
|
|
997
|
+
`${held}=cp.acc;${collect?.restore ?? ""}${n}=cp.count;p=cp.pos;`,
|
|
998
|
+
"",
|
|
999
|
+
`${done}:{if(!${r.resumed}){${first}}${rest}}${collect?.finish ?? ""}`
|
|
1000
|
+
)
|
|
919
1001
|
}
|
|
920
1002
|
|
|
921
1003
|
/**
|
|
@@ -927,59 +1009,61 @@ class Emitter {
|
|
|
927
1009
|
*/
|
|
928
1010
|
private entries(p: ParserLike, out: string, fail: string, fn: Fn, d: number): string {
|
|
929
1011
|
const node = nodeOf(p)
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1012
|
+
if (Predicate.isTagged(node, "Mode")) {
|
|
1013
|
+
const ok = this.label()
|
|
1014
|
+
const bad = this.label()
|
|
1015
|
+
return (
|
|
1016
|
+
`${ok}:{${bad}:{${this.entries(node.self, out, `break ${bad};`, fn, d)}break ${ok};}` +
|
|
1017
|
+
`if($S.mode===${JSON.stringify(node.from)})$S.mode=${JSON.stringify(node.to)};${fail}}`
|
|
1018
|
+
)
|
|
1019
|
+
}
|
|
1020
|
+
if (Predicate.isTagged(node, "Seq")) {
|
|
1021
|
+
// SAFETY: `buildsEntries` passes through a sequence only when it keeps one output, by index
|
|
1022
|
+
const i = node.output as number
|
|
1023
|
+
return node.parsers
|
|
1024
|
+
.map((q, j) => (j === i ? this.entries(q, out, fail, fn, d) : this.gen(q, false, null, fail, fn, d)))
|
|
1025
|
+
.join("")
|
|
1026
|
+
}
|
|
1027
|
+
if (Predicate.isTagged(node, "Repeat") || Predicate.isTagged(node, "Separated")) {
|
|
1028
|
+
const pair = pairOf(node.self)
|
|
1029
|
+
if (pair === undefined) {
|
|
1030
|
+
// Not reached (`buildsEntries` found the pair): build the pairs, then the object
|
|
1031
|
+
const t = this.v(fn)
|
|
1032
|
+
return this.gen(p, true, t, fail, fn, d) + `${out}=${this.const(fromEntries)}(${t});`
|
|
944
1033
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
.map((q, j) =>
|
|
961
|
-
this.gen(
|
|
962
|
-
q,
|
|
963
|
-
j === pair.key || j === pair.value,
|
|
964
|
-
j === pair.key ? key : j === pair.value ? value : null,
|
|
965
|
-
f,
|
|
966
|
-
fn,
|
|
967
|
-
d
|
|
968
|
-
)
|
|
1034
|
+
const deferred = this.v(fn)
|
|
1035
|
+
const key = this.v(fn)
|
|
1036
|
+
const value = this.v(fn)
|
|
1037
|
+
const collect: Collect = {
|
|
1038
|
+
init: `${out}={};${deferred}=void 0;`,
|
|
1039
|
+
element: (f, check) =>
|
|
1040
|
+
pair.parsers
|
|
1041
|
+
.map((q, j) =>
|
|
1042
|
+
this.gen(
|
|
1043
|
+
q,
|
|
1044
|
+
j === pair.key || j === pair.value,
|
|
1045
|
+
j === pair.key ? key : j === pair.value ? value : null,
|
|
1046
|
+
f,
|
|
1047
|
+
fn,
|
|
1048
|
+
d
|
|
969
1049
|
)
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1050
|
+
)
|
|
1051
|
+
.join("") +
|
|
1052
|
+
check +
|
|
1053
|
+
`if(${deferred}===void 0&&R.isPlainKey(${key}))${out}[${key}]=${value};` +
|
|
1054
|
+
`else ${deferred}=R.addEntry(${out},${deferred},${key},${value});`,
|
|
1055
|
+
finish: `if(${deferred}!==void 0)R.flushEntries(${out},${deferred});`,
|
|
1056
|
+
value: out,
|
|
1057
|
+
// A streaming build's checkpoint holds the object so far, with its deferred entries
|
|
1058
|
+
saved: `def:${deferred},`,
|
|
1059
|
+
restore: `${deferred}=cp.def;`
|
|
979
1060
|
}
|
|
980
|
-
|
|
981
|
-
|
|
1061
|
+
return Predicate.isTagged(node, "Repeat")
|
|
1062
|
+
? this.repeat(node, true, out, fail, fn, d, collect)
|
|
1063
|
+
: this.separated(node, true, out, fail, fn, d, collect)
|
|
982
1064
|
}
|
|
1065
|
+
// SAFETY: the other nodes `buildsEntries` passes through are diagnostics, which all wrap a `self`
|
|
1066
|
+
return this.entries((node as { readonly self: ParserLike }).self, out, fail, fn, d)
|
|
983
1067
|
}
|
|
984
1068
|
|
|
985
1069
|
// ---------------------------------------------------------------------------
|
|
@@ -1027,6 +1111,7 @@ class Emitter {
|
|
|
1027
1111
|
): string | undefined {
|
|
1028
1112
|
const set = (value: string) => (out === null ? "" : `${out}=${value};`)
|
|
1029
1113
|
const enc = node.encoding
|
|
1114
|
+
// SAFETY: `leaf`, the only caller, checks `native` first, which never holds on array input (kind 2)
|
|
1030
1115
|
const kind = this.kind as 0 | 1
|
|
1031
1116
|
const failBT = `{${BT}${fail}}`
|
|
1032
1117
|
switch (node._tag) {
|
|
@@ -1055,6 +1140,7 @@ class Emitter {
|
|
|
1055
1140
|
const lit = node.literal
|
|
1056
1141
|
if (enc !== undefined) {
|
|
1057
1142
|
if (node.caseless) return undefined
|
|
1143
|
+
// SAFETY: a `Literal` with an encoding is a `Text` literal, which records the string it encodes
|
|
1058
1144
|
const encoded = enc.encode(lit as string)
|
|
1059
1145
|
return (
|
|
1060
1146
|
this.literalUnits(Predicate.isString(encoded) ? unitsOf(encoded) : Array.from(encoded), false, fail) +
|
|
@@ -1062,12 +1148,10 @@ class Emitter {
|
|
|
1062
1148
|
)
|
|
1063
1149
|
}
|
|
1064
1150
|
const literalKind = Predicate.isString(lit) ? 0 : lit instanceof Uint8Array ? 1 : 2
|
|
1065
|
-
|
|
1066
|
-
if (
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
? unitsOf(lit as string)
|
|
1070
|
-
: Array.from(literalKind === 0 ? new TextEncoder().encode(lit as string) : (lit as Uint8Array))
|
|
1151
|
+
let codes: Array<number>
|
|
1152
|
+
if (Predicate.isString(lit)) codes = kind === 0 ? unitsOf(lit) : Array.from(new TextEncoder().encode(lit))
|
|
1153
|
+
else if (kind === 1 && lit instanceof Uint8Array) codes = Array.from(lit)
|
|
1154
|
+
else return undefined
|
|
1071
1155
|
const len = codes.length
|
|
1072
1156
|
const value =
|
|
1073
1157
|
!node.caseless && literalKind === kind
|
|
@@ -1139,6 +1223,7 @@ class Emitter {
|
|
|
1139
1223
|
private takeWhile(node: NodeOf<"TakeWhile">, out: string | null, fail: string, fn: Fn): string {
|
|
1140
1224
|
const enc = node.encoding
|
|
1141
1225
|
const { max, min, negate } = node
|
|
1226
|
+
// SAFETY: called from `token` only, whose `kind` is not 2
|
|
1142
1227
|
const kind = this.kind as 0 | 1
|
|
1143
1228
|
const cs = enc === undefined ? tokenSet(node.set, kind) : textSet(node.set)
|
|
1144
1229
|
const accept = acceptTable(cs, negate)
|
|
@@ -1149,8 +1234,7 @@ class Emitter {
|
|
|
1149
1234
|
const counted = max !== Infinity || min > 1
|
|
1150
1235
|
const count = counted ? `${n}++;` : ""
|
|
1151
1236
|
const bound = max !== Infinity ? `&&${n}<${max}` : ""
|
|
1152
|
-
const slice =
|
|
1153
|
-
enc !== undefined ? `${this.const(enc.text)}(I,${st},p)` : kind === 0 ? `I.slice(${st},p)` : `I.subarray(${st},p)`
|
|
1237
|
+
const slice = enc !== undefined ? this.text(enc, st) : kind === 0 ? `I.slice(${st},p)` : `I.subarray(${st},p)`
|
|
1154
1238
|
const finish =
|
|
1155
1239
|
(min > 0 ? `if(${counted ? n : `p-${st}`}<${min}){${BT}${fail}}` : "") + (out === null ? "" : `${out}=${slice};`)
|
|
1156
1240
|
const head = `${st}=p;${counted ? `${n}=0;` : ""}${loop}:while(p<E${bound}){${c}=${this.unit("p")};`
|
|
@@ -1173,6 +1257,8 @@ class Emitter {
|
|
|
1173
1257
|
`p+=${this.const(enc.width)}(${c})}${count}}${finish}`
|
|
1174
1258
|
)
|
|
1175
1259
|
}
|
|
1260
|
+
const stops = negate && !counted ? stopChars(cs) : undefined
|
|
1261
|
+
if (stops !== undefined) return this.stopSearch(stops, st, c, finish, fn)
|
|
1176
1262
|
const fast = 0xd800
|
|
1177
1263
|
const small = TableSize
|
|
1178
1264
|
const highTest = (v: string) => this.memberHigh(cs, negate, v)
|
|
@@ -1193,6 +1279,34 @@ class Emitter {
|
|
|
1193
1279
|
return `${head}if(${c}<${small}){if(!(${member}))break;p++}else ${rest}${count}}${finish}`
|
|
1194
1280
|
}
|
|
1195
1281
|
|
|
1282
|
+
/**
|
|
1283
|
+
* `Token.takeTill` on a string, of one to `MaxStops` characters that are
|
|
1284
|
+
* whole code points: the set accepts every other unit, surrogates
|
|
1285
|
+
* included, so the run ends at the first stop character. The first
|
|
1286
|
+
* `SearchHead` units are tested one at a time, which beats a native
|
|
1287
|
+
* search on short runs; a longer run ends at the next occurrence that
|
|
1288
|
+
* `nextStop` finds with `indexOf` (#75).
|
|
1289
|
+
*/
|
|
1290
|
+
private stopSearch(stops: ReadonlyArray<number>, st: string, c: string, finish: string, fn: Fn): string {
|
|
1291
|
+
const h = this.v(fn)
|
|
1292
|
+
const test = stops.map((u) => `${c}!==${u}`).join("&&")
|
|
1293
|
+
// One stop character is searched for inline: a run that doesn't find it ends at `E`, so it is
|
|
1294
|
+
// searched for once. Several are searched for through `nextStop`'s cache, so that one which
|
|
1295
|
+
// doesn't occur isn't searched for again, to the end of the input, on every run.
|
|
1296
|
+
const search =
|
|
1297
|
+
stops.length === 1
|
|
1298
|
+
? `{p=I.indexOf(${JSON.stringify(String.fromCharCode(stops[0]))},p);if(p<0)p=E}`
|
|
1299
|
+
: `p=R.nextStop(${this.const(new StopCache(stops.map((u) => String.fromCharCode(u))))},$S,p);`
|
|
1300
|
+
// A cached search costs more than a plain one, so it waits for a longer run
|
|
1301
|
+
const head = stops.length === 1 ? SearchHead : 2 * SearchHead
|
|
1302
|
+
return (
|
|
1303
|
+
`${st}=p;${h}=p+${head}<E?p+${head}:E;` +
|
|
1304
|
+
`while(p<${h}&&(${c}=I.charCodeAt(p),${test}))p++;` +
|
|
1305
|
+
// A run that ends right after the head needs no search
|
|
1306
|
+
`if(p===${h}&&p<E&&(${c}=I.charCodeAt(p),${test}))${search}${finish}`
|
|
1307
|
+
)
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1196
1310
|
/**
|
|
1197
1311
|
* `escaped`. In a resumable build it keeps the closure compiler's
|
|
1198
1312
|
* checkpoints: where the next segment starts and, unless raw, the text
|
|
@@ -1222,12 +1336,7 @@ class Emitter {
|
|
|
1222
1336
|
const onNormal = raw
|
|
1223
1337
|
? `if(p===${b}){$S.mode="Cut";${fail}}continue ${loop};`
|
|
1224
1338
|
: `if(p!==${b}){${acc}+=${x};continue ${loop}}`
|
|
1225
|
-
const slice =
|
|
1226
|
-
enc !== undefined
|
|
1227
|
-
? `${this.const(enc.text)}(I,${st},p)`
|
|
1228
|
-
: this.kind === 0
|
|
1229
|
-
? `I.slice(${st},p)`
|
|
1230
|
-
: `I.subarray(${st},p)`
|
|
1339
|
+
const slice = enc !== undefined ? this.text(enc, st) : this.kind === 0 ? `I.slice(${st},p)` : `I.subarray(${st},p)`
|
|
1231
1340
|
const ctrlTest =
|
|
1232
1341
|
rd === undefined ? `p<E&&${this.unit("p")}===${ctrl}` : `${rd.guard("p")}&&${rd.unit("p")}===${ctrl}`
|
|
1233
1342
|
// In a resumable build, running out of a partial input (not stopping
|
|
@@ -1239,7 +1348,7 @@ class Emitter {
|
|
|
1239
1348
|
`if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${save(b)}${fail}}p=${b}}` +
|
|
1240
1349
|
`if(${ctrlTest}){p+=${rd?.width ?? 1};${escape}${raw ? "" : `${acc}+=${x};`}}else break ${stop};}`
|
|
1241
1350
|
const body =
|
|
1242
|
-
r === undefined ? loopCode : `${stop}:{${loopCode}if($S.partial){$S.
|
|
1351
|
+
r === undefined ? loopCode : `${stop}:{${loopCode}if($S.partial){$S.incomplete(0);${save("p")}${fail}}}`
|
|
1243
1352
|
if (r === undefined) {
|
|
1244
1353
|
return `${raw ? `${st}=p;` : `${acc}="";`}${body}${raw && out !== null ? `${out}=${slice};` : ""}`
|
|
1245
1354
|
}
|
|
@@ -1250,6 +1359,18 @@ class Emitter {
|
|
|
1250
1359
|
}
|
|
1251
1360
|
}
|
|
1252
1361
|
|
|
1362
|
+
/**
|
|
1363
|
+
* A parser that exists only as a description, for the rewrites of
|
|
1364
|
+
* `Emitter.repeatRun`. `step` is never run on the paths they compile (the
|
|
1365
|
+
* token code of a native `takeWhile`, and alternatives inline), so it is
|
|
1366
|
+
* the step of the repetition's element, which describes the same language
|
|
1367
|
+
* when its value is unused.
|
|
1368
|
+
*/
|
|
1369
|
+
const synthetic = (node: Node, step: Step): ParserLike => {
|
|
1370
|
+
const described: ParserLike & { readonly [NodeId]: Node } = { step, name: undefined, [NodeId]: node }
|
|
1371
|
+
return described
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1253
1374
|
/** The UTF-16 code units of a string. */
|
|
1254
1375
|
const unitsOf = (s: string): Array<number> => Array.from({ length: s.length }, (_, i) => s.charCodeAt(i))
|
|
1255
1376
|
|
|
@@ -1264,10 +1385,20 @@ interface Generated {
|
|
|
1264
1385
|
const generate = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): Generated => {
|
|
1265
1386
|
const e = new Emitter(root, kind, resumable)
|
|
1266
1387
|
const main = e.rule(root, true)
|
|
1388
|
+
// Generated code reads past `end` expecting nothing to match there, which
|
|
1389
|
+
// holds when `end` is the input's length, as it is in the runners; other
|
|
1390
|
+
// states run the closure compiler's step, built on first use.
|
|
1391
|
+
let closure: Step | undefined
|
|
1392
|
+
const short = e.const((state: State<any>) =>
|
|
1393
|
+
(closure ??= createClosureCompiler(kind, resumable).compile(root, true))(state)
|
|
1394
|
+
)
|
|
1267
1395
|
const source =
|
|
1268
1396
|
`"use strict";let $I,$E,$S,P;${e.functions.join("\n")}\n` +
|
|
1269
|
-
`return function run(state){
|
|
1270
|
-
`
|
|
1397
|
+
`return function run(state){if(state.end!==state.input.length)return ${short}(state);` +
|
|
1398
|
+
`const sI=$I,sE=$E,sS=$S,sP=P;$I=state.input;$E=state.end;$S=state;` +
|
|
1399
|
+
`try{const v=${main}(state.pos);if(v!==F)state.pos=P;return v}` +
|
|
1400
|
+
// A call-stack overflow is a `TooDeep` failure, as `Stack.run` reports it
|
|
1401
|
+
`catch(e){if(!R.isStackOverflow(e))throw e;return R.tooDeep(state)}finally{$I=sI;$E=sE;$S=sS;P=sP}}`
|
|
1271
1402
|
return { source, constants: e.constants, fallbacks: e.fallbacks }
|
|
1272
1403
|
}
|
|
1273
1404
|
|
|
@@ -1276,17 +1407,15 @@ interface JitCompiled extends CompiledRoot {
|
|
|
1276
1407
|
readonly source: string
|
|
1277
1408
|
}
|
|
1278
1409
|
|
|
1279
|
-
/**
|
|
1410
|
+
/**
|
|
1411
|
+
* Compiles a root parser for one input kind with `new Function`. The step
|
|
1412
|
+
* is the generated entry, which handles every state and reports call-stack
|
|
1413
|
+
* overflows itself.
|
|
1414
|
+
*/
|
|
1280
1415
|
const compileJit = (root: ParserLike, kind: 0 | 1 | 2, resumable = false): JitCompiled => {
|
|
1281
1416
|
const { constants, fallbacks, source } = generate(root, kind, resumable)
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
// holds when `end` is the input's length, as it is in the runners.
|
|
1285
|
-
let closure: Step | undefined
|
|
1286
|
-
const step: Step = (state) =>
|
|
1287
|
-
state.end === state.input.length
|
|
1288
|
-
? run(state)
|
|
1289
|
-
: (closure ??= createClosureCompiler(kind, resumable).compile(root, true))(state)
|
|
1417
|
+
// SAFETY: the generated source returns its `run` function, which takes a state and returns a value or `Fail`
|
|
1418
|
+
const step = globalThis.Function("C", "F", "R", source)(constants, Fail, runtime) as Step
|
|
1290
1419
|
return { step, fallbacks, source }
|
|
1291
1420
|
}
|
|
1292
1421
|
|
|
@@ -1303,6 +1432,7 @@ export const backend: Backend = {
|
|
|
1303
1432
|
onSuccess: ({ fallbacks, source, step }) => ({
|
|
1304
1433
|
backend: "jit" as const,
|
|
1305
1434
|
step,
|
|
1435
|
+
guarded: true,
|
|
1306
1436
|
fallbacks,
|
|
1307
1437
|
error: undefined,
|
|
1308
1438
|
source
|