@creationix/rex 0.3.1 → 0.4.1
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 +1 -1
- package/rex-cli.js +615 -269
- package/rex-cli.ts +32 -11
- package/rex-repl.js +582 -256
- package/rex-repl.ts +4 -3
- package/rex.js +310 -180
- package/rex.ohm +50 -22
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +26 -8
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +342 -201
- package/rexc-interpreter.ts +262 -85
package/rex.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type IRNode =
|
|
|
19
19
|
| { type: "string"; raw: string }
|
|
20
20
|
| { type: "array"; items: IRNode[] }
|
|
21
21
|
| { type: "arrayComprehension"; binding: IRBindingOrExpr; body: IRNode }
|
|
22
|
+
| { type: "whileArrayComprehension"; condition: IRNode; body: IRNode }
|
|
22
23
|
| { type: "object"; entries: { key: IRNode; value: IRNode }[] }
|
|
23
24
|
| {
|
|
24
25
|
type: "objectComprehension";
|
|
@@ -26,9 +27,15 @@ export type IRNode =
|
|
|
26
27
|
key: IRNode;
|
|
27
28
|
value: IRNode;
|
|
28
29
|
}
|
|
30
|
+
| {
|
|
31
|
+
type: "whileObjectComprehension";
|
|
32
|
+
condition: IRNode;
|
|
33
|
+
key: IRNode;
|
|
34
|
+
value: IRNode;
|
|
35
|
+
}
|
|
29
36
|
| { type: "key"; name: string }
|
|
30
37
|
| { type: "group"; expression: IRNode }
|
|
31
|
-
| { type: "unary"; op: "neg" | "not" | "delete"; value: IRNode }
|
|
38
|
+
| { type: "unary"; op: "neg" | "not" | "logicalNot" | "delete"; value: IRNode }
|
|
32
39
|
| {
|
|
33
40
|
type: "binary";
|
|
34
41
|
op:
|
|
@@ -42,6 +49,7 @@ export type IRNode =
|
|
|
42
49
|
| "bitXor"
|
|
43
50
|
| "and"
|
|
44
51
|
| "or"
|
|
52
|
+
| "nor"
|
|
45
53
|
| "eq"
|
|
46
54
|
| "neq"
|
|
47
55
|
| "gt"
|
|
@@ -53,7 +61,7 @@ export type IRNode =
|
|
|
53
61
|
}
|
|
54
62
|
| {
|
|
55
63
|
type: "assign";
|
|
56
|
-
op: "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=";
|
|
64
|
+
op: ":=" | "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=";
|
|
57
65
|
place: IRNode;
|
|
58
66
|
value: IRNode;
|
|
59
67
|
}
|
|
@@ -72,15 +80,18 @@ export type IRNode =
|
|
|
72
80
|
}
|
|
73
81
|
| { type: "for"; binding: IRBindingOrExpr; body: IRNode[] }
|
|
74
82
|
| { type: "while"; condition: IRNode; body: IRNode[] }
|
|
83
|
+
| { type: "range"; from: IRNode; to: IRNode }
|
|
75
84
|
| { type: "break" }
|
|
76
85
|
| { type: "continue" };
|
|
77
86
|
|
|
78
87
|
export type IRBinding =
|
|
79
88
|
| { type: "binding:keyValueIn"; key: string; value: string; source: IRNode }
|
|
80
89
|
| { type: "binding:valueIn"; value: string; source: IRNode }
|
|
81
|
-
| { type: "binding:keyOf"; key: string; source: IRNode }
|
|
90
|
+
| { type: "binding:keyOf"; key: string; source: IRNode }
|
|
91
|
+
| { type: "binding:bareIn"; source: IRNode }
|
|
92
|
+
| { type: "binding:bareOf"; source: IRNode };
|
|
82
93
|
|
|
83
|
-
export type IRBindingOrExpr = IRBinding
|
|
94
|
+
export type IRBindingOrExpr = IRBinding;
|
|
84
95
|
|
|
85
96
|
export type IRConditionalElse =
|
|
86
97
|
| { type: "else"; block: IRNode[] }
|
|
@@ -99,34 +110,40 @@ function byteLength(value: string): number {
|
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
const OPCODE_IDS = {
|
|
102
|
-
do:
|
|
103
|
-
add:
|
|
104
|
-
sub:
|
|
105
|
-
mul:
|
|
106
|
-
div:
|
|
107
|
-
eq:
|
|
108
|
-
neq:
|
|
109
|
-
lt:
|
|
110
|
-
lte:
|
|
111
|
-
gt:
|
|
112
|
-
gte:
|
|
113
|
-
and:
|
|
114
|
-
or:
|
|
115
|
-
xor:
|
|
116
|
-
not:
|
|
117
|
-
boolean:
|
|
118
|
-
number:
|
|
119
|
-
string:
|
|
120
|
-
array:
|
|
121
|
-
object:
|
|
122
|
-
mod:
|
|
123
|
-
neg:
|
|
113
|
+
do: "",
|
|
114
|
+
add: "ad",
|
|
115
|
+
sub: "sb",
|
|
116
|
+
mul: "ml",
|
|
117
|
+
div: "dv",
|
|
118
|
+
eq: "eq",
|
|
119
|
+
neq: "nq",
|
|
120
|
+
lt: "lt",
|
|
121
|
+
lte: "le",
|
|
122
|
+
gt: "gt",
|
|
123
|
+
gte: "ge",
|
|
124
|
+
and: "an",
|
|
125
|
+
or: "or",
|
|
126
|
+
xor: "xr",
|
|
127
|
+
not: "nt",
|
|
128
|
+
boolean: "bt",
|
|
129
|
+
number: "nm",
|
|
130
|
+
string: "st",
|
|
131
|
+
array: "ar",
|
|
132
|
+
object: "ob",
|
|
133
|
+
mod: "md",
|
|
134
|
+
neg: "ng",
|
|
135
|
+
range: "rn",
|
|
136
|
+
size: "sz",
|
|
124
137
|
} as const;
|
|
125
138
|
|
|
126
139
|
type OpcodeName = keyof typeof OPCODE_IDS;
|
|
127
140
|
|
|
141
|
+
// Keyword identifiers that are reserved in the grammar and compile to opcodes when called.
|
|
142
|
+
const KEYWORD_OPCODES: ReadonlySet<string> = new Set(["boolean", "number", "string", "array", "object", "size"]);
|
|
143
|
+
|
|
128
144
|
type EncodeOptions = {
|
|
129
|
-
domainRefs?: Record<string,
|
|
145
|
+
domainRefs?: Record<string, string>;
|
|
146
|
+
domainOpcodes?: Record<string, string>;
|
|
130
147
|
};
|
|
131
148
|
|
|
132
149
|
type CompileOptions = {
|
|
@@ -141,10 +158,7 @@ type RexDomainConfigEntry = {
|
|
|
141
158
|
names?: unknown;
|
|
142
159
|
};
|
|
143
160
|
|
|
144
|
-
const
|
|
145
|
-
const DOMAIN_DIGIT_INDEX = new Map<string, number>(Array.from(DIGITS).map((char, index) => [char, index]));
|
|
146
|
-
|
|
147
|
-
const BINARY_TO_OPCODE: Record<Extract<IRNode, { type: "binary" }> ["op"], OpcodeName> = {
|
|
161
|
+
const BINARY_TO_OPCODE: Record<Exclude<Extract<IRNode, { type: "binary" }> ["op"], "nor">, OpcodeName> = {
|
|
148
162
|
add: "add",
|
|
149
163
|
sub: "sub",
|
|
150
164
|
mul: "mul",
|
|
@@ -267,9 +281,9 @@ function encodeDecimal(significand: number, power: number): string {
|
|
|
267
281
|
|
|
268
282
|
function encodeNumberNode(node: Extract<IRNode, { type: "number" }>): string {
|
|
269
283
|
const numberValue = node.value;
|
|
270
|
-
if (Number.isNaN(numberValue)) return "
|
|
271
|
-
if (numberValue === Infinity) return "
|
|
272
|
-
if (numberValue === -Infinity) return "
|
|
284
|
+
if (Number.isNaN(numberValue)) return "nan'";
|
|
285
|
+
if (numberValue === Infinity) return "inf'";
|
|
286
|
+
if (numberValue === -Infinity) return "nif'";
|
|
273
287
|
|
|
274
288
|
if (Number.isInteger(numberValue)) {
|
|
275
289
|
const { base, exp } = splitDecimal(numberValue);
|
|
@@ -302,7 +316,7 @@ function encodeNumberNode(node: Extract<IRNode, { type: "number" }>): string {
|
|
|
302
316
|
}
|
|
303
317
|
|
|
304
318
|
function encodeOpcode(opcode: OpcodeName): string {
|
|
305
|
-
return `${
|
|
319
|
+
return `${OPCODE_IDS[opcode]}%`;
|
|
306
320
|
}
|
|
307
321
|
|
|
308
322
|
function encodeCallParts(parts: string[]): string {
|
|
@@ -321,7 +335,7 @@ function addOptionalPrefix(encoded: string): string {
|
|
|
321
335
|
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
|
|
322
336
|
payload = encoded.slice(2, -1);
|
|
323
337
|
}
|
|
324
|
-
else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
338
|
+
else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
|
|
325
339
|
payload = encoded.slice(2, -1);
|
|
326
340
|
}
|
|
327
341
|
else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
|
|
@@ -334,7 +348,7 @@ function addOptionalPrefix(encoded: string): string {
|
|
|
334
348
|
}
|
|
335
349
|
|
|
336
350
|
function encodeBlockExpression(block: IRNode[]): string {
|
|
337
|
-
if (block.length === 0) return "
|
|
351
|
+
if (block.length === 0) return "un'";
|
|
338
352
|
if (block.length === 1) return encodeNode(block[0] as IRNode);
|
|
339
353
|
return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
|
|
340
354
|
}
|
|
@@ -351,9 +365,14 @@ function encodeConditionalElse(elseBranch: IRConditionalElse): string {
|
|
|
351
365
|
return encodeNode(nested);
|
|
352
366
|
}
|
|
353
367
|
|
|
368
|
+
function encodeDomainLookup(shortCode: string, tag: string): string {
|
|
369
|
+
return `${shortCode}${tag}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
354
372
|
function encodeNavigation(node: Extract<IRNode, { type: "navigation" }>): string {
|
|
355
373
|
const domainRefs = activeEncodeOptions?.domainRefs;
|
|
356
|
-
|
|
374
|
+
const domainOpcodes = activeEncodeOptions?.domainOpcodes;
|
|
375
|
+
if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
|
|
357
376
|
const staticPath = [node.target.name];
|
|
358
377
|
for (const segment of node.segments) {
|
|
359
378
|
if (segment.type !== "static") break;
|
|
@@ -362,15 +381,18 @@ function encodeNavigation(node: Extract<IRNode, { type: "navigation" }>): string
|
|
|
362
381
|
|
|
363
382
|
for (let pathLength = staticPath.length; pathLength >= 1; pathLength -= 1) {
|
|
364
383
|
const dottedName = staticPath.slice(0, pathLength).join(".");
|
|
365
|
-
const
|
|
366
|
-
|
|
384
|
+
const refCode = domainRefs?.[dottedName];
|
|
385
|
+
const opcodeCode = domainOpcodes?.[dottedName];
|
|
386
|
+
const shortCode = refCode ?? opcodeCode;
|
|
387
|
+
if (shortCode === undefined) continue;
|
|
388
|
+
const tag = refCode !== undefined ? "'" : "%";
|
|
367
389
|
|
|
368
390
|
const consumedStaticSegments = pathLength - 1;
|
|
369
391
|
if (consumedStaticSegments === node.segments.length) {
|
|
370
|
-
return
|
|
392
|
+
return encodeDomainLookup(shortCode, tag);
|
|
371
393
|
}
|
|
372
394
|
|
|
373
|
-
const parts = [
|
|
395
|
+
const parts = [encodeDomainLookup(shortCode, tag)];
|
|
374
396
|
for (const segment of node.segments.slice(consumedStaticSegments)) {
|
|
375
397
|
if (segment.type === "static") parts.push(encodeBareOrLengthString(segment.key));
|
|
376
398
|
else parts.push(encodeNode(segment.key));
|
|
@@ -395,9 +417,12 @@ function encodeWhile(node: Extract<IRNode, { type: "while" }>): string {
|
|
|
395
417
|
|
|
396
418
|
function encodeFor(node: Extract<IRNode, { type: "for" }>): string {
|
|
397
419
|
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
398
|
-
if (node.binding.type === "binding:
|
|
420
|
+
if (node.binding.type === "binding:bareIn") {
|
|
399
421
|
return `>(${encodeNode(node.binding.source)}${body})`;
|
|
400
422
|
}
|
|
423
|
+
if (node.binding.type === "binding:bareOf") {
|
|
424
|
+
return `<(${encodeNode(node.binding.source)}${body})`;
|
|
425
|
+
}
|
|
401
426
|
if (node.binding.type === "binding:valueIn") {
|
|
402
427
|
return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
|
|
403
428
|
}
|
|
@@ -409,31 +434,50 @@ function encodeFor(node: Extract<IRNode, { type: "for" }>): string {
|
|
|
409
434
|
|
|
410
435
|
function encodeArrayComprehension(node: Extract<IRNode, { type: "arrayComprehension" }>): string {
|
|
411
436
|
const body = addOptionalPrefix(encodeNode(node.body));
|
|
412
|
-
if (node.binding.type === "binding:
|
|
437
|
+
if (node.binding.type === "binding:bareIn") {
|
|
413
438
|
return `>[${encodeNode(node.binding.source)}${body}]`;
|
|
414
439
|
}
|
|
440
|
+
if (node.binding.type === "binding:bareOf") {
|
|
441
|
+
return `<[${encodeNode(node.binding.source)}${body}]`;
|
|
442
|
+
}
|
|
415
443
|
if (node.binding.type === "binding:valueIn") {
|
|
416
444
|
return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
|
|
417
445
|
}
|
|
418
446
|
if (node.binding.type === "binding:keyValueIn") {
|
|
419
447
|
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
|
|
420
448
|
}
|
|
421
|
-
return
|
|
449
|
+
return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
|
|
422
450
|
}
|
|
423
451
|
|
|
424
452
|
function encodeObjectComprehension(node: Extract<IRNode, { type: "objectComprehension" }>): string {
|
|
425
453
|
const key = addOptionalPrefix(encodeNode(node.key));
|
|
426
454
|
const value = addOptionalPrefix(encodeNode(node.value));
|
|
427
|
-
if (node.binding.type === "binding:
|
|
455
|
+
if (node.binding.type === "binding:bareIn") {
|
|
428
456
|
return `>{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
429
457
|
}
|
|
458
|
+
if (node.binding.type === "binding:bareOf") {
|
|
459
|
+
return `<{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
460
|
+
}
|
|
430
461
|
if (node.binding.type === "binding:valueIn") {
|
|
431
462
|
return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
|
|
432
463
|
}
|
|
433
464
|
if (node.binding.type === "binding:keyValueIn") {
|
|
434
465
|
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
|
|
435
466
|
}
|
|
436
|
-
return
|
|
467
|
+
return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function encodeWhileArrayComprehension(node: Extract<IRNode, { type: "whileArrayComprehension" }>): string {
|
|
471
|
+
const cond = encodeNode(node.condition);
|
|
472
|
+
const body = addOptionalPrefix(encodeNode(node.body));
|
|
473
|
+
return `#[${cond}${body}]`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function encodeWhileObjectComprehension(node: Extract<IRNode, { type: "whileObjectComprehension" }>): string {
|
|
477
|
+
const cond = encodeNode(node.condition);
|
|
478
|
+
const key = addOptionalPrefix(encodeNode(node.key));
|
|
479
|
+
const value = addOptionalPrefix(encodeNode(node.value));
|
|
480
|
+
return `#{${cond}${key}${value}}`;
|
|
437
481
|
}
|
|
438
482
|
|
|
439
483
|
let activeEncodeOptions: EncodeOptions | undefined;
|
|
@@ -444,7 +488,9 @@ function encodeNode(node: IRNode): string {
|
|
|
444
488
|
return encodeBlockExpression(node.body);
|
|
445
489
|
case "identifier": {
|
|
446
490
|
const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
|
|
447
|
-
if (domainRef !== undefined) return `${
|
|
491
|
+
if (domainRef !== undefined) return `${domainRef}'`;
|
|
492
|
+
const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
|
|
493
|
+
if (domainOpcode !== undefined) return `${domainOpcode}%`;
|
|
448
494
|
return `${node.name}$`;
|
|
449
495
|
}
|
|
450
496
|
case "self":
|
|
@@ -455,11 +501,11 @@ function encodeNode(node: IRNode): string {
|
|
|
455
501
|
return `${encodeUint(node.depth - 1)}@`;
|
|
456
502
|
}
|
|
457
503
|
case "boolean":
|
|
458
|
-
return node.value ? "
|
|
504
|
+
return node.value ? "tr'" : "fl'";
|
|
459
505
|
case "null":
|
|
460
|
-
return "
|
|
506
|
+
return "nl'";
|
|
461
507
|
case "undefined":
|
|
462
|
-
return "
|
|
508
|
+
return "un'";
|
|
463
509
|
case "number":
|
|
464
510
|
return encodeNumberNode(node);
|
|
465
511
|
case "string":
|
|
@@ -470,6 +516,8 @@ function encodeNode(node: IRNode): string {
|
|
|
470
516
|
}
|
|
471
517
|
case "arrayComprehension":
|
|
472
518
|
return encodeArrayComprehension(node);
|
|
519
|
+
case "whileArrayComprehension":
|
|
520
|
+
return encodeWhileArrayComprehension(node);
|
|
473
521
|
case "object": {
|
|
474
522
|
const body = node.entries
|
|
475
523
|
.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`)
|
|
@@ -478,6 +526,8 @@ function encodeNode(node: IRNode): string {
|
|
|
478
526
|
}
|
|
479
527
|
case "objectComprehension":
|
|
480
528
|
return encodeObjectComprehension(node);
|
|
529
|
+
case "whileObjectComprehension":
|
|
530
|
+
return encodeWhileObjectComprehension(node);
|
|
481
531
|
case "key":
|
|
482
532
|
return encodeBareOrLengthString(node.name);
|
|
483
533
|
case "group":
|
|
@@ -485,6 +535,10 @@ function encodeNode(node: IRNode): string {
|
|
|
485
535
|
case "unary":
|
|
486
536
|
if (node.op === "delete") return `~${encodeNode(node.value)}`;
|
|
487
537
|
if (node.op === "neg") return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
|
|
538
|
+
if (node.op === "logicalNot") {
|
|
539
|
+
const val = encodeNode(node.value);
|
|
540
|
+
return `!(${val}tr')`;
|
|
541
|
+
}
|
|
488
542
|
return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
|
|
489
543
|
case "binary":
|
|
490
544
|
if (node.op === "and") {
|
|
@@ -507,12 +561,18 @@ function encodeNode(node: IRNode): string {
|
|
|
507
561
|
.join("");
|
|
508
562
|
return `|(${body})`;
|
|
509
563
|
}
|
|
564
|
+
if (node.op === "nor") {
|
|
565
|
+
const left = encodeNode(node.left);
|
|
566
|
+
const right = addOptionalPrefix(encodeNode(node.right));
|
|
567
|
+
return `!(${left}${right})`;
|
|
568
|
+
}
|
|
510
569
|
return encodeCallParts([
|
|
511
570
|
encodeOpcode(BINARY_TO_OPCODE[node.op]),
|
|
512
571
|
encodeNode(node.left),
|
|
513
572
|
encodeNode(node.right),
|
|
514
573
|
]);
|
|
515
574
|
case "assign": {
|
|
575
|
+
if (node.op === ":=") return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
516
576
|
if (node.op === "=") return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
517
577
|
const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
|
|
518
578
|
if (!opcode) throw new Error(`Unsupported assignment op: ${node.op}`);
|
|
@@ -521,8 +581,14 @@ function encodeNode(node: IRNode): string {
|
|
|
521
581
|
}
|
|
522
582
|
case "navigation":
|
|
523
583
|
return encodeNavigation(node);
|
|
524
|
-
case "call":
|
|
584
|
+
case "call": {
|
|
585
|
+
// Keyword identifiers (boolean, number, string, array, object, size) are parsed
|
|
586
|
+
// as identifier nodes but must be encoded as opcode calls, not variable navigation.
|
|
587
|
+
if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
|
|
588
|
+
return encodeCallParts([encodeOpcode(node.callee.name as OpcodeName), ...node.args.map((arg) => encodeNode(arg))]);
|
|
589
|
+
}
|
|
525
590
|
return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
|
|
591
|
+
}
|
|
526
592
|
case "conditional": {
|
|
527
593
|
const opener = node.head === "when" ? "?(" : "!(";
|
|
528
594
|
const cond = encodeNode(node.condition);
|
|
@@ -530,6 +596,8 @@ function encodeNode(node: IRNode): string {
|
|
|
530
596
|
const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
|
|
531
597
|
return `${opener}${cond}${thenExpr}${elseExpr})`;
|
|
532
598
|
}
|
|
599
|
+
case "range":
|
|
600
|
+
return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
|
|
533
601
|
case "for":
|
|
534
602
|
return encodeFor(node);
|
|
535
603
|
case "while":
|
|
@@ -690,81 +758,62 @@ export function parse(source: string): unknown {
|
|
|
690
758
|
return parseDataNode(parseToIR(source));
|
|
691
759
|
}
|
|
692
760
|
|
|
693
|
-
|
|
761
|
+
type DomainMaps = { domainRefs: Record<string, string>; domainOpcodes: Record<string, string> };
|
|
762
|
+
|
|
763
|
+
export function domainRefsFromConfig(config: unknown): DomainMaps {
|
|
694
764
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
695
765
|
throw new Error("Domain config must be an object");
|
|
696
766
|
}
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
mapConfigEntries(section as Record<string, unknown>, refs);
|
|
701
|
-
}
|
|
702
|
-
return refs;
|
|
703
|
-
}
|
|
767
|
+
const configObj = config as Record<string, unknown>;
|
|
768
|
+
const domainRefs: Record<string, string> = {};
|
|
769
|
+
const domainOpcodes: Record<string, string> = {};
|
|
704
770
|
|
|
705
|
-
|
|
706
|
-
if (
|
|
707
|
-
|
|
708
|
-
throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
|
|
709
|
-
}
|
|
710
|
-
if (refText.length > 1 && refText[0] === "0") {
|
|
711
|
-
throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
|
|
712
|
-
}
|
|
713
|
-
if (/^[1-9]$/.test(refText)) {
|
|
714
|
-
throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
|
|
771
|
+
const dataSection = configObj.data;
|
|
772
|
+
if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
|
|
773
|
+
mapConfigEntries(dataSection as Record<string, unknown>, domainRefs);
|
|
715
774
|
}
|
|
716
775
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (digit === undefined) throw new Error(`Invalid domain ref key '${refText}'`);
|
|
721
|
-
value = value * 64 + digit;
|
|
722
|
-
if (value > Number.MAX_SAFE_INTEGER) {
|
|
723
|
-
throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
|
|
724
|
-
}
|
|
776
|
+
const functionsSection = configObj.functions;
|
|
777
|
+
if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
|
|
778
|
+
mapConfigEntries(functionsSection as Record<string, unknown>, domainOpcodes);
|
|
725
779
|
}
|
|
726
780
|
|
|
727
|
-
|
|
728
|
-
throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
return value;
|
|
781
|
+
return { domainRefs, domainOpcodes };
|
|
732
782
|
}
|
|
733
783
|
|
|
734
|
-
function mapConfigEntries(entries: Record<string, unknown>, refs: Record<string,
|
|
784
|
+
function mapConfigEntries(entries: Record<string, unknown>, refs: Record<string, string>) {
|
|
735
785
|
const sourceKindByRoot = new Map<string, "explicit" | "implicit">();
|
|
736
786
|
for (const root of Object.keys(refs)) {
|
|
737
787
|
sourceKindByRoot.set(root, "explicit");
|
|
738
788
|
}
|
|
739
789
|
|
|
740
|
-
for (const [
|
|
790
|
+
for (const [shortCode, rawEntry] of Object.entries(entries)) {
|
|
741
791
|
const entry = rawEntry as RexDomainConfigEntry;
|
|
742
792
|
if (!entry || typeof entry !== "object") continue;
|
|
743
793
|
if (!Array.isArray(entry.names)) continue;
|
|
744
794
|
|
|
745
|
-
const refId = decodeDomainRefKey(refText);
|
|
746
795
|
for (const rawName of entry.names) {
|
|
747
796
|
if (typeof rawName !== "string") continue;
|
|
748
|
-
const
|
|
749
|
-
if (
|
|
750
|
-
throw new Error(`Conflicting refs for '${rawName}': ${
|
|
797
|
+
const existingRef = refs[rawName];
|
|
798
|
+
if (existingRef !== undefined && existingRef !== shortCode) {
|
|
799
|
+
throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
|
|
751
800
|
}
|
|
752
|
-
refs[rawName] =
|
|
801
|
+
refs[rawName] = shortCode;
|
|
753
802
|
|
|
754
803
|
const root = rawName.split(".")[0];
|
|
755
804
|
if (!root) continue;
|
|
756
805
|
const currentKind: "explicit" | "implicit" = rawName.includes(".") ? "implicit" : "explicit";
|
|
757
806
|
const existing = refs[root];
|
|
758
807
|
if (existing !== undefined) {
|
|
759
|
-
if (existing ===
|
|
808
|
+
if (existing === shortCode) continue;
|
|
760
809
|
const existingKind = sourceKindByRoot.get(root) ?? "explicit";
|
|
761
810
|
if (currentKind === "explicit") {
|
|
762
|
-
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${
|
|
811
|
+
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
|
|
763
812
|
}
|
|
764
813
|
if (existingKind === "explicit") continue;
|
|
765
814
|
continue;
|
|
766
815
|
}
|
|
767
|
-
refs[root] =
|
|
816
|
+
refs[root] = shortCode;
|
|
768
817
|
sourceKindByRoot.set(root, currentKind);
|
|
769
818
|
}
|
|
770
819
|
}
|
|
@@ -1053,6 +1102,22 @@ function dropBindingNames(env: OptimizeEnv, binding: IRBindingOrExpr) {
|
|
|
1053
1102
|
}
|
|
1054
1103
|
}
|
|
1055
1104
|
|
|
1105
|
+
function optimizeBinding(binding: IRBindingOrExpr, sourceEnv: OptimizeEnv, currentDepth: number): IRBinding {
|
|
1106
|
+
const source = optimizeNode(binding.source, sourceEnv, currentDepth);
|
|
1107
|
+
switch (binding.type) {
|
|
1108
|
+
case "binding:bareIn":
|
|
1109
|
+
return { type: "binding:bareIn", source };
|
|
1110
|
+
case "binding:bareOf":
|
|
1111
|
+
return { type: "binding:bareOf", source };
|
|
1112
|
+
case "binding:valueIn":
|
|
1113
|
+
return { type: "binding:valueIn", value: binding.value, source };
|
|
1114
|
+
case "binding:keyValueIn":
|
|
1115
|
+
return { type: "binding:keyValueIn", key: binding.key, value: binding.value, source };
|
|
1116
|
+
case "binding:keyOf":
|
|
1117
|
+
return { type: "binding:keyOf", key: binding.key, source };
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1056
1121
|
function collectReads(node: IRNode, out: Set<string>) {
|
|
1057
1122
|
switch (node.type) {
|
|
1058
1123
|
case "identifier":
|
|
@@ -1074,11 +1139,20 @@ function collectReads(node: IRNode, out: Set<string>) {
|
|
|
1074
1139
|
collectReads(node.binding.source, out);
|
|
1075
1140
|
collectReads(node.body, out);
|
|
1076
1141
|
return;
|
|
1142
|
+
case "whileArrayComprehension":
|
|
1143
|
+
collectReads(node.condition, out);
|
|
1144
|
+
collectReads(node.body, out);
|
|
1145
|
+
return;
|
|
1077
1146
|
case "objectComprehension":
|
|
1078
1147
|
collectReads(node.binding.source, out);
|
|
1079
1148
|
collectReads(node.key, out);
|
|
1080
1149
|
collectReads(node.value, out);
|
|
1081
1150
|
return;
|
|
1151
|
+
case "whileObjectComprehension":
|
|
1152
|
+
collectReads(node.condition, out);
|
|
1153
|
+
collectReads(node.key, out);
|
|
1154
|
+
collectReads(node.value, out);
|
|
1155
|
+
return;
|
|
1082
1156
|
case "unary":
|
|
1083
1157
|
collectReads(node.value, out);
|
|
1084
1158
|
return;
|
|
@@ -1109,6 +1183,10 @@ function collectReads(node: IRNode, out: Set<string>) {
|
|
|
1109
1183
|
collectReads(node.binding.source, out);
|
|
1110
1184
|
for (const part of node.body) collectReads(part, out);
|
|
1111
1185
|
return;
|
|
1186
|
+
case "range":
|
|
1187
|
+
collectReads(node.from, out);
|
|
1188
|
+
collectReads(node.to, out);
|
|
1189
|
+
return;
|
|
1112
1190
|
case "program":
|
|
1113
1191
|
for (const part of node.body) collectReads(part, out);
|
|
1114
1192
|
return;
|
|
@@ -1151,6 +1229,8 @@ function isPureNode(node: IRNode): boolean {
|
|
|
1151
1229
|
return node.op !== "delete" && isPureNode(node.value);
|
|
1152
1230
|
case "binary":
|
|
1153
1231
|
return isPureNode(node.left) && isPureNode(node.right);
|
|
1232
|
+
case "range":
|
|
1233
|
+
return isPureNode(node.from) && isPureNode(node.to);
|
|
1154
1234
|
default:
|
|
1155
1235
|
return false;
|
|
1156
1236
|
}
|
|
@@ -1223,6 +1303,8 @@ function hasIdentifierRead(node: IRNode, name: string, asPlace = false): boolean
|
|
|
1223
1303
|
return hasIdentifierRead(node.value, name, node.op === "delete");
|
|
1224
1304
|
case "binary":
|
|
1225
1305
|
return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
|
|
1306
|
+
case "range":
|
|
1307
|
+
return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
|
|
1226
1308
|
case "assign":
|
|
1227
1309
|
return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
|
|
1228
1310
|
default:
|
|
@@ -1245,6 +1327,8 @@ function countIdentifierReads(node: IRNode, name: string, asPlace = false): numb
|
|
|
1245
1327
|
return countIdentifierReads(node.value, name, node.op === "delete");
|
|
1246
1328
|
case "binary":
|
|
1247
1329
|
return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
|
|
1330
|
+
case "range":
|
|
1331
|
+
return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
|
|
1248
1332
|
case "assign":
|
|
1249
1333
|
return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
|
|
1250
1334
|
default:
|
|
@@ -1302,6 +1386,12 @@ function replaceIdentifier(node: IRNode, name: string, replacement: IRNode, asPl
|
|
|
1302
1386
|
place: replaceIdentifier(node.place, name, replacement, true),
|
|
1303
1387
|
value: replaceIdentifier(node.value, name, replacement),
|
|
1304
1388
|
} satisfies IRNode;
|
|
1389
|
+
case "range":
|
|
1390
|
+
return {
|
|
1391
|
+
type: "range",
|
|
1392
|
+
from: replaceIdentifier(node.from, name, replacement),
|
|
1393
|
+
to: replaceIdentifier(node.to, name, replacement),
|
|
1394
|
+
} satisfies IRNode;
|
|
1305
1395
|
default:
|
|
1306
1396
|
return node;
|
|
1307
1397
|
}
|
|
@@ -1435,6 +1525,9 @@ function foldUnary(op: Extract<IRNode, { type: "unary" }> ["op"], value: unknown
|
|
|
1435
1525
|
if (typeof value === "number") return ~value;
|
|
1436
1526
|
return undefined;
|
|
1437
1527
|
}
|
|
1528
|
+
if (op === "logicalNot") {
|
|
1529
|
+
return value === undefined ? true : undefined;
|
|
1530
|
+
}
|
|
1438
1531
|
return undefined;
|
|
1439
1532
|
}
|
|
1440
1533
|
|
|
@@ -1597,6 +1690,12 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1597
1690
|
}
|
|
1598
1691
|
return { type: "binary", op: node.op, left, right } satisfies IRNode;
|
|
1599
1692
|
}
|
|
1693
|
+
case "range":
|
|
1694
|
+
return {
|
|
1695
|
+
type: "range",
|
|
1696
|
+
from: optimizeNode(node.from, env, currentDepth),
|
|
1697
|
+
to: optimizeNode(node.to, env, currentDepth),
|
|
1698
|
+
} satisfies IRNode;
|
|
1600
1699
|
case "navigation": {
|
|
1601
1700
|
const target = optimizeNode(node.target, env, currentDepth);
|
|
1602
1701
|
const segments = node.segments.map((segment) => (segment.type === "static"
|
|
@@ -1706,31 +1805,7 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1706
1805
|
}
|
|
1707
1806
|
case "for": {
|
|
1708
1807
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1709
|
-
const binding = (
|
|
1710
|
-
if (node.binding.type === "binding:expr") {
|
|
1711
|
-
return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } satisfies IRBindingOrExpr;
|
|
1712
|
-
}
|
|
1713
|
-
if (node.binding.type === "binding:valueIn") {
|
|
1714
|
-
return {
|
|
1715
|
-
type: "binding:valueIn",
|
|
1716
|
-
value: node.binding.value,
|
|
1717
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1718
|
-
} satisfies IRBinding;
|
|
1719
|
-
}
|
|
1720
|
-
if (node.binding.type === "binding:keyValueIn") {
|
|
1721
|
-
return {
|
|
1722
|
-
type: "binding:keyValueIn",
|
|
1723
|
-
key: node.binding.key,
|
|
1724
|
-
value: node.binding.value,
|
|
1725
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1726
|
-
} satisfies IRBinding;
|
|
1727
|
-
}
|
|
1728
|
-
return {
|
|
1729
|
-
type: "binding:keyOf",
|
|
1730
|
-
key: node.binding.key,
|
|
1731
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1732
|
-
} satisfies IRBinding;
|
|
1733
|
-
})();
|
|
1808
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1734
1809
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1735
1810
|
dropBindingNames(bodyEnv, binding);
|
|
1736
1811
|
return {
|
|
@@ -1741,26 +1816,7 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1741
1816
|
}
|
|
1742
1817
|
case "arrayComprehension": {
|
|
1743
1818
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1744
|
-
const binding = node.binding
|
|
1745
|
-
? ({ type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } satisfies IRBindingOrExpr)
|
|
1746
|
-
: node.binding.type === "binding:valueIn"
|
|
1747
|
-
? ({
|
|
1748
|
-
type: "binding:valueIn",
|
|
1749
|
-
value: node.binding.value,
|
|
1750
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1751
|
-
} satisfies IRBinding)
|
|
1752
|
-
: node.binding.type === "binding:keyValueIn"
|
|
1753
|
-
? ({
|
|
1754
|
-
type: "binding:keyValueIn",
|
|
1755
|
-
key: node.binding.key,
|
|
1756
|
-
value: node.binding.value,
|
|
1757
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1758
|
-
} satisfies IRBinding)
|
|
1759
|
-
: ({
|
|
1760
|
-
type: "binding:keyOf",
|
|
1761
|
-
key: node.binding.key,
|
|
1762
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1763
|
-
} satisfies IRBinding);
|
|
1819
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1764
1820
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1765
1821
|
dropBindingNames(bodyEnv, binding);
|
|
1766
1822
|
return {
|
|
@@ -1769,28 +1825,15 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1769
1825
|
body: optimizeNode(node.body, bodyEnv, currentDepth + 1),
|
|
1770
1826
|
} satisfies IRNode;
|
|
1771
1827
|
}
|
|
1828
|
+
case "whileArrayComprehension":
|
|
1829
|
+
return {
|
|
1830
|
+
type: "whileArrayComprehension",
|
|
1831
|
+
condition: optimizeNode(node.condition, env, currentDepth),
|
|
1832
|
+
body: optimizeNode(node.body, env, currentDepth + 1),
|
|
1833
|
+
} satisfies IRNode;
|
|
1772
1834
|
case "objectComprehension": {
|
|
1773
1835
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1774
|
-
const binding = node.binding
|
|
1775
|
-
? ({ type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } satisfies IRBindingOrExpr)
|
|
1776
|
-
: node.binding.type === "binding:valueIn"
|
|
1777
|
-
? ({
|
|
1778
|
-
type: "binding:valueIn",
|
|
1779
|
-
value: node.binding.value,
|
|
1780
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1781
|
-
} satisfies IRBinding)
|
|
1782
|
-
: node.binding.type === "binding:keyValueIn"
|
|
1783
|
-
? ({
|
|
1784
|
-
type: "binding:keyValueIn",
|
|
1785
|
-
key: node.binding.key,
|
|
1786
|
-
value: node.binding.value,
|
|
1787
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1788
|
-
} satisfies IRBinding)
|
|
1789
|
-
: ({
|
|
1790
|
-
type: "binding:keyOf",
|
|
1791
|
-
key: node.binding.key,
|
|
1792
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1793
|
-
} satisfies IRBinding);
|
|
1836
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1794
1837
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1795
1838
|
dropBindingNames(bodyEnv, binding);
|
|
1796
1839
|
return {
|
|
@@ -1800,6 +1843,13 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1800
1843
|
value: optimizeNode(node.value, bodyEnv, currentDepth + 1),
|
|
1801
1844
|
} satisfies IRNode;
|
|
1802
1845
|
}
|
|
1846
|
+
case "whileObjectComprehension":
|
|
1847
|
+
return {
|
|
1848
|
+
type: "whileObjectComprehension",
|
|
1849
|
+
condition: optimizeNode(node.condition, env, currentDepth),
|
|
1850
|
+
key: optimizeNode(node.key, env, currentDepth + 1),
|
|
1851
|
+
value: optimizeNode(node.value, env, currentDepth + 1),
|
|
1852
|
+
} satisfies IRNode;
|
|
1803
1853
|
default:
|
|
1804
1854
|
return node;
|
|
1805
1855
|
}
|
|
@@ -1848,6 +1898,10 @@ function collectLocalBindings(node: IRNode, locals: Set<string>) {
|
|
|
1848
1898
|
collectLocalBindings(node.left, locals);
|
|
1849
1899
|
collectLocalBindings(node.right, locals);
|
|
1850
1900
|
return;
|
|
1901
|
+
case "range":
|
|
1902
|
+
collectLocalBindings(node.from, locals);
|
|
1903
|
+
collectLocalBindings(node.to, locals);
|
|
1904
|
+
return;
|
|
1851
1905
|
case "conditional":
|
|
1852
1906
|
collectLocalBindings(node.condition, locals);
|
|
1853
1907
|
for (const part of node.thenBlock) collectLocalBindings(part, locals);
|
|
@@ -1861,11 +1915,20 @@ function collectLocalBindings(node: IRNode, locals: Set<string>) {
|
|
|
1861
1915
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1862
1916
|
collectLocalBindings(node.body, locals);
|
|
1863
1917
|
return;
|
|
1918
|
+
case "whileArrayComprehension":
|
|
1919
|
+
collectLocalBindings(node.condition, locals);
|
|
1920
|
+
collectLocalBindings(node.body, locals);
|
|
1921
|
+
return;
|
|
1864
1922
|
case "objectComprehension":
|
|
1865
1923
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1866
1924
|
collectLocalBindings(node.key, locals);
|
|
1867
1925
|
collectLocalBindings(node.value, locals);
|
|
1868
1926
|
return;
|
|
1927
|
+
case "whileObjectComprehension":
|
|
1928
|
+
collectLocalBindings(node.condition, locals);
|
|
1929
|
+
collectLocalBindings(node.key, locals);
|
|
1930
|
+
collectLocalBindings(node.value, locals);
|
|
1931
|
+
return;
|
|
1869
1932
|
default:
|
|
1870
1933
|
return;
|
|
1871
1934
|
}
|
|
@@ -1952,6 +2015,10 @@ function collectNameFrequencies(node: IRNode, locals: Set<string>, frequencies:
|
|
|
1952
2015
|
collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
|
|
1953
2016
|
collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
|
|
1954
2017
|
return;
|
|
2018
|
+
case "range":
|
|
2019
|
+
collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
|
|
2020
|
+
collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
|
|
2021
|
+
return;
|
|
1955
2022
|
case "conditional":
|
|
1956
2023
|
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1957
2024
|
for (const part of node.thenBlock) collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
@@ -1965,11 +2032,20 @@ function collectNameFrequencies(node: IRNode, locals: Set<string>, frequencies:
|
|
|
1965
2032
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1966
2033
|
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1967
2034
|
return;
|
|
2035
|
+
case "whileArrayComprehension":
|
|
2036
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
2037
|
+
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
2038
|
+
return;
|
|
1968
2039
|
case "objectComprehension":
|
|
1969
2040
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1970
2041
|
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1971
2042
|
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1972
2043
|
return;
|
|
2044
|
+
case "whileObjectComprehension":
|
|
2045
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
2046
|
+
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
2047
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
2048
|
+
return;
|
|
1973
2049
|
default:
|
|
1974
2050
|
return;
|
|
1975
2051
|
}
|
|
@@ -2046,6 +2122,12 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2046
2122
|
left: renameLocalNames(node.left, map),
|
|
2047
2123
|
right: renameLocalNames(node.right, map),
|
|
2048
2124
|
} satisfies IRNode;
|
|
2125
|
+
case "range":
|
|
2126
|
+
return {
|
|
2127
|
+
type: "range",
|
|
2128
|
+
from: renameLocalNames(node.from, map),
|
|
2129
|
+
to: renameLocalNames(node.to, map),
|
|
2130
|
+
} satisfies IRNode;
|
|
2049
2131
|
case "assign": {
|
|
2050
2132
|
const place = node.place.type === "identifier" && map.has(node.place.name)
|
|
2051
2133
|
? ({ type: "identifier", name: map.get(node.place.name) as string } satisfies IRNode)
|
|
@@ -2077,6 +2159,12 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2077
2159
|
binding: renameLocalNamesBinding(node.binding, map),
|
|
2078
2160
|
body: renameLocalNames(node.body, map),
|
|
2079
2161
|
} satisfies IRNode;
|
|
2162
|
+
case "whileArrayComprehension":
|
|
2163
|
+
return {
|
|
2164
|
+
type: "whileArrayComprehension",
|
|
2165
|
+
condition: renameLocalNames(node.condition, map),
|
|
2166
|
+
body: renameLocalNames(node.body, map),
|
|
2167
|
+
} satisfies IRNode;
|
|
2080
2168
|
case "objectComprehension":
|
|
2081
2169
|
return {
|
|
2082
2170
|
type: "objectComprehension",
|
|
@@ -2084,35 +2172,32 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2084
2172
|
key: renameLocalNames(node.key, map),
|
|
2085
2173
|
value: renameLocalNames(node.value, map),
|
|
2086
2174
|
} satisfies IRNode;
|
|
2175
|
+
case "whileObjectComprehension":
|
|
2176
|
+
return {
|
|
2177
|
+
type: "whileObjectComprehension",
|
|
2178
|
+
condition: renameLocalNames(node.condition, map),
|
|
2179
|
+
key: renameLocalNames(node.key, map),
|
|
2180
|
+
value: renameLocalNames(node.value, map),
|
|
2181
|
+
} satisfies IRNode;
|
|
2087
2182
|
default:
|
|
2088
2183
|
return node;
|
|
2089
2184
|
}
|
|
2090
2185
|
}
|
|
2091
2186
|
|
|
2092
2187
|
function renameLocalNamesBinding(binding: IRBindingOrExpr, map: Map<string, string>): IRBindingOrExpr {
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
type: "binding:
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2188
|
+
const source = renameLocalNames(binding.source, map);
|
|
2189
|
+
switch (binding.type) {
|
|
2190
|
+
case "binding:bareIn":
|
|
2191
|
+
return { type: "binding:bareIn", source };
|
|
2192
|
+
case "binding:bareOf":
|
|
2193
|
+
return { type: "binding:bareOf", source };
|
|
2194
|
+
case "binding:valueIn":
|
|
2195
|
+
return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
|
|
2196
|
+
case "binding:keyValueIn":
|
|
2197
|
+
return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
|
|
2198
|
+
case "binding:keyOf":
|
|
2199
|
+
return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
|
|
2102
2200
|
}
|
|
2103
|
-
if (binding.type === "binding:keyValueIn") {
|
|
2104
|
-
return {
|
|
2105
|
-
type: "binding:keyValueIn",
|
|
2106
|
-
key: map.get(binding.key) ?? binding.key,
|
|
2107
|
-
value: map.get(binding.value) ?? binding.value,
|
|
2108
|
-
source: renameLocalNames(binding.source, map),
|
|
2109
|
-
} satisfies IRBinding;
|
|
2110
|
-
}
|
|
2111
|
-
return {
|
|
2112
|
-
type: "binding:keyOf",
|
|
2113
|
-
key: map.get(binding.key) ?? binding.key,
|
|
2114
|
-
source: renameLocalNames(binding.source, map),
|
|
2115
|
-
} satisfies IRBinding;
|
|
2116
2201
|
}
|
|
2117
2202
|
|
|
2118
2203
|
function renameLocalNamesElse(elseBranch: IRConditionalElse, map: Map<string, string>): IRConditionalElse {
|
|
@@ -2163,9 +2248,9 @@ export function compile(source: string, options?: CompileOptions): string {
|
|
|
2163
2248
|
const ir = parseToIR(source);
|
|
2164
2249
|
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
2165
2250
|
if (options?.minifyNames) lowered = minifyLocalNamesIR(lowered);
|
|
2166
|
-
const
|
|
2251
|
+
const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
|
|
2167
2252
|
return encodeIR(lowered, {
|
|
2168
|
-
|
|
2253
|
+
...domainMaps,
|
|
2169
2254
|
dedupeValues: options?.dedupeValues,
|
|
2170
2255
|
dedupeMinBytes: options?.dedupeMinBytes,
|
|
2171
2256
|
});
|
|
@@ -2299,6 +2384,9 @@ semantics.addOperation("toIR", {
|
|
|
2299
2384
|
ExistenceExpr_or(left, _or, right) {
|
|
2300
2385
|
return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2301
2386
|
},
|
|
2387
|
+
ExistenceExpr_nor(left, _nor, right) {
|
|
2388
|
+
return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2389
|
+
},
|
|
2302
2390
|
|
|
2303
2391
|
BitExpr_and(left, _op, right) {
|
|
2304
2392
|
return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
@@ -2310,6 +2398,10 @@ semantics.addOperation("toIR", {
|
|
|
2310
2398
|
return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2311
2399
|
},
|
|
2312
2400
|
|
|
2401
|
+
RangeExpr_range(left, _op, right) {
|
|
2402
|
+
return { type: "range", from: left.toIR(), to: right.toIR() } satisfies IRNode;
|
|
2403
|
+
},
|
|
2404
|
+
|
|
2313
2405
|
CompareExpr_binary(left, op, right) {
|
|
2314
2406
|
const map: Record<string, Extract<IRNode, { type: "binary" }>["op"]> = {
|
|
2315
2407
|
"==": "eq",
|
|
@@ -2352,6 +2444,9 @@ semantics.addOperation("toIR", {
|
|
|
2352
2444
|
UnaryExpr_not(_op, value) {
|
|
2353
2445
|
return { type: "unary", op: "not", value: value.toIR() } satisfies IRNode;
|
|
2354
2446
|
},
|
|
2447
|
+
UnaryExpr_logicalNot(_not, value) {
|
|
2448
|
+
return { type: "unary", op: "logicalNot", value: value.toIR() } satisfies IRNode;
|
|
2449
|
+
},
|
|
2355
2450
|
UnaryExpr_delete(_del, place) {
|
|
2356
2451
|
return { type: "unary", op: "delete", value: place.toIR() } satisfies IRNode;
|
|
2357
2452
|
},
|
|
@@ -2408,13 +2503,6 @@ semantics.addOperation("toIR", {
|
|
|
2408
2503
|
return { type: "else", block: block.toIR() as IRNode[] } satisfies IRConditionalElse;
|
|
2409
2504
|
},
|
|
2410
2505
|
|
|
2411
|
-
DoExpr(_do, block, _end) {
|
|
2412
|
-
const body = block.toIR() as IRNode[];
|
|
2413
|
-
if (body.length === 0) return { type: "undefined" } satisfies IRNode;
|
|
2414
|
-
if (body.length === 1) return body[0] as IRNode;
|
|
2415
|
-
return { type: "program", body } satisfies IRNode;
|
|
2416
|
-
},
|
|
2417
|
-
|
|
2418
2506
|
WhileExpr(_while, condition, _do, block, _end) {
|
|
2419
2507
|
return {
|
|
2420
2508
|
type: "while",
|
|
@@ -2426,25 +2514,39 @@ semantics.addOperation("toIR", {
|
|
|
2426
2514
|
ForExpr(_for, binding, _do, block, _end) {
|
|
2427
2515
|
return {
|
|
2428
2516
|
type: "for",
|
|
2429
|
-
binding: binding.toIR() as
|
|
2517
|
+
binding: binding.toIR() as IRBinding,
|
|
2430
2518
|
body: block.toIR() as IRNode[],
|
|
2431
2519
|
} satisfies IRNode;
|
|
2432
2520
|
},
|
|
2433
|
-
BindingExpr(iterOrExpr) {
|
|
2434
|
-
const node = iterOrExpr.toIR();
|
|
2435
|
-
if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
|
|
2436
|
-
return node as IRBinding;
|
|
2437
|
-
}
|
|
2438
|
-
return { type: "binding:expr", source: node as IRNode } satisfies IRBindingOrExpr;
|
|
2439
|
-
},
|
|
2440
2521
|
|
|
2441
2522
|
Array_empty(_open, _close) {
|
|
2442
2523
|
return { type: "array", items: [] } satisfies IRNode;
|
|
2443
2524
|
},
|
|
2444
|
-
|
|
2525
|
+
Array_forComprehension(_open, body, _for, binding, _close) {
|
|
2445
2526
|
return {
|
|
2446
2527
|
type: "arrayComprehension",
|
|
2447
|
-
binding: binding.toIR() as
|
|
2528
|
+
binding: binding.toIR() as IRBinding,
|
|
2529
|
+
body: body.toIR(),
|
|
2530
|
+
} satisfies IRNode;
|
|
2531
|
+
},
|
|
2532
|
+
Array_whileComprehension(_open, body, _while, condition, _close) {
|
|
2533
|
+
return {
|
|
2534
|
+
type: "whileArrayComprehension",
|
|
2535
|
+
condition: condition.toIR(),
|
|
2536
|
+
body: body.toIR(),
|
|
2537
|
+
} satisfies IRNode;
|
|
2538
|
+
},
|
|
2539
|
+
Array_inComprehension(_open, body, _in, source, _close) {
|
|
2540
|
+
return {
|
|
2541
|
+
type: "arrayComprehension",
|
|
2542
|
+
binding: { type: "binding:bareIn", source: source.toIR() } satisfies IRBinding,
|
|
2543
|
+
body: body.toIR(),
|
|
2544
|
+
} satisfies IRNode;
|
|
2545
|
+
},
|
|
2546
|
+
Array_ofComprehension(_open, body, _of, source, _close) {
|
|
2547
|
+
return {
|
|
2548
|
+
type: "arrayComprehension",
|
|
2549
|
+
binding: { type: "binding:bareOf", source: source.toIR() } satisfies IRBinding,
|
|
2448
2550
|
body: body.toIR(),
|
|
2449
2551
|
} satisfies IRNode;
|
|
2450
2552
|
},
|
|
@@ -2455,10 +2557,34 @@ semantics.addOperation("toIR", {
|
|
|
2455
2557
|
Object_empty(_open, _close) {
|
|
2456
2558
|
return { type: "object", entries: [] } satisfies IRNode;
|
|
2457
2559
|
},
|
|
2458
|
-
|
|
2560
|
+
Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
|
|
2561
|
+
return {
|
|
2562
|
+
type: "objectComprehension",
|
|
2563
|
+
binding: binding.toIR() as IRBinding,
|
|
2564
|
+
key: key.toIR(),
|
|
2565
|
+
value: value.toIR(),
|
|
2566
|
+
} satisfies IRNode;
|
|
2567
|
+
},
|
|
2568
|
+
Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
|
|
2569
|
+
return {
|
|
2570
|
+
type: "whileObjectComprehension",
|
|
2571
|
+
condition: condition.toIR(),
|
|
2572
|
+
key: key.toIR(),
|
|
2573
|
+
value: value.toIR(),
|
|
2574
|
+
} satisfies IRNode;
|
|
2575
|
+
},
|
|
2576
|
+
Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
|
|
2577
|
+
return {
|
|
2578
|
+
type: "objectComprehension",
|
|
2579
|
+
binding: { type: "binding:bareIn", source: source.toIR() } satisfies IRBinding,
|
|
2580
|
+
key: key.toIR(),
|
|
2581
|
+
value: value.toIR(),
|
|
2582
|
+
} satisfies IRNode;
|
|
2583
|
+
},
|
|
2584
|
+
Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
|
|
2459
2585
|
return {
|
|
2460
2586
|
type: "objectComprehension",
|
|
2461
|
-
binding: binding.toIR()
|
|
2587
|
+
binding: { type: "binding:bareOf", source: source.toIR() } satisfies IRBinding,
|
|
2462
2588
|
key: key.toIR(),
|
|
2463
2589
|
value: value.toIR(),
|
|
2464
2590
|
} satisfies IRNode;
|
|
@@ -2492,6 +2618,18 @@ semantics.addOperation("toIR", {
|
|
|
2492
2618
|
source: source.toIR(),
|
|
2493
2619
|
} satisfies IRBinding;
|
|
2494
2620
|
},
|
|
2621
|
+
IterBinding_bareIn(_in, source) {
|
|
2622
|
+
return {
|
|
2623
|
+
type: "binding:bareIn",
|
|
2624
|
+
source: source.toIR(),
|
|
2625
|
+
} satisfies IRBinding;
|
|
2626
|
+
},
|
|
2627
|
+
IterBinding_bareOf(_of, source) {
|
|
2628
|
+
return {
|
|
2629
|
+
type: "binding:bareOf",
|
|
2630
|
+
source: source.toIR(),
|
|
2631
|
+
} satisfies IRBinding;
|
|
2632
|
+
},
|
|
2495
2633
|
|
|
2496
2634
|
Pair(key, _colon, value) {
|
|
2497
2635
|
return { key: key.toIR(), value: value.toIR() };
|
|
@@ -2557,6 +2695,9 @@ semantics.addOperation("toIR", {
|
|
|
2557
2695
|
BooleanKw(_kw) {
|
|
2558
2696
|
return { type: "identifier", name: "boolean" } satisfies IRNode;
|
|
2559
2697
|
},
|
|
2698
|
+
SizeKw(_kw) {
|
|
2699
|
+
return { type: "identifier", name: "size" } satisfies IRNode;
|
|
2700
|
+
},
|
|
2560
2701
|
|
|
2561
2702
|
identifier(_a, _b) {
|
|
2562
2703
|
return { type: "identifier", name: this.sourceString } satisfies IRNode;
|