@thomasfosterau/effect-parse 0.1.0 → 0.2.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 +247 -230
- package/dist/Ascii.d.ts +32 -53
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +576 -571
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +6 -29
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +129 -116
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts +4 -2
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +121 -123
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +39 -0
- package/dist/Compiler.d.ts.map +1 -0
- package/dist/Compiler.js +48 -0
- package/dist/Compiler.js.map +1 -0
- package/dist/Encoding.d.ts +29 -46
- package/dist/Encoding.d.ts.map +1 -1
- package/dist/Encoding.js +76 -75
- package/dist/Encoding.js.map +1 -1
- package/dist/Expression.d.ts +15 -49
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +143 -131
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +206 -177
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +317 -299
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +44 -84
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js +423 -453
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts +54 -49
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +233 -275
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +345 -313
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +2010 -1802
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +8 -7
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +49 -49
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +26 -20
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +267 -255
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +401 -0
- package/dist/Syntax.d.ts.map +1 -0
- package/dist/Syntax.js +717 -0
- package/dist/Syntax.js.map +1 -0
- package/dist/Text.d.ts +98 -134
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +534 -496
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +60 -42
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +331 -265
- package/dist/Token.js.map +1 -1
- package/dist/index.d.ts +16 -17
- package/dist/index.js +16 -17
- package/dist/internal/compiler/closure.d.ts +14 -16
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +1458 -1637
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +11 -11
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +36 -26
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts +19 -19
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +251 -227
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts +6 -17
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +860 -988
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +20 -21
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js +21 -41
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/sets.d.ts +9 -13
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +76 -73
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/entries.d.ts +3 -3
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +38 -30
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +177 -173
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +77 -66
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts +7 -8
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js +246 -276
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/resume.d.ts +6 -24
- package/dist/internal/resume.d.ts.map +1 -1
- package/dist/internal/resume.js +30 -29
- package/dist/internal/resume.js.map +1 -1
- package/dist/internal/run.d.ts +15 -0
- package/dist/internal/run.d.ts.map +1 -0
- package/dist/internal/run.js +36 -0
- package/dist/internal/run.js.map +1 -0
- package/dist/internal/stack.d.ts +16 -0
- package/dist/internal/stack.d.ts.map +1 -0
- package/dist/internal/stack.js +26 -0
- package/dist/internal/stack.js.map +1 -0
- package/dist/internal/syntax.d.ts +146 -0
- package/dist/internal/syntax.d.ts.map +1 -0
- package/dist/internal/syntax.js +305 -0
- package/dist/internal/syntax.js.map +1 -0
- package/dist/internal/text.d.ts +10 -2
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +295 -345
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/tokens.d.ts +25 -30
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +369 -363
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +42 -28
- package/src/Ascii.ts +278 -187
- package/src/Binary.ts +150 -44
- package/src/Bits.ts +6 -3
- package/src/Compiler.ts +98 -0
- package/src/Expression.ts +10 -5
- package/src/Input.ts +106 -28
- package/src/Nom.ts +4 -3
- package/src/ParseError.ts +48 -51
- package/src/Parser.ts +1163 -795
- package/src/ParserStream.ts +104 -80
- package/src/Syntax.ts +900 -0
- package/src/Text.ts +272 -187
- package/src/Token.ts +180 -87
- package/src/index.ts +2 -0
- package/src/internal/compiler/closure.ts +121 -88
- package/src/internal/compiler/entries.ts +21 -8
- package/src/internal/compiler/first.ts +17 -15
- package/src/internal/compiler/jit.ts +360 -157
- package/src/internal/compiler/registry.ts +12 -33
- package/src/internal/compiler/sets.ts +17 -11
- package/src/internal/entries.ts +4 -2
- package/src/internal/node.ts +51 -37
- package/src/internal/numbers.ts +22 -20
- package/src/internal/resume.ts +8 -4
- package/src/internal/run.ts +41 -0
- package/src/internal/stack.ts +36 -0
- package/src/internal/syntax.ts +421 -0
- package/src/internal/text.ts +32 -25
- package/src/internal/tokens.ts +96 -51
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,1030 +1,902 @@
|
|
|
1
|
-
|
|
2
|
-
* The JIT: emits JavaScript source for a parser's description and builds
|
|
3
|
-
* it with `new Function`.
|
|
4
|
-
*
|
|
5
|
-
* Every rule (the root, every `suspend` target and every non-leaf parser
|
|
6
|
-
* with several parents) becomes a function `f(p)` taking the cursor and
|
|
7
|
-
* returning the value, or `Fail`, leaving the new cursor in the register
|
|
8
|
-
* `P`. Everything else is inlined as straight-line code, with labelled
|
|
9
|
-
* blocks for failure. The cursor is a local; the input and its end are
|
|
10
|
-
* hoisted into locals. Failures only set `state.mode`, as in the closure
|
|
11
|
-
* compiler.
|
|
12
|
-
*
|
|
13
|
-
* Nodes without an inline form run their closure-compiled steps (which run
|
|
14
|
-
* the interpreter for what they can't compile) on the shared state, with
|
|
15
|
-
* `state.pos` kept in sync.
|
|
16
|
-
*/
|
|
17
|
-
import * as Option from "effect/Option";
|
|
1
|
+
import * as Predicate from "effect/Predicate";
|
|
18
2
|
import { Fail } from "../../Input.js";
|
|
19
|
-
import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js";
|
|
20
|
-
import { children, nodeOf } from "../node.js";
|
|
21
3
|
import { floatScan, floatTexts, scanFloatBytes, scanFloatString, toFloat } from "../numbers.js";
|
|
22
4
|
import * as Resume from "../resume.js";
|
|
5
|
+
import { compileAny } from "../tokens.js";
|
|
6
|
+
import { backend as backend$1, createClosureCompiler } from "./closure.js";
|
|
7
|
+
import * as Option from "effect/Option";
|
|
23
8
|
import { charOf, fastLimit, latin1, string, utf16be, utf16le, utf8, validate } from "../text.js";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
9
|
+
import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js";
|
|
10
|
+
import { children, nodeOf } from "../node.js";
|
|
11
|
+
import { buildsEntries, pairOf } from "./entries.js";
|
|
12
|
+
import { Strict, planAlt } from "./first.js";
|
|
13
|
+
import { TableSize, kindOfEncoding, reachesAbove, textSet, tokenSet } from "./sets.js";
|
|
14
|
+
//#region src/internal/compiler/jit.ts
|
|
15
|
+
/**
|
|
16
|
+
* The JIT: emits JavaScript source for a parser's description and builds
|
|
17
|
+
* it with `new Function`.
|
|
18
|
+
*
|
|
19
|
+
* Every rule (the root, every `suspend` target and every non-leaf parser
|
|
20
|
+
* with several parents) becomes a function `f(p)` taking the cursor and
|
|
21
|
+
* returning the value, or `Fail`, leaving the new cursor in the register
|
|
22
|
+
* `P`. Everything else is inlined as straight-line code, with labelled
|
|
23
|
+
* blocks for failure. The cursor is a local; the input and its end are
|
|
24
|
+
* hoisted into locals. Failures only set `state.mode`, as in the closure
|
|
25
|
+
* compiler.
|
|
26
|
+
*
|
|
27
|
+
* Nodes without an inline form run their closure-compiled steps (which run
|
|
28
|
+
* the interpreter for what they can't compile) on the shared state, with
|
|
29
|
+
* `state.pos` kept in sync.
|
|
30
|
+
*/
|
|
28
31
|
/** Helpers available to generated code as `R`. */
|
|
29
32
|
const runtime = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return Fail;
|
|
56
|
-
}
|
|
57
|
-
return enc.text(input, start, end);
|
|
58
|
-
}
|
|
33
|
+
take: Resume.take,
|
|
34
|
+
save: Resume.save,
|
|
35
|
+
Done: Resume.Done,
|
|
36
|
+
/** Records the result of a resumed repetition (see `Resume.done`). */
|
|
37
|
+
done: (state, key, entry, value, pos) => Resume.save(state, key, entry, new Resume.Done(value, pos)),
|
|
38
|
+
some: Option.some,
|
|
39
|
+
none: Option.none,
|
|
40
|
+
charOf,
|
|
41
|
+
scanFloatString,
|
|
42
|
+
scanFloatBytes,
|
|
43
|
+
isPlainKey,
|
|
44
|
+
addEntry,
|
|
45
|
+
flushEntries,
|
|
46
|
+
/** `Text.taken`: the text of `[start, end)`, or `Fail` if it is malformed. */
|
|
47
|
+
textOf: (enc, fast, s, start, end) => {
|
|
48
|
+
const input = s.input;
|
|
49
|
+
let i = start;
|
|
50
|
+
if (s.inputKind === 0) while (i < end && input.charCodeAt(i) < fast) i++;
|
|
51
|
+
else while (i < end && input[i] < fast) i++;
|
|
52
|
+
if (i < end && validate(enc, s, i, end) < 0) {
|
|
53
|
+
s.mode = "Backtrack";
|
|
54
|
+
return Fail;
|
|
55
|
+
}
|
|
56
|
+
return enc.text(input, start, end);
|
|
57
|
+
}
|
|
59
58
|
};
|
|
60
59
|
let checked;
|
|
61
60
|
let supported = false;
|
|
62
61
|
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return supported;
|
|
62
|
+
* Whether `new Function` works here (it doesn't under a Content Security
|
|
63
|
+
* Policy). Checked once, and again if `globalThis.Function` is replaced.
|
|
64
|
+
*/
|
|
65
|
+
const jitSupported = () => {
|
|
66
|
+
if (checked !== globalThis.Function) {
|
|
67
|
+
checked = globalThis.Function;
|
|
68
|
+
try {
|
|
69
|
+
supported = checked("return true")() === true;
|
|
70
|
+
} catch {
|
|
71
|
+
supported = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return supported;
|
|
77
75
|
};
|
|
78
76
|
const BT = `$S.mode="Backtrack";`;
|
|
79
|
-
const builtinEncodings = new Set([
|
|
77
|
+
const builtinEncodings = /*#__PURE__*/ new Set([
|
|
78
|
+
string,
|
|
79
|
+
utf8,
|
|
80
|
+
utf16le,
|
|
81
|
+
utf16be,
|
|
82
|
+
latin1
|
|
83
|
+
]);
|
|
80
84
|
/** How many units past the cursor a single token can depend on (see the closure compiler). */
|
|
81
85
|
const Margin = 4;
|
|
82
86
|
/** Per generated function: its local variables. */
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
87
|
+
var Fn = class {
|
|
88
|
+
vars = [];
|
|
89
|
+
};
|
|
86
90
|
const isLeaf = (node) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
91
|
+
switch (node._tag) {
|
|
92
|
+
case "Succeed":
|
|
93
|
+
case "Fail":
|
|
94
|
+
case "Eof":
|
|
95
|
+
case "Location":
|
|
96
|
+
case "RestLen":
|
|
97
|
+
case "Any":
|
|
98
|
+
case "Literal":
|
|
99
|
+
case "OneOf":
|
|
100
|
+
case "TakeWhile": return true;
|
|
101
|
+
default: return false;
|
|
102
|
+
}
|
|
101
103
|
};
|
|
102
104
|
const lowerAscii = (c) => c >= 65 && c <= 90 ? c + 32 : c;
|
|
103
|
-
const isLetter = (c) =>
|
|
105
|
+
const isLetter = (c) => c >= 65 && c <= 90 || c >= 97 && c <= 122;
|
|
104
106
|
/** The inclusive ranges of 1s in a table. */
|
|
105
107
|
const runsOf = (table) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
return out;
|
|
108
|
+
const out = [];
|
|
109
|
+
for (let i = 0; i < table.length; i++) {
|
|
110
|
+
if (table[i] !== 1) continue;
|
|
111
|
+
let j = i;
|
|
112
|
+
while (j + 1 < table.length && table[j + 1] === 1) j++;
|
|
113
|
+
out.push([i, j]);
|
|
114
|
+
i = j;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
117
|
};
|
|
118
118
|
/** A JavaScript expression testing `v` against ranges, or `undefined` if too long. */
|
|
119
119
|
const rangeTest = (runs, v) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return undefined;
|
|
124
|
-
return runs.map(([a, b]) => a === b ? `${v}===${a}` : `(${v}>=${a}&&${v}<=${b})`).join("||");
|
|
120
|
+
if (runs.length === 0) return "false";
|
|
121
|
+
if (runs.length > 4) return void 0;
|
|
122
|
+
return runs.map(([a, b]) => a === b ? `${v}===${a}` : `(${v}>=${a}&&${v}<=${b})`).join("||");
|
|
125
123
|
};
|
|
126
124
|
/** The inclusive ranges of `[0, size)` where `table[c] === value`. */
|
|
127
125
|
const runsWhere = (table, size, value) => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
return out;
|
|
126
|
+
const out = [];
|
|
127
|
+
for (let i = 0; i < size; i++) {
|
|
128
|
+
if (table[i] !== value) continue;
|
|
129
|
+
let j = i;
|
|
130
|
+
while (j + 1 < size && table[j + 1] === value) j++;
|
|
131
|
+
out.push([i, j]);
|
|
132
|
+
i = j;
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
139
135
|
};
|
|
140
136
|
/** How many comparisons it takes to test a run of `[0, size)`. */
|
|
141
137
|
const runCost = ([a, b], size) => a === b || a === 0 || b === size - 1 ? 1 : 2;
|
|
142
138
|
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
139
|
+
* A JavaScript expression testing whether `v`, known to be in `[0, size)`,
|
|
140
|
+
* is accepted by `table`, or `undefined` if that takes too many comparisons.
|
|
141
|
+
* It tests the accepted runs or the rejected ones, whichever is cheaper:
|
|
142
|
+
* `c!==34&&c!==92&&c>=32` rather than three two-sided ranges.
|
|
143
|
+
*/
|
|
148
144
|
const tableTest = (table, v, size) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
145
|
+
const accept = runsWhere(table, size, 1);
|
|
146
|
+
const reject = runsWhere(table, size, 0);
|
|
147
|
+
if (accept.length === 0) return "false";
|
|
148
|
+
if (reject.length === 0) return "true";
|
|
149
|
+
const cost = (runs) => runs.reduce((n, r) => n + runCost(r, size), 0);
|
|
150
|
+
const acceptCost = cost(accept);
|
|
151
|
+
const rejectCost = cost(reject);
|
|
152
|
+
if (Math.min(acceptCost, rejectCost) > 6) return void 0;
|
|
153
|
+
return `(${acceptCost <= rejectCost ? accept.map(([a, b]) => a === b ? `${v}===${a}` : a === 0 ? `${v}<=${b}` : b === size - 1 ? `${v}>=${a}` : `(${v}>=${a}&&${v}<=${b})`).join("||") : reject.map(([a, b]) => a === b ? `${v}!==${a}` : a === 0 ? `${v}>${b}` : b === size - 1 ? `${v}<${a}` : `(${v}<${a}||${v}>${b})`).join("&&")})`;
|
|
154
|
+
};
|
|
155
|
+
var Emitter = class {
|
|
156
|
+
root;
|
|
157
|
+
kind;
|
|
158
|
+
resumable;
|
|
159
|
+
constants = [];
|
|
160
|
+
constantIndex = /* @__PURE__ */ new Map();
|
|
161
|
+
functions = [];
|
|
162
|
+
rules = [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()];
|
|
163
|
+
refs = /* @__PURE__ */ new Map();
|
|
164
|
+
closure;
|
|
165
|
+
next = 0;
|
|
166
|
+
constructor(root, kind, resumable = false) {
|
|
167
|
+
this.root = root;
|
|
168
|
+
this.kind = kind;
|
|
169
|
+
this.resumable = resumable;
|
|
170
|
+
this.closure = createClosureCompiler(kind, resumable);
|
|
171
|
+
this.countRefs(root);
|
|
172
|
+
}
|
|
173
|
+
get fallbacks() {
|
|
174
|
+
return this.closure.fallbacks;
|
|
175
|
+
}
|
|
176
|
+
/** Counts the distinct parents of each parser. */
|
|
177
|
+
countRefs(root) {
|
|
178
|
+
const seen = /* @__PURE__ */ new Set();
|
|
179
|
+
const stack = [root];
|
|
180
|
+
for (let p = stack.pop(); p !== void 0; p = stack.pop()) {
|
|
181
|
+
if (seen.has(p)) continue;
|
|
182
|
+
seen.add(p);
|
|
183
|
+
for (const c of new Set(children(nodeOf(p)))) {
|
|
184
|
+
this.refs.set(c, (this.refs.get(c) ?? 0) + 1);
|
|
185
|
+
stack.push(c);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const(value) {
|
|
190
|
+
let i = this.constantIndex.get(value);
|
|
191
|
+
if (i === void 0) {
|
|
192
|
+
i = this.constants.length;
|
|
193
|
+
this.constants.push(value);
|
|
194
|
+
this.constantIndex.set(value, i);
|
|
195
|
+
}
|
|
196
|
+
return `C[${i}]`;
|
|
197
|
+
}
|
|
198
|
+
/** A JavaScript literal for simple values, otherwise a constant. */
|
|
199
|
+
lit(value) {
|
|
200
|
+
if (value === void 0) return "void 0";
|
|
201
|
+
if (value === null || Predicate.isBoolean(value)) return String(value);
|
|
202
|
+
if (Predicate.isNumber(value) && Number.isFinite(value) && !Object.is(value, -0)) return String(value);
|
|
203
|
+
if (Predicate.isString(value) && value.length < 64) return JSON.stringify(value);
|
|
204
|
+
return this.const(value);
|
|
205
|
+
}
|
|
206
|
+
v(fn) {
|
|
207
|
+
const name = `v${this.next++}`;
|
|
208
|
+
fn.vars.push(name);
|
|
209
|
+
return name;
|
|
210
|
+
}
|
|
211
|
+
label() {
|
|
212
|
+
return `L${this.next++}`;
|
|
213
|
+
}
|
|
214
|
+
unit(i) {
|
|
215
|
+
return this.kind === 0 ? `I.charCodeAt(${i})` : `I[${i}]`;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* How generated code reads an encoding's code units: units below `fast`
|
|
219
|
+
* are whole code points, `width` input units long. `guard(i)` says that a
|
|
220
|
+
* whole unit is available at `i`.
|
|
221
|
+
*/
|
|
222
|
+
reader(enc) {
|
|
223
|
+
if (enc === utf16le || enc === utf16be) {
|
|
224
|
+
const [lo, hi] = enc === utf16le ? ["", "+1"] : ["+1", ""];
|
|
225
|
+
return {
|
|
226
|
+
width: 2,
|
|
227
|
+
fast: 55296,
|
|
228
|
+
guard: (i) => `${i}+1<E`,
|
|
229
|
+
unit: (i) => `(I[${i}${lo}]|I[${i}${hi}]<<8)`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
width: 1,
|
|
234
|
+
fast: enc.unit === 1 ? fastLimit(enc) : 0,
|
|
235
|
+
guard: (i) => `${i}<E`,
|
|
236
|
+
unit: (i) => this.unit(i)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** The function implementing a rule, generating it on first use. */
|
|
240
|
+
rule(p, used) {
|
|
241
|
+
const rules = this.rules[used ? 1 : 0];
|
|
242
|
+
let name = rules.get(p);
|
|
243
|
+
if (name !== void 0) return name;
|
|
244
|
+
name = `f${this.next++}`;
|
|
245
|
+
rules.set(p, name);
|
|
246
|
+
const fn = new Fn();
|
|
247
|
+
const out = used ? this.v(fn) : null;
|
|
248
|
+
const done = this.label();
|
|
249
|
+
const body = this.inline(p, used, out, `break ${done};`, fn, 0);
|
|
250
|
+
this.functions.push(`function ${name}(p){const I=$I,E=$E;${fn.vars.length > 0 ? `let ${fn.vars.join(",")};` : ""}${done}:{${body}P=p;return ${out ?? "void 0"}}return F}`);
|
|
251
|
+
return name;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Code for `p`: on success it falls through with `p` advanced and, when
|
|
255
|
+
* `out` is given, the value in `out`; on failure it runs `fail`.
|
|
256
|
+
*/
|
|
257
|
+
gen(p, used, out, fail, fn, depth) {
|
|
258
|
+
const node = nodeOf(p);
|
|
259
|
+
if (node._tag === "Suspend") return this.call(this.rule(node.resolve(), used), out, fail);
|
|
260
|
+
if (!isLeaf(node) && ((this.refs.get(p) ?? 0) > 1 && !this.small(p) || depth > 32)) return this.call(this.rule(p, used), out, fail);
|
|
261
|
+
return this.inline(p, used, out, fail, fn, depth);
|
|
262
|
+
}
|
|
263
|
+
/** Whether a parser is small enough to inline wherever it is used. */
|
|
264
|
+
small(p) {
|
|
265
|
+
let budget = 6;
|
|
266
|
+
const visit = (q) => {
|
|
267
|
+
const node = nodeOf(q);
|
|
268
|
+
if (--budget < 0 || node._tag === "Suspend" || node._tag === "Escaped" || node._tag === "Dispatch") return false;
|
|
269
|
+
return children(node).every(visit);
|
|
270
|
+
};
|
|
271
|
+
return visit(p);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* In a resumable build: token code (which fails by breaking out of `bad`)
|
|
275
|
+
* that read within `lookahead` units of the end of a partial input re-runs
|
|
276
|
+
* interpreted, since it may have to report `Incomplete` there. Token code
|
|
277
|
+
* leaves `p` where it stopped reading, even when it fails.
|
|
278
|
+
*/
|
|
279
|
+
partialSafe(p, code, bad, lookahead, out, fail, fn) {
|
|
280
|
+
const ahead = Math.max(lookahead, Margin);
|
|
281
|
+
const st = this.v(fn);
|
|
282
|
+
const ok = this.v(fn);
|
|
283
|
+
const t = this.v(fn);
|
|
284
|
+
const leaf = this.label();
|
|
285
|
+
return `${st}=p;${ok}=false;${leaf}:{${bad}:{${code}${ok}=true;}if(!$S.partial||p+${ahead}<=E){if(${ok})break ${leaf};${fail}}p=${st};$S.pos=p;${t}=${this.const(p.step)}($S);if(${t}===F){${fail}}p=$S.pos;${out === null ? "" : `${out}=${t};`}}`;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Token code for string and byte input, or the parser's step for input
|
|
289
|
+
* the code can't read. In a resumable build, code that read within
|
|
290
|
+
* `lookahead` units of the end of a partial input re-runs interpreted.
|
|
291
|
+
*/
|
|
292
|
+
leaf(p, node, used, out, fail, fn, lookahead = Margin) {
|
|
293
|
+
if (!this.native(node.encoding)) return this.delegate(p, used, out, fail, fn);
|
|
294
|
+
if (!this.resumable) return this.token(node, out, fail, fn) ?? this.delegate(p, used, out, fail, fn);
|
|
295
|
+
const bad = this.label();
|
|
296
|
+
const code = this.token(node, out, `break ${bad};`, fn);
|
|
297
|
+
if (code === void 0) return this.delegate(p, used, out, fail, fn);
|
|
298
|
+
return this.partialSafe(p, code, bad, lookahead, out, fail, fn);
|
|
299
|
+
}
|
|
300
|
+
/** Whether a token parser reads array input as such (`Token`'s, on an array). */
|
|
301
|
+
array(enc) {
|
|
302
|
+
return this.kind === 2 && enc === void 0;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Token code for array input, built by `code` with its failure code. Its
|
|
306
|
+
* set's predicate (or membership test) is called on the element, as the
|
|
307
|
+
* interpreter calls it. In a resumable build, code that read the last
|
|
308
|
+
* element of a partial input re-runs interpreted.
|
|
309
|
+
*/
|
|
310
|
+
arrayLeaf(p, code, out, fail, fn) {
|
|
311
|
+
if (!this.resumable) return code(fail);
|
|
312
|
+
const bad = this.label();
|
|
313
|
+
return this.partialSafe(p, code(`break ${bad};`), bad, 1, out, fail, fn);
|
|
314
|
+
}
|
|
315
|
+
arrayAny(out, fail) {
|
|
316
|
+
return `if(p>=E){${BT}${fail}}${out === null ? "" : `${out}=I[p];`}p++;`;
|
|
317
|
+
}
|
|
318
|
+
arrayOneOf(node, out, fail) {
|
|
319
|
+
return `if(p>=E||${this.const(compileAny(node.set).forCode.token)}(I[p])===${node.negate}){${BT}${fail}}${out === null ? "" : `${out}=I[p];`}p++;`;
|
|
320
|
+
}
|
|
321
|
+
arrayTakeWhile(node, out, fail, fn) {
|
|
322
|
+
const m = this.const(compileAny(node.set).forCode.token);
|
|
323
|
+
const st = this.v(fn);
|
|
324
|
+
return `${st}=p;while(p<E${node.max === Infinity ? "" : `&&p-${st}<${node.max}`}&&${m}(I[p])!==${node.negate})p++;` + (node.min > 0 ? `if(p-${st}<${node.min}){${BT}${fail}}` : "") + (out === null ? "" : `${out}=I.slice(${st},p);`);
|
|
325
|
+
}
|
|
326
|
+
call(name, out, fail) {
|
|
327
|
+
if (out === null) return `if(${name}(p)===F){${fail}}p=P;`;
|
|
328
|
+
return `${out}=${name}(p);if(${out}===F){${fail}}p=P;`;
|
|
329
|
+
}
|
|
330
|
+
/** Runs the closure-compiled step of `p` on the shared state. */
|
|
331
|
+
delegate(p, used, out, fail, fn) {
|
|
332
|
+
const step = this.const(this.closure.compile(p, used));
|
|
333
|
+
const t = out ?? this.v(fn);
|
|
334
|
+
return `$S.pos=p;${t}=${step}($S);if(${t}===F){${fail}}p=$S.pos;`;
|
|
335
|
+
}
|
|
336
|
+
native(enc) {
|
|
337
|
+
return enc === void 0 ? this.kind !== 2 : kindOfEncoding(enc) === this.kind;
|
|
338
|
+
}
|
|
339
|
+
inline(p, used, out, fail, fn, depth) {
|
|
340
|
+
const node = nodeOf(p);
|
|
341
|
+
const set = (value) => out === null ? "" : `${out}=${value};`;
|
|
342
|
+
const d = depth + 1;
|
|
343
|
+
switch (node._tag) {
|
|
344
|
+
case "Succeed": return set(this.lit(node.value));
|
|
345
|
+
case "Sync": return used ? set(`${this.const(node.evaluate)}()`) : "";
|
|
346
|
+
case "Fail": return `$S.mode=${JSON.stringify(node.mode)};${fail}`;
|
|
347
|
+
case "Eof": return `if(p<E){${BT}${fail}}` + (this.resumable ? `if($S.partial){$S.mode="Incomplete";${fail}}` : "") + set("void 0");
|
|
348
|
+
case "Location": return set("p");
|
|
349
|
+
case "RestLen": return set("E-p");
|
|
350
|
+
case "Suspend": return this.call(this.rule(node.resolve(), used), out, fail);
|
|
351
|
+
case "Any":
|
|
352
|
+
if (this.array(node.encoding)) return this.arrayLeaf(p, (f) => this.arrayAny(out, f), out, fail, fn);
|
|
353
|
+
return this.leaf(p, node, used, out, fail, fn);
|
|
354
|
+
case "OneOf":
|
|
355
|
+
if (this.array(node.encoding)) return this.arrayLeaf(p, (f) => this.arrayOneOf(node, out, f), out, fail, fn);
|
|
356
|
+
return this.leaf(p, node, used, out, fail, fn);
|
|
357
|
+
case "TakeWhile":
|
|
358
|
+
if (this.array(node.encoding)) return this.arrayLeaf(p, (f) => this.arrayTakeWhile(node, out, f, fn), out, fail, fn);
|
|
359
|
+
return this.leaf(p, node, used, out, fail, fn);
|
|
360
|
+
case "Literal": return this.leaf(p, node, used, out, fail, fn, node.literal.length * 4);
|
|
361
|
+
case "Escaped":
|
|
362
|
+
if (!this.native(node.encoding) || this.resumable) return this.delegate(p, used, out, fail, fn);
|
|
363
|
+
return this.escaped(p, node, out, fail, fn, d);
|
|
364
|
+
case "Map": {
|
|
365
|
+
if (!used) return this.gen(node.self, false, null, fail, fn, d);
|
|
366
|
+
if (node.f === toFloat && out !== null) {
|
|
367
|
+
const code = this.float(node, out, fail, fn, d);
|
|
368
|
+
if (code !== void 0) return code;
|
|
369
|
+
}
|
|
370
|
+
if (node.f === fromEntries && out !== null && !this.resumable && buildsEntries(node.self)) return this.entries(node.self, out, fail, fn, d);
|
|
371
|
+
const t = this.v(fn);
|
|
372
|
+
return this.gen(node.self, true, t, fail, fn, d) + set(`${this.const(node.f)}(${t})`);
|
|
373
|
+
}
|
|
374
|
+
case "As": return this.gen(node.self, false, null, fail, fn, d) + (used ? set(this.lit(node.value)) : "");
|
|
375
|
+
case "Tap": {
|
|
376
|
+
const t = out ?? this.v(fn);
|
|
377
|
+
return this.gen(node.self, true, t, fail, fn, d) + `${this.const(node.f)}(${t});`;
|
|
378
|
+
}
|
|
379
|
+
case "TryMap":
|
|
380
|
+
case "FilterMap": {
|
|
381
|
+
const t = this.v(fn);
|
|
382
|
+
const r = this.v(fn);
|
|
383
|
+
const [tag, field] = node._tag === "TryMap" ? ["Success", "success"] : ["Some", "value"];
|
|
384
|
+
return this.gen(node.self, true, t, fail, fn, d) + `${r}=${this.const(node.f)}(${t});if(${r}._tag!=="${tag}"){${BT}${fail}}${set(`${r}.${field}`)}`;
|
|
385
|
+
}
|
|
386
|
+
case "Filter": {
|
|
387
|
+
const t = out ?? this.v(fn);
|
|
388
|
+
return this.gen(node.self, true, t, fail, fn, d) + `if(!${this.const(node.predicate)}(${t})){${BT}${fail}}`;
|
|
389
|
+
}
|
|
390
|
+
case "FlatMap": {
|
|
391
|
+
const t = this.v(fn);
|
|
392
|
+
const r = out ?? this.v(fn);
|
|
393
|
+
this.closure.note(p, node, "the parser returned by the function runs interpreted");
|
|
394
|
+
return this.gen(node.self, true, t, fail, fn, d) + `$S.pos=p;${r}=${this.const(node.f)}(${t}).step($S);if(${r}===F){${fail}}p=$S.pos;`;
|
|
395
|
+
}
|
|
396
|
+
case "Taken": {
|
|
397
|
+
if (node.encoding !== void 0 && !this.native(node.encoding)) return this.delegate(p, used, out, fail, fn);
|
|
398
|
+
const st = this.v(fn);
|
|
399
|
+
const body = `${st}=p;` + this.gen(node.self, false, null, fail, fn, d);
|
|
400
|
+
if (out === null && node.encoding === void 0) return body;
|
|
401
|
+
if (node.encoding === void 0) return body + set(this.kind === 0 ? `I.slice(${st},p)` : this.kind === 1 ? `I.subarray(${st},p)` : `$S.slice(${st},p)`);
|
|
402
|
+
const t = out ?? this.v(fn);
|
|
403
|
+
return body + `${t}=R.textOf(${this.const(node.encoding)},${fastLimit(node.encoding)},$S,${st},p);if(${t}===F){${fail}}`;
|
|
404
|
+
}
|
|
405
|
+
case "WithTaken":
|
|
406
|
+
case "WithSpan": {
|
|
407
|
+
const st = this.v(fn);
|
|
408
|
+
const t = this.v(fn);
|
|
409
|
+
const extra = node._tag === "WithSpan" ? `{start:${st},end:p}` : `$S.slice(${st},p)`;
|
|
410
|
+
return `${st}=p;` + this.gen(node.self, true, t, fail, fn, d) + set(`[${t},${extra}]`);
|
|
411
|
+
}
|
|
412
|
+
case "Span": {
|
|
413
|
+
const st = this.v(fn);
|
|
414
|
+
return `${st}=p;` + this.gen(node.self, false, null, fail, fn, d) + set(`{start:${st},end:p}`);
|
|
415
|
+
}
|
|
416
|
+
case "Mode": {
|
|
417
|
+
const ok = this.label();
|
|
418
|
+
const bad = this.label();
|
|
419
|
+
return `${ok}:{${bad}:{${this.gen(node.self, used, out, `break ${bad};`, fn, d)}break ${ok};}if($S.mode===${JSON.stringify(node.from)})$S.mode=${JSON.stringify(node.to)};${fail}}`;
|
|
420
|
+
}
|
|
421
|
+
case "Context":
|
|
422
|
+
case "Expected":
|
|
423
|
+
case "MapError":
|
|
424
|
+
case "OrFail":
|
|
425
|
+
case "Trace":
|
|
426
|
+
case "Syntax":
|
|
427
|
+
case "RetryAfter": return this.gen(node.self, used, out, fail, fn, d);
|
|
428
|
+
case "ResumeAfter": {
|
|
429
|
+
if (!used) return this.gen(node.self, false, null, fail, fn, d);
|
|
430
|
+
const t = this.v(fn);
|
|
431
|
+
return this.gen(node.self, true, t, fail, fn, d) + set(`R.some(${t})`);
|
|
432
|
+
}
|
|
433
|
+
case "Opt":
|
|
434
|
+
case "OrElseSucceed": {
|
|
435
|
+
const st = this.v(fn);
|
|
436
|
+
const ok = this.label();
|
|
437
|
+
const bad = this.label();
|
|
438
|
+
const t = out === null ? null : this.v(fn);
|
|
439
|
+
const some = node._tag === "Opt" ? `R.some(${t})` : `${t}`;
|
|
440
|
+
const orElse = node._tag === "Opt" ? "R.none()" : `${this.const(node.value)}()`;
|
|
441
|
+
return `${st}=p;${ok}:{${bad}:{${this.gen(node.self, used, t, `break ${bad};`, fn, d)}${set(some)}break ${ok};}if($S.mode!=="Backtrack"){${fail}}p=${st};${set(orElse)}}`;
|
|
442
|
+
}
|
|
443
|
+
case "Peek": {
|
|
444
|
+
const st = this.v(fn);
|
|
445
|
+
return `${st}=p;${this.gen(node.self, used, out, fail, fn, d)}p=${st};`;
|
|
446
|
+
}
|
|
447
|
+
case "Not": {
|
|
448
|
+
const st = this.v(fn);
|
|
449
|
+
const ok = this.label();
|
|
450
|
+
const bad = this.label();
|
|
451
|
+
return `${st}=p;${ok}:{${bad}:{${this.gen(node.self, false, null, `break ${bad};`, fn, d)}p=${st};${BT}${fail}}if($S.mode!=="Backtrack"){${fail}}p=${st};}${set("void 0")}`;
|
|
452
|
+
}
|
|
453
|
+
case "Seq": return this.seq(node, used, out, fail, fn, d);
|
|
454
|
+
case "Struct": {
|
|
455
|
+
if (out === null) return node.parsers.map((q) => this.gen(q, false, null, fail, fn, d)).join("");
|
|
456
|
+
const vars = node.parsers.map(() => this.v(fn));
|
|
457
|
+
const code = node.parsers.map((q, i) => this.gen(q, true, vars[i], fail, fn, d)).join("");
|
|
458
|
+
if (node.keys.includes("__proto__")) return code + `${out}={};` + node.keys.map((k, i) => `${out}[${JSON.stringify(k)}]=${vars[i]};`).join("");
|
|
459
|
+
return code + `${out}={${node.keys.map((k, i) => `${JSON.stringify(k)}:${vars[i]}`).join(",")}};`;
|
|
460
|
+
}
|
|
461
|
+
case "ZipWith": {
|
|
462
|
+
if (out === null) return this.gen(node.self, false, null, fail, fn, d) + this.gen(node.that, false, null, fail, fn, d);
|
|
463
|
+
const x = this.v(fn);
|
|
464
|
+
const y = this.v(fn);
|
|
465
|
+
return this.gen(node.self, true, x, fail, fn, d) + this.gen(node.that, true, y, fail, fn, d) + `${out}=${this.const(node.f)}(${x},${y});`;
|
|
466
|
+
}
|
|
467
|
+
case "Alt": return this.alt(node, used, out, fail, fn, d);
|
|
468
|
+
case "Dispatch": return this.dispatch(p, node, used, out, fail, fn, d);
|
|
469
|
+
case "Repeat":
|
|
470
|
+
case "Fold": return this.repeat(node, used, out, fail, fn, d);
|
|
471
|
+
case "Separated": return this.separated(node, used, out, fail, fn, d);
|
|
472
|
+
default: return this.delegate(p, used, out, fail, fn);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* `Ascii.float` and `Text.float` (`map(floatText, toFloat)`): numbers in
|
|
477
|
+
* decimal notation are matched and converted in one pass by
|
|
478
|
+
* `scanFloatString` or `scanFloatBytes`, without slicing the input; the
|
|
479
|
+
* rest (`nan`, `inf`, failures before a digit) runs the parser. Returns
|
|
480
|
+
* `undefined` when this does not apply.
|
|
481
|
+
*/
|
|
482
|
+
float(node, out, fail, fn, d) {
|
|
483
|
+
if (!floatTexts.has(node.self) || this.kind === 2) return void 0;
|
|
484
|
+
let taken = nodeOf(node.self);
|
|
485
|
+
while (taken._tag === "Expected") taken = nodeOf(taken.self);
|
|
486
|
+
if (taken._tag !== "Taken") return void 0;
|
|
487
|
+
const enc = taken.encoding;
|
|
488
|
+
if (enc !== void 0 && !(this.native(enc) && enc.unit === 1 && enc.asciiCompatible)) return void 0;
|
|
489
|
+
const t = this.v(fn);
|
|
490
|
+
const scan = this.const(floatScan);
|
|
491
|
+
const near = this.resumable ? `||$S.partial&&${scan}.read+${Margin}>E` : "";
|
|
492
|
+
return `${out}=${this.kind === 0 ? "R.scanFloatString" : "R.scanFloatBytes"}(I,p,E);if(${out}===void 0${near}){${this.gen(node.self, true, t, fail, fn, d)}${out}=${this.const(toFloat)}(${t})}else if(${out}===F){${BT}${fail}}else p=${scan}.end;`;
|
|
493
|
+
}
|
|
494
|
+
seq(node, used, out, fail, fn, d) {
|
|
495
|
+
const output = node.output;
|
|
496
|
+
if (out === null || !used) return node.parsers.map((q) => this.gen(q, false, null, fail, fn, d)).join("");
|
|
497
|
+
if (Predicate.isNumber(output)) return node.parsers.map((q, i) => this.gen(q, i === output, i === output ? out : null, fail, fn, d)).join("");
|
|
498
|
+
const keep = (i) => output === "tuple" || output.includes(i);
|
|
499
|
+
const vars = node.parsers.map((_, i) => keep(i) ? this.v(fn) : null);
|
|
500
|
+
return node.parsers.map((q, i) => this.gen(q, keep(i), vars[i], fail, fn, d)).join("") + `${out}=[${vars.filter((v) => v !== null).join(",")}];`;
|
|
501
|
+
}
|
|
502
|
+
/** A test that the unit `u` may start `first` (false for -1, the end of the input). */
|
|
503
|
+
startTest(first, u) {
|
|
504
|
+
const runs = runsOf(first.units);
|
|
505
|
+
const small = rangeTest(runs, u);
|
|
506
|
+
const low = small ?? `${this.const(first.units)}[${u}]===1`;
|
|
507
|
+
if (!first.high) return small !== void 0 ? `(${low})` : `(${u}<${TableSize}&&${low})`;
|
|
508
|
+
return `(${u}>=${TableSize}||${low})`;
|
|
509
|
+
}
|
|
510
|
+
alt(node, used, out, fail, fn, d) {
|
|
511
|
+
const parsers = node.parsers;
|
|
512
|
+
if (parsers.length === 0) return `${BT}${fail}`;
|
|
513
|
+
if (parsers.length === 1) return this.gen(parsers[0], used, out, fail, fn, d);
|
|
514
|
+
const firsts = this.kind === 2 ? void 0 : parsers.map((q) => this.closure.firsts.of(q));
|
|
515
|
+
const prune = firsts !== void 0 && planAlt(firsts) !== void 0;
|
|
516
|
+
const st = this.v(fn);
|
|
517
|
+
const u = prune ? this.v(fn) : "";
|
|
518
|
+
const done = this.label();
|
|
519
|
+
const read = this.resumable ? `$S.partial&&p+${Margin}>E?-2:p<E?${this.unit("p")}:-1` : `p<E?${this.unit("p")}:-1`;
|
|
520
|
+
let code = `${st}=p;${prune ? `${u}=${read};` : ""}${done}:{`;
|
|
521
|
+
parsers.forEach((q, i) => {
|
|
522
|
+
const bad = this.label();
|
|
523
|
+
const branch = `${bad}:{${this.gen(q, used, out, `break ${bad};`, fn, d)}break ${done};}if($S.mode!=="Backtrack"){${fail}}p=${st};`;
|
|
524
|
+
const f = firsts?.[i];
|
|
525
|
+
code += prune && f !== void 0 && f.mode === Strict ? `if(${this.resumable ? `${u}===-2||` : ""}${this.startTest(f, u)}){${branch}}` : branch;
|
|
526
|
+
});
|
|
527
|
+
return code + `${BT}${fail}}`;
|
|
528
|
+
}
|
|
529
|
+
dispatch(p, node, used, out, fail, fn, d) {
|
|
530
|
+
const choose = this.const(node.choose);
|
|
531
|
+
if (node.cases === void 0) {
|
|
532
|
+
this.closure.note(p, node, "the parser returned by the function of cases runs interpreted");
|
|
533
|
+
const st = this.v(fn);
|
|
534
|
+
const k = this.v(fn);
|
|
535
|
+
const q = this.v(fn);
|
|
536
|
+
const r = out ?? this.v(fn);
|
|
537
|
+
return `${st}=p;${this.gen(node.selector, true, k, fail, fn, d)}${q}=${choose}(${k});if(${q}===void 0){p=${st};${BT}${fail}}$S.pos=p;${r}=${q}.step($S);if(${r}===F){${fail}}p=$S.pos;`;
|
|
538
|
+
}
|
|
539
|
+
const cases = /* @__PURE__ */ new Map();
|
|
540
|
+
for (const key of Reflect.ownKeys(node.cases)) {
|
|
541
|
+
const q = node.cases[key];
|
|
542
|
+
if (!cases.has(q)) cases.set(q, cases.size);
|
|
543
|
+
}
|
|
544
|
+
if (node.otherwise !== void 0 && !cases.has(node.otherwise)) cases.set(node.otherwise, cases.size);
|
|
545
|
+
const index = this.const(cases);
|
|
546
|
+
const general = (inline) => {
|
|
547
|
+
const st = this.v(fn);
|
|
548
|
+
const k = this.v(fn);
|
|
549
|
+
const q = this.v(fn);
|
|
550
|
+
const sw = this.label();
|
|
551
|
+
let code = `${st}=p;${this.gen(node.selector, true, k, fail, fn, d)}${q}=${choose}(${k});if(${q}===void 0){p=${st};${BT}${fail}}${sw}:switch(${index}.get(${q})){`;
|
|
552
|
+
for (const [c, i] of cases) {
|
|
553
|
+
const body = inline ? this.gen(c, used, out, fail, fn, d) : this.call(this.rule(c, used), out, fail);
|
|
554
|
+
code += `case ${i}:{${body}break ${sw};}`;
|
|
555
|
+
}
|
|
556
|
+
const r = out ?? this.v(fn);
|
|
557
|
+
return code + `default:{$S.pos=p;${r}=${q}.step($S);if(${r}===F){${fail}}p=$S.pos;}}`;
|
|
558
|
+
};
|
|
559
|
+
let sel = nodeOf(node.selector);
|
|
560
|
+
const peek = sel._tag === "Peek";
|
|
561
|
+
if (peek) sel = nodeOf(sel.self);
|
|
562
|
+
if (sel._tag !== "Any" || !this.native(sel.encoding)) return general(true);
|
|
563
|
+
const enc = sel.encoding;
|
|
564
|
+
const r = enc === void 0 ? void 0 : this.reader(enc);
|
|
565
|
+
const limit = r === void 0 ? this.kind === 0 ? 55296 : TableSize : Math.min(r.fast, 55296);
|
|
566
|
+
const size = Math.min(limit, TableSize);
|
|
567
|
+
if (size === 0) return general(true);
|
|
568
|
+
const groups = /* @__PURE__ */ new Map();
|
|
569
|
+
for (let unit = 0; unit < size; unit++) {
|
|
570
|
+
const q = node.choose(enc === void 0 && this.kind === 1 ? unit : charOf(unit));
|
|
571
|
+
if (q === void 0) continue;
|
|
572
|
+
let g = groups.get(q);
|
|
573
|
+
if (g === void 0) groups.set(q, g = []);
|
|
574
|
+
g.push(unit);
|
|
575
|
+
}
|
|
576
|
+
const u = this.v(fn);
|
|
577
|
+
const sw = this.label();
|
|
578
|
+
const read = r === void 0 ? `p<E?${this.unit("p")}:-1` : `${r.guard("p")}?${r.unit("p")}:p<E?${size}:-1`;
|
|
579
|
+
let code = `${u}=${this.resumable ? `$S.partial&&p+${Margin}>E?${size}:` : ""}${read};${sw}:switch(${u}){`;
|
|
580
|
+
for (const [q, units] of groups) code += units.map((x) => `case ${x}:`).join("") + `{${peek ? "" : `p+=${r?.width ?? 1};`}${this.gen(q, used, out, fail, fn, d)}break ${sw};}`;
|
|
581
|
+
code += `case -1:{${BT}${fail}}default:{if(${u}<${size}){${BT}${fail}}${general(false)}}}`;
|
|
582
|
+
return code;
|
|
583
|
+
}
|
|
584
|
+
repeat(node, used, out, fail, fn, d, collect) {
|
|
585
|
+
const { max, min } = node;
|
|
586
|
+
const loop = this.label();
|
|
587
|
+
const bad = this.label();
|
|
588
|
+
const n = this.v(fn);
|
|
589
|
+
const st = this.v(fn);
|
|
590
|
+
const isFold = node._tag === "Fold";
|
|
591
|
+
const cond = isFold ? node.while : void 0;
|
|
592
|
+
const keep = out !== null && used && collect === void 0;
|
|
593
|
+
const tracked = keep || cond !== void 0;
|
|
594
|
+
const x = tracked ? this.v(fn) : null;
|
|
595
|
+
const acc = out !== null && keep ? out : this.v(fn);
|
|
596
|
+
let init = collect?.init ?? "";
|
|
597
|
+
let add = "";
|
|
598
|
+
if (tracked) {
|
|
599
|
+
if (isFold) {
|
|
600
|
+
init = `${acc}=${this.const(node.init)}();`;
|
|
601
|
+
add = `${acc}=${this.const(node.f)}(${acc},${x});`;
|
|
602
|
+
} else {
|
|
603
|
+
init = `${acc}=[];`;
|
|
604
|
+
add = `${acc}.push(${x});`;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const check = max === Infinity ? `if(p===${st}){$S.mode="Cut";${fail}}` : "";
|
|
608
|
+
const bound = max === Infinity ? "" : `${n}<${max}`;
|
|
609
|
+
const r = this.resumable ? this.resumePoint(fn) : void 0;
|
|
610
|
+
const save = r === void 0 ? "" : `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${acc},count:${n},pos:${st}});`;
|
|
611
|
+
const element = collect === void 0 ? `${this.gen(node.self, tracked, x, `break ${bad};`, fn, d)}${check}${add}` : collect.element(`break ${bad};`, check);
|
|
612
|
+
const body = `${loop}:for(;${bound};${n}++){${cond === void 0 ? "" : `if(${min > 0 ? `${n}>=${min}&&` : ""}!${this.const(cond)}(${acc}))break ${loop};`}${st}=p;${bad}:{${element}continue ${loop};}${save}if($S.mode!=="Backtrack"${min > 0 ? `||${n}<${min}` : ""}){${fail}}p=${st};break ${loop};}`;
|
|
613
|
+
if (r === void 0) return `${init}${n}=0;${body}${collect?.finish ?? ""}`;
|
|
614
|
+
return r.wrap(acc, `${acc}=cp.acc;${n}=cp.count;p=cp.pos;`, `${init}${n}=0;`, body);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* In a resumable build, a repetition entered at `entry` may restart from a
|
|
618
|
+
* checkpoint (`restore`, with the checkpoint in `cp`), or return the result
|
|
619
|
+
* of a `Done` one; a resumed repetition records its result when it ends.
|
|
620
|
+
*/
|
|
621
|
+
resumePoint(fn) {
|
|
622
|
+
const key = this.const({});
|
|
623
|
+
const entry = this.v(fn);
|
|
624
|
+
const resumed = this.v(fn);
|
|
625
|
+
const done = this.label();
|
|
626
|
+
return {
|
|
627
|
+
key,
|
|
628
|
+
entry,
|
|
629
|
+
resumed,
|
|
630
|
+
wrap: (value, restore, fresh, body) => `${entry}=p;${resumed}=false;${done}:{if($S.resume!==void 0){const cp=R.take($S,${key},p);if(cp instanceof R.Done){p=cp.pos;${value}=cp.value;break ${done};}if(cp!==void 0){${restore}${resumed}=true;}}if(!${resumed}){${fresh}}${body}if(${resumed})R.done($S,${key},${entry},${value},p);}`
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
separated(node, used, out, fail, fn, d, collect) {
|
|
634
|
+
const { max, min } = node;
|
|
635
|
+
const keep = out !== null && used && collect === void 0;
|
|
636
|
+
if (max === 0) return collect !== void 0 ? collect.init + collect.finish : keep ? `${out}=[];` : "";
|
|
637
|
+
const x = keep ? this.v(fn) : null;
|
|
638
|
+
const acc = out !== null && keep ? out : this.v(fn);
|
|
639
|
+
const n = this.v(fn);
|
|
640
|
+
const st = this.v(fn);
|
|
641
|
+
const done = this.label();
|
|
642
|
+
const loop = this.label();
|
|
643
|
+
const push = keep ? `${acc}.push(${x});` : "";
|
|
644
|
+
const r = this.resumable ? this.resumePoint(fn) : void 0;
|
|
645
|
+
const save = r === void 0 ? "" : `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${acc},count:${n},pos:${st}});`;
|
|
646
|
+
const attempt = (body, success, fatal, checkpoint) => {
|
|
647
|
+
const ok = this.label();
|
|
648
|
+
const bad = this.label();
|
|
649
|
+
return `${ok}:{${bad}:{${body(`break ${bad};`)}${success}break ${ok};}${checkpoint}if($S.mode!=="Backtrack"${fatal}){${fail}}p=${st};break ${done};}`;
|
|
650
|
+
};
|
|
651
|
+
const self = (f) => collect === void 0 ? this.gen(node.self, keep, x, f, fn, d) : collect.element(f, "");
|
|
652
|
+
const sep = (f) => this.gen(node.separator, false, null, f, fn, d);
|
|
653
|
+
const check = max === Infinity ? `if(p===${st}){$S.mode="Cut";${fail}}` : "";
|
|
654
|
+
const first = `${collect?.init ?? (keep ? `${acc}=[];` : "")}${n}=0;${st}=p;` + attempt(self, `${push}${n}++;`, min > 0 ? "||true" : "", "");
|
|
655
|
+
const rest = `${loop}:while(${max === Infinity ? "true" : `${n}<${max}`}){${st}=p;` + attempt(sep, "", `||${n}<${min}`, save) + check + attempt(self, `${push}${n}++;continue ${loop};`, `||${n}<${min}`, save) + `}`;
|
|
656
|
+
if (r === void 0) return `${done}:{${first}${rest}}${collect?.finish ?? ""}`;
|
|
657
|
+
return r.wrap(acc, `${acc}=cp.acc;${n}=cp.count;p=cp.pos;`, "", `${done}:{if(!${r.resumed}){${first}}${rest}}`);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* `map(self, Object.fromEntries)`, where `buildsEntries(self)`: builds the
|
|
661
|
+
* object directly, assigning each entry as it is parsed (see
|
|
662
|
+
* `internal/entries.ts`), without the pairs and their array. `cut`,
|
|
663
|
+
* `preceded`, `terminated` and diagnostics around the repetition are
|
|
664
|
+
* compiled as usual around it.
|
|
665
|
+
*/
|
|
666
|
+
entries(p, out, fail, fn, d) {
|
|
667
|
+
const node = nodeOf(p);
|
|
668
|
+
switch (node._tag) {
|
|
669
|
+
case "Mode": {
|
|
670
|
+
const ok = this.label();
|
|
671
|
+
const bad = this.label();
|
|
672
|
+
return `${ok}:{${bad}:{${this.entries(node.self, out, `break ${bad};`, fn, d)}break ${ok};}if($S.mode===${JSON.stringify(node.from)})$S.mode=${JSON.stringify(node.to)};${fail}}`;
|
|
673
|
+
}
|
|
674
|
+
case "Seq": {
|
|
675
|
+
const i = node.output;
|
|
676
|
+
return node.parsers.map((q, j) => j === i ? this.entries(q, out, fail, fn, d) : this.gen(q, false, null, fail, fn, d)).join("");
|
|
677
|
+
}
|
|
678
|
+
case "Repeat":
|
|
679
|
+
case "Separated": {
|
|
680
|
+
const pair = pairOf(node.self);
|
|
681
|
+
if (pair === void 0) {
|
|
682
|
+
const t = this.v(fn);
|
|
683
|
+
return this.gen(p, true, t, fail, fn, d) + `${out}=${this.const(fromEntries)}(${t});`;
|
|
684
|
+
}
|
|
685
|
+
const deferred = this.v(fn);
|
|
686
|
+
const key = this.v(fn);
|
|
687
|
+
const value = this.v(fn);
|
|
688
|
+
const collect = {
|
|
689
|
+
init: `${out}={};${deferred}=void 0;`,
|
|
690
|
+
element: (f, check) => pair.parsers.map((q, j) => this.gen(q, j === pair.key || j === pair.value, j === pair.key ? key : j === pair.value ? value : null, f, fn, d)).join("") + check + `if(${deferred}===void 0&&R.isPlainKey(${key}))${out}[${key}]=${value};else ${deferred}=R.addEntry(${out},${deferred},${key},${value});`,
|
|
691
|
+
finish: `if(${deferred}!==void 0)R.flushEntries(${out},${deferred});`
|
|
692
|
+
};
|
|
693
|
+
return node._tag === "Repeat" ? this.repeat(node, true, out, fail, fn, d, collect) : this.separated(node, true, out, fail, fn, d, collect);
|
|
694
|
+
}
|
|
695
|
+
default: return this.entries(node.self, out, fail, fn, d);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/** An expression: is the unit `c`, known to be below `size` (at most `TableSize`), accepted? */
|
|
699
|
+
member(accept, c, size = TableSize) {
|
|
700
|
+
return tableTest(accept, c, size) ?? `${this.const(accept)}[${c}]===1`;
|
|
701
|
+
}
|
|
702
|
+
/** An expression: is the code point `c` (at least `TableSize`) accepted? */
|
|
703
|
+
memberHigh(set, negate, c) {
|
|
704
|
+
if (set.ranges !== void 0 && !set.ranges.some(([, b]) => b >= TableSize)) return negate ? "true" : "false";
|
|
705
|
+
return `${this.const(set.test)}(${c})!==${negate}`;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* An expression: does a `Text` set accept the unit `c`, known to be below
|
|
709
|
+
* `r.fast`? Code points from `TableSize` up are tested by the set's
|
|
710
|
+
* function, unless its ranges show they are all rejected.
|
|
711
|
+
*/
|
|
712
|
+
textAccepts(r, cs, accept, negate, c) {
|
|
713
|
+
if (r.fast <= TableSize) return `(${this.member(accept, c, r.fast)})`;
|
|
714
|
+
const member = this.member(accept, c);
|
|
715
|
+
if (reachesAbove(cs, TableSize)) return `(${c}<${TableSize}?${member}:${this.const(cs.test)}(${c})!==${negate})`;
|
|
716
|
+
return negate ? `(${c}>=${TableSize}||${member})` : `(${c}<${TableSize}&&${member})`;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* An expression: does a `Text` set reject `c`, a code point that `decode`
|
|
720
|
+
* returned? The built-in encodings only decode code points from `r.fast`
|
|
721
|
+
* up there.
|
|
722
|
+
*/
|
|
723
|
+
textRejectsDecoded(enc, r, cs, negate, c) {
|
|
724
|
+
if (r.fast > 0 && builtinEncodings.has(enc) && !reachesAbove(cs, r.fast)) return String(!negate);
|
|
725
|
+
return `${this.const(cs.test)}(${c})===${negate}`;
|
|
726
|
+
}
|
|
727
|
+
token(node, out, fail, fn) {
|
|
728
|
+
const set = (value) => out === null ? "" : `${out}=${value};`;
|
|
729
|
+
const enc = node.encoding;
|
|
730
|
+
const kind = this.kind;
|
|
731
|
+
const failBT = `{${BT}${fail}}`;
|
|
732
|
+
switch (node._tag) {
|
|
733
|
+
case "Any": {
|
|
734
|
+
if (enc === void 0) {
|
|
735
|
+
if (kind === 1) return `if(p>=E)${failBT}${set("I[p]")}p++;`;
|
|
736
|
+
const c = this.v(fn);
|
|
737
|
+
return `if(p>=E)${failBT}${c}=I.charCodeAt(p);if(${c}>=55296&&${c}<=56319&&p+1<I.length&&(I.charCodeAt(p+1)&64512)===56320){${set("I.slice(p,p+2)")}p+=2}else{${set("I[p]")}p++}`;
|
|
738
|
+
}
|
|
739
|
+
const r = this.reader(enc);
|
|
740
|
+
const c = this.v(fn);
|
|
741
|
+
const dec = `${this.const(enc.decode)}(I,p,E)`;
|
|
742
|
+
return `if(p>=E)${failBT}` + (r.fast > 0 ? `if(${r.guard("p")}&&(${c}=${r.unit("p")})<${r.fast}){${set(`R.charOf(${c})`)}p+=${r.width}}else` : "") + `{${c}=${dec};if(${c}<0)${failBT}${set(`R.charOf(${c})`)}p+=${this.const(enc.width)}(${c})}`;
|
|
743
|
+
}
|
|
744
|
+
case "Literal": {
|
|
745
|
+
const lit = node.literal;
|
|
746
|
+
if (enc !== void 0) {
|
|
747
|
+
if (node.caseless) return void 0;
|
|
748
|
+
const encoded = enc.encode(lit);
|
|
749
|
+
return this.literalUnits(Predicate.isString(encoded) ? unitsOf(encoded) : Array.from(encoded), false, fail) + set(this.lit(lit));
|
|
750
|
+
}
|
|
751
|
+
const literalKind = Predicate.isString(lit) ? 0 : lit instanceof Uint8Array ? 1 : 2;
|
|
752
|
+
if (kind === 0 && literalKind !== 0) return void 0;
|
|
753
|
+
if (literalKind === 2) return void 0;
|
|
754
|
+
const codes = kind === 0 ? unitsOf(lit) : Array.from(literalKind === 0 ? new TextEncoder().encode(lit) : lit);
|
|
755
|
+
const len = codes.length;
|
|
756
|
+
const value = !node.caseless && literalKind === kind ? this.lit(lit) : kind === 0 ? `I.slice(p-${len},p)` : `I.subarray(p-${len},p)`;
|
|
757
|
+
return this.literalUnits(codes, node.caseless, fail) + set(value);
|
|
758
|
+
}
|
|
759
|
+
case "OneOf": {
|
|
760
|
+
const negate = node.negate;
|
|
761
|
+
const cs = enc === void 0 ? tokenSet(node.set, kind) : textSet(node.set);
|
|
762
|
+
const accept = acceptTable(cs, negate);
|
|
763
|
+
const c = this.v(fn);
|
|
764
|
+
const value = (len) => this.kind === 0 ? `I.slice(p,p+${len})` : `I[p]`;
|
|
765
|
+
if (enc === void 0) {
|
|
766
|
+
if (kind === 1) return `if(p>=E)${failBT}${c}=I[p];if(!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++;`;
|
|
767
|
+
const cp = this.v(fn);
|
|
768
|
+
if (!negate && this.memberHigh(cs, negate, cp) === "false") return `if(p>=E)${failBT}${c}=I.charCodeAt(p);if(${c}>=${TableSize}||!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++;`;
|
|
769
|
+
return `if(p>=E)${failBT}${c}=I.charCodeAt(p);if(${c}<${TableSize}){if(!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++}else{${cp}=${c};if(${c}>=55296&&${c}<=56319&&p+1<I.length){const d=I.charCodeAt(p+1);if((d&64512)===56320)${cp}=((${c}-55296)<<10)+(d-56320)+65536}if(!(${this.memberHigh(cs, negate, cp)}))${failBT}if(${cp}>65535){${set(value("2"))}p+=2}else{${set("I[p]")}p++}}`;
|
|
770
|
+
}
|
|
771
|
+
const r = this.reader(enc);
|
|
772
|
+
return `if(p>=E)${failBT}` + (r.fast > 0 ? `if(${r.guard("p")}&&(${c}=${r.unit("p")})<${r.fast}){if(!${this.textAccepts(r, cs, accept, negate, c)})${failBT}${set(`R.charOf(${c})`)}p+=${r.width}}else` : "") + `{${c}=${this.const(enc.decode)}(I,p,E);if(${c}<0||${this.textRejectsDecoded(enc, r, cs, negate, c)})${failBT}${set(`R.charOf(${c})`)}p+=${this.const(enc.width)}(${c})}`;
|
|
773
|
+
}
|
|
774
|
+
case "TakeWhile": return this.takeWhile(node, out, fail, fn);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
/** Matches literal units at `p`, advancing past them. */
|
|
778
|
+
literalUnits(codes, caseless, fail) {
|
|
779
|
+
const len = codes.length;
|
|
780
|
+
if (len === 0) return "";
|
|
781
|
+
const test = (c, i) => {
|
|
782
|
+
const at = this.unit(i === 0 ? "p" : `p+${i}`);
|
|
783
|
+
if (caseless && isLetter(c)) return `(${at}|32)===${lowerAscii(c)}`;
|
|
784
|
+
return `${at}===${c}`;
|
|
785
|
+
};
|
|
786
|
+
if (len <= 8) return `if(!(${codes.map(test).join("&&")})){${BT}${fail}}p+=${len};`;
|
|
787
|
+
if (this.kind === 0 && !caseless) {
|
|
788
|
+
const str = String.fromCharCode(...codes);
|
|
789
|
+
return `if(!I.startsWith(${JSON.stringify(str)},p)){${BT}${fail}}p+=${len};`;
|
|
790
|
+
}
|
|
791
|
+
const expect = this.const(Uint8Array.from(caseless ? codes.map(lowerAscii) : codes));
|
|
792
|
+
const lower = caseless ? `${this.const(lowerAscii)}` : "";
|
|
793
|
+
return `if(E-p<${len}){${BT}${fail}}for(let i=0;i<${len};i++)if(${lower}(${this.unit("p+i")})!==${expect}[i]){${BT}${fail}}p+=${len};`;
|
|
794
|
+
}
|
|
795
|
+
takeWhile(node, out, fail, fn) {
|
|
796
|
+
const enc = node.encoding;
|
|
797
|
+
const { max, min, negate } = node;
|
|
798
|
+
const kind = this.kind;
|
|
799
|
+
const cs = enc === void 0 ? tokenSet(node.set, kind) : textSet(node.set);
|
|
800
|
+
const accept = acceptTable(cs, negate);
|
|
801
|
+
const st = this.v(fn);
|
|
802
|
+
const c = this.v(fn);
|
|
803
|
+
const n = this.v(fn);
|
|
804
|
+
const loop = this.label();
|
|
805
|
+
const counted = max !== Infinity || min > 1;
|
|
806
|
+
const count = counted ? `${n}++;` : "";
|
|
807
|
+
const bound = max !== Infinity ? `&&${n}<${max}` : "";
|
|
808
|
+
const slice = enc !== void 0 ? `${this.const(enc.text)}(I,${st},p)` : kind === 0 ? `I.slice(${st},p)` : `I.subarray(${st},p)`;
|
|
809
|
+
const finish = (min > 0 ? `if(${counted ? n : `p-${st}`}<${min}){${BT}${fail}}` : "") + (out === null ? "" : `${out}=${slice};`);
|
|
810
|
+
const head = `${st}=p;${counted ? `${n}=0;` : ""}${loop}:while(p<E${bound}){${c}=${this.unit("p")};`;
|
|
811
|
+
const member = this.member(accept, c);
|
|
812
|
+
if (kind === 1 && (enc === void 0 || fastLimit(enc) >= TableSize)) return `${head}if(!(${member}))break;p++;${count}}${finish}`;
|
|
813
|
+
if (enc !== void 0) {
|
|
814
|
+
const r = this.reader(enc);
|
|
815
|
+
const guard = r.width === 1 ? "" : `${r.guard("p")}&&`;
|
|
816
|
+
return `${st}=p;${counted ? `${n}=0;` : ""}${loop}:while(p<E${bound}){` + (r.fast > 0 ? `if(${guard}(${c}=${r.unit("p")})<${r.fast}){if(!${this.textAccepts(r, cs, accept, negate, c)})break ${loop};p+=${r.width}}else` : "") + `{${c}=${this.const(enc.decode)}(I,p,E);if(${c}<0||${this.textRejectsDecoded(enc, r, cs, negate, c)})break ${loop};p+=${this.const(enc.width)}(${c})}${count}}${finish}`;
|
|
817
|
+
}
|
|
818
|
+
const fast = 55296;
|
|
819
|
+
const small = TableSize;
|
|
820
|
+
const highTest = (v) => this.memberHigh(cs, negate, v);
|
|
821
|
+
const cp = this.v(fn);
|
|
822
|
+
const never = highTest(c) === "false";
|
|
823
|
+
const always = highTest(c) === "true";
|
|
824
|
+
return `${head}if(${c}<${small}){if(!(${member}))break;p++}else ${never && !negate ? `break;` : always && !counted ? `p++;` : `if(${c}<${fast}||${c}>56319){if(!(${highTest(c)}))break ${loop};p++}else{${cp}=${c};if(p+1<I.length){const d=I.charCodeAt(p+1);if((d&64512)===56320)${cp}=((${c}-55296)<<10)+(d-56320)+65536}if(!(${highTest(cp)}))break ${loop};p+=${cp}>65535?2:1}`}${count}}${finish}`;
|
|
825
|
+
}
|
|
826
|
+
escaped(p, node, out, fail, fn, d) {
|
|
827
|
+
const enc = node.encoding;
|
|
828
|
+
const ctrl = node.control;
|
|
829
|
+
const r = enc === void 0 ? void 0 : this.reader(enc);
|
|
830
|
+
if (r !== void 0 && !(ctrl < r.fast)) return this.delegate(p, !node.raw, out, fail, fn);
|
|
831
|
+
const raw = node.raw;
|
|
832
|
+
const acc = raw ? null : out ?? this.v(fn);
|
|
833
|
+
const x = raw ? null : this.v(fn);
|
|
834
|
+
const b = this.v(fn);
|
|
835
|
+
const st = this.v(fn);
|
|
836
|
+
const okv = this.v(fn);
|
|
837
|
+
const loop = this.label();
|
|
838
|
+
const bad = this.label();
|
|
839
|
+
const normal = this.gen(node.normal, !raw, x, `break ${bad};`, fn, d);
|
|
840
|
+
const escape = this.gen(node.escape, !raw, x, fail, fn, d);
|
|
841
|
+
const onNormal = raw ? `if(p===${b}){$S.mode="Cut";${fail}}continue ${loop};` : `if(p!==${b}){${acc}+=${x};continue ${loop}}`;
|
|
842
|
+
const slice = enc !== void 0 ? `${this.const(enc.text)}(I,${st},p)` : this.kind === 0 ? `I.slice(${st},p)` : `I.subarray(${st},p)`;
|
|
843
|
+
const ctrlTest = r === void 0 ? `p<E&&${this.unit("p")}===${ctrl}` : `${r.guard("p")}&&${r.unit("p")}===${ctrl}`;
|
|
844
|
+
return `${raw ? `${st}=p;` : `${acc}="";`}${loop}:while(p<E){${b}=p;${okv}=false;${bad}:{${normal}${okv}=true;}if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${fail}}p=${b}}if(${ctrlTest}){p+=${r?.width ?? 1};${escape}${raw ? "" : `${acc}+=${x};`}}else break ${loop};}` + (raw && out !== null ? `${out}=${slice};` : "");
|
|
845
|
+
}
|
|
164
846
|
};
|
|
165
|
-
class Emitter {
|
|
166
|
-
root;
|
|
167
|
-
kind;
|
|
168
|
-
resumable;
|
|
169
|
-
constants = [];
|
|
170
|
-
constantIndex = new Map();
|
|
171
|
-
functions = [];
|
|
172
|
-
rules = [new Map(), new Map()];
|
|
173
|
-
refs = new Map();
|
|
174
|
-
closure;
|
|
175
|
-
next = 0;
|
|
176
|
-
constructor(root, kind, resumable = false) {
|
|
177
|
-
this.root = root;
|
|
178
|
-
this.kind = kind;
|
|
179
|
-
this.resumable = resumable;
|
|
180
|
-
this.closure = createClosureCompiler(kind, resumable);
|
|
181
|
-
this.countRefs(root);
|
|
182
|
-
}
|
|
183
|
-
get fallbacks() {
|
|
184
|
-
return this.closure.fallbacks;
|
|
185
|
-
}
|
|
186
|
-
/** Counts the distinct parents of each parser. */
|
|
187
|
-
countRefs(root) {
|
|
188
|
-
const seen = new Set();
|
|
189
|
-
const stack = [root];
|
|
190
|
-
while (stack.length > 0) {
|
|
191
|
-
const p = stack.pop();
|
|
192
|
-
if (seen.has(p))
|
|
193
|
-
continue;
|
|
194
|
-
seen.add(p);
|
|
195
|
-
for (const c of new Set(children(nodeOf(p)))) {
|
|
196
|
-
this.refs.set(c, (this.refs.get(c) ?? 0) + 1);
|
|
197
|
-
stack.push(c);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const(value) {
|
|
202
|
-
let i = this.constantIndex.get(value);
|
|
203
|
-
if (i === undefined) {
|
|
204
|
-
i = this.constants.length;
|
|
205
|
-
this.constants.push(value);
|
|
206
|
-
this.constantIndex.set(value, i);
|
|
207
|
-
}
|
|
208
|
-
return `C[${i}]`;
|
|
209
|
-
}
|
|
210
|
-
/** A JavaScript literal for simple values, otherwise a constant. */
|
|
211
|
-
lit(value) {
|
|
212
|
-
if (value === undefined)
|
|
213
|
-
return "void 0";
|
|
214
|
-
if (value === null || typeof value === "boolean")
|
|
215
|
-
return String(value);
|
|
216
|
-
if (typeof value === "number" && Number.isFinite(value) && !Object.is(value, -0))
|
|
217
|
-
return String(value);
|
|
218
|
-
if (typeof value === "string" && value.length < 64)
|
|
219
|
-
return JSON.stringify(value);
|
|
220
|
-
return this.const(value);
|
|
221
|
-
}
|
|
222
|
-
v(fn) {
|
|
223
|
-
const name = `v${this.next++}`;
|
|
224
|
-
fn.vars.push(name);
|
|
225
|
-
return name;
|
|
226
|
-
}
|
|
227
|
-
label() {
|
|
228
|
-
return `L${this.next++}`;
|
|
229
|
-
}
|
|
230
|
-
unit(i) {
|
|
231
|
-
return this.kind === 0 ? `I.charCodeAt(${i})` : `I[${i}]`;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* How generated code reads an encoding's code units: units below `fast`
|
|
235
|
-
* are whole code points, `width` input units long. `guard(i)` says that a
|
|
236
|
-
* whole unit is available at `i`.
|
|
237
|
-
*/
|
|
238
|
-
reader(enc) {
|
|
239
|
-
if (enc === utf16le || enc === utf16be) {
|
|
240
|
-
const [lo, hi] = enc === utf16le ? ["", "+1"] : ["+1", ""];
|
|
241
|
-
return {
|
|
242
|
-
width: 2,
|
|
243
|
-
fast: 0xd800,
|
|
244
|
-
guard: (i) => `${i}+1<E`,
|
|
245
|
-
unit: (i) => `(I[${i}${lo}]|I[${i}${hi}]<<8)`
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
return { width: 1, fast: enc.unit === 1 ? fastLimit(enc) : 0, guard: (i) => `${i}<E`, unit: (i) => this.unit(i) };
|
|
249
|
-
}
|
|
250
|
-
/** The function implementing a rule, generating it on first use. */
|
|
251
|
-
rule(p, used) {
|
|
252
|
-
const rules = this.rules[used ? 1 : 0];
|
|
253
|
-
let name = rules.get(p);
|
|
254
|
-
if (name !== undefined)
|
|
255
|
-
return name;
|
|
256
|
-
name = `f${this.next++}`;
|
|
257
|
-
rules.set(p, name);
|
|
258
|
-
const fn = new Fn();
|
|
259
|
-
const out = used ? this.v(fn) : null;
|
|
260
|
-
const done = this.label();
|
|
261
|
-
const body = this.inline(p, used, out, `break ${done};`, fn, 0);
|
|
262
|
-
this.functions.push(`function ${name}(p){const I=$I,E=$E;${fn.vars.length > 0 ? `let ${fn.vars.join(",")};` : ""}` +
|
|
263
|
-
`${done}:{${body}P=p;return ${out ?? "void 0"}}return F}`);
|
|
264
|
-
return name;
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Code for `p`: on success it falls through with `p` advanced and, when
|
|
268
|
-
* `out` is given, the value in `out`; on failure it runs `fail`.
|
|
269
|
-
*/
|
|
270
|
-
gen(p, used, out, fail, fn, depth) {
|
|
271
|
-
const node = nodeOf(p);
|
|
272
|
-
if (node._tag === "Suspend")
|
|
273
|
-
return this.call(this.rule(node.resolve(), used), out, fail);
|
|
274
|
-
if (!isLeaf(node) && (((this.refs.get(p) ?? 0) > 1 && !this.small(p)) || depth > 32)) {
|
|
275
|
-
return this.call(this.rule(p, used), out, fail);
|
|
276
|
-
}
|
|
277
|
-
return this.inline(p, used, out, fail, fn, depth);
|
|
278
|
-
}
|
|
279
|
-
/** Whether a parser is small enough to inline wherever it is used. */
|
|
280
|
-
small(p) {
|
|
281
|
-
let budget = 6;
|
|
282
|
-
const visit = (q) => {
|
|
283
|
-
const node = nodeOf(q);
|
|
284
|
-
if (--budget < 0 || node._tag === "Suspend" || node._tag === "Escaped" || node._tag === "Dispatch")
|
|
285
|
-
return false;
|
|
286
|
-
return children(node).every(visit);
|
|
287
|
-
};
|
|
288
|
-
return visit(p);
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* In a resumable build: token code (which fails by breaking out of `bad`)
|
|
292
|
-
* that read within `lookahead` units of the end of a partial input re-runs
|
|
293
|
-
* interpreted, since it may have to report `Incomplete` there. Token code
|
|
294
|
-
* leaves `p` where it stopped reading, even when it fails.
|
|
295
|
-
*/
|
|
296
|
-
partialSafe(p, code, bad, lookahead, out, fail, fn) {
|
|
297
|
-
const ahead = Math.max(lookahead, Margin);
|
|
298
|
-
const st = this.v(fn);
|
|
299
|
-
const ok = this.v(fn);
|
|
300
|
-
const t = this.v(fn);
|
|
301
|
-
const leaf = this.label();
|
|
302
|
-
return `${st}=p;${ok}=false;${leaf}:{${bad}:{${code}${ok}=true;}` +
|
|
303
|
-
`if(!$S.partial||p+${ahead}<=E){if(${ok})break ${leaf};${fail}}` +
|
|
304
|
-
`p=${st};$S.pos=p;${t}=${this.const(p.step)}($S);if(${t}===F){${fail}}p=$S.pos;${out === null ? "" : `${out}=${t};`}}`;
|
|
305
|
-
}
|
|
306
|
-
call(name, out, fail) {
|
|
307
|
-
if (out === null)
|
|
308
|
-
return `if(${name}(p)===F){${fail}}p=P;`;
|
|
309
|
-
return `${out}=${name}(p);if(${out}===F){${fail}}p=P;`;
|
|
310
|
-
}
|
|
311
|
-
/** Runs the closure-compiled step of `p` on the shared state. */
|
|
312
|
-
delegate(p, used, out, fail, fn) {
|
|
313
|
-
const step = this.const(this.closure.compile(p, used));
|
|
314
|
-
const t = out ?? this.v(fn);
|
|
315
|
-
return `$S.pos=p;${t}=${step}($S);if(${t}===F){${fail}}p=$S.pos;`;
|
|
316
|
-
}
|
|
317
|
-
native(enc) {
|
|
318
|
-
return enc === undefined ? this.kind !== 2 : kindOfEncoding(enc) === this.kind;
|
|
319
|
-
}
|
|
320
|
-
inline(p, used, out, fail, fn, depth) {
|
|
321
|
-
const node = nodeOf(p);
|
|
322
|
-
const set = (value) => out === null ? "" : `${out}=${value};`;
|
|
323
|
-
const d = depth + 1;
|
|
324
|
-
switch (node._tag) {
|
|
325
|
-
case "Succeed":
|
|
326
|
-
return set(this.lit(node.value));
|
|
327
|
-
case "Sync":
|
|
328
|
-
return used ? set(`${this.const(node.evaluate)}()`) : "";
|
|
329
|
-
case "Fail":
|
|
330
|
-
return `$S.mode=${JSON.stringify(node.mode)};${fail}`;
|
|
331
|
-
case "Eof":
|
|
332
|
-
return `if(p<E){${BT}${fail}}` + (this.resumable ? `if($S.partial){$S.mode="Incomplete";${fail}}` : "") +
|
|
333
|
-
set("void 0");
|
|
334
|
-
case "Location":
|
|
335
|
-
return set("p");
|
|
336
|
-
case "RestLen":
|
|
337
|
-
return set("E-p");
|
|
338
|
-
case "Suspend":
|
|
339
|
-
return this.call(this.rule(node.resolve(), used), out, fail);
|
|
340
|
-
case "Any":
|
|
341
|
-
case "Literal":
|
|
342
|
-
case "OneOf":
|
|
343
|
-
case "TakeWhile": {
|
|
344
|
-
if (!this.native(node.encoding))
|
|
345
|
-
return this.delegate(p, used, out, fail, fn);
|
|
346
|
-
if (!this.resumable)
|
|
347
|
-
return this.token(node, out, fail, fn) ?? this.delegate(p, used, out, fail, fn);
|
|
348
|
-
const bad = this.label();
|
|
349
|
-
const code = this.token(node, out, `break ${bad};`, fn);
|
|
350
|
-
if (code === undefined)
|
|
351
|
-
return this.delegate(p, used, out, fail, fn);
|
|
352
|
-
return this.partialSafe(p, code, bad, node._tag === "Literal" ? node.literal.length * 4 : Margin, out, fail, fn);
|
|
353
|
-
}
|
|
354
|
-
case "Escaped": {
|
|
355
|
-
// Streaming builds use the closure compiler's escapes, which keep checkpoints
|
|
356
|
-
if (!this.native(node.encoding) || this.resumable)
|
|
357
|
-
return this.delegate(p, used, out, fail, fn);
|
|
358
|
-
return this.escaped(p, node, out, fail, fn, d);
|
|
359
|
-
}
|
|
360
|
-
case "Map": {
|
|
361
|
-
if (!used)
|
|
362
|
-
return this.gen(node.self, false, null, fail, fn, d);
|
|
363
|
-
if (node.f === toFloat && out !== null) {
|
|
364
|
-
const code = this.float(node, out, fail, fn, d);
|
|
365
|
-
if (code !== undefined)
|
|
366
|
-
return code;
|
|
367
|
-
}
|
|
368
|
-
// Streaming builds keep the array, which their checkpoints hold
|
|
369
|
-
if (node.f === fromEntries && out !== null && !this.resumable && entriesShape(node.self)) {
|
|
370
|
-
return this.entries(node.self, out, fail, fn, d);
|
|
371
|
-
}
|
|
372
|
-
const t = this.v(fn);
|
|
373
|
-
return this.gen(node.self, true, t, fail, fn, d) + set(`${this.const(node.f)}(${t})`);
|
|
374
|
-
}
|
|
375
|
-
case "As":
|
|
376
|
-
return this.gen(node.self, false, null, fail, fn, d) + (used ? set(this.lit(node.value)) : "");
|
|
377
|
-
case "Tap": {
|
|
378
|
-
const t = out ?? this.v(fn);
|
|
379
|
-
return this.gen(node.self, true, t, fail, fn, d) + `${this.const(node.f)}(${t});`;
|
|
380
|
-
}
|
|
381
|
-
case "TryMap":
|
|
382
|
-
case "FilterMap": {
|
|
383
|
-
const t = this.v(fn);
|
|
384
|
-
const r = this.v(fn);
|
|
385
|
-
const [tag, field] = node._tag === "TryMap" ? ["Success", "success"] : ["Some", "value"];
|
|
386
|
-
return this.gen(node.self, true, t, fail, fn, d) +
|
|
387
|
-
`${r}=${this.const(node.f)}(${t});if(${r}._tag!=="${tag}"){${BT}${fail}}${set(`${r}.${field}`)}`;
|
|
388
|
-
}
|
|
389
|
-
case "Filter": {
|
|
390
|
-
const t = out ?? this.v(fn);
|
|
391
|
-
return this.gen(node.self, true, t, fail, fn, d) + `if(!${this.const(node.predicate)}(${t})){${BT}${fail}}`;
|
|
392
|
-
}
|
|
393
|
-
case "FlatMap": {
|
|
394
|
-
const t = this.v(fn);
|
|
395
|
-
const r = out ?? this.v(fn);
|
|
396
|
-
this.closure.note(p, node, "the parser returned by the function runs interpreted");
|
|
397
|
-
return this.gen(node.self, true, t, fail, fn, d) +
|
|
398
|
-
`$S.pos=p;${r}=${this.const(node.f)}(${t}).step($S);if(${r}===F){${fail}}p=$S.pos;`;
|
|
399
|
-
}
|
|
400
|
-
case "Taken": {
|
|
401
|
-
if (node.encoding !== undefined && !this.native(node.encoding))
|
|
402
|
-
return this.delegate(p, used, out, fail, fn);
|
|
403
|
-
const st = this.v(fn);
|
|
404
|
-
const body = `${st}=p;` + this.gen(node.self, false, null, fail, fn, d);
|
|
405
|
-
if (out === null && node.encoding === undefined)
|
|
406
|
-
return body;
|
|
407
|
-
if (node.encoding === undefined) {
|
|
408
|
-
return body + set(this.kind === 0 ? `I.slice(${st},p)` : this.kind === 1 ? `I.subarray(${st},p)` : `$S.slice(${st},p)`);
|
|
409
|
-
}
|
|
410
|
-
const t = out ?? this.v(fn);
|
|
411
|
-
const enc = this.const(node.encoding);
|
|
412
|
-
return body + `${t}=R.textOf(${enc},${fastLimit(node.encoding)},$S,${st},p);if(${t}===F){${fail}}`;
|
|
413
|
-
}
|
|
414
|
-
case "WithTaken":
|
|
415
|
-
case "WithSpan": {
|
|
416
|
-
const st = this.v(fn);
|
|
417
|
-
const t = this.v(fn);
|
|
418
|
-
const extra = node._tag === "WithSpan" ? `{start:${st},end:p}` : `$S.slice(${st},p)`;
|
|
419
|
-
return `${st}=p;` + this.gen(node.self, true, t, fail, fn, d) + set(`[${t},${extra}]`);
|
|
420
|
-
}
|
|
421
|
-
case "Span": {
|
|
422
|
-
const st = this.v(fn);
|
|
423
|
-
return `${st}=p;` + this.gen(node.self, false, null, fail, fn, d) + set(`{start:${st},end:p}`);
|
|
424
|
-
}
|
|
425
|
-
case "Mode": {
|
|
426
|
-
const ok = this.label();
|
|
427
|
-
const bad = this.label();
|
|
428
|
-
return `${ok}:{${bad}:{${this.gen(node.self, used, out, `break ${bad};`, fn, d)}break ${ok};}` +
|
|
429
|
-
`if($S.mode===${JSON.stringify(node.from)})$S.mode=${JSON.stringify(node.to)};${fail}}`;
|
|
430
|
-
}
|
|
431
|
-
case "Context":
|
|
432
|
-
case "Expected":
|
|
433
|
-
case "MapError":
|
|
434
|
-
case "OrFail":
|
|
435
|
-
case "Trace":
|
|
436
|
-
case "RetryAfter":
|
|
437
|
-
return this.gen(node.self, used, out, fail, fn, d);
|
|
438
|
-
case "ResumeAfter": {
|
|
439
|
-
if (!used)
|
|
440
|
-
return this.gen(node.self, false, null, fail, fn, d);
|
|
441
|
-
const t = this.v(fn);
|
|
442
|
-
return this.gen(node.self, true, t, fail, fn, d) + set(`R.some(${t})`);
|
|
443
|
-
}
|
|
444
|
-
case "Opt":
|
|
445
|
-
case "OrElseSucceed": {
|
|
446
|
-
const st = this.v(fn);
|
|
447
|
-
const ok = this.label();
|
|
448
|
-
const bad = this.label();
|
|
449
|
-
const t = out === null ? null : this.v(fn);
|
|
450
|
-
const some = node._tag === "Opt" ? `R.some(${t})` : t;
|
|
451
|
-
const orElse = node._tag === "Opt" ? "R.none()" : `${this.const(node.value)}()`;
|
|
452
|
-
return `${st}=p;${ok}:{${bad}:{${this.gen(node.self, used, t, `break ${bad};`, fn, d)}${set(some)}break ${ok};}` +
|
|
453
|
-
`if($S.mode!=="Backtrack"){${fail}}p=${st};${set(orElse)}}`;
|
|
454
|
-
}
|
|
455
|
-
case "Peek": {
|
|
456
|
-
const st = this.v(fn);
|
|
457
|
-
return `${st}=p;${this.gen(node.self, used, out, fail, fn, d)}p=${st};`;
|
|
458
|
-
}
|
|
459
|
-
case "Not": {
|
|
460
|
-
const st = this.v(fn);
|
|
461
|
-
const ok = this.label();
|
|
462
|
-
const bad = this.label();
|
|
463
|
-
return `${st}=p;${ok}:{${bad}:{${this.gen(node.self, false, null, `break ${bad};`, fn, d)}p=${st};${BT}${fail}}` +
|
|
464
|
-
`if($S.mode!=="Backtrack"){${fail}}p=${st};}${set("void 0")}`;
|
|
465
|
-
}
|
|
466
|
-
case "Seq":
|
|
467
|
-
return this.seq(node, used, out, fail, fn, d);
|
|
468
|
-
case "Struct": {
|
|
469
|
-
if (out === null)
|
|
470
|
-
return node.parsers.map((q) => this.gen(q, false, null, fail, fn, d)).join("");
|
|
471
|
-
const vars = node.parsers.map(() => this.v(fn));
|
|
472
|
-
const code = node.parsers.map((q, i) => this.gen(q, true, vars[i], fail, fn, d)).join("");
|
|
473
|
-
if (node.keys.includes("__proto__")) {
|
|
474
|
-
return code + `${out}={};` + node.keys.map((k, i) => `${out}[${JSON.stringify(k)}]=${vars[i]};`).join("");
|
|
475
|
-
}
|
|
476
|
-
return code + `${out}={${node.keys.map((k, i) => `${JSON.stringify(k)}:${vars[i]}`).join(",")}};`;
|
|
477
|
-
}
|
|
478
|
-
case "ZipWith": {
|
|
479
|
-
if (out === null)
|
|
480
|
-
return this.gen(node.self, false, null, fail, fn, d) + this.gen(node.that, false, null, fail, fn, d);
|
|
481
|
-
const x = this.v(fn);
|
|
482
|
-
const y = this.v(fn);
|
|
483
|
-
return this.gen(node.self, true, x, fail, fn, d) + this.gen(node.that, true, y, fail, fn, d) +
|
|
484
|
-
`${out}=${this.const(node.f)}(${x},${y});`;
|
|
485
|
-
}
|
|
486
|
-
case "Alt":
|
|
487
|
-
return this.alt(node, used, out, fail, fn, d);
|
|
488
|
-
case "Dispatch":
|
|
489
|
-
return this.dispatch(p, node, used, out, fail, fn, d);
|
|
490
|
-
case "Repeat":
|
|
491
|
-
case "Fold":
|
|
492
|
-
return this.repeat(node, used, out, fail, fn, d);
|
|
493
|
-
case "Separated":
|
|
494
|
-
return this.separated(node, used, out, fail, fn, d);
|
|
495
|
-
default:
|
|
496
|
-
return this.delegate(p, used, out, fail, fn);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* `Ascii.float` and `Text.float` (`map(floatText, toFloat)`): numbers in
|
|
501
|
-
* decimal notation are matched and converted in one pass by
|
|
502
|
-
* `scanFloatString` or `scanFloatBytes`, without slicing the input; the
|
|
503
|
-
* rest (`nan`, `inf`, failures before a digit) runs the parser. Returns
|
|
504
|
-
* `undefined` when this does not apply.
|
|
505
|
-
*/
|
|
506
|
-
float(node, out, fail, fn, d) {
|
|
507
|
-
if (!floatTexts.has(node.self) || this.kind === 2)
|
|
508
|
-
return undefined;
|
|
509
|
-
let taken = nodeOf(node.self);
|
|
510
|
-
while (taken._tag === "Expected")
|
|
511
|
-
taken = nodeOf(taken.self);
|
|
512
|
-
if (taken._tag !== "Taken")
|
|
513
|
-
return undefined;
|
|
514
|
-
const enc = taken.encoding;
|
|
515
|
-
// Decimal notation is ASCII, which these encodings read unit by unit
|
|
516
|
-
if (enc !== undefined && !(this.native(enc) && enc.unit === 1 && enc.asciiCompatible))
|
|
517
|
-
return undefined;
|
|
518
|
-
const t = this.v(fn);
|
|
519
|
-
const scan = this.const(floatScan);
|
|
520
|
-
// Near the end of a partial input, the parser may have to report `Incomplete`
|
|
521
|
-
const near = this.resumable ? `||$S.partial&&${scan}.read+${Margin}>E` : "";
|
|
522
|
-
return `${out}=${this.kind === 0 ? "R.scanFloatString" : "R.scanFloatBytes"}(I,p,E);` +
|
|
523
|
-
`if(${out}===void 0${near}){${this.gen(node.self, true, t, fail, fn, d)}${out}=${this.const(toFloat)}(${t})}` +
|
|
524
|
-
`else if(${out}===F){${BT}${fail}}else p=${scan}.end;`;
|
|
525
|
-
}
|
|
526
|
-
seq(node, used, out, fail, fn, d) {
|
|
527
|
-
const output = node.output;
|
|
528
|
-
if (out === null || !used)
|
|
529
|
-
return node.parsers.map((q) => this.gen(q, false, null, fail, fn, d)).join("");
|
|
530
|
-
if (typeof output === "number") {
|
|
531
|
-
return node.parsers.map((q, i) => this.gen(q, i === output, i === output ? out : null, fail, fn, d)).join("");
|
|
532
|
-
}
|
|
533
|
-
const keep = (i) => output === "tuple" || output.includes(i);
|
|
534
|
-
const vars = node.parsers.map((_, i) => keep(i) ? this.v(fn) : null);
|
|
535
|
-
const code = node.parsers.map((q, i) => this.gen(q, keep(i), vars[i], fail, fn, d)).join("");
|
|
536
|
-
return code + `${out}=[${vars.filter((v) => v !== null).join(",")}];`;
|
|
537
|
-
}
|
|
538
|
-
/** A test that the unit `u` may start `first` (false for -1, the end of the input). */
|
|
539
|
-
startTest(first, u) {
|
|
540
|
-
const runs = runsOf(first.units);
|
|
541
|
-
const small = rangeTest(runs, u);
|
|
542
|
-
const low = small ?? `${this.const(first.units)}[${u}]===1`;
|
|
543
|
-
if (!first.high)
|
|
544
|
-
return small !== undefined ? `(${low})` : `(${u}<${TableSize}&&${low})`;
|
|
545
|
-
return `(${u}>=${TableSize}||${low})`;
|
|
546
|
-
}
|
|
547
|
-
alt(node, used, out, fail, fn, d) {
|
|
548
|
-
const parsers = node.parsers;
|
|
549
|
-
if (parsers.length === 0)
|
|
550
|
-
return `${BT}${fail}`;
|
|
551
|
-
if (parsers.length === 1)
|
|
552
|
-
return this.gen(parsers[0], used, out, fail, fn, d);
|
|
553
|
-
const firsts = this.kind === 2 ? undefined : parsers.map((q) => this.closure.firsts.of(q));
|
|
554
|
-
const prune = firsts !== undefined && planAlt(firsts) !== undefined;
|
|
555
|
-
const st = this.v(fn);
|
|
556
|
-
const u = prune ? this.v(fn) : "";
|
|
557
|
-
const done = this.label();
|
|
558
|
-
// Near the end of a partial input, no branch is skipped: it may be incomplete rather than fail
|
|
559
|
-
const read = this.resumable ? `$S.partial&&p+${Margin}>E?-2:p<E?${this.unit("p")}:-1` : `p<E?${this.unit("p")}:-1`;
|
|
560
|
-
let code = `${st}=p;${prune ? `${u}=${read};` : ""}${done}:{`;
|
|
561
|
-
parsers.forEach((q, i) => {
|
|
562
|
-
const bad = this.label();
|
|
563
|
-
const branch = `${bad}:{${this.gen(q, used, out, `break ${bad};`, fn, d)}break ${done};}` +
|
|
564
|
-
`if($S.mode!=="Backtrack"){${fail}}p=${st};`;
|
|
565
|
-
const f = firsts?.[i];
|
|
566
|
-
code += prune && f.mode === Strict
|
|
567
|
-
? `if(${this.resumable ? `${u}===-2||` : ""}${this.startTest(f, u)}){${branch}}`
|
|
568
|
-
: branch;
|
|
569
|
-
});
|
|
570
|
-
return code + `${BT}${fail}}`;
|
|
571
|
-
}
|
|
572
|
-
dispatch(p, node, used, out, fail, fn, d) {
|
|
573
|
-
const choose = this.const(node.choose);
|
|
574
|
-
// The general path: run the selector, choose, then run the case's compiled step
|
|
575
|
-
const cases = new Map();
|
|
576
|
-
const caseStep = (q) => {
|
|
577
|
-
let s = cases.get(q);
|
|
578
|
-
if (s === undefined)
|
|
579
|
-
cases.set(q, s = this.closure.compile(q, used));
|
|
580
|
-
return s;
|
|
581
|
-
};
|
|
582
|
-
if (node.cases !== undefined) {
|
|
583
|
-
for (const key of Reflect.ownKeys(node.cases))
|
|
584
|
-
caseStep(node.cases[key]);
|
|
585
|
-
if (node.otherwise !== undefined)
|
|
586
|
-
caseStep(node.otherwise);
|
|
587
|
-
}
|
|
588
|
-
const table = this.const(cases);
|
|
589
|
-
const general = () => {
|
|
590
|
-
const st = this.v(fn);
|
|
591
|
-
const k = this.v(fn);
|
|
592
|
-
const q = this.v(fn);
|
|
593
|
-
const r = out ?? this.v(fn);
|
|
594
|
-
return `${st}=p;${this.gen(node.selector, true, k, fail, fn, d)}${q}=${choose}(${k});` +
|
|
595
|
-
`if(${q}===void 0){p=${st};${BT}${fail}}$S.pos=p;${r}=(${table}.get(${q})??${q}.step)($S);` +
|
|
596
|
-
`if(${r}===F){${fail}}p=$S.pos;`;
|
|
597
|
-
};
|
|
598
|
-
if (node.cases === undefined) {
|
|
599
|
-
this.closure.note(p, node, "the parser returned by the function of cases runs interpreted");
|
|
600
|
-
return general();
|
|
601
|
-
}
|
|
602
|
-
let sel = nodeOf(node.selector);
|
|
603
|
-
const peek = sel._tag === "Peek";
|
|
604
|
-
if (peek)
|
|
605
|
-
sel = nodeOf(sel.self);
|
|
606
|
-
if (sel._tag !== "Any" || !this.native(sel.encoding))
|
|
607
|
-
return general();
|
|
608
|
-
const enc = sel.encoding;
|
|
609
|
-
const r = enc === undefined ? undefined : this.reader(enc);
|
|
610
|
-
const limit = r === undefined ? (this.kind === 0 ? 0xd800 : TableSize) : Math.min(r.fast, 0xd800);
|
|
611
|
-
const size = Math.min(limit, TableSize);
|
|
612
|
-
if (size === 0)
|
|
613
|
-
return general();
|
|
614
|
-
// Group the units by the case they select
|
|
615
|
-
const groups = new Map();
|
|
616
|
-
for (let unit = 0; unit < size; unit++) {
|
|
617
|
-
const q = (node.choose)(enc === undefined && this.kind === 1 ? unit : charOf(unit));
|
|
618
|
-
if (q === undefined)
|
|
619
|
-
continue;
|
|
620
|
-
let g = groups.get(q);
|
|
621
|
-
if (g === undefined)
|
|
622
|
-
groups.set(q, g = []);
|
|
623
|
-
g.push(unit);
|
|
624
|
-
}
|
|
625
|
-
const u = this.v(fn);
|
|
626
|
-
const sw = this.label();
|
|
627
|
-
// A unit cut short by the end of the input takes the general path
|
|
628
|
-
const read = r === undefined
|
|
629
|
-
? `p<E?${this.unit("p")}:-1`
|
|
630
|
-
: `${r.guard("p")}?${r.unit("p")}:p<E?${size}:-1`;
|
|
631
|
-
// Near the end of a partial input, the general path handles incomplete units
|
|
632
|
-
const near = this.resumable ? `$S.partial&&p+${Margin}>E?${size}:` : "";
|
|
633
|
-
let code = `${u}=${near}${read};${sw}:switch(${u}){`;
|
|
634
|
-
for (const [q, units] of groups) {
|
|
635
|
-
code += units.map((x) => `case ${x}:`).join("") +
|
|
636
|
-
`{${peek ? "" : `p+=${r?.width ?? 1};`}${this.gen(q, used, out, fail, fn, d)}break ${sw};}`;
|
|
637
|
-
}
|
|
638
|
-
code += `case -1:{${BT}${fail}}default:{if(${u}<${size}){${BT}${fail}}${general()}}}`;
|
|
639
|
-
return code;
|
|
640
|
-
}
|
|
641
|
-
repeat(node, used, out, fail, fn, d, collect) {
|
|
642
|
-
const { max, min } = node;
|
|
643
|
-
const loop = this.label();
|
|
644
|
-
const bad = this.label();
|
|
645
|
-
const n = this.v(fn);
|
|
646
|
-
const st = this.v(fn);
|
|
647
|
-
const isFold = node._tag === "Fold";
|
|
648
|
-
const keep = out !== null && used && collect === undefined;
|
|
649
|
-
const x = keep ? this.v(fn) : null;
|
|
650
|
-
const acc = keep ? out : this.v(fn);
|
|
651
|
-
let init = collect?.init ?? "";
|
|
652
|
-
let add = "";
|
|
653
|
-
if (keep) {
|
|
654
|
-
if (isFold) {
|
|
655
|
-
init = `${acc}=${this.const(node.init)}();`;
|
|
656
|
-
add = `${acc}=${this.const(node.f)}(${acc},${x});`;
|
|
657
|
-
}
|
|
658
|
-
else {
|
|
659
|
-
init = `${acc}=[];`;
|
|
660
|
-
add = `${acc}.push(${x});`;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
const check = max === Infinity ? `if(p===${st}){$S.mode="Cut";${fail}}` : "";
|
|
664
|
-
const bound = max === Infinity ? "" : `${n}<${max}`;
|
|
665
|
-
const r = this.resumable ? this.resumePoint(fn) : undefined;
|
|
666
|
-
const save = r === undefined
|
|
667
|
-
? ""
|
|
668
|
-
: `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${acc},count:${n},pos:${st}});`;
|
|
669
|
-
// With `collect` (never in resumable builds), the element adds itself once the loop check passed
|
|
670
|
-
const element = collect === undefined
|
|
671
|
-
? `${this.gen(node.self, keep, x, `break ${bad};`, fn, d)}${check}${add}`
|
|
672
|
-
: collect.element(`break ${bad};`, check);
|
|
673
|
-
const body = `${loop}:for(;${bound};${n}++){${st}=p;` +
|
|
674
|
-
`${bad}:{${element}continue ${loop};}` +
|
|
675
|
-
`${save}if($S.mode!=="Backtrack"${min > 0 ? `||${n}<${min}` : ""}){${fail}}p=${st};break ${loop};}`;
|
|
676
|
-
if (r === undefined)
|
|
677
|
-
return `${init}${n}=0;${body}${collect?.finish ?? ""}`;
|
|
678
|
-
return r.wrap(acc, `${acc}=cp.acc;${n}=cp.count;p=cp.pos;`, `${init}${n}=0;`, body);
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* In a resumable build, a repetition entered at `entry` may restart from a
|
|
682
|
-
* checkpoint (`restore`, with the checkpoint in `cp`), or return the result
|
|
683
|
-
* of a `Done` one; a resumed repetition records its result when it ends.
|
|
684
|
-
*/
|
|
685
|
-
resumePoint(fn) {
|
|
686
|
-
const key = this.const({});
|
|
687
|
-
const entry = this.v(fn);
|
|
688
|
-
const resumed = this.v(fn);
|
|
689
|
-
const done = this.label();
|
|
690
|
-
return {
|
|
691
|
-
key,
|
|
692
|
-
entry,
|
|
693
|
-
resumed,
|
|
694
|
-
wrap: (value, restore, fresh, body) => `${entry}=p;${resumed}=false;${done}:{if($S.resume!==void 0){const cp=R.take($S,${key},p);` +
|
|
695
|
-
`if(cp instanceof R.Done){p=cp.pos;${value}=cp.value;break ${done};}` +
|
|
696
|
-
`if(cp!==void 0){${restore}${resumed}=true;}}if(!${resumed}){${fresh}}${body}` +
|
|
697
|
-
`if(${resumed})R.done($S,${key},${entry},${value},p);}`
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
separated(node, used, out, fail, fn, d, collect) {
|
|
701
|
-
const { max, min } = node;
|
|
702
|
-
const keep = out !== null && used && collect === undefined;
|
|
703
|
-
if (max === 0)
|
|
704
|
-
return collect !== undefined ? collect.init + collect.finish : keep ? `${out}=[];` : "";
|
|
705
|
-
const x = keep ? this.v(fn) : null;
|
|
706
|
-
const acc = keep ? out : this.v(fn);
|
|
707
|
-
const n = this.v(fn);
|
|
708
|
-
const st = this.v(fn);
|
|
709
|
-
const done = this.label();
|
|
710
|
-
const loop = this.label();
|
|
711
|
-
const push = keep ? `${acc}.push(${x});` : "";
|
|
712
|
-
const r = this.resumable ? this.resumePoint(fn) : undefined;
|
|
713
|
-
const save = r === undefined
|
|
714
|
-
? ""
|
|
715
|
-
: `if($S.mode==="Incomplete"&&$S.resume!==void 0)R.save($S,${r.key},${r.entry},{acc:${acc},count:${n},pos:${st}});`;
|
|
716
|
-
// Runs `body`; on its failure, stops the repetition (or fails if `fatal`)
|
|
717
|
-
const attempt = (body, success, fatal, checkpoint) => {
|
|
718
|
-
const ok = this.label();
|
|
719
|
-
const bad = this.label();
|
|
720
|
-
return `${ok}:{${bad}:{${body(`break ${bad};`)}${success}break ${ok};}` +
|
|
721
|
-
`${checkpoint}if($S.mode!=="Backtrack"${fatal}){${fail}}p=${st};break ${done};}`;
|
|
722
|
-
};
|
|
723
|
-
const self = (f) => collect === undefined ? this.gen(node.self, keep, x, f, fn, d) : collect.element(f, "");
|
|
724
|
-
const sep = (f) => this.gen(node.separator, false, null, f, fn, d);
|
|
725
|
-
const check = max === Infinity ? `if(p===${st}){$S.mode="Cut";${fail}}` : "";
|
|
726
|
-
const first = `${collect?.init ?? (keep ? `${acc}=[];` : "")}${n}=0;${st}=p;` + attempt(self, `${push}${n}++;`, min > 0 ? "||true" : "", "");
|
|
727
|
-
const rest = `${loop}:while(${max === Infinity ? "true" : `${n}<${max}`}){${st}=p;` +
|
|
728
|
-
attempt(sep, "", `||${n}<${min}`, save) + check +
|
|
729
|
-
attempt(self, `${push}${n}++;continue ${loop};`, `||${n}<${min}`, save) +
|
|
730
|
-
`}`;
|
|
731
|
-
if (r === undefined)
|
|
732
|
-
return `${done}:{${first}${rest}}${collect?.finish ?? ""}`;
|
|
733
|
-
// `done` ends the repetition; the checkpoint wrapper records the result after it
|
|
734
|
-
return r.wrap(acc, `${acc}=cp.acc;${n}=cp.count;p=cp.pos;`, "", `${done}:{if(!${r.resumed}){${first}}${rest}}`);
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* `map(self, Object.fromEntries)`, where `entriesShape(self)`: builds the
|
|
738
|
-
* object directly, assigning each entry as it is parsed (see
|
|
739
|
-
* `internal/entries.ts`), without the pairs and their array. `cut`,
|
|
740
|
-
* `preceded`, `terminated` and diagnostics around the repetition are
|
|
741
|
-
* compiled as usual around it.
|
|
742
|
-
*/
|
|
743
|
-
entries(p, out, fail, fn, d) {
|
|
744
|
-
const node = nodeOf(p);
|
|
745
|
-
switch (node._tag) {
|
|
746
|
-
case "Mode": {
|
|
747
|
-
const ok = this.label();
|
|
748
|
-
const bad = this.label();
|
|
749
|
-
return `${ok}:{${bad}:{${this.entries(node.self, out, `break ${bad};`, fn, d)}break ${ok};}` +
|
|
750
|
-
`if($S.mode===${JSON.stringify(node.from)})$S.mode=${JSON.stringify(node.to)};${fail}}`;
|
|
751
|
-
}
|
|
752
|
-
case "Seq": {
|
|
753
|
-
const i = node.output;
|
|
754
|
-
return node.parsers.map((q, j) => j === i ? this.entries(q, out, fail, fn, d) : this.gen(q, false, null, fail, fn, d)).join("");
|
|
755
|
-
}
|
|
756
|
-
case "Repeat":
|
|
757
|
-
case "Separated": {
|
|
758
|
-
const pair = pairOf(node.self);
|
|
759
|
-
const deferred = this.v(fn);
|
|
760
|
-
const key = this.v(fn);
|
|
761
|
-
const value = this.v(fn);
|
|
762
|
-
const collect = {
|
|
763
|
-
init: `${out}={};${deferred}=void 0;`,
|
|
764
|
-
element: (f, check) => pair.parsers.map((q, j) => this.gen(q, j === pair.key || j === pair.value, j === pair.key ? key : j === pair.value ? value : null, f, fn, d)).join("") + check +
|
|
765
|
-
`if(${deferred}===void 0&&R.isPlainKey(${key}))${out}[${key}]=${value};` +
|
|
766
|
-
`else ${deferred}=R.addEntry(${out},${deferred},${key},${value});`,
|
|
767
|
-
finish: `if(${deferred}!==void 0)R.flushEntries(${out},${deferred});`
|
|
768
|
-
};
|
|
769
|
-
return node._tag === "Repeat"
|
|
770
|
-
? this.repeat(node, true, out, fail, fn, d, collect)
|
|
771
|
-
: this.separated(node, true, out, fail, fn, d, collect);
|
|
772
|
-
}
|
|
773
|
-
default:
|
|
774
|
-
return this.entries(node.self, out, fail, fn, d);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
// ---------------------------------------------------------------------------
|
|
778
|
-
// Tokens
|
|
779
|
-
// ---------------------------------------------------------------------------
|
|
780
|
-
/** An expression: is the unit `c`, known to be below `size` (at most `TableSize`), accepted? */
|
|
781
|
-
member(accept, c, size = TableSize) {
|
|
782
|
-
return tableTest(accept, c, size) ?? `${this.const(accept)}[${c}]===1`;
|
|
783
|
-
}
|
|
784
|
-
/** An expression: is the code point `c` (at least `TableSize`) accepted? */
|
|
785
|
-
memberHigh(set, negate, c) {
|
|
786
|
-
if (set.ranges !== undefined && !set.ranges.some(([, b]) => b >= TableSize))
|
|
787
|
-
return negate ? "true" : "false";
|
|
788
|
-
return `${this.const(set.test)}(${c})!==${negate}`;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* An expression: does a `Text` set accept the unit `c`, known to be below
|
|
792
|
-
* `r.fast`? Code points from `TableSize` up are tested by the set's
|
|
793
|
-
* function, unless its ranges show they are all rejected.
|
|
794
|
-
*/
|
|
795
|
-
textAccepts(r, cs, accept, negate, c) {
|
|
796
|
-
if (r.fast <= TableSize)
|
|
797
|
-
return `(${this.member(accept, c, r.fast)})`;
|
|
798
|
-
const member = this.member(accept, c);
|
|
799
|
-
if (reachesAbove(cs, TableSize))
|
|
800
|
-
return `(${c}<${TableSize}?${member}:${this.const(cs.test)}(${c})!==${negate})`;
|
|
801
|
-
return negate ? `(${c}>=${TableSize}||${member})` : `(${c}<${TableSize}&&${member})`;
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* An expression: does a `Text` set reject `c`, a code point that `decode`
|
|
805
|
-
* returned? The built-in encodings only decode code points from `r.fast`
|
|
806
|
-
* up there.
|
|
807
|
-
*/
|
|
808
|
-
textRejectsDecoded(enc, r, cs, negate, c) {
|
|
809
|
-
if (r.fast > 0 && builtinEncodings.has(enc) && !reachesAbove(cs, r.fast))
|
|
810
|
-
return String(!negate);
|
|
811
|
-
return `${this.const(cs.test)}(${c})===${negate}`;
|
|
812
|
-
}
|
|
813
|
-
token(node, out, fail, fn) {
|
|
814
|
-
const set = (value) => out === null ? "" : `${out}=${value};`;
|
|
815
|
-
const enc = node.encoding;
|
|
816
|
-
const kind = this.kind;
|
|
817
|
-
const failBT = `{${BT}${fail}}`;
|
|
818
|
-
switch (node._tag) {
|
|
819
|
-
case "Any": {
|
|
820
|
-
if (enc === undefined) {
|
|
821
|
-
if (kind === 1)
|
|
822
|
-
return `if(p>=E)${failBT}${set("I[p]")}p++;`;
|
|
823
|
-
const c = this.v(fn);
|
|
824
|
-
return `if(p>=E)${failBT}${c}=I.charCodeAt(p);` +
|
|
825
|
-
`if(${c}>=55296&&${c}<=56319&&p+1<I.length&&(I.charCodeAt(p+1)&64512)===56320){${set("I.slice(p,p+2)")}p+=2}` +
|
|
826
|
-
`else{${set("I[p]")}p++}`;
|
|
827
|
-
}
|
|
828
|
-
const r = this.reader(enc);
|
|
829
|
-
const c = this.v(fn);
|
|
830
|
-
const dec = `${this.const(enc.decode)}(I,p,E)`;
|
|
831
|
-
return `if(p>=E)${failBT}` +
|
|
832
|
-
(r.fast > 0 ? `if(${r.guard("p")}&&(${c}=${r.unit("p")})<${r.fast}){${set(`R.charOf(${c})`)}p+=${r.width}}else` : "") +
|
|
833
|
-
`{${c}=${dec};if(${c}<0)${failBT}${set(`R.charOf(${c})`)}p+=${this.const(enc.width)}(${c})}`;
|
|
834
|
-
}
|
|
835
|
-
case "Literal": {
|
|
836
|
-
const lit = node.literal;
|
|
837
|
-
if (enc !== undefined) {
|
|
838
|
-
if (node.caseless)
|
|
839
|
-
return undefined;
|
|
840
|
-
const encoded = enc.encode(lit);
|
|
841
|
-
return this.literalUnits(typeof encoded === "string" ? unitsOf(encoded) : Array.from(encoded), false, fail) + set(this.lit(lit));
|
|
842
|
-
}
|
|
843
|
-
const literalKind = typeof lit === "string" ? 0 : lit instanceof Uint8Array ? 1 : 2;
|
|
844
|
-
if (kind === 0 && literalKind !== 0)
|
|
845
|
-
return undefined;
|
|
846
|
-
if (literalKind === 2)
|
|
847
|
-
return undefined;
|
|
848
|
-
const codes = kind === 0
|
|
849
|
-
? unitsOf(lit)
|
|
850
|
-
: Array.from(literalKind === 0 ? new TextEncoder().encode(lit) : lit);
|
|
851
|
-
const len = codes.length;
|
|
852
|
-
const value = !node.caseless && literalKind === kind
|
|
853
|
-
? this.lit(lit)
|
|
854
|
-
: kind === 0
|
|
855
|
-
? `I.slice(p-${len},p)`
|
|
856
|
-
: `I.subarray(p-${len},p)`;
|
|
857
|
-
return this.literalUnits(codes, node.caseless, fail) + set(value);
|
|
858
|
-
}
|
|
859
|
-
case "OneOf": {
|
|
860
|
-
const negate = node.negate;
|
|
861
|
-
const cs = enc === undefined ? tokenSet(node.set, kind) : textSet(node.set);
|
|
862
|
-
const accept = acceptTable(cs, negate);
|
|
863
|
-
const c = this.v(fn);
|
|
864
|
-
const value = (len) => this.kind === 0 ? `I.slice(p,p+${len})` : `I[p]`;
|
|
865
|
-
if (enc === undefined) {
|
|
866
|
-
if (kind === 1)
|
|
867
|
-
return `if(p>=E)${failBT}${c}=I[p];if(!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++;`;
|
|
868
|
-
const cp = this.v(fn);
|
|
869
|
-
if (!negate && this.memberHigh(cs, negate, cp) === "false") {
|
|
870
|
-
return `if(p>=E)${failBT}${c}=I.charCodeAt(p);if(${c}>=${TableSize}||!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++;`;
|
|
871
|
-
}
|
|
872
|
-
return `if(p>=E)${failBT}${c}=I.charCodeAt(p);` +
|
|
873
|
-
`if(${c}<${TableSize}){if(!(${this.member(accept, c)}))${failBT}${set("I[p]")}p++}` +
|
|
874
|
-
`else{${cp}=${c};if(${c}>=55296&&${c}<=56319&&p+1<I.length){const d=I.charCodeAt(p+1);` +
|
|
875
|
-
`if((d&64512)===56320)${cp}=((${c}-55296)<<10)+(d-56320)+65536}` +
|
|
876
|
-
`if(!(${this.memberHigh(cs, negate, cp)}))${failBT}` +
|
|
877
|
-
`if(${cp}>65535){${set(value("2"))}p+=2}else{${set("I[p]")}p++}}`;
|
|
878
|
-
}
|
|
879
|
-
const r = this.reader(enc);
|
|
880
|
-
return `if(p>=E)${failBT}` +
|
|
881
|
-
(r.fast > 0
|
|
882
|
-
? `if(${r.guard("p")}&&(${c}=${r.unit("p")})<${r.fast}){if(!${this.textAccepts(r, cs, accept, negate, c)})${failBT}${set(`R.charOf(${c})`)}p+=${r.width}}else`
|
|
883
|
-
: "") +
|
|
884
|
-
`{${c}=${this.const(enc.decode)}(I,p,E);if(${c}<0||${this.textRejectsDecoded(enc, r, cs, negate, c)})${failBT}` +
|
|
885
|
-
`${set(`R.charOf(${c})`)}p+=${this.const(enc.width)}(${c})}`;
|
|
886
|
-
}
|
|
887
|
-
case "TakeWhile":
|
|
888
|
-
return this.takeWhile(node, out, fail, fn);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
/** Matches literal units at `p`, advancing past them. */
|
|
892
|
-
literalUnits(codes, caseless, fail) {
|
|
893
|
-
const len = codes.length;
|
|
894
|
-
if (len === 0)
|
|
895
|
-
return "";
|
|
896
|
-
const test = (c, i) => {
|
|
897
|
-
const at = this.unit(i === 0 ? "p" : `p+${i}`);
|
|
898
|
-
if (caseless && isLetter(c))
|
|
899
|
-
return `(${at}|32)===${lowerAscii(c)}`;
|
|
900
|
-
return `${at}===${c}`;
|
|
901
|
-
};
|
|
902
|
-
if (len <= 8) {
|
|
903
|
-
// Reading past the end gives NaN or undefined, which matches nothing
|
|
904
|
-
return `if(!(${codes.map(test).join("&&")})){${BT}${fail}}p+=${len};`;
|
|
905
|
-
}
|
|
906
|
-
if (this.kind === 0 && !caseless) {
|
|
907
|
-
const str = String.fromCharCode(...codes);
|
|
908
|
-
return `if(!I.startsWith(${JSON.stringify(str)},p)){${BT}${fail}}p+=${len};`;
|
|
909
|
-
}
|
|
910
|
-
const expect = this.const(Uint8Array.from(caseless ? codes.map(lowerAscii) : codes));
|
|
911
|
-
const lower = caseless ? `${this.const(lowerAscii)}` : "";
|
|
912
|
-
return `if(E-p<${len}){${BT}${fail}}for(let i=0;i<${len};i++)if(${lower}(${this.unit("p+i")})!==${expect}[i]){${BT}${fail}}p+=${len};`;
|
|
913
|
-
}
|
|
914
|
-
takeWhile(node, out, fail, fn) {
|
|
915
|
-
const enc = node.encoding;
|
|
916
|
-
const { max, min, negate } = node;
|
|
917
|
-
const kind = this.kind;
|
|
918
|
-
const cs = enc === undefined ? tokenSet(node.set, kind) : textSet(node.set);
|
|
919
|
-
const accept = acceptTable(cs, negate);
|
|
920
|
-
const st = this.v(fn);
|
|
921
|
-
const c = this.v(fn);
|
|
922
|
-
const n = this.v(fn);
|
|
923
|
-
const loop = this.label();
|
|
924
|
-
const counted = max !== Infinity || min > 1;
|
|
925
|
-
const count = counted ? `${n}++;` : "";
|
|
926
|
-
const bound = max !== Infinity ? `&&${n}<${max}` : "";
|
|
927
|
-
const slice = enc !== undefined
|
|
928
|
-
? `${this.const(enc.text)}(I,${st},p)`
|
|
929
|
-
: kind === 0
|
|
930
|
-
? `I.slice(${st},p)`
|
|
931
|
-
: `I.subarray(${st},p)`;
|
|
932
|
-
const finish = (min > 0 ? `if(${counted ? n : `p-${st}`}<${min}){${BT}${fail}}` : "") +
|
|
933
|
-
(out === null ? "" : `${out}=${slice};`);
|
|
934
|
-
const head = `${st}=p;${counted ? `${n}=0;` : ""}${loop}:while(p<E${bound}){${c}=${this.unit("p")};`;
|
|
935
|
-
const member = this.member(accept, c);
|
|
936
|
-
if (kind === 1 && (enc === undefined || fastLimit(enc) >= TableSize)) {
|
|
937
|
-
// Bytes, one token each
|
|
938
|
-
return `${head}if(!(${member}))break;p++;${count}}${finish}`;
|
|
939
|
-
}
|
|
940
|
-
if (enc !== undefined) {
|
|
941
|
-
// Text: inline the encoding's single-unit code points, decode the rest
|
|
942
|
-
const r = this.reader(enc);
|
|
943
|
-
// The loop condition already checks a unit of width 1
|
|
944
|
-
const guard = r.width === 1 ? "" : `${r.guard("p")}&&`;
|
|
945
|
-
return `${st}=p;${counted ? `${n}=0;` : ""}${loop}:while(p<E${bound}){` +
|
|
946
|
-
(r.fast > 0
|
|
947
|
-
? `if(${guard}(${c}=${r.unit("p")})<${r.fast}){if(!${this.textAccepts(r, cs, accept, negate, c)})break ${loop};p+=${r.width}}else`
|
|
948
|
-
: "") +
|
|
949
|
-
`{${c}=${this.const(enc.decode)}(I,p,E);if(${c}<0||${this.textRejectsDecoded(enc, r, cs, negate, c)})break ${loop};` +
|
|
950
|
-
`p+=${this.const(enc.width)}(${c})}${count}}${finish}`;
|
|
951
|
-
}
|
|
952
|
-
const fast = 0xd800;
|
|
953
|
-
const small = TableSize;
|
|
954
|
-
const highTest = (v) => this.memberHigh(cs, negate, v);
|
|
955
|
-
// Token on strings: code points, with lenient surrogates
|
|
956
|
-
const cp = this.v(fn);
|
|
957
|
-
const never = highTest(c) === "false";
|
|
958
|
-
// Every code point from `TableSize` up is accepted: without a count,
|
|
959
|
-
// surrogate pairs can be skipped a unit at a time
|
|
960
|
-
const always = highTest(c) === "true";
|
|
961
|
-
const rest = never && !negate
|
|
962
|
-
? `break;`
|
|
963
|
-
: always && !counted
|
|
964
|
-
? `p++;`
|
|
965
|
-
: `if(${c}<${fast}||${c}>56319){if(!(${highTest(c)}))break ${loop};p++}` +
|
|
966
|
-
`else{${cp}=${c};if(p+1<I.length){const d=I.charCodeAt(p+1);if((d&64512)===56320)${cp}=((${c}-55296)<<10)+(d-56320)+65536}` +
|
|
967
|
-
`if(!(${highTest(cp)}))break ${loop};p+=${cp}>65535?2:1}`;
|
|
968
|
-
return `${head}if(${c}<${small}){if(!(${member}))break;p++}else ${rest}${count}}${finish}`;
|
|
969
|
-
}
|
|
970
|
-
escaped(p, node, out, fail, fn, d) {
|
|
971
|
-
const enc = node.encoding;
|
|
972
|
-
const ctrl = node.control;
|
|
973
|
-
const r = enc === undefined ? undefined : this.reader(enc);
|
|
974
|
-
if (r !== undefined && !(ctrl < r.fast))
|
|
975
|
-
return this.delegate(p, !node.raw, out, fail, fn);
|
|
976
|
-
const raw = node.raw;
|
|
977
|
-
const acc = raw ? null : out ?? this.v(fn);
|
|
978
|
-
const x = raw ? null : this.v(fn);
|
|
979
|
-
const b = this.v(fn);
|
|
980
|
-
const st = this.v(fn);
|
|
981
|
-
const okv = this.v(fn);
|
|
982
|
-
const loop = this.label();
|
|
983
|
-
const bad = this.label();
|
|
984
|
-
const normal = this.gen(node.normal, !raw, x, `break ${bad};`, fn, d);
|
|
985
|
-
const escape = this.gen(node.escape, !raw, x, fail, fn, d);
|
|
986
|
-
const onNormal = raw
|
|
987
|
-
? `if(p===${b}){$S.mode="Cut";${fail}}continue ${loop};`
|
|
988
|
-
: `if(p!==${b}){${acc}+=${x};continue ${loop}}`;
|
|
989
|
-
const slice = enc !== undefined
|
|
990
|
-
? `${this.const(enc.text)}(I,${st},p)`
|
|
991
|
-
: this.kind === 0
|
|
992
|
-
? `I.slice(${st},p)`
|
|
993
|
-
: `I.subarray(${st},p)`;
|
|
994
|
-
const ctrlTest = r === undefined ? `p<E&&${this.unit("p")}===${ctrl}` : `${r.guard("p")}&&${r.unit("p")}===${ctrl}`;
|
|
995
|
-
return `${raw ? `${st}=p;` : `${acc}="";`}${loop}:while(p<E){${b}=p;${okv}=false;` +
|
|
996
|
-
`${bad}:{${normal}${okv}=true;}` +
|
|
997
|
-
`if(${okv}){${onNormal}}else{if($S.mode!=="Backtrack"){${fail}}p=${b}}` +
|
|
998
|
-
`if(${ctrlTest}){p+=${r?.width ?? 1};${escape}${raw ? "" : `${acc}+=${x};`}}else break ${loop};}` +
|
|
999
|
-
(raw && out !== null ? `${out}=${slice};` : "");
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
847
|
/** The UTF-16 code units of a string. */
|
|
1003
848
|
const unitsOf = (s) => Array.from({ length: s.length }, (_, i) => s.charCodeAt(i));
|
|
1004
849
|
/** 1 where a small code is accepted (taking `negate` into account). */
|
|
1005
850
|
const acceptTable = (set, negate) => {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
return t;
|
|
851
|
+
const t = new Uint8Array(TableSize);
|
|
852
|
+
for (let c = 0; c < TableSize; c++) t[c] = set.table[c] === 1 !== negate ? 1 : 0;
|
|
853
|
+
return t;
|
|
1010
854
|
};
|
|
1011
855
|
/** Generates the source of a compiled parser, without building it. */
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
856
|
+
const generate = (root, kind, resumable = false) => {
|
|
857
|
+
const e = new Emitter(root, kind, resumable);
|
|
858
|
+
const main = e.rule(root, true);
|
|
859
|
+
return {
|
|
860
|
+
source: `"use strict";let $I,$E,$S,P;${e.functions.join("\n")}\nreturn function run(state){const sI=$I,sE=$E,sS=$S,sP=P;$I=state.input;$E=state.end;$S=state;try{const v=${main}(state.pos);if(v!==F)state.pos=P;return v}finally{$I=sI;$E=sE;$S=sS;P=sP}}`,
|
|
861
|
+
constants: e.constants,
|
|
862
|
+
fallbacks: e.fallbacks
|
|
863
|
+
};
|
|
1019
864
|
};
|
|
1020
865
|
/** Compiles a root parser for one input kind with `new Function`. */
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
866
|
+
const compileJit = (root, kind, resumable = false) => {
|
|
867
|
+
const { constants, fallbacks, source } = generate(root, kind, resumable);
|
|
868
|
+
const run = globalThis.Function("C", "F", "R", source)(constants, Fail, runtime);
|
|
869
|
+
let closure;
|
|
870
|
+
const step = (state) => state.end === state.input.length ? run(state) : (closure ??= createClosureCompiler(kind, resumable).compile(root, true))(state);
|
|
871
|
+
return {
|
|
872
|
+
step,
|
|
873
|
+
fallbacks,
|
|
874
|
+
source
|
|
875
|
+
};
|
|
1029
876
|
};
|
|
877
|
+
/**
|
|
878
|
+
* The JIT as a backend: generated code where `new Function` is allowed,
|
|
879
|
+
* otherwise (or if generating fails) the closure compiler.
|
|
880
|
+
*/
|
|
881
|
+
const backend = { build: (p, kind, resumable) => {
|
|
882
|
+
if (!jitSupported()) return backend$1.build(p, kind, resumable);
|
|
883
|
+
try {
|
|
884
|
+
const { fallbacks, source, step } = compileJit(p, kind, resumable);
|
|
885
|
+
return {
|
|
886
|
+
backend: "jit",
|
|
887
|
+
step,
|
|
888
|
+
fallbacks,
|
|
889
|
+
error: void 0,
|
|
890
|
+
source
|
|
891
|
+
};
|
|
892
|
+
} catch (error) {
|
|
893
|
+
return {
|
|
894
|
+
...backend$1.build(p, kind, resumable),
|
|
895
|
+
jitError: error
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
} };
|
|
899
|
+
//#endregion
|
|
900
|
+
export { backend };
|
|
901
|
+
|
|
1030
902
|
//# sourceMappingURL=jit.js.map
|