@creationix/rex 0.1.2 → 0.1.3
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/package.json +7 -4
- package/rex-cli.js +2 -16
- package/rex-compile.js +2340 -0
- package/rex.js +2319 -0
package/rex-compile.js
ADDED
|
@@ -0,0 +1,2340 @@
|
|
|
1
|
+
// rex.ts
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var require2 = createRequire(import.meta.url);
|
|
4
|
+
var rexGrammarModule = require2("./rex.ohm-bundle.cjs");
|
|
5
|
+
var rexGrammar = rexGrammarModule?.default ?? rexGrammarModule;
|
|
6
|
+
var grammar = rexGrammar;
|
|
7
|
+
var semantics = rexGrammar.createSemantics();
|
|
8
|
+
var DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
|
|
9
|
+
function byteLength(value) {
|
|
10
|
+
return Buffer.byteLength(value, "utf8");
|
|
11
|
+
}
|
|
12
|
+
var OPCODE_IDS = {
|
|
13
|
+
do: 0,
|
|
14
|
+
add: 1,
|
|
15
|
+
sub: 2,
|
|
16
|
+
mul: 3,
|
|
17
|
+
div: 4,
|
|
18
|
+
eq: 5,
|
|
19
|
+
neq: 6,
|
|
20
|
+
lt: 7,
|
|
21
|
+
lte: 8,
|
|
22
|
+
gt: 9,
|
|
23
|
+
gte: 10,
|
|
24
|
+
and: 11,
|
|
25
|
+
or: 12,
|
|
26
|
+
xor: 13,
|
|
27
|
+
not: 14,
|
|
28
|
+
boolean: 15,
|
|
29
|
+
number: 16,
|
|
30
|
+
string: 17,
|
|
31
|
+
array: 18,
|
|
32
|
+
object: 19,
|
|
33
|
+
mod: 20,
|
|
34
|
+
neg: 21
|
|
35
|
+
};
|
|
36
|
+
var registeredDomainRefs = {};
|
|
37
|
+
function resolveDomainRefMap(overrides) {
|
|
38
|
+
if (!overrides || Object.keys(overrides).length === 0) {
|
|
39
|
+
return Object.keys(registeredDomainRefs).length > 0 ? { ...registeredDomainRefs } : undefined;
|
|
40
|
+
}
|
|
41
|
+
const merged = { ...registeredDomainRefs };
|
|
42
|
+
for (const [name, refId] of Object.entries(overrides)) {
|
|
43
|
+
if (!Number.isInteger(refId) || refId < 0)
|
|
44
|
+
throw new Error(`Invalid domain extension ref id for '${name}': ${refId}`);
|
|
45
|
+
merged[name] = refId;
|
|
46
|
+
}
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
49
|
+
var BINARY_TO_OPCODE = {
|
|
50
|
+
add: "add",
|
|
51
|
+
sub: "sub",
|
|
52
|
+
mul: "mul",
|
|
53
|
+
div: "div",
|
|
54
|
+
mod: "mod",
|
|
55
|
+
bitAnd: "and",
|
|
56
|
+
bitOr: "or",
|
|
57
|
+
bitXor: "xor",
|
|
58
|
+
and: "and",
|
|
59
|
+
or: "or",
|
|
60
|
+
eq: "eq",
|
|
61
|
+
neq: "neq",
|
|
62
|
+
gt: "gt",
|
|
63
|
+
gte: "gte",
|
|
64
|
+
lt: "lt",
|
|
65
|
+
lte: "lte"
|
|
66
|
+
};
|
|
67
|
+
var ASSIGN_COMPOUND_TO_OPCODE = {
|
|
68
|
+
"+=": "add",
|
|
69
|
+
"-=": "sub",
|
|
70
|
+
"*=": "mul",
|
|
71
|
+
"/=": "div",
|
|
72
|
+
"%=": "mod",
|
|
73
|
+
"&=": "and",
|
|
74
|
+
"|=": "or",
|
|
75
|
+
"^=": "xor"
|
|
76
|
+
};
|
|
77
|
+
function encodeUint(value) {
|
|
78
|
+
if (!Number.isInteger(value) || value < 0)
|
|
79
|
+
throw new Error(`Cannot encode non-uint value: ${value}`);
|
|
80
|
+
if (value === 0)
|
|
81
|
+
return "";
|
|
82
|
+
let current = value;
|
|
83
|
+
let out = "";
|
|
84
|
+
while (current > 0) {
|
|
85
|
+
const digit = current % 64;
|
|
86
|
+
out = `${DIGITS[digit]}${out}`;
|
|
87
|
+
current = Math.floor(current / 64);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function encodeZigzag(value) {
|
|
92
|
+
if (!Number.isInteger(value))
|
|
93
|
+
throw new Error(`Cannot zigzag non-integer: ${value}`);
|
|
94
|
+
const encoded = value >= 0 ? value * 2 : -value * 2 - 1;
|
|
95
|
+
return encodeUint(encoded);
|
|
96
|
+
}
|
|
97
|
+
function encodeInt(value) {
|
|
98
|
+
return `${encodeZigzag(value)}+`;
|
|
99
|
+
}
|
|
100
|
+
function canUseBareString(value) {
|
|
101
|
+
for (const char of value) {
|
|
102
|
+
if (!DIGITS.includes(char))
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
function decodeStringLiteral(raw) {
|
|
108
|
+
const quote = raw[0];
|
|
109
|
+
if (quote !== '"' && quote !== "'" || raw[raw.length - 1] !== quote) {
|
|
110
|
+
throw new Error(`Invalid string literal: ${raw}`);
|
|
111
|
+
}
|
|
112
|
+
let out = "";
|
|
113
|
+
for (let index = 1;index < raw.length - 1; index += 1) {
|
|
114
|
+
const char = raw[index];
|
|
115
|
+
if (char !== "\\") {
|
|
116
|
+
out += char;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
index += 1;
|
|
120
|
+
const esc = raw[index];
|
|
121
|
+
if (esc === undefined)
|
|
122
|
+
throw new Error(`Invalid escape sequence in ${raw}`);
|
|
123
|
+
if (esc === "n")
|
|
124
|
+
out += `
|
|
125
|
+
`;
|
|
126
|
+
else if (esc === "r")
|
|
127
|
+
out += "\r";
|
|
128
|
+
else if (esc === "t")
|
|
129
|
+
out += "\t";
|
|
130
|
+
else if (esc === "b")
|
|
131
|
+
out += "\b";
|
|
132
|
+
else if (esc === "f")
|
|
133
|
+
out += "\f";
|
|
134
|
+
else if (esc === "v")
|
|
135
|
+
out += "\v";
|
|
136
|
+
else if (esc === "0")
|
|
137
|
+
out += "\x00";
|
|
138
|
+
else if (esc === "x") {
|
|
139
|
+
const hex = raw.slice(index + 1, index + 3);
|
|
140
|
+
if (!/^[0-9a-fA-F]{2}$/.test(hex))
|
|
141
|
+
throw new Error(`Invalid hex escape in ${raw}`);
|
|
142
|
+
out += String.fromCodePoint(parseInt(hex, 16));
|
|
143
|
+
index += 2;
|
|
144
|
+
} else if (esc === "u") {
|
|
145
|
+
const hex = raw.slice(index + 1, index + 5);
|
|
146
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex))
|
|
147
|
+
throw new Error(`Invalid unicode escape in ${raw}`);
|
|
148
|
+
out += String.fromCodePoint(parseInt(hex, 16));
|
|
149
|
+
index += 4;
|
|
150
|
+
} else {
|
|
151
|
+
out += esc;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function encodeBareOrLengthString(value) {
|
|
157
|
+
if (canUseBareString(value))
|
|
158
|
+
return `${value}:`;
|
|
159
|
+
return `${encodeUint(byteLength(value))},${value}`;
|
|
160
|
+
}
|
|
161
|
+
function encodeNumberNode(node) {
|
|
162
|
+
const numberValue = node.value;
|
|
163
|
+
if (!Number.isFinite(numberValue))
|
|
164
|
+
throw new Error(`Cannot encode non-finite number: ${node.raw}`);
|
|
165
|
+
if (Number.isInteger(numberValue))
|
|
166
|
+
return encodeInt(numberValue);
|
|
167
|
+
const raw = node.raw.toLowerCase();
|
|
168
|
+
const sign = raw.startsWith("-") ? -1 : 1;
|
|
169
|
+
const unsigned = sign < 0 ? raw.slice(1) : raw;
|
|
170
|
+
const splitExp = unsigned.split("e");
|
|
171
|
+
const mantissaText = splitExp[0];
|
|
172
|
+
const exponentText = splitExp[1] ?? "0";
|
|
173
|
+
if (!mantissaText)
|
|
174
|
+
throw new Error(`Invalid decimal literal: ${node.raw}`);
|
|
175
|
+
const exponent = Number(exponentText);
|
|
176
|
+
if (!Number.isInteger(exponent))
|
|
177
|
+
throw new Error(`Invalid decimal exponent: ${node.raw}`);
|
|
178
|
+
const dotIndex = mantissaText.indexOf(".");
|
|
179
|
+
const decimals = dotIndex === -1 ? 0 : mantissaText.length - dotIndex - 1;
|
|
180
|
+
const digits = mantissaText.replace(".", "");
|
|
181
|
+
if (!/^\d+$/.test(digits))
|
|
182
|
+
throw new Error(`Invalid decimal digits: ${node.raw}`);
|
|
183
|
+
let significand = Number(digits) * sign;
|
|
184
|
+
let power = exponent - decimals;
|
|
185
|
+
while (significand !== 0 && significand % 10 === 0) {
|
|
186
|
+
significand /= 10;
|
|
187
|
+
power += 1;
|
|
188
|
+
}
|
|
189
|
+
return `${encodeZigzag(power)}*${encodeInt(significand)}`;
|
|
190
|
+
}
|
|
191
|
+
function encodeOpcode(opcode) {
|
|
192
|
+
return `${encodeUint(OPCODE_IDS[opcode])}%`;
|
|
193
|
+
}
|
|
194
|
+
function encodeCallParts(parts) {
|
|
195
|
+
return `(${parts.join("")})`;
|
|
196
|
+
}
|
|
197
|
+
function needsOptionalPrefix(encoded) {
|
|
198
|
+
const first = encoded[0];
|
|
199
|
+
if (!first)
|
|
200
|
+
return false;
|
|
201
|
+
return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<";
|
|
202
|
+
}
|
|
203
|
+
function addOptionalPrefix(encoded) {
|
|
204
|
+
if (!needsOptionalPrefix(encoded))
|
|
205
|
+
return encoded;
|
|
206
|
+
let payload = encoded;
|
|
207
|
+
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(")) {
|
|
208
|
+
payload = encoded.slice(2, -1);
|
|
209
|
+
} else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
210
|
+
payload = encoded.slice(2, -1);
|
|
211
|
+
} else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
|
|
212
|
+
payload = encoded.slice(1, -1);
|
|
213
|
+
} else if (encoded.startsWith("=") || encoded.startsWith("~")) {
|
|
214
|
+
payload = encoded.slice(1);
|
|
215
|
+
}
|
|
216
|
+
return `${encodeUint(byteLength(payload))}${encoded}`;
|
|
217
|
+
}
|
|
218
|
+
function encodeBlockExpression(block) {
|
|
219
|
+
if (block.length === 0)
|
|
220
|
+
return "4'";
|
|
221
|
+
if (block.length === 1)
|
|
222
|
+
return encodeNode(block[0]);
|
|
223
|
+
return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
|
|
224
|
+
}
|
|
225
|
+
function encodeConditionalElse(elseBranch) {
|
|
226
|
+
if (elseBranch.type === "else")
|
|
227
|
+
return encodeBlockExpression(elseBranch.block);
|
|
228
|
+
const nested = {
|
|
229
|
+
type: "conditional",
|
|
230
|
+
head: elseBranch.head,
|
|
231
|
+
condition: elseBranch.condition,
|
|
232
|
+
thenBlock: elseBranch.thenBlock,
|
|
233
|
+
elseBranch: elseBranch.elseBranch
|
|
234
|
+
};
|
|
235
|
+
return encodeNode(nested);
|
|
236
|
+
}
|
|
237
|
+
function encodeNavigation(node) {
|
|
238
|
+
const parts = [encodeNode(node.target)];
|
|
239
|
+
for (const segment of node.segments) {
|
|
240
|
+
if (segment.type === "static")
|
|
241
|
+
parts.push(encodeBareOrLengthString(segment.key));
|
|
242
|
+
else
|
|
243
|
+
parts.push(encodeNode(segment.key));
|
|
244
|
+
}
|
|
245
|
+
return encodeCallParts(parts);
|
|
246
|
+
}
|
|
247
|
+
function encodeFor(node) {
|
|
248
|
+
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
249
|
+
if (node.binding.type === "binding:expr") {
|
|
250
|
+
return `>(${encodeNode(node.binding.source)}${body})`;
|
|
251
|
+
}
|
|
252
|
+
if (node.binding.type === "binding:valueIn") {
|
|
253
|
+
return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
|
|
254
|
+
}
|
|
255
|
+
if (node.binding.type === "binding:keyValueIn") {
|
|
256
|
+
return `>(${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body})`;
|
|
257
|
+
}
|
|
258
|
+
return `<(${encodeNode(node.binding.source)}${node.binding.key}$${body})`;
|
|
259
|
+
}
|
|
260
|
+
function encodeArrayComprehension(node) {
|
|
261
|
+
const body = addOptionalPrefix(encodeNode(node.body));
|
|
262
|
+
if (node.binding.type === "binding:expr") {
|
|
263
|
+
return `>[${encodeNode(node.binding.source)}${body}]`;
|
|
264
|
+
}
|
|
265
|
+
if (node.binding.type === "binding:valueIn") {
|
|
266
|
+
return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
|
|
267
|
+
}
|
|
268
|
+
if (node.binding.type === "binding:keyValueIn") {
|
|
269
|
+
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
|
|
270
|
+
}
|
|
271
|
+
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
|
|
272
|
+
}
|
|
273
|
+
function encodeObjectComprehension(node) {
|
|
274
|
+
const key = addOptionalPrefix(encodeNode(node.key));
|
|
275
|
+
const value = addOptionalPrefix(encodeNode(node.value));
|
|
276
|
+
if (node.binding.type === "binding:expr") {
|
|
277
|
+
return `>{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
278
|
+
}
|
|
279
|
+
if (node.binding.type === "binding:valueIn") {
|
|
280
|
+
return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
|
|
281
|
+
}
|
|
282
|
+
if (node.binding.type === "binding:keyValueIn") {
|
|
283
|
+
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
|
|
284
|
+
}
|
|
285
|
+
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
|
|
286
|
+
}
|
|
287
|
+
var activeEncodeOptions;
|
|
288
|
+
function encodeNode(node) {
|
|
289
|
+
switch (node.type) {
|
|
290
|
+
case "program":
|
|
291
|
+
return encodeBlockExpression(node.body);
|
|
292
|
+
case "identifier": {
|
|
293
|
+
const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
|
|
294
|
+
if (domainRef !== undefined)
|
|
295
|
+
return `${encodeUint(domainRef)}'`;
|
|
296
|
+
return `${node.name}$`;
|
|
297
|
+
}
|
|
298
|
+
case "self":
|
|
299
|
+
return "@";
|
|
300
|
+
case "selfDepth": {
|
|
301
|
+
if (!Number.isInteger(node.depth) || node.depth < 1)
|
|
302
|
+
throw new Error(`Invalid self depth: ${node.depth}`);
|
|
303
|
+
if (node.depth === 1)
|
|
304
|
+
return "@";
|
|
305
|
+
return `${encodeUint(node.depth - 1)}@`;
|
|
306
|
+
}
|
|
307
|
+
case "boolean":
|
|
308
|
+
return node.value ? "1'" : "2'";
|
|
309
|
+
case "null":
|
|
310
|
+
return "3'";
|
|
311
|
+
case "undefined":
|
|
312
|
+
return "4'";
|
|
313
|
+
case "number":
|
|
314
|
+
return encodeNumberNode(node);
|
|
315
|
+
case "string":
|
|
316
|
+
return encodeBareOrLengthString(decodeStringLiteral(node.raw));
|
|
317
|
+
case "array": {
|
|
318
|
+
const body = node.items.map((item) => addOptionalPrefix(encodeNode(item))).join("");
|
|
319
|
+
return `[${body}]`;
|
|
320
|
+
}
|
|
321
|
+
case "arrayComprehension":
|
|
322
|
+
return encodeArrayComprehension(node);
|
|
323
|
+
case "object": {
|
|
324
|
+
const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
|
|
325
|
+
return `{${body}}`;
|
|
326
|
+
}
|
|
327
|
+
case "objectComprehension":
|
|
328
|
+
return encodeObjectComprehension(node);
|
|
329
|
+
case "key":
|
|
330
|
+
return encodeBareOrLengthString(node.name);
|
|
331
|
+
case "group":
|
|
332
|
+
return encodeNode(node.expression);
|
|
333
|
+
case "unary":
|
|
334
|
+
if (node.op === "delete")
|
|
335
|
+
return `~${encodeNode(node.value)}`;
|
|
336
|
+
if (node.op === "neg")
|
|
337
|
+
return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
|
|
338
|
+
return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
|
|
339
|
+
case "binary":
|
|
340
|
+
if (node.op === "and") {
|
|
341
|
+
const operands = collectLogicalChain(node, "and");
|
|
342
|
+
const body = operands.map((operand, index) => {
|
|
343
|
+
const encoded = encodeNode(operand);
|
|
344
|
+
return index === 0 ? encoded : addOptionalPrefix(encoded);
|
|
345
|
+
}).join("");
|
|
346
|
+
return `&(${body})`;
|
|
347
|
+
}
|
|
348
|
+
if (node.op === "or") {
|
|
349
|
+
const operands = collectLogicalChain(node, "or");
|
|
350
|
+
const body = operands.map((operand, index) => {
|
|
351
|
+
const encoded = encodeNode(operand);
|
|
352
|
+
return index === 0 ? encoded : addOptionalPrefix(encoded);
|
|
353
|
+
}).join("");
|
|
354
|
+
return `|(${body})`;
|
|
355
|
+
}
|
|
356
|
+
return encodeCallParts([
|
|
357
|
+
encodeOpcode(BINARY_TO_OPCODE[node.op]),
|
|
358
|
+
encodeNode(node.left),
|
|
359
|
+
encodeNode(node.right)
|
|
360
|
+
]);
|
|
361
|
+
case "assign": {
|
|
362
|
+
if (node.op === "=")
|
|
363
|
+
return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
364
|
+
const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
|
|
365
|
+
if (!opcode)
|
|
366
|
+
throw new Error(`Unsupported assignment op: ${node.op}`);
|
|
367
|
+
const computedValue = encodeCallParts([encodeOpcode(opcode), encodeNode(node.place), encodeNode(node.value)]);
|
|
368
|
+
return `=${encodeNode(node.place)}${addOptionalPrefix(computedValue)}`;
|
|
369
|
+
}
|
|
370
|
+
case "navigation":
|
|
371
|
+
return encodeNavigation(node);
|
|
372
|
+
case "call":
|
|
373
|
+
return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
|
|
374
|
+
case "conditional": {
|
|
375
|
+
const opener = node.head === "when" ? "?(" : "!(";
|
|
376
|
+
const cond = encodeNode(node.condition);
|
|
377
|
+
const thenExpr = addOptionalPrefix(encodeBlockExpression(node.thenBlock));
|
|
378
|
+
const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
|
|
379
|
+
return `${opener}${cond}${thenExpr}${elseExpr})`;
|
|
380
|
+
}
|
|
381
|
+
case "for":
|
|
382
|
+
return encodeFor(node);
|
|
383
|
+
case "break":
|
|
384
|
+
return ";";
|
|
385
|
+
case "continue":
|
|
386
|
+
return "1;";
|
|
387
|
+
default: {
|
|
388
|
+
const exhaustive = node;
|
|
389
|
+
throw new Error(`Unsupported IR node ${exhaustive.type ?? "unknown"}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function collectLogicalChain(node, op) {
|
|
394
|
+
if (node.type !== "binary" || node.op !== op)
|
|
395
|
+
return [node];
|
|
396
|
+
return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
|
|
397
|
+
}
|
|
398
|
+
function parseToIR(source) {
|
|
399
|
+
const match = grammar.match(source);
|
|
400
|
+
if (!match.succeeded()) {
|
|
401
|
+
const failure = match;
|
|
402
|
+
throw new Error(failure.message ?? "Parse failed");
|
|
403
|
+
}
|
|
404
|
+
return semantics(match).toIR();
|
|
405
|
+
}
|
|
406
|
+
var DIGIT_SET = new Set(DIGITS.split(""));
|
|
407
|
+
var DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
|
|
408
|
+
function readPrefixAt(text, start) {
|
|
409
|
+
let index = start;
|
|
410
|
+
while (index < text.length && DIGIT_SET.has(text[index]))
|
|
411
|
+
index += 1;
|
|
412
|
+
const raw = text.slice(start, index);
|
|
413
|
+
let value = 0;
|
|
414
|
+
for (const char of raw) {
|
|
415
|
+
const digit = DIGIT_INDEX.get(char);
|
|
416
|
+
if (digit === undefined)
|
|
417
|
+
throw new Error(`Invalid prefix in encoded stream at ${start}`);
|
|
418
|
+
value = value * 64 + digit;
|
|
419
|
+
}
|
|
420
|
+
return { end: index, raw, value };
|
|
421
|
+
}
|
|
422
|
+
function parsePlaceEnd(text, start, out) {
|
|
423
|
+
if (text[start] === "(") {
|
|
424
|
+
let index2 = start + 1;
|
|
425
|
+
while (index2 < text.length && text[index2] !== ")") {
|
|
426
|
+
index2 = parseValueEnd(text, index2, out).end;
|
|
427
|
+
}
|
|
428
|
+
if (text[index2] !== ")")
|
|
429
|
+
throw new Error(`Unterminated place at ${start}`);
|
|
430
|
+
return index2 + 1;
|
|
431
|
+
}
|
|
432
|
+
const prefix = readPrefixAt(text, start);
|
|
433
|
+
const tag = text[prefix.end];
|
|
434
|
+
if (tag !== "$" && tag !== "'")
|
|
435
|
+
throw new Error(`Invalid place at ${start}`);
|
|
436
|
+
let index = prefix.end + 1;
|
|
437
|
+
if (text[index] !== "(")
|
|
438
|
+
return index;
|
|
439
|
+
index += 1;
|
|
440
|
+
while (index < text.length && text[index] !== ")") {
|
|
441
|
+
index = parseValueEnd(text, index, out).end;
|
|
442
|
+
}
|
|
443
|
+
if (text[index] !== ")")
|
|
444
|
+
throw new Error(`Unterminated place at ${start}`);
|
|
445
|
+
return index + 1;
|
|
446
|
+
}
|
|
447
|
+
function parseValueEnd(text, start, out) {
|
|
448
|
+
const prefix = readPrefixAt(text, start);
|
|
449
|
+
const tag = text[prefix.end];
|
|
450
|
+
if (!tag)
|
|
451
|
+
throw new Error(`Unexpected end of encoded stream at ${start}`);
|
|
452
|
+
if (tag === ",") {
|
|
453
|
+
const strStart = prefix.end + 1;
|
|
454
|
+
const strEnd = strStart + prefix.value;
|
|
455
|
+
if (strEnd > text.length)
|
|
456
|
+
throw new Error(`String overflows encoded stream at ${start}`);
|
|
457
|
+
const raw = text.slice(start, strEnd);
|
|
458
|
+
if (Buffer.byteLength(text.slice(strStart, strEnd), "utf8") !== prefix.value) {
|
|
459
|
+
throw new Error(`Non-ASCII length-string not currently dedupe-safe at ${start}`);
|
|
460
|
+
}
|
|
461
|
+
const span2 = { start, end: strEnd, raw };
|
|
462
|
+
if (out)
|
|
463
|
+
out.push(span2);
|
|
464
|
+
return span2;
|
|
465
|
+
}
|
|
466
|
+
if (tag === "=") {
|
|
467
|
+
const placeEnd = parsePlaceEnd(text, prefix.end + 1, out);
|
|
468
|
+
const valueEnd = parseValueEnd(text, placeEnd, out).end;
|
|
469
|
+
const span2 = { start, end: valueEnd, raw: text.slice(start, valueEnd) };
|
|
470
|
+
if (out)
|
|
471
|
+
out.push(span2);
|
|
472
|
+
return span2;
|
|
473
|
+
}
|
|
474
|
+
if (tag === "~") {
|
|
475
|
+
const placeEnd = parsePlaceEnd(text, prefix.end + 1, out);
|
|
476
|
+
const span2 = { start, end: placeEnd, raw: text.slice(start, placeEnd) };
|
|
477
|
+
if (out)
|
|
478
|
+
out.push(span2);
|
|
479
|
+
return span2;
|
|
480
|
+
}
|
|
481
|
+
if (tag === "(" || tag === "[" || tag === "{") {
|
|
482
|
+
const close = tag === "(" ? ")" : tag === "[" ? "]" : "}";
|
|
483
|
+
let index = prefix.end + 1;
|
|
484
|
+
while (index < text.length && text[index] !== close) {
|
|
485
|
+
index = parseValueEnd(text, index, out).end;
|
|
486
|
+
}
|
|
487
|
+
if (text[index] !== close)
|
|
488
|
+
throw new Error(`Unterminated container at ${start}`);
|
|
489
|
+
const span2 = { start, end: index + 1, raw: text.slice(start, index + 1) };
|
|
490
|
+
if (out)
|
|
491
|
+
out.push(span2);
|
|
492
|
+
return span2;
|
|
493
|
+
}
|
|
494
|
+
if (tag === "?" || tag === "!" || tag === "|" || tag === "&") {
|
|
495
|
+
if (text[prefix.end + 1] !== "(")
|
|
496
|
+
throw new Error(`Expected '(' after '${tag}' at ${start}`);
|
|
497
|
+
let index = prefix.end + 2;
|
|
498
|
+
while (index < text.length && text[index] !== ")") {
|
|
499
|
+
index = parseValueEnd(text, index, out).end;
|
|
500
|
+
}
|
|
501
|
+
if (text[index] !== ")")
|
|
502
|
+
throw new Error(`Unterminated flow container at ${start}`);
|
|
503
|
+
const span2 = { start, end: index + 1, raw: text.slice(start, index + 1) };
|
|
504
|
+
if (out)
|
|
505
|
+
out.push(span2);
|
|
506
|
+
return span2;
|
|
507
|
+
}
|
|
508
|
+
if (tag === ">" || tag === "<") {
|
|
509
|
+
const open = text[prefix.end + 1];
|
|
510
|
+
if (open !== "(" && open !== "[" && open !== "{")
|
|
511
|
+
throw new Error(`Invalid loop opener at ${start}`);
|
|
512
|
+
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
513
|
+
let index = prefix.end + 2;
|
|
514
|
+
while (index < text.length && text[index] !== close) {
|
|
515
|
+
index = parseValueEnd(text, index, out).end;
|
|
516
|
+
}
|
|
517
|
+
if (text[index] !== close)
|
|
518
|
+
throw new Error(`Unterminated loop container at ${start}`);
|
|
519
|
+
const span2 = { start, end: index + 1, raw: text.slice(start, index + 1) };
|
|
520
|
+
if (out)
|
|
521
|
+
out.push(span2);
|
|
522
|
+
return span2;
|
|
523
|
+
}
|
|
524
|
+
const span = { start, end: prefix.end + 1, raw: text.slice(start, prefix.end + 1) };
|
|
525
|
+
if (out)
|
|
526
|
+
out.push(span);
|
|
527
|
+
return span;
|
|
528
|
+
}
|
|
529
|
+
function gatherEncodedValueSpans(text) {
|
|
530
|
+
const spans = [];
|
|
531
|
+
let index = 0;
|
|
532
|
+
while (index < text.length) {
|
|
533
|
+
const span = parseValueEnd(text, index, spans);
|
|
534
|
+
index = span.end;
|
|
535
|
+
}
|
|
536
|
+
return spans;
|
|
537
|
+
}
|
|
538
|
+
function buildPointerToken(pointerStart, targetStart) {
|
|
539
|
+
let offset = targetStart - (pointerStart + 1);
|
|
540
|
+
if (offset < 0)
|
|
541
|
+
return;
|
|
542
|
+
for (let guard = 0;guard < 8; guard += 1) {
|
|
543
|
+
const prefix = encodeUint(offset);
|
|
544
|
+
const recalculated = targetStart - (pointerStart + prefix.length + 1);
|
|
545
|
+
if (recalculated === offset)
|
|
546
|
+
return `${prefix}^`;
|
|
547
|
+
offset = recalculated;
|
|
548
|
+
if (offset < 0)
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
function buildDedupeCandidateTable(encoded, minBytes) {
|
|
554
|
+
const spans = gatherEncodedValueSpans(encoded);
|
|
555
|
+
const table = new Map;
|
|
556
|
+
for (const span of spans) {
|
|
557
|
+
const sizeBytes = span.raw.length;
|
|
558
|
+
if (sizeBytes < minBytes)
|
|
559
|
+
continue;
|
|
560
|
+
const prefix = readPrefixAt(encoded, span.start);
|
|
561
|
+
const tag = encoded[prefix.end];
|
|
562
|
+
if (tag !== "{" && tag !== "[" && tag !== "," && tag !== ":")
|
|
563
|
+
continue;
|
|
564
|
+
const offsetFromEnd = encoded.length - span.end;
|
|
565
|
+
const entry = {
|
|
566
|
+
span,
|
|
567
|
+
sizeBytes,
|
|
568
|
+
offsetFromEnd
|
|
569
|
+
};
|
|
570
|
+
if (!table.has(span.raw))
|
|
571
|
+
table.set(span.raw, []);
|
|
572
|
+
table.get(span.raw).push(entry);
|
|
573
|
+
}
|
|
574
|
+
return table;
|
|
575
|
+
}
|
|
576
|
+
function dedupeLargeEncodedValues(encoded, minBytes = 4) {
|
|
577
|
+
const effectiveMinBytes = Math.max(1, minBytes);
|
|
578
|
+
let current = encoded;
|
|
579
|
+
while (true) {
|
|
580
|
+
const groups = buildDedupeCandidateTable(current, effectiveMinBytes);
|
|
581
|
+
let replaced = false;
|
|
582
|
+
for (const [value, occurrences] of groups.entries()) {
|
|
583
|
+
if (occurrences.length < 2)
|
|
584
|
+
continue;
|
|
585
|
+
const canonical = occurrences[occurrences.length - 1];
|
|
586
|
+
for (let index = occurrences.length - 2;index >= 0; index -= 1) {
|
|
587
|
+
const occurrence = occurrences[index];
|
|
588
|
+
if (occurrence.span.end > canonical.span.start)
|
|
589
|
+
continue;
|
|
590
|
+
if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
|
|
591
|
+
continue;
|
|
592
|
+
const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
|
|
593
|
+
const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
|
|
594
|
+
if (!pointerToken)
|
|
595
|
+
continue;
|
|
596
|
+
if (pointerToken.length >= occurrence.sizeBytes)
|
|
597
|
+
continue;
|
|
598
|
+
current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
|
|
599
|
+
replaced = true;
|
|
600
|
+
break;
|
|
601
|
+
}
|
|
602
|
+
if (replaced)
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
if (!replaced)
|
|
606
|
+
return current;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function encodeIR(node, options) {
|
|
610
|
+
const previous = activeEncodeOptions;
|
|
611
|
+
activeEncodeOptions = options;
|
|
612
|
+
try {
|
|
613
|
+
const encoded = encodeNode(node);
|
|
614
|
+
if (options?.dedupeValues) {
|
|
615
|
+
return dedupeLargeEncodedValues(encoded, options.dedupeMinBytes ?? 4);
|
|
616
|
+
}
|
|
617
|
+
return encoded;
|
|
618
|
+
} finally {
|
|
619
|
+
activeEncodeOptions = previous;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function cloneNode(node) {
|
|
623
|
+
return structuredClone(node);
|
|
624
|
+
}
|
|
625
|
+
function emptyOptimizeEnv() {
|
|
626
|
+
return { constants: {}, selfCaptures: {} };
|
|
627
|
+
}
|
|
628
|
+
function cloneOptimizeEnv(env) {
|
|
629
|
+
return {
|
|
630
|
+
constants: { ...env.constants },
|
|
631
|
+
selfCaptures: { ...env.selfCaptures }
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
function clearOptimizeEnv(env) {
|
|
635
|
+
for (const key of Object.keys(env.constants))
|
|
636
|
+
delete env.constants[key];
|
|
637
|
+
for (const key of Object.keys(env.selfCaptures))
|
|
638
|
+
delete env.selfCaptures[key];
|
|
639
|
+
}
|
|
640
|
+
function clearBinding(env, name) {
|
|
641
|
+
delete env.constants[name];
|
|
642
|
+
delete env.selfCaptures[name];
|
|
643
|
+
}
|
|
644
|
+
function selfTargetFromNode(node, currentDepth) {
|
|
645
|
+
if (node.type === "self")
|
|
646
|
+
return currentDepth;
|
|
647
|
+
if (node.type === "selfDepth") {
|
|
648
|
+
const target = currentDepth - (node.depth - 1);
|
|
649
|
+
if (target >= 1)
|
|
650
|
+
return target;
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
function selfNodeFromTarget(targetDepth, currentDepth) {
|
|
655
|
+
const relDepth = currentDepth - targetDepth + 1;
|
|
656
|
+
if (!Number.isInteger(relDepth) || relDepth < 1)
|
|
657
|
+
return;
|
|
658
|
+
if (relDepth === 1)
|
|
659
|
+
return { type: "self" };
|
|
660
|
+
return { type: "selfDepth", depth: relDepth };
|
|
661
|
+
}
|
|
662
|
+
function dropBindingNames(env, binding) {
|
|
663
|
+
if (binding.type === "binding:valueIn") {
|
|
664
|
+
clearBinding(env, binding.value);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (binding.type === "binding:keyValueIn") {
|
|
668
|
+
clearBinding(env, binding.key);
|
|
669
|
+
clearBinding(env, binding.value);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (binding.type === "binding:keyOf") {
|
|
673
|
+
clearBinding(env, binding.key);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function collectReads(node, out) {
|
|
677
|
+
switch (node.type) {
|
|
678
|
+
case "identifier":
|
|
679
|
+
out.add(node.name);
|
|
680
|
+
return;
|
|
681
|
+
case "group":
|
|
682
|
+
collectReads(node.expression, out);
|
|
683
|
+
return;
|
|
684
|
+
case "array":
|
|
685
|
+
for (const item of node.items)
|
|
686
|
+
collectReads(item, out);
|
|
687
|
+
return;
|
|
688
|
+
case "object":
|
|
689
|
+
for (const entry of node.entries) {
|
|
690
|
+
collectReads(entry.key, out);
|
|
691
|
+
collectReads(entry.value, out);
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
case "arrayComprehension":
|
|
695
|
+
collectReads(node.binding.source, out);
|
|
696
|
+
collectReads(node.body, out);
|
|
697
|
+
return;
|
|
698
|
+
case "objectComprehension":
|
|
699
|
+
collectReads(node.binding.source, out);
|
|
700
|
+
collectReads(node.key, out);
|
|
701
|
+
collectReads(node.value, out);
|
|
702
|
+
return;
|
|
703
|
+
case "unary":
|
|
704
|
+
collectReads(node.value, out);
|
|
705
|
+
return;
|
|
706
|
+
case "binary":
|
|
707
|
+
collectReads(node.left, out);
|
|
708
|
+
collectReads(node.right, out);
|
|
709
|
+
return;
|
|
710
|
+
case "assign":
|
|
711
|
+
if (!(node.op === "=" && node.place.type === "identifier"))
|
|
712
|
+
collectReads(node.place, out);
|
|
713
|
+
collectReads(node.value, out);
|
|
714
|
+
return;
|
|
715
|
+
case "navigation":
|
|
716
|
+
collectReads(node.target, out);
|
|
717
|
+
for (const segment of node.segments) {
|
|
718
|
+
if (segment.type === "dynamic")
|
|
719
|
+
collectReads(segment.key, out);
|
|
720
|
+
}
|
|
721
|
+
return;
|
|
722
|
+
case "call":
|
|
723
|
+
collectReads(node.callee, out);
|
|
724
|
+
for (const arg of node.args)
|
|
725
|
+
collectReads(arg, out);
|
|
726
|
+
return;
|
|
727
|
+
case "conditional":
|
|
728
|
+
collectReads(node.condition, out);
|
|
729
|
+
for (const part of node.thenBlock)
|
|
730
|
+
collectReads(part, out);
|
|
731
|
+
if (node.elseBranch)
|
|
732
|
+
collectReadsElse(node.elseBranch, out);
|
|
733
|
+
return;
|
|
734
|
+
case "for":
|
|
735
|
+
collectReads(node.binding.source, out);
|
|
736
|
+
for (const part of node.body)
|
|
737
|
+
collectReads(part, out);
|
|
738
|
+
return;
|
|
739
|
+
case "program":
|
|
740
|
+
for (const part of node.body)
|
|
741
|
+
collectReads(part, out);
|
|
742
|
+
return;
|
|
743
|
+
default:
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function collectReadsElse(elseBranch, out) {
|
|
748
|
+
if (elseBranch.type === "else") {
|
|
749
|
+
for (const part of elseBranch.block)
|
|
750
|
+
collectReads(part, out);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
collectReads(elseBranch.condition, out);
|
|
754
|
+
for (const part of elseBranch.thenBlock)
|
|
755
|
+
collectReads(part, out);
|
|
756
|
+
if (elseBranch.elseBranch)
|
|
757
|
+
collectReadsElse(elseBranch.elseBranch, out);
|
|
758
|
+
}
|
|
759
|
+
function isPureNode(node) {
|
|
760
|
+
switch (node.type) {
|
|
761
|
+
case "identifier":
|
|
762
|
+
case "self":
|
|
763
|
+
case "selfDepth":
|
|
764
|
+
case "boolean":
|
|
765
|
+
case "null":
|
|
766
|
+
case "undefined":
|
|
767
|
+
case "number":
|
|
768
|
+
case "string":
|
|
769
|
+
case "key":
|
|
770
|
+
return true;
|
|
771
|
+
case "group":
|
|
772
|
+
return isPureNode(node.expression);
|
|
773
|
+
case "array":
|
|
774
|
+
return node.items.every((item) => isPureNode(item));
|
|
775
|
+
case "object":
|
|
776
|
+
return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
|
|
777
|
+
case "navigation":
|
|
778
|
+
return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
|
|
779
|
+
case "unary":
|
|
780
|
+
return node.op !== "delete" && isPureNode(node.value);
|
|
781
|
+
case "binary":
|
|
782
|
+
return isPureNode(node.left) && isPureNode(node.right);
|
|
783
|
+
default:
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
function eliminateDeadAssignments(block) {
|
|
788
|
+
const needed = new Set;
|
|
789
|
+
const out = [];
|
|
790
|
+
for (let index = block.length - 1;index >= 0; index -= 1) {
|
|
791
|
+
const node = block[index];
|
|
792
|
+
if (node.type === "conditional") {
|
|
793
|
+
let rewritten = node;
|
|
794
|
+
if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
|
|
795
|
+
const name = node.condition.place.name;
|
|
796
|
+
const branchReads = new Set;
|
|
797
|
+
for (const part of node.thenBlock)
|
|
798
|
+
collectReads(part, branchReads);
|
|
799
|
+
if (node.elseBranch)
|
|
800
|
+
collectReadsElse(node.elseBranch, branchReads);
|
|
801
|
+
if (!needed.has(name) && !branchReads.has(name)) {
|
|
802
|
+
rewritten = {
|
|
803
|
+
type: "conditional",
|
|
804
|
+
head: node.head,
|
|
805
|
+
condition: node.condition.value,
|
|
806
|
+
thenBlock: node.thenBlock,
|
|
807
|
+
elseBranch: node.elseBranch
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
collectReads(rewritten, needed);
|
|
812
|
+
out.push(rewritten);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
|
|
816
|
+
collectReads(node.value, needed);
|
|
817
|
+
const name = node.place.name;
|
|
818
|
+
const canDrop = !needed.has(name) && isPureNode(node.value);
|
|
819
|
+
needed.delete(name);
|
|
820
|
+
if (canDrop)
|
|
821
|
+
continue;
|
|
822
|
+
out.push(node);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
collectReads(node, needed);
|
|
826
|
+
out.push(node);
|
|
827
|
+
}
|
|
828
|
+
out.reverse();
|
|
829
|
+
return out;
|
|
830
|
+
}
|
|
831
|
+
function hasIdentifierRead(node, name, asPlace = false) {
|
|
832
|
+
if (node.type === "identifier")
|
|
833
|
+
return !asPlace && node.name === name;
|
|
834
|
+
switch (node.type) {
|
|
835
|
+
case "group":
|
|
836
|
+
return hasIdentifierRead(node.expression, name);
|
|
837
|
+
case "array":
|
|
838
|
+
return node.items.some((item) => hasIdentifierRead(item, name));
|
|
839
|
+
case "object":
|
|
840
|
+
return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
|
|
841
|
+
case "navigation":
|
|
842
|
+
return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
|
|
843
|
+
case "unary":
|
|
844
|
+
return hasIdentifierRead(node.value, name, node.op === "delete");
|
|
845
|
+
case "binary":
|
|
846
|
+
return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
|
|
847
|
+
case "assign":
|
|
848
|
+
return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
|
|
849
|
+
default:
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function countIdentifierReads(node, name, asPlace = false) {
|
|
854
|
+
if (node.type === "identifier")
|
|
855
|
+
return !asPlace && node.name === name ? 1 : 0;
|
|
856
|
+
switch (node.type) {
|
|
857
|
+
case "group":
|
|
858
|
+
return countIdentifierReads(node.expression, name);
|
|
859
|
+
case "array":
|
|
860
|
+
return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
|
|
861
|
+
case "object":
|
|
862
|
+
return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
|
|
863
|
+
case "navigation":
|
|
864
|
+
return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
|
|
865
|
+
case "unary":
|
|
866
|
+
return countIdentifierReads(node.value, name, node.op === "delete");
|
|
867
|
+
case "binary":
|
|
868
|
+
return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
|
|
869
|
+
case "assign":
|
|
870
|
+
return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
|
|
871
|
+
default:
|
|
872
|
+
return 0;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
function replaceIdentifier(node, name, replacement, asPlace = false) {
|
|
876
|
+
if (node.type === "identifier") {
|
|
877
|
+
if (!asPlace && node.name === name)
|
|
878
|
+
return cloneNode(replacement);
|
|
879
|
+
return node;
|
|
880
|
+
}
|
|
881
|
+
switch (node.type) {
|
|
882
|
+
case "group":
|
|
883
|
+
return {
|
|
884
|
+
type: "group",
|
|
885
|
+
expression: replaceIdentifier(node.expression, name, replacement)
|
|
886
|
+
};
|
|
887
|
+
case "array":
|
|
888
|
+
return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
|
|
889
|
+
case "object":
|
|
890
|
+
return {
|
|
891
|
+
type: "object",
|
|
892
|
+
entries: node.entries.map((entry) => ({
|
|
893
|
+
key: replaceIdentifier(entry.key, name, replacement),
|
|
894
|
+
value: replaceIdentifier(entry.value, name, replacement)
|
|
895
|
+
}))
|
|
896
|
+
};
|
|
897
|
+
case "navigation":
|
|
898
|
+
return {
|
|
899
|
+
type: "navigation",
|
|
900
|
+
target: replaceIdentifier(node.target, name, replacement),
|
|
901
|
+
segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
|
|
902
|
+
};
|
|
903
|
+
case "unary":
|
|
904
|
+
return {
|
|
905
|
+
type: "unary",
|
|
906
|
+
op: node.op,
|
|
907
|
+
value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
|
|
908
|
+
};
|
|
909
|
+
case "binary":
|
|
910
|
+
return {
|
|
911
|
+
type: "binary",
|
|
912
|
+
op: node.op,
|
|
913
|
+
left: replaceIdentifier(node.left, name, replacement),
|
|
914
|
+
right: replaceIdentifier(node.right, name, replacement)
|
|
915
|
+
};
|
|
916
|
+
case "assign":
|
|
917
|
+
return {
|
|
918
|
+
type: "assign",
|
|
919
|
+
op: node.op,
|
|
920
|
+
place: replaceIdentifier(node.place, name, replacement, true),
|
|
921
|
+
value: replaceIdentifier(node.value, name, replacement)
|
|
922
|
+
};
|
|
923
|
+
default:
|
|
924
|
+
return node;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function isSafeInlineTargetNode(node) {
|
|
928
|
+
if (isPureNode(node))
|
|
929
|
+
return true;
|
|
930
|
+
if (node.type === "assign" && node.op === "=") {
|
|
931
|
+
return isPureNode(node.place) && isPureNode(node.value);
|
|
932
|
+
}
|
|
933
|
+
return false;
|
|
934
|
+
}
|
|
935
|
+
function inlineAdjacentPureAssignments(block) {
|
|
936
|
+
const out = [...block];
|
|
937
|
+
let changed = true;
|
|
938
|
+
while (changed) {
|
|
939
|
+
changed = false;
|
|
940
|
+
for (let index = 0;index < out.length - 1; index += 1) {
|
|
941
|
+
const current = out[index];
|
|
942
|
+
if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
|
|
943
|
+
continue;
|
|
944
|
+
if (!isPureNode(current.value))
|
|
945
|
+
continue;
|
|
946
|
+
const name = current.place.name;
|
|
947
|
+
if (hasIdentifierRead(current.value, name))
|
|
948
|
+
continue;
|
|
949
|
+
const next = out[index + 1];
|
|
950
|
+
if (!isSafeInlineTargetNode(next))
|
|
951
|
+
continue;
|
|
952
|
+
if (countIdentifierReads(next, name) !== 1)
|
|
953
|
+
continue;
|
|
954
|
+
out[index + 1] = replaceIdentifier(next, name, current.value);
|
|
955
|
+
out.splice(index, 1);
|
|
956
|
+
changed = true;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return out;
|
|
961
|
+
}
|
|
962
|
+
function toNumberNode(value) {
|
|
963
|
+
return { type: "number", raw: String(value), value };
|
|
964
|
+
}
|
|
965
|
+
function toStringNode(value) {
|
|
966
|
+
return { type: "string", raw: JSON.stringify(value) };
|
|
967
|
+
}
|
|
968
|
+
function toLiteralNode(value) {
|
|
969
|
+
if (value === undefined)
|
|
970
|
+
return { type: "undefined" };
|
|
971
|
+
if (value === null)
|
|
972
|
+
return { type: "null" };
|
|
973
|
+
if (typeof value === "boolean")
|
|
974
|
+
return { type: "boolean", value };
|
|
975
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
976
|
+
return toNumberNode(value);
|
|
977
|
+
if (typeof value === "string")
|
|
978
|
+
return toStringNode(value);
|
|
979
|
+
if (Array.isArray(value)) {
|
|
980
|
+
const items = [];
|
|
981
|
+
for (const item of value) {
|
|
982
|
+
const lowered = toLiteralNode(item);
|
|
983
|
+
if (!lowered)
|
|
984
|
+
return;
|
|
985
|
+
items.push(lowered);
|
|
986
|
+
}
|
|
987
|
+
return { type: "array", items };
|
|
988
|
+
}
|
|
989
|
+
if (value && typeof value === "object") {
|
|
990
|
+
const entries = [];
|
|
991
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
992
|
+
const loweredValue = toLiteralNode(entryValue);
|
|
993
|
+
if (!loweredValue)
|
|
994
|
+
return;
|
|
995
|
+
entries.push({ key: { type: "key", name: key }, value: loweredValue });
|
|
996
|
+
}
|
|
997
|
+
return { type: "object", entries };
|
|
998
|
+
}
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
function constValue(node) {
|
|
1002
|
+
switch (node.type) {
|
|
1003
|
+
case "undefined":
|
|
1004
|
+
return;
|
|
1005
|
+
case "null":
|
|
1006
|
+
return null;
|
|
1007
|
+
case "boolean":
|
|
1008
|
+
return node.value;
|
|
1009
|
+
case "number":
|
|
1010
|
+
return node.value;
|
|
1011
|
+
case "string":
|
|
1012
|
+
return decodeStringLiteral(node.raw);
|
|
1013
|
+
case "key":
|
|
1014
|
+
return node.name;
|
|
1015
|
+
case "array": {
|
|
1016
|
+
const out = [];
|
|
1017
|
+
for (const item of node.items) {
|
|
1018
|
+
const value = constValue(item);
|
|
1019
|
+
if (value === undefined && item.type !== "undefined")
|
|
1020
|
+
return;
|
|
1021
|
+
out.push(value);
|
|
1022
|
+
}
|
|
1023
|
+
return out;
|
|
1024
|
+
}
|
|
1025
|
+
case "object": {
|
|
1026
|
+
const out = {};
|
|
1027
|
+
for (const entry of node.entries) {
|
|
1028
|
+
const key = constValue(entry.key);
|
|
1029
|
+
if (key === undefined && entry.key.type !== "undefined")
|
|
1030
|
+
return;
|
|
1031
|
+
const value = constValue(entry.value);
|
|
1032
|
+
if (value === undefined && entry.value.type !== "undefined")
|
|
1033
|
+
return;
|
|
1034
|
+
out[String(key)] = value;
|
|
1035
|
+
}
|
|
1036
|
+
return out;
|
|
1037
|
+
}
|
|
1038
|
+
default:
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function isDefinedValue(value) {
|
|
1043
|
+
return value !== undefined;
|
|
1044
|
+
}
|
|
1045
|
+
function foldUnary(op, value) {
|
|
1046
|
+
if (op === "neg") {
|
|
1047
|
+
if (typeof value !== "number")
|
|
1048
|
+
return;
|
|
1049
|
+
return -value;
|
|
1050
|
+
}
|
|
1051
|
+
if (op === "not") {
|
|
1052
|
+
if (typeof value === "boolean")
|
|
1053
|
+
return !value;
|
|
1054
|
+
if (typeof value === "number")
|
|
1055
|
+
return ~value;
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
function foldBinary(op, left, right) {
|
|
1061
|
+
if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
|
|
1062
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
1063
|
+
return;
|
|
1064
|
+
if (op === "add")
|
|
1065
|
+
return left + right;
|
|
1066
|
+
if (op === "sub")
|
|
1067
|
+
return left - right;
|
|
1068
|
+
if (op === "mul")
|
|
1069
|
+
return left * right;
|
|
1070
|
+
if (op === "div")
|
|
1071
|
+
return left / right;
|
|
1072
|
+
return left % right;
|
|
1073
|
+
}
|
|
1074
|
+
if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
|
|
1075
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
1076
|
+
return;
|
|
1077
|
+
if (op === "bitAnd")
|
|
1078
|
+
return left & right;
|
|
1079
|
+
if (op === "bitOr")
|
|
1080
|
+
return left | right;
|
|
1081
|
+
return left ^ right;
|
|
1082
|
+
}
|
|
1083
|
+
if (op === "eq")
|
|
1084
|
+
return left === right ? left : undefined;
|
|
1085
|
+
if (op === "neq")
|
|
1086
|
+
return left !== right ? left : undefined;
|
|
1087
|
+
if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
|
|
1088
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
1089
|
+
return;
|
|
1090
|
+
if (op === "gt")
|
|
1091
|
+
return left > right ? left : undefined;
|
|
1092
|
+
if (op === "gte")
|
|
1093
|
+
return left >= right ? left : undefined;
|
|
1094
|
+
if (op === "lt")
|
|
1095
|
+
return left < right ? left : undefined;
|
|
1096
|
+
return left <= right ? left : undefined;
|
|
1097
|
+
}
|
|
1098
|
+
if (op === "and")
|
|
1099
|
+
return isDefinedValue(left) ? right : undefined;
|
|
1100
|
+
if (op === "or")
|
|
1101
|
+
return isDefinedValue(left) ? left : right;
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
function optimizeElse(elseBranch, env, currentDepth) {
|
|
1105
|
+
if (!elseBranch)
|
|
1106
|
+
return;
|
|
1107
|
+
if (elseBranch.type === "else") {
|
|
1108
|
+
return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
|
|
1109
|
+
}
|
|
1110
|
+
const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
|
|
1111
|
+
const foldedCondition = constValue(optimizedCondition);
|
|
1112
|
+
if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
|
|
1113
|
+
const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
|
|
1114
|
+
if (passes) {
|
|
1115
|
+
return {
|
|
1116
|
+
type: "else",
|
|
1117
|
+
block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
return optimizeElse(elseBranch.elseBranch, env, currentDepth);
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
type: "elseChain",
|
|
1124
|
+
head: elseBranch.head,
|
|
1125
|
+
condition: optimizedCondition,
|
|
1126
|
+
thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
|
|
1127
|
+
elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
function optimizeBlock(block, env, currentDepth) {
|
|
1131
|
+
const out = [];
|
|
1132
|
+
for (const node of block) {
|
|
1133
|
+
const optimized = optimizeNode(node, env, currentDepth);
|
|
1134
|
+
out.push(optimized);
|
|
1135
|
+
if (optimized.type === "break" || optimized.type === "continue")
|
|
1136
|
+
break;
|
|
1137
|
+
if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
|
|
1138
|
+
const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
|
|
1139
|
+
if (selfTarget !== undefined) {
|
|
1140
|
+
env.selfCaptures[optimized.place.name] = selfTarget;
|
|
1141
|
+
delete env.constants[optimized.place.name];
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
const folded = constValue(optimized.value);
|
|
1145
|
+
if (folded !== undefined || optimized.value.type === "undefined") {
|
|
1146
|
+
env.constants[optimized.place.name] = cloneNode(optimized.value);
|
|
1147
|
+
delete env.selfCaptures[optimized.place.name];
|
|
1148
|
+
} else {
|
|
1149
|
+
clearBinding(env, optimized.place.name);
|
|
1150
|
+
}
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
|
|
1154
|
+
clearBinding(env, optimized.value.name);
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (optimized.type === "assign" && optimized.place.type === "identifier") {
|
|
1158
|
+
clearBinding(env, optimized.place.name);
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
|
|
1162
|
+
clearOptimizeEnv(env);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
|
|
1166
|
+
}
|
|
1167
|
+
function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
1168
|
+
switch (node.type) {
|
|
1169
|
+
case "program": {
|
|
1170
|
+
const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
|
|
1171
|
+
if (body.length === 0)
|
|
1172
|
+
return { type: "undefined" };
|
|
1173
|
+
if (body.length === 1)
|
|
1174
|
+
return body[0];
|
|
1175
|
+
return { type: "program", body };
|
|
1176
|
+
}
|
|
1177
|
+
case "identifier": {
|
|
1178
|
+
if (asPlace)
|
|
1179
|
+
return node;
|
|
1180
|
+
const selfTarget = env.selfCaptures[node.name];
|
|
1181
|
+
if (selfTarget !== undefined) {
|
|
1182
|
+
const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
|
|
1183
|
+
if (rewritten)
|
|
1184
|
+
return rewritten;
|
|
1185
|
+
}
|
|
1186
|
+
const replacement = env.constants[node.name];
|
|
1187
|
+
return replacement ? cloneNode(replacement) : node;
|
|
1188
|
+
}
|
|
1189
|
+
case "group": {
|
|
1190
|
+
return optimizeNode(node.expression, env, currentDepth);
|
|
1191
|
+
}
|
|
1192
|
+
case "array": {
|
|
1193
|
+
return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
|
|
1194
|
+
}
|
|
1195
|
+
case "object": {
|
|
1196
|
+
return {
|
|
1197
|
+
type: "object",
|
|
1198
|
+
entries: node.entries.map((entry) => ({
|
|
1199
|
+
key: optimizeNode(entry.key, env, currentDepth),
|
|
1200
|
+
value: optimizeNode(entry.value, env, currentDepth)
|
|
1201
|
+
}))
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
case "unary": {
|
|
1205
|
+
const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
|
|
1206
|
+
const foldedValue = constValue(value);
|
|
1207
|
+
if (foldedValue !== undefined || value.type === "undefined") {
|
|
1208
|
+
const folded = foldUnary(node.op, foldedValue);
|
|
1209
|
+
const literal = folded === undefined ? undefined : toLiteralNode(folded);
|
|
1210
|
+
if (literal)
|
|
1211
|
+
return literal;
|
|
1212
|
+
}
|
|
1213
|
+
return { type: "unary", op: node.op, value };
|
|
1214
|
+
}
|
|
1215
|
+
case "binary": {
|
|
1216
|
+
const left = optimizeNode(node.left, env, currentDepth);
|
|
1217
|
+
const right = optimizeNode(node.right, env, currentDepth);
|
|
1218
|
+
const leftValue = constValue(left);
|
|
1219
|
+
const rightValue = constValue(right);
|
|
1220
|
+
if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
|
|
1221
|
+
const folded = foldBinary(node.op, leftValue, rightValue);
|
|
1222
|
+
const literal = folded === undefined ? undefined : toLiteralNode(folded);
|
|
1223
|
+
if (literal)
|
|
1224
|
+
return literal;
|
|
1225
|
+
}
|
|
1226
|
+
return { type: "binary", op: node.op, left, right };
|
|
1227
|
+
}
|
|
1228
|
+
case "navigation": {
|
|
1229
|
+
const target = optimizeNode(node.target, env, currentDepth);
|
|
1230
|
+
const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
|
|
1231
|
+
const targetValue = constValue(target);
|
|
1232
|
+
if (targetValue !== undefined || target.type === "undefined") {
|
|
1233
|
+
let current = targetValue;
|
|
1234
|
+
let foldable = true;
|
|
1235
|
+
for (const segment of segments) {
|
|
1236
|
+
if (!foldable)
|
|
1237
|
+
break;
|
|
1238
|
+
const key = segment.type === "static" ? segment.key : constValue(segment.key);
|
|
1239
|
+
if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
|
|
1240
|
+
foldable = false;
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
if (current === null || current === undefined) {
|
|
1244
|
+
current = undefined;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
current = current[String(key)];
|
|
1248
|
+
}
|
|
1249
|
+
if (foldable) {
|
|
1250
|
+
const literal = toLiteralNode(current);
|
|
1251
|
+
if (literal)
|
|
1252
|
+
return literal;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return {
|
|
1256
|
+
type: "navigation",
|
|
1257
|
+
target,
|
|
1258
|
+
segments
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
case "call": {
|
|
1262
|
+
return {
|
|
1263
|
+
type: "call",
|
|
1264
|
+
callee: optimizeNode(node.callee, env, currentDepth),
|
|
1265
|
+
args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
case "assign": {
|
|
1269
|
+
return {
|
|
1270
|
+
type: "assign",
|
|
1271
|
+
op: node.op,
|
|
1272
|
+
place: optimizeNode(node.place, env, currentDepth, true),
|
|
1273
|
+
value: optimizeNode(node.value, env, currentDepth)
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
case "conditional": {
|
|
1277
|
+
const condition = optimizeNode(node.condition, env, currentDepth);
|
|
1278
|
+
const thenEnv = cloneOptimizeEnv(env);
|
|
1279
|
+
if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
|
|
1280
|
+
thenEnv.selfCaptures[condition.place.name] = currentDepth;
|
|
1281
|
+
delete thenEnv.constants[condition.place.name];
|
|
1282
|
+
}
|
|
1283
|
+
const conditionValue = constValue(condition);
|
|
1284
|
+
if (conditionValue !== undefined || condition.type === "undefined") {
|
|
1285
|
+
const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
|
|
1286
|
+
if (passes) {
|
|
1287
|
+
const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
|
|
1288
|
+
if (thenBlock.length === 0)
|
|
1289
|
+
return { type: "undefined" };
|
|
1290
|
+
if (thenBlock.length === 1)
|
|
1291
|
+
return thenBlock[0];
|
|
1292
|
+
return { type: "program", body: thenBlock };
|
|
1293
|
+
}
|
|
1294
|
+
if (!node.elseBranch)
|
|
1295
|
+
return { type: "undefined" };
|
|
1296
|
+
const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
|
|
1297
|
+
if (!loweredElse)
|
|
1298
|
+
return { type: "undefined" };
|
|
1299
|
+
if (loweredElse.type === "else") {
|
|
1300
|
+
if (loweredElse.block.length === 0)
|
|
1301
|
+
return { type: "undefined" };
|
|
1302
|
+
if (loweredElse.block.length === 1)
|
|
1303
|
+
return loweredElse.block[0];
|
|
1304
|
+
return { type: "program", body: loweredElse.block };
|
|
1305
|
+
}
|
|
1306
|
+
return {
|
|
1307
|
+
type: "conditional",
|
|
1308
|
+
head: loweredElse.head,
|
|
1309
|
+
condition: loweredElse.condition,
|
|
1310
|
+
thenBlock: loweredElse.thenBlock,
|
|
1311
|
+
elseBranch: loweredElse.elseBranch
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
type: "conditional",
|
|
1316
|
+
head: node.head,
|
|
1317
|
+
condition,
|
|
1318
|
+
thenBlock: optimizeBlock(node.thenBlock, thenEnv, currentDepth),
|
|
1319
|
+
elseBranch: optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth)
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
case "for": {
|
|
1323
|
+
const sourceEnv = cloneOptimizeEnv(env);
|
|
1324
|
+
const binding = (() => {
|
|
1325
|
+
if (node.binding.type === "binding:expr") {
|
|
1326
|
+
return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
|
|
1327
|
+
}
|
|
1328
|
+
if (node.binding.type === "binding:valueIn") {
|
|
1329
|
+
return {
|
|
1330
|
+
type: "binding:valueIn",
|
|
1331
|
+
value: node.binding.value,
|
|
1332
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
if (node.binding.type === "binding:keyValueIn") {
|
|
1336
|
+
return {
|
|
1337
|
+
type: "binding:keyValueIn",
|
|
1338
|
+
key: node.binding.key,
|
|
1339
|
+
value: node.binding.value,
|
|
1340
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
return {
|
|
1344
|
+
type: "binding:keyOf",
|
|
1345
|
+
key: node.binding.key,
|
|
1346
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1347
|
+
};
|
|
1348
|
+
})();
|
|
1349
|
+
const bodyEnv = cloneOptimizeEnv(env);
|
|
1350
|
+
dropBindingNames(bodyEnv, binding);
|
|
1351
|
+
return {
|
|
1352
|
+
type: "for",
|
|
1353
|
+
binding,
|
|
1354
|
+
body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
case "arrayComprehension": {
|
|
1358
|
+
const sourceEnv = cloneOptimizeEnv(env);
|
|
1359
|
+
const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
|
|
1360
|
+
type: "binding:valueIn",
|
|
1361
|
+
value: node.binding.value,
|
|
1362
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1363
|
+
} : node.binding.type === "binding:keyValueIn" ? {
|
|
1364
|
+
type: "binding:keyValueIn",
|
|
1365
|
+
key: node.binding.key,
|
|
1366
|
+
value: node.binding.value,
|
|
1367
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1368
|
+
} : {
|
|
1369
|
+
type: "binding:keyOf",
|
|
1370
|
+
key: node.binding.key,
|
|
1371
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1372
|
+
};
|
|
1373
|
+
const bodyEnv = cloneOptimizeEnv(env);
|
|
1374
|
+
dropBindingNames(bodyEnv, binding);
|
|
1375
|
+
return {
|
|
1376
|
+
type: "arrayComprehension",
|
|
1377
|
+
binding,
|
|
1378
|
+
body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
case "objectComprehension": {
|
|
1382
|
+
const sourceEnv = cloneOptimizeEnv(env);
|
|
1383
|
+
const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
|
|
1384
|
+
type: "binding:valueIn",
|
|
1385
|
+
value: node.binding.value,
|
|
1386
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1387
|
+
} : node.binding.type === "binding:keyValueIn" ? {
|
|
1388
|
+
type: "binding:keyValueIn",
|
|
1389
|
+
key: node.binding.key,
|
|
1390
|
+
value: node.binding.value,
|
|
1391
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1392
|
+
} : {
|
|
1393
|
+
type: "binding:keyOf",
|
|
1394
|
+
key: node.binding.key,
|
|
1395
|
+
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1396
|
+
};
|
|
1397
|
+
const bodyEnv = cloneOptimizeEnv(env);
|
|
1398
|
+
dropBindingNames(bodyEnv, binding);
|
|
1399
|
+
return {
|
|
1400
|
+
type: "objectComprehension",
|
|
1401
|
+
binding,
|
|
1402
|
+
key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
|
|
1403
|
+
value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
default:
|
|
1407
|
+
return node;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function optimizeIR(node) {
|
|
1411
|
+
return optimizeNode(node, emptyOptimizeEnv(), 1);
|
|
1412
|
+
}
|
|
1413
|
+
function collectLocalBindings(node, locals) {
|
|
1414
|
+
switch (node.type) {
|
|
1415
|
+
case "assign":
|
|
1416
|
+
if (node.place.type === "identifier")
|
|
1417
|
+
locals.add(node.place.name);
|
|
1418
|
+
collectLocalBindings(node.place, locals);
|
|
1419
|
+
collectLocalBindings(node.value, locals);
|
|
1420
|
+
return;
|
|
1421
|
+
case "program":
|
|
1422
|
+
for (const part of node.body)
|
|
1423
|
+
collectLocalBindings(part, locals);
|
|
1424
|
+
return;
|
|
1425
|
+
case "group":
|
|
1426
|
+
collectLocalBindings(node.expression, locals);
|
|
1427
|
+
return;
|
|
1428
|
+
case "array":
|
|
1429
|
+
for (const item of node.items)
|
|
1430
|
+
collectLocalBindings(item, locals);
|
|
1431
|
+
return;
|
|
1432
|
+
case "object":
|
|
1433
|
+
for (const entry of node.entries) {
|
|
1434
|
+
collectLocalBindings(entry.key, locals);
|
|
1435
|
+
collectLocalBindings(entry.value, locals);
|
|
1436
|
+
}
|
|
1437
|
+
return;
|
|
1438
|
+
case "navigation":
|
|
1439
|
+
collectLocalBindings(node.target, locals);
|
|
1440
|
+
for (const segment of node.segments) {
|
|
1441
|
+
if (segment.type === "dynamic")
|
|
1442
|
+
collectLocalBindings(segment.key, locals);
|
|
1443
|
+
}
|
|
1444
|
+
return;
|
|
1445
|
+
case "call":
|
|
1446
|
+
collectLocalBindings(node.callee, locals);
|
|
1447
|
+
for (const arg of node.args)
|
|
1448
|
+
collectLocalBindings(arg, locals);
|
|
1449
|
+
return;
|
|
1450
|
+
case "unary":
|
|
1451
|
+
collectLocalBindings(node.value, locals);
|
|
1452
|
+
return;
|
|
1453
|
+
case "binary":
|
|
1454
|
+
collectLocalBindings(node.left, locals);
|
|
1455
|
+
collectLocalBindings(node.right, locals);
|
|
1456
|
+
return;
|
|
1457
|
+
case "conditional":
|
|
1458
|
+
collectLocalBindings(node.condition, locals);
|
|
1459
|
+
for (const part of node.thenBlock)
|
|
1460
|
+
collectLocalBindings(part, locals);
|
|
1461
|
+
if (node.elseBranch)
|
|
1462
|
+
collectLocalBindingsElse(node.elseBranch, locals);
|
|
1463
|
+
return;
|
|
1464
|
+
case "for":
|
|
1465
|
+
collectLocalBindingFromBinding(node.binding, locals);
|
|
1466
|
+
for (const part of node.body)
|
|
1467
|
+
collectLocalBindings(part, locals);
|
|
1468
|
+
return;
|
|
1469
|
+
case "arrayComprehension":
|
|
1470
|
+
collectLocalBindingFromBinding(node.binding, locals);
|
|
1471
|
+
collectLocalBindings(node.body, locals);
|
|
1472
|
+
return;
|
|
1473
|
+
case "objectComprehension":
|
|
1474
|
+
collectLocalBindingFromBinding(node.binding, locals);
|
|
1475
|
+
collectLocalBindings(node.key, locals);
|
|
1476
|
+
collectLocalBindings(node.value, locals);
|
|
1477
|
+
return;
|
|
1478
|
+
default:
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
function collectLocalBindingFromBinding(binding, locals) {
|
|
1483
|
+
if (binding.type === "binding:valueIn") {
|
|
1484
|
+
locals.add(binding.value);
|
|
1485
|
+
collectLocalBindings(binding.source, locals);
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
if (binding.type === "binding:keyValueIn") {
|
|
1489
|
+
locals.add(binding.key);
|
|
1490
|
+
locals.add(binding.value);
|
|
1491
|
+
collectLocalBindings(binding.source, locals);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
if (binding.type === "binding:keyOf") {
|
|
1495
|
+
locals.add(binding.key);
|
|
1496
|
+
collectLocalBindings(binding.source, locals);
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
collectLocalBindings(binding.source, locals);
|
|
1500
|
+
}
|
|
1501
|
+
function collectLocalBindingsElse(elseBranch, locals) {
|
|
1502
|
+
if (elseBranch.type === "else") {
|
|
1503
|
+
for (const part of elseBranch.block)
|
|
1504
|
+
collectLocalBindings(part, locals);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
collectLocalBindings(elseBranch.condition, locals);
|
|
1508
|
+
for (const part of elseBranch.thenBlock)
|
|
1509
|
+
collectLocalBindings(part, locals);
|
|
1510
|
+
if (elseBranch.elseBranch)
|
|
1511
|
+
collectLocalBindingsElse(elseBranch.elseBranch, locals);
|
|
1512
|
+
}
|
|
1513
|
+
function bumpNameFrequency(name, locals, frequencies, order, nextOrder) {
|
|
1514
|
+
if (!locals.has(name))
|
|
1515
|
+
return;
|
|
1516
|
+
if (!order.has(name)) {
|
|
1517
|
+
order.set(name, nextOrder.value);
|
|
1518
|
+
nextOrder.value += 1;
|
|
1519
|
+
}
|
|
1520
|
+
frequencies.set(name, (frequencies.get(name) ?? 0) + 1);
|
|
1521
|
+
}
|
|
1522
|
+
function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
|
|
1523
|
+
switch (node.type) {
|
|
1524
|
+
case "identifier":
|
|
1525
|
+
bumpNameFrequency(node.name, locals, frequencies, order, nextOrder);
|
|
1526
|
+
return;
|
|
1527
|
+
case "assign":
|
|
1528
|
+
if (node.place.type === "identifier")
|
|
1529
|
+
bumpNameFrequency(node.place.name, locals, frequencies, order, nextOrder);
|
|
1530
|
+
collectNameFrequencies(node.place, locals, frequencies, order, nextOrder);
|
|
1531
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1532
|
+
return;
|
|
1533
|
+
case "program":
|
|
1534
|
+
for (const part of node.body)
|
|
1535
|
+
collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
1536
|
+
return;
|
|
1537
|
+
case "group":
|
|
1538
|
+
collectNameFrequencies(node.expression, locals, frequencies, order, nextOrder);
|
|
1539
|
+
return;
|
|
1540
|
+
case "array":
|
|
1541
|
+
for (const item of node.items)
|
|
1542
|
+
collectNameFrequencies(item, locals, frequencies, order, nextOrder);
|
|
1543
|
+
return;
|
|
1544
|
+
case "object":
|
|
1545
|
+
for (const entry of node.entries) {
|
|
1546
|
+
collectNameFrequencies(entry.key, locals, frequencies, order, nextOrder);
|
|
1547
|
+
collectNameFrequencies(entry.value, locals, frequencies, order, nextOrder);
|
|
1548
|
+
}
|
|
1549
|
+
return;
|
|
1550
|
+
case "navigation":
|
|
1551
|
+
collectNameFrequencies(node.target, locals, frequencies, order, nextOrder);
|
|
1552
|
+
for (const segment of node.segments) {
|
|
1553
|
+
if (segment.type === "dynamic")
|
|
1554
|
+
collectNameFrequencies(segment.key, locals, frequencies, order, nextOrder);
|
|
1555
|
+
}
|
|
1556
|
+
return;
|
|
1557
|
+
case "call":
|
|
1558
|
+
collectNameFrequencies(node.callee, locals, frequencies, order, nextOrder);
|
|
1559
|
+
for (const arg of node.args)
|
|
1560
|
+
collectNameFrequencies(arg, locals, frequencies, order, nextOrder);
|
|
1561
|
+
return;
|
|
1562
|
+
case "unary":
|
|
1563
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1564
|
+
return;
|
|
1565
|
+
case "binary":
|
|
1566
|
+
collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
|
|
1567
|
+
collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
|
|
1568
|
+
return;
|
|
1569
|
+
case "conditional":
|
|
1570
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1571
|
+
for (const part of node.thenBlock)
|
|
1572
|
+
collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
1573
|
+
if (node.elseBranch)
|
|
1574
|
+
collectNameFrequenciesElse(node.elseBranch, locals, frequencies, order, nextOrder);
|
|
1575
|
+
return;
|
|
1576
|
+
case "for":
|
|
1577
|
+
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1578
|
+
for (const part of node.body)
|
|
1579
|
+
collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
1580
|
+
return;
|
|
1581
|
+
case "arrayComprehension":
|
|
1582
|
+
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1583
|
+
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1584
|
+
return;
|
|
1585
|
+
case "objectComprehension":
|
|
1586
|
+
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1587
|
+
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1588
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1589
|
+
return;
|
|
1590
|
+
default:
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
function collectNameFrequenciesBinding(binding, locals, frequencies, order, nextOrder) {
|
|
1595
|
+
if (binding.type === "binding:valueIn") {
|
|
1596
|
+
bumpNameFrequency(binding.value, locals, frequencies, order, nextOrder);
|
|
1597
|
+
collectNameFrequencies(binding.source, locals, frequencies, order, nextOrder);
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (binding.type === "binding:keyValueIn") {
|
|
1601
|
+
bumpNameFrequency(binding.key, locals, frequencies, order, nextOrder);
|
|
1602
|
+
bumpNameFrequency(binding.value, locals, frequencies, order, nextOrder);
|
|
1603
|
+
collectNameFrequencies(binding.source, locals, frequencies, order, nextOrder);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (binding.type === "binding:keyOf") {
|
|
1607
|
+
bumpNameFrequency(binding.key, locals, frequencies, order, nextOrder);
|
|
1608
|
+
collectNameFrequencies(binding.source, locals, frequencies, order, nextOrder);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
collectNameFrequencies(binding.source, locals, frequencies, order, nextOrder);
|
|
1612
|
+
}
|
|
1613
|
+
function collectNameFrequenciesElse(elseBranch, locals, frequencies, order, nextOrder) {
|
|
1614
|
+
if (elseBranch.type === "else") {
|
|
1615
|
+
for (const part of elseBranch.block)
|
|
1616
|
+
collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
collectNameFrequencies(elseBranch.condition, locals, frequencies, order, nextOrder);
|
|
1620
|
+
for (const part of elseBranch.thenBlock)
|
|
1621
|
+
collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
1622
|
+
if (elseBranch.elseBranch)
|
|
1623
|
+
collectNameFrequenciesElse(elseBranch.elseBranch, locals, frequencies, order, nextOrder);
|
|
1624
|
+
}
|
|
1625
|
+
function renameLocalNames(node, map) {
|
|
1626
|
+
switch (node.type) {
|
|
1627
|
+
case "identifier":
|
|
1628
|
+
return map.has(node.name) ? { type: "identifier", name: map.get(node.name) } : node;
|
|
1629
|
+
case "program":
|
|
1630
|
+
return { type: "program", body: node.body.map((part) => renameLocalNames(part, map)) };
|
|
1631
|
+
case "group":
|
|
1632
|
+
return { type: "group", expression: renameLocalNames(node.expression, map) };
|
|
1633
|
+
case "array":
|
|
1634
|
+
return { type: "array", items: node.items.map((item) => renameLocalNames(item, map)) };
|
|
1635
|
+
case "object":
|
|
1636
|
+
return {
|
|
1637
|
+
type: "object",
|
|
1638
|
+
entries: node.entries.map((entry) => ({
|
|
1639
|
+
key: renameLocalNames(entry.key, map),
|
|
1640
|
+
value: renameLocalNames(entry.value, map)
|
|
1641
|
+
}))
|
|
1642
|
+
};
|
|
1643
|
+
case "navigation":
|
|
1644
|
+
return {
|
|
1645
|
+
type: "navigation",
|
|
1646
|
+
target: renameLocalNames(node.target, map),
|
|
1647
|
+
segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: renameLocalNames(segment.key, map) })
|
|
1648
|
+
};
|
|
1649
|
+
case "call":
|
|
1650
|
+
return {
|
|
1651
|
+
type: "call",
|
|
1652
|
+
callee: renameLocalNames(node.callee, map),
|
|
1653
|
+
args: node.args.map((arg) => renameLocalNames(arg, map))
|
|
1654
|
+
};
|
|
1655
|
+
case "unary":
|
|
1656
|
+
return { type: "unary", op: node.op, value: renameLocalNames(node.value, map) };
|
|
1657
|
+
case "binary":
|
|
1658
|
+
return {
|
|
1659
|
+
type: "binary",
|
|
1660
|
+
op: node.op,
|
|
1661
|
+
left: renameLocalNames(node.left, map),
|
|
1662
|
+
right: renameLocalNames(node.right, map)
|
|
1663
|
+
};
|
|
1664
|
+
case "assign": {
|
|
1665
|
+
const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
|
|
1666
|
+
return {
|
|
1667
|
+
type: "assign",
|
|
1668
|
+
op: node.op,
|
|
1669
|
+
place,
|
|
1670
|
+
value: renameLocalNames(node.value, map)
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
case "conditional":
|
|
1674
|
+
return {
|
|
1675
|
+
type: "conditional",
|
|
1676
|
+
head: node.head,
|
|
1677
|
+
condition: renameLocalNames(node.condition, map),
|
|
1678
|
+
thenBlock: node.thenBlock.map((part) => renameLocalNames(part, map)),
|
|
1679
|
+
elseBranch: node.elseBranch ? renameLocalNamesElse(node.elseBranch, map) : undefined
|
|
1680
|
+
};
|
|
1681
|
+
case "for":
|
|
1682
|
+
return {
|
|
1683
|
+
type: "for",
|
|
1684
|
+
binding: renameLocalNamesBinding(node.binding, map),
|
|
1685
|
+
body: node.body.map((part) => renameLocalNames(part, map))
|
|
1686
|
+
};
|
|
1687
|
+
case "arrayComprehension":
|
|
1688
|
+
return {
|
|
1689
|
+
type: "arrayComprehension",
|
|
1690
|
+
binding: renameLocalNamesBinding(node.binding, map),
|
|
1691
|
+
body: renameLocalNames(node.body, map)
|
|
1692
|
+
};
|
|
1693
|
+
case "objectComprehension":
|
|
1694
|
+
return {
|
|
1695
|
+
type: "objectComprehension",
|
|
1696
|
+
binding: renameLocalNamesBinding(node.binding, map),
|
|
1697
|
+
key: renameLocalNames(node.key, map),
|
|
1698
|
+
value: renameLocalNames(node.value, map)
|
|
1699
|
+
};
|
|
1700
|
+
default:
|
|
1701
|
+
return node;
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
function renameLocalNamesBinding(binding, map) {
|
|
1705
|
+
if (binding.type === "binding:expr") {
|
|
1706
|
+
return { type: "binding:expr", source: renameLocalNames(binding.source, map) };
|
|
1707
|
+
}
|
|
1708
|
+
if (binding.type === "binding:valueIn") {
|
|
1709
|
+
return {
|
|
1710
|
+
type: "binding:valueIn",
|
|
1711
|
+
value: map.get(binding.value) ?? binding.value,
|
|
1712
|
+
source: renameLocalNames(binding.source, map)
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
if (binding.type === "binding:keyValueIn") {
|
|
1716
|
+
return {
|
|
1717
|
+
type: "binding:keyValueIn",
|
|
1718
|
+
key: map.get(binding.key) ?? binding.key,
|
|
1719
|
+
value: map.get(binding.value) ?? binding.value,
|
|
1720
|
+
source: renameLocalNames(binding.source, map)
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
return {
|
|
1724
|
+
type: "binding:keyOf",
|
|
1725
|
+
key: map.get(binding.key) ?? binding.key,
|
|
1726
|
+
source: renameLocalNames(binding.source, map)
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
function renameLocalNamesElse(elseBranch, map) {
|
|
1730
|
+
if (elseBranch.type === "else") {
|
|
1731
|
+
return {
|
|
1732
|
+
type: "else",
|
|
1733
|
+
block: elseBranch.block.map((part) => renameLocalNames(part, map))
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
return {
|
|
1737
|
+
type: "elseChain",
|
|
1738
|
+
head: elseBranch.head,
|
|
1739
|
+
condition: renameLocalNames(elseBranch.condition, map),
|
|
1740
|
+
thenBlock: elseBranch.thenBlock.map((part) => renameLocalNames(part, map)),
|
|
1741
|
+
elseBranch: elseBranch.elseBranch ? renameLocalNamesElse(elseBranch.elseBranch, map) : undefined
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function minifyLocalNamesIR(node) {
|
|
1745
|
+
const locals = new Set;
|
|
1746
|
+
collectLocalBindings(node, locals);
|
|
1747
|
+
if (locals.size === 0)
|
|
1748
|
+
return node;
|
|
1749
|
+
const frequencies = new Map;
|
|
1750
|
+
const order = new Map;
|
|
1751
|
+
collectNameFrequencies(node, locals, frequencies, order, { value: 0 });
|
|
1752
|
+
const ranked = Array.from(locals).sort((a, b) => {
|
|
1753
|
+
const freqA = frequencies.get(a) ?? 0;
|
|
1754
|
+
const freqB = frequencies.get(b) ?? 0;
|
|
1755
|
+
if (freqA !== freqB)
|
|
1756
|
+
return freqB - freqA;
|
|
1757
|
+
const orderA = order.get(a) ?? Number.MAX_SAFE_INTEGER;
|
|
1758
|
+
const orderB = order.get(b) ?? Number.MAX_SAFE_INTEGER;
|
|
1759
|
+
if (orderA !== orderB)
|
|
1760
|
+
return orderA - orderB;
|
|
1761
|
+
return a.localeCompare(b);
|
|
1762
|
+
});
|
|
1763
|
+
const renameMap = new Map;
|
|
1764
|
+
ranked.forEach((name, index) => {
|
|
1765
|
+
renameMap.set(name, encodeUint(index));
|
|
1766
|
+
});
|
|
1767
|
+
return renameLocalNames(node, renameMap);
|
|
1768
|
+
}
|
|
1769
|
+
function compile(source, options) {
|
|
1770
|
+
const ir = parseToIR(source);
|
|
1771
|
+
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
1772
|
+
if (options?.minifyNames)
|
|
1773
|
+
lowered = minifyLocalNamesIR(lowered);
|
|
1774
|
+
return encodeIR(lowered, {
|
|
1775
|
+
domainRefs: resolveDomainRefMap(options?.domainRefs),
|
|
1776
|
+
dedupeValues: options?.dedupeValues,
|
|
1777
|
+
dedupeMinBytes: options?.dedupeMinBytes
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
function parseNumber(raw) {
|
|
1781
|
+
if (/^-?0x/i.test(raw))
|
|
1782
|
+
return parseInt(raw, 16);
|
|
1783
|
+
if (/^-?0b/i.test(raw)) {
|
|
1784
|
+
const isNegative = raw.startsWith("-");
|
|
1785
|
+
const digits = raw.replace(/^-?0b/i, "");
|
|
1786
|
+
const value = parseInt(digits, 2);
|
|
1787
|
+
return isNegative ? -value : value;
|
|
1788
|
+
}
|
|
1789
|
+
return Number(raw);
|
|
1790
|
+
}
|
|
1791
|
+
function collectStructured(value, out) {
|
|
1792
|
+
if (Array.isArray(value)) {
|
|
1793
|
+
for (const part of value)
|
|
1794
|
+
collectStructured(part, out);
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
if (!value || typeof value !== "object")
|
|
1798
|
+
return;
|
|
1799
|
+
if ("type" in value || "key" in value && "value" in value) {
|
|
1800
|
+
out.push(value);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
function normalizeList(value) {
|
|
1804
|
+
const out = [];
|
|
1805
|
+
collectStructured(value, out);
|
|
1806
|
+
return out;
|
|
1807
|
+
}
|
|
1808
|
+
function collectPostfixSteps(value, out) {
|
|
1809
|
+
if (Array.isArray(value)) {
|
|
1810
|
+
for (const part of value)
|
|
1811
|
+
collectPostfixSteps(part, out);
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (!value || typeof value !== "object")
|
|
1815
|
+
return;
|
|
1816
|
+
if ("kind" in value)
|
|
1817
|
+
out.push(value);
|
|
1818
|
+
}
|
|
1819
|
+
function normalizePostfixSteps(value) {
|
|
1820
|
+
const out = [];
|
|
1821
|
+
collectPostfixSteps(value, out);
|
|
1822
|
+
return out;
|
|
1823
|
+
}
|
|
1824
|
+
function buildPostfix(base, steps) {
|
|
1825
|
+
let current = base;
|
|
1826
|
+
let pendingSegments = [];
|
|
1827
|
+
const flushSegments = () => {
|
|
1828
|
+
if (pendingSegments.length === 0)
|
|
1829
|
+
return;
|
|
1830
|
+
current = {
|
|
1831
|
+
type: "navigation",
|
|
1832
|
+
target: current,
|
|
1833
|
+
segments: pendingSegments
|
|
1834
|
+
};
|
|
1835
|
+
pendingSegments = [];
|
|
1836
|
+
};
|
|
1837
|
+
for (const step of steps) {
|
|
1838
|
+
if (step.kind === "navStatic") {
|
|
1839
|
+
pendingSegments.push({ type: "static", key: step.key });
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
if (step.kind === "navDynamic") {
|
|
1843
|
+
pendingSegments.push({ type: "dynamic", key: step.key });
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
flushSegments();
|
|
1847
|
+
current = { type: "call", callee: current, args: step.args };
|
|
1848
|
+
}
|
|
1849
|
+
flushSegments();
|
|
1850
|
+
return current;
|
|
1851
|
+
}
|
|
1852
|
+
semantics.addOperation("toIR", {
|
|
1853
|
+
_iter(...children) {
|
|
1854
|
+
return children.map((child) => child.toIR());
|
|
1855
|
+
},
|
|
1856
|
+
_terminal() {
|
|
1857
|
+
return this.sourceString;
|
|
1858
|
+
},
|
|
1859
|
+
_nonterminal(...children) {
|
|
1860
|
+
if (children.length === 1 && children[0])
|
|
1861
|
+
return children[0].toIR();
|
|
1862
|
+
return children.map((child) => child.toIR());
|
|
1863
|
+
},
|
|
1864
|
+
Program(expressions) {
|
|
1865
|
+
const body = normalizeList(expressions.toIR());
|
|
1866
|
+
if (body.length === 1)
|
|
1867
|
+
return body[0];
|
|
1868
|
+
return { type: "program", body };
|
|
1869
|
+
},
|
|
1870
|
+
Block(expressions) {
|
|
1871
|
+
return normalizeList(expressions.toIR());
|
|
1872
|
+
},
|
|
1873
|
+
Elements(first, separatorsAndItems, maybeTrailingComma, maybeEmpty) {
|
|
1874
|
+
return normalizeList([
|
|
1875
|
+
first.toIR(),
|
|
1876
|
+
separatorsAndItems.toIR(),
|
|
1877
|
+
maybeTrailingComma.toIR(),
|
|
1878
|
+
maybeEmpty.toIR()
|
|
1879
|
+
]);
|
|
1880
|
+
},
|
|
1881
|
+
AssignExpr_assign(place, op, value) {
|
|
1882
|
+
return {
|
|
1883
|
+
type: "assign",
|
|
1884
|
+
op: op.sourceString,
|
|
1885
|
+
place: place.toIR(),
|
|
1886
|
+
value: value.toIR()
|
|
1887
|
+
};
|
|
1888
|
+
},
|
|
1889
|
+
ExistenceExpr_and(left, _and, right) {
|
|
1890
|
+
return { type: "binary", op: "and", left: left.toIR(), right: right.toIR() };
|
|
1891
|
+
},
|
|
1892
|
+
ExistenceExpr_or(left, _or, right) {
|
|
1893
|
+
return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
|
|
1894
|
+
},
|
|
1895
|
+
BitExpr_and(left, _op, right) {
|
|
1896
|
+
return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
|
|
1897
|
+
},
|
|
1898
|
+
BitExpr_xor(left, _op, right) {
|
|
1899
|
+
return { type: "binary", op: "bitXor", left: left.toIR(), right: right.toIR() };
|
|
1900
|
+
},
|
|
1901
|
+
BitExpr_or(left, _op, right) {
|
|
1902
|
+
return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
|
|
1903
|
+
},
|
|
1904
|
+
CompareExpr_binary(left, op, right) {
|
|
1905
|
+
const map = {
|
|
1906
|
+
"==": "eq",
|
|
1907
|
+
"!=": "neq",
|
|
1908
|
+
">": "gt",
|
|
1909
|
+
">=": "gte",
|
|
1910
|
+
"<": "lt",
|
|
1911
|
+
"<=": "lte"
|
|
1912
|
+
};
|
|
1913
|
+
const mapped = map[op.sourceString];
|
|
1914
|
+
if (!mapped)
|
|
1915
|
+
throw new Error(`Unsupported compare op: ${op.sourceString}`);
|
|
1916
|
+
return { type: "binary", op: mapped, left: left.toIR(), right: right.toIR() };
|
|
1917
|
+
},
|
|
1918
|
+
AddExpr_add(left, _op, right) {
|
|
1919
|
+
return { type: "binary", op: "add", left: left.toIR(), right: right.toIR() };
|
|
1920
|
+
},
|
|
1921
|
+
AddExpr_sub(left, _op, right) {
|
|
1922
|
+
return { type: "binary", op: "sub", left: left.toIR(), right: right.toIR() };
|
|
1923
|
+
},
|
|
1924
|
+
MulExpr_mul(left, _op, right) {
|
|
1925
|
+
return { type: "binary", op: "mul", left: left.toIR(), right: right.toIR() };
|
|
1926
|
+
},
|
|
1927
|
+
MulExpr_div(left, _op, right) {
|
|
1928
|
+
return { type: "binary", op: "div", left: left.toIR(), right: right.toIR() };
|
|
1929
|
+
},
|
|
1930
|
+
MulExpr_mod(left, _op, right) {
|
|
1931
|
+
return { type: "binary", op: "mod", left: left.toIR(), right: right.toIR() };
|
|
1932
|
+
},
|
|
1933
|
+
UnaryExpr_neg(_op, value) {
|
|
1934
|
+
const lowered = value.toIR();
|
|
1935
|
+
if (lowered.type === "number") {
|
|
1936
|
+
const raw = lowered.raw.startsWith("-") ? lowered.raw.slice(1) : `-${lowered.raw}`;
|
|
1937
|
+
return { type: "number", raw, value: -lowered.value };
|
|
1938
|
+
}
|
|
1939
|
+
return { type: "unary", op: "neg", value: lowered };
|
|
1940
|
+
},
|
|
1941
|
+
UnaryExpr_not(_op, value) {
|
|
1942
|
+
return { type: "unary", op: "not", value: value.toIR() };
|
|
1943
|
+
},
|
|
1944
|
+
UnaryExpr_delete(_del, place) {
|
|
1945
|
+
return { type: "unary", op: "delete", value: place.toIR() };
|
|
1946
|
+
},
|
|
1947
|
+
PostfixExpr_chain(base, tails) {
|
|
1948
|
+
return buildPostfix(base.toIR(), normalizePostfixSteps(tails.toIR()));
|
|
1949
|
+
},
|
|
1950
|
+
Place(base, tails) {
|
|
1951
|
+
return buildPostfix(base.toIR(), normalizePostfixSteps(tails.toIR()));
|
|
1952
|
+
},
|
|
1953
|
+
PlaceTail_navStatic(_dot, key) {
|
|
1954
|
+
return { kind: "navStatic", key: key.sourceString };
|
|
1955
|
+
},
|
|
1956
|
+
PlaceTail_navDynamic(_dotOpen, key, _close) {
|
|
1957
|
+
return { kind: "navDynamic", key: key.toIR() };
|
|
1958
|
+
},
|
|
1959
|
+
PostfixTail_navStatic(_dot, key) {
|
|
1960
|
+
return { kind: "navStatic", key: key.sourceString };
|
|
1961
|
+
},
|
|
1962
|
+
PostfixTail_navDynamic(_dotOpen, key, _close) {
|
|
1963
|
+
return { kind: "navDynamic", key: key.toIR() };
|
|
1964
|
+
},
|
|
1965
|
+
PostfixTail_callEmpty(_open, _close) {
|
|
1966
|
+
return { kind: "call", args: [] };
|
|
1967
|
+
},
|
|
1968
|
+
PostfixTail_call(_open, args, _close) {
|
|
1969
|
+
return { kind: "call", args: normalizeList(args.toIR()) };
|
|
1970
|
+
},
|
|
1971
|
+
ConditionalExpr(head, condition, _do, thenBlock, elseBranch, _end) {
|
|
1972
|
+
const nextElse = elseBranch.children[0];
|
|
1973
|
+
return {
|
|
1974
|
+
type: "conditional",
|
|
1975
|
+
head: head.toIR(),
|
|
1976
|
+
condition: condition.toIR(),
|
|
1977
|
+
thenBlock: thenBlock.toIR(),
|
|
1978
|
+
elseBranch: nextElse ? nextElse.toIR() : undefined
|
|
1979
|
+
};
|
|
1980
|
+
},
|
|
1981
|
+
ConditionalHead(_kw) {
|
|
1982
|
+
return this.sourceString;
|
|
1983
|
+
},
|
|
1984
|
+
ConditionalElse_elseChain(_else, head, condition, _do, thenBlock, elseBranch) {
|
|
1985
|
+
const nextElse = elseBranch.children[0];
|
|
1986
|
+
return {
|
|
1987
|
+
type: "elseChain",
|
|
1988
|
+
head: head.toIR(),
|
|
1989
|
+
condition: condition.toIR(),
|
|
1990
|
+
thenBlock: thenBlock.toIR(),
|
|
1991
|
+
elseBranch: nextElse ? nextElse.toIR() : undefined
|
|
1992
|
+
};
|
|
1993
|
+
},
|
|
1994
|
+
ConditionalElse_else(_else, block) {
|
|
1995
|
+
return { type: "else", block: block.toIR() };
|
|
1996
|
+
},
|
|
1997
|
+
DoExpr(_do, block, _end) {
|
|
1998
|
+
const body = block.toIR();
|
|
1999
|
+
if (body.length === 0)
|
|
2000
|
+
return { type: "undefined" };
|
|
2001
|
+
if (body.length === 1)
|
|
2002
|
+
return body[0];
|
|
2003
|
+
return { type: "program", body };
|
|
2004
|
+
},
|
|
2005
|
+
ForExpr(_for, binding, _do, block, _end) {
|
|
2006
|
+
return {
|
|
2007
|
+
type: "for",
|
|
2008
|
+
binding: binding.toIR(),
|
|
2009
|
+
body: block.toIR()
|
|
2010
|
+
};
|
|
2011
|
+
},
|
|
2012
|
+
BindingExpr(iterOrExpr) {
|
|
2013
|
+
const node = iterOrExpr.toIR();
|
|
2014
|
+
if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
|
|
2015
|
+
return node;
|
|
2016
|
+
}
|
|
2017
|
+
return { type: "binding:expr", source: node };
|
|
2018
|
+
},
|
|
2019
|
+
Array_empty(_open, _close) {
|
|
2020
|
+
return { type: "array", items: [] };
|
|
2021
|
+
},
|
|
2022
|
+
Array_comprehension(_open, binding, _semi, body, _close) {
|
|
2023
|
+
return {
|
|
2024
|
+
type: "arrayComprehension",
|
|
2025
|
+
binding: binding.toIR(),
|
|
2026
|
+
body: body.toIR()
|
|
2027
|
+
};
|
|
2028
|
+
},
|
|
2029
|
+
Array_values(_open, items, _close) {
|
|
2030
|
+
return { type: "array", items: normalizeList(items.toIR()) };
|
|
2031
|
+
},
|
|
2032
|
+
Object_empty(_open, _close) {
|
|
2033
|
+
return { type: "object", entries: [] };
|
|
2034
|
+
},
|
|
2035
|
+
Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
|
|
2036
|
+
return {
|
|
2037
|
+
type: "objectComprehension",
|
|
2038
|
+
binding: binding.toIR(),
|
|
2039
|
+
key: key.toIR(),
|
|
2040
|
+
value: value.toIR()
|
|
2041
|
+
};
|
|
2042
|
+
},
|
|
2043
|
+
Object_pairs(_open, pairs, _close) {
|
|
2044
|
+
return {
|
|
2045
|
+
type: "object",
|
|
2046
|
+
entries: normalizeList(pairs.toIR())
|
|
2047
|
+
};
|
|
2048
|
+
},
|
|
2049
|
+
IterBinding_keyValueIn(key, _comma, value, _in, source) {
|
|
2050
|
+
return {
|
|
2051
|
+
type: "binding:keyValueIn",
|
|
2052
|
+
key: key.sourceString,
|
|
2053
|
+
value: value.sourceString,
|
|
2054
|
+
source: source.toIR()
|
|
2055
|
+
};
|
|
2056
|
+
},
|
|
2057
|
+
IterBinding_valueIn(value, _in, source) {
|
|
2058
|
+
return {
|
|
2059
|
+
type: "binding:valueIn",
|
|
2060
|
+
value: value.sourceString,
|
|
2061
|
+
source: source.toIR()
|
|
2062
|
+
};
|
|
2063
|
+
},
|
|
2064
|
+
IterBinding_keyOf(key, _of, source) {
|
|
2065
|
+
return {
|
|
2066
|
+
type: "binding:keyOf",
|
|
2067
|
+
key: key.sourceString,
|
|
2068
|
+
source: source.toIR()
|
|
2069
|
+
};
|
|
2070
|
+
},
|
|
2071
|
+
Pair(key, _colon, value) {
|
|
2072
|
+
return { key: key.toIR(), value: value.toIR() };
|
|
2073
|
+
},
|
|
2074
|
+
ObjKey_bare(key) {
|
|
2075
|
+
return { type: "key", name: key.sourceString };
|
|
2076
|
+
},
|
|
2077
|
+
ObjKey_number(num) {
|
|
2078
|
+
return num.toIR();
|
|
2079
|
+
},
|
|
2080
|
+
ObjKey_string(str) {
|
|
2081
|
+
return str.toIR();
|
|
2082
|
+
},
|
|
2083
|
+
ObjKey_computed(_open, expr, _close) {
|
|
2084
|
+
return expr.toIR();
|
|
2085
|
+
},
|
|
2086
|
+
BreakKw(_kw) {
|
|
2087
|
+
return { type: "break" };
|
|
2088
|
+
},
|
|
2089
|
+
ContinueKw(_kw) {
|
|
2090
|
+
return { type: "continue" };
|
|
2091
|
+
},
|
|
2092
|
+
SelfExpr_depth(_self, _at, depth) {
|
|
2093
|
+
const value = depth.toIR();
|
|
2094
|
+
if (value.type !== "number" || !Number.isInteger(value.value) || value.value < 1) {
|
|
2095
|
+
throw new Error("self depth must be a positive integer literal");
|
|
2096
|
+
}
|
|
2097
|
+
if (value.value === 1)
|
|
2098
|
+
return { type: "self" };
|
|
2099
|
+
return { type: "selfDepth", depth: value.value };
|
|
2100
|
+
},
|
|
2101
|
+
SelfExpr_plain(selfKw) {
|
|
2102
|
+
return selfKw.toIR();
|
|
2103
|
+
},
|
|
2104
|
+
SelfKw(_kw) {
|
|
2105
|
+
return { type: "self" };
|
|
2106
|
+
},
|
|
2107
|
+
TrueKw(_kw) {
|
|
2108
|
+
return { type: "boolean", value: true };
|
|
2109
|
+
},
|
|
2110
|
+
FalseKw(_kw) {
|
|
2111
|
+
return { type: "boolean", value: false };
|
|
2112
|
+
},
|
|
2113
|
+
NullKw(_kw) {
|
|
2114
|
+
return { type: "null" };
|
|
2115
|
+
},
|
|
2116
|
+
UndefinedKw(_kw) {
|
|
2117
|
+
return { type: "undefined" };
|
|
2118
|
+
},
|
|
2119
|
+
StringKw(_kw) {
|
|
2120
|
+
return { type: "identifier", name: "string" };
|
|
2121
|
+
},
|
|
2122
|
+
NumberKw(_kw) {
|
|
2123
|
+
return { type: "identifier", name: "number" };
|
|
2124
|
+
},
|
|
2125
|
+
ObjectKw(_kw) {
|
|
2126
|
+
return { type: "identifier", name: "object" };
|
|
2127
|
+
},
|
|
2128
|
+
ArrayKw(_kw) {
|
|
2129
|
+
return { type: "identifier", name: "array" };
|
|
2130
|
+
},
|
|
2131
|
+
BooleanKw(_kw) {
|
|
2132
|
+
return { type: "identifier", name: "boolean" };
|
|
2133
|
+
},
|
|
2134
|
+
identifier(_a, _b) {
|
|
2135
|
+
return { type: "identifier", name: this.sourceString };
|
|
2136
|
+
},
|
|
2137
|
+
String(_value) {
|
|
2138
|
+
return { type: "string", raw: this.sourceString };
|
|
2139
|
+
},
|
|
2140
|
+
Number(_value) {
|
|
2141
|
+
return { type: "number", raw: this.sourceString, value: parseNumber(this.sourceString) };
|
|
2142
|
+
},
|
|
2143
|
+
PrimaryExpr_group(_open, expr, _close) {
|
|
2144
|
+
return { type: "group", expression: expr.toIR() };
|
|
2145
|
+
}
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
// rex-compile.ts
|
|
2149
|
+
import { dirname, resolve } from "node:path";
|
|
2150
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2151
|
+
function parseArgs(argv) {
|
|
2152
|
+
const options = {
|
|
2153
|
+
ir: false,
|
|
2154
|
+
minifyNames: false,
|
|
2155
|
+
dedupeValues: false,
|
|
2156
|
+
domainRefs: {},
|
|
2157
|
+
help: false
|
|
2158
|
+
};
|
|
2159
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
2160
|
+
const arg = argv[index];
|
|
2161
|
+
if (!arg)
|
|
2162
|
+
continue;
|
|
2163
|
+
if (arg === "--help" || arg === "-h") {
|
|
2164
|
+
options.help = true;
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
if (arg === "--ir") {
|
|
2168
|
+
options.ir = true;
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
if (arg === "--minify-names" || arg === "-m") {
|
|
2172
|
+
options.minifyNames = true;
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
if (arg === "--dedupe-values") {
|
|
2176
|
+
options.dedupeValues = true;
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
if (arg === "--dedupe-min-bytes") {
|
|
2180
|
+
const value = argv[index + 1];
|
|
2181
|
+
if (!value)
|
|
2182
|
+
throw new Error("Missing value for --dedupe-min-bytes");
|
|
2183
|
+
const parsed = Number(value);
|
|
2184
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
2185
|
+
throw new Error("--dedupe-min-bytes must be a positive integer");
|
|
2186
|
+
options.dedupeMinBytes = parsed;
|
|
2187
|
+
index += 1;
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
if (arg === "--domain-extension") {
|
|
2191
|
+
const value = argv[index + 1];
|
|
2192
|
+
if (!value)
|
|
2193
|
+
throw new Error("Missing value for --domain-extension");
|
|
2194
|
+
options.domainRefs[value] = 0;
|
|
2195
|
+
index += 1;
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
if (arg === "--domain-ref") {
|
|
2199
|
+
const value = argv[index + 1];
|
|
2200
|
+
if (!value)
|
|
2201
|
+
throw new Error("Missing value for --domain-ref");
|
|
2202
|
+
const separator = value.indexOf("=");
|
|
2203
|
+
if (separator < 1 || separator === value.length - 1) {
|
|
2204
|
+
throw new Error("--domain-ref expects NAME=ID (for example: headers=0)");
|
|
2205
|
+
}
|
|
2206
|
+
const name = value.slice(0, separator);
|
|
2207
|
+
const idText = value.slice(separator + 1);
|
|
2208
|
+
const id = Number(idText);
|
|
2209
|
+
if (!Number.isInteger(id) || id < 0)
|
|
2210
|
+
throw new Error(`Invalid domain ref id in --domain-ref '${value}'`);
|
|
2211
|
+
options.domainRefs[name] = id;
|
|
2212
|
+
index += 1;
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
if (arg === "--expr" || arg === "-e") {
|
|
2216
|
+
const value = argv[index + 1];
|
|
2217
|
+
if (!value)
|
|
2218
|
+
throw new Error("Missing value for --expr");
|
|
2219
|
+
options.expr = value;
|
|
2220
|
+
index += 1;
|
|
2221
|
+
continue;
|
|
2222
|
+
}
|
|
2223
|
+
if (arg === "--file" || arg === "-f") {
|
|
2224
|
+
const value = argv[index + 1];
|
|
2225
|
+
if (!value)
|
|
2226
|
+
throw new Error("Missing value for --file");
|
|
2227
|
+
options.file = value;
|
|
2228
|
+
index += 1;
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
if (arg === "--out" || arg === "-o") {
|
|
2232
|
+
const value = argv[index + 1];
|
|
2233
|
+
if (!value)
|
|
2234
|
+
throw new Error("Missing value for --out");
|
|
2235
|
+
options.out = value;
|
|
2236
|
+
index += 1;
|
|
2237
|
+
continue;
|
|
2238
|
+
}
|
|
2239
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
2240
|
+
}
|
|
2241
|
+
return options;
|
|
2242
|
+
}
|
|
2243
|
+
function usage() {
|
|
2244
|
+
return [
|
|
2245
|
+
"Compile high-level Rex to compact encoding (rexc).",
|
|
2246
|
+
"",
|
|
2247
|
+
"Usage:",
|
|
2248
|
+
' rex --expr "when x do y end"',
|
|
2249
|
+
" rex --file input.rex",
|
|
2250
|
+
" cat input.rex | rex",
|
|
2251
|
+
"",
|
|
2252
|
+
'(Repo script alternative: bun run rex:compile --expr "when x do y end")',
|
|
2253
|
+
"",
|
|
2254
|
+
"Options:",
|
|
2255
|
+
" -e, --expr <source> Compile an inline expression/program",
|
|
2256
|
+
" -f, --file <path> Compile source from a file",
|
|
2257
|
+
" -o, --out <path> Write output to file instead of stdout",
|
|
2258
|
+
" --ir Output lowered IR JSON instead of compact encoding",
|
|
2259
|
+
" -m, --minify-names Minify local variable names in compiled output",
|
|
2260
|
+
" --dedupe-values Deduplicate large repeated values using forward pointers",
|
|
2261
|
+
" --dedupe-min-bytes <n> Minimum encoded value bytes for pointer dedupe (default: 4)",
|
|
2262
|
+
" --domain-extension <name> Map domain symbol name to ref 0 (apostrophe)",
|
|
2263
|
+
" --domain-ref <name=id> Map domain symbol name to a specific ref id",
|
|
2264
|
+
" -h, --help Show this message"
|
|
2265
|
+
].join(`
|
|
2266
|
+
`);
|
|
2267
|
+
}
|
|
2268
|
+
async function readStdin() {
|
|
2269
|
+
const chunks = [];
|
|
2270
|
+
for await (const chunk of process.stdin) {
|
|
2271
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2272
|
+
}
|
|
2273
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
2274
|
+
}
|
|
2275
|
+
async function resolveSource(options) {
|
|
2276
|
+
if (options.expr && options.file)
|
|
2277
|
+
throw new Error("Use only one of --expr or --file");
|
|
2278
|
+
if (options.expr)
|
|
2279
|
+
return options.expr;
|
|
2280
|
+
if (options.file)
|
|
2281
|
+
return readFile(options.file, "utf8");
|
|
2282
|
+
if (!process.stdin.isTTY) {
|
|
2283
|
+
const piped = await readStdin();
|
|
2284
|
+
if (piped.trim().length > 0)
|
|
2285
|
+
return piped;
|
|
2286
|
+
}
|
|
2287
|
+
throw new Error("No input provided. Use --expr, --file, or pipe source via stdin.");
|
|
2288
|
+
}
|
|
2289
|
+
async function loadDomainRefsFromFolder(folderPath) {
|
|
2290
|
+
const schemaPath = resolve(folderPath, "rex-domain.json");
|
|
2291
|
+
let parsed;
|
|
2292
|
+
try {
|
|
2293
|
+
parsed = JSON.parse(await readFile(schemaPath, "utf8"));
|
|
2294
|
+
} catch (error) {
|
|
2295
|
+
if (error.code === "ENOENT")
|
|
2296
|
+
return {};
|
|
2297
|
+
throw error;
|
|
2298
|
+
}
|
|
2299
|
+
if (!parsed || typeof parsed !== "object" || !parsed.globals || typeof parsed.globals !== "object") {
|
|
2300
|
+
throw new Error(`Invalid rex-domain.json at ${schemaPath}: expected { globals: { ... } }`);
|
|
2301
|
+
}
|
|
2302
|
+
const refs = {};
|
|
2303
|
+
let nextRef = 0;
|
|
2304
|
+
for (const name of Object.keys(parsed.globals)) {
|
|
2305
|
+
refs[name] = nextRef;
|
|
2306
|
+
nextRef += 1;
|
|
2307
|
+
}
|
|
2308
|
+
return refs;
|
|
2309
|
+
}
|
|
2310
|
+
async function resolveDomainRefs(options) {
|
|
2311
|
+
const baseFolder = options.file ? dirname(resolve(options.file)) : process.cwd();
|
|
2312
|
+
const autoRefs = await loadDomainRefsFromFolder(baseFolder);
|
|
2313
|
+
return { ...autoRefs, ...options.domainRefs };
|
|
2314
|
+
}
|
|
2315
|
+
async function main() {
|
|
2316
|
+
const options = parseArgs(process.argv.slice(2));
|
|
2317
|
+
if (options.help) {
|
|
2318
|
+
console.log(usage());
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2321
|
+
const source = await resolveSource(options);
|
|
2322
|
+
const domainRefs = await resolveDomainRefs(options);
|
|
2323
|
+
const output = options.ir ? JSON.stringify(parseToIR(source), null, 2) : compile(source, {
|
|
2324
|
+
minifyNames: options.minifyNames,
|
|
2325
|
+
dedupeValues: options.dedupeValues,
|
|
2326
|
+
dedupeMinBytes: options.dedupeMinBytes,
|
|
2327
|
+
domainRefs
|
|
2328
|
+
});
|
|
2329
|
+
if (options.out) {
|
|
2330
|
+
await writeFile(options.out, `${output}
|
|
2331
|
+
`, "utf8");
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
console.log(output);
|
|
2335
|
+
}
|
|
2336
|
+
await main().catch((error) => {
|
|
2337
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2338
|
+
console.error(`rex:compile error: ${message}`);
|
|
2339
|
+
process.exit(1);
|
|
2340
|
+
});
|