@creationix/rex 0.1.2 → 0.1.4

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