@creationix/rex 0.3.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/package.json +9 -6
- package/rex-cli.js +1334 -1190
- package/rex-cli.ts +268 -27
- package/rex-repl.js +1048 -1135
- package/rex-repl.ts +392 -103
- package/rex.js +290 -954
- package/rex.ohm +48 -21
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +27 -8
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +388 -218
- package/rexc-interpreter.ts +386 -88
- package/rx-cli.js +2836 -0
- package/rx-cli.ts +298 -0
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,39 @@ 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",
|
|
124
136
|
} as const;
|
|
125
137
|
|
|
126
138
|
type OpcodeName = keyof typeof OPCODE_IDS;
|
|
127
139
|
|
|
140
|
+
// Keyword identifiers that are reserved in the grammar and compile to opcodes when called.
|
|
141
|
+
const KEYWORD_OPCODES: ReadonlySet<string> = new Set(["boolean", "number", "string", "array", "object"]);
|
|
142
|
+
|
|
128
143
|
type EncodeOptions = {
|
|
129
|
-
domainRefs?: Record<string,
|
|
144
|
+
domainRefs?: Record<string, string>;
|
|
145
|
+
domainOpcodes?: Record<string, string>;
|
|
130
146
|
};
|
|
131
147
|
|
|
132
148
|
type CompileOptions = {
|
|
@@ -141,10 +157,7 @@ type RexDomainConfigEntry = {
|
|
|
141
157
|
names?: unknown;
|
|
142
158
|
};
|
|
143
159
|
|
|
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> = {
|
|
160
|
+
const BINARY_TO_OPCODE: Record<Exclude<Extract<IRNode, { type: "binary" }> ["op"], "nor">, OpcodeName> = {
|
|
148
161
|
add: "add",
|
|
149
162
|
sub: "sub",
|
|
150
163
|
mul: "mul",
|
|
@@ -267,9 +280,9 @@ function encodeDecimal(significand: number, power: number): string {
|
|
|
267
280
|
|
|
268
281
|
function encodeNumberNode(node: Extract<IRNode, { type: "number" }>): string {
|
|
269
282
|
const numberValue = node.value;
|
|
270
|
-
if (Number.isNaN(numberValue)) return "
|
|
271
|
-
if (numberValue === Infinity) return "
|
|
272
|
-
if (numberValue === -Infinity) return "
|
|
283
|
+
if (Number.isNaN(numberValue)) return "nan'";
|
|
284
|
+
if (numberValue === Infinity) return "inf'";
|
|
285
|
+
if (numberValue === -Infinity) return "nif'";
|
|
273
286
|
|
|
274
287
|
if (Number.isInteger(numberValue)) {
|
|
275
288
|
const { base, exp } = splitDecimal(numberValue);
|
|
@@ -302,7 +315,7 @@ function encodeNumberNode(node: Extract<IRNode, { type: "number" }>): string {
|
|
|
302
315
|
}
|
|
303
316
|
|
|
304
317
|
function encodeOpcode(opcode: OpcodeName): string {
|
|
305
|
-
return `${
|
|
318
|
+
return `${OPCODE_IDS[opcode]}%`;
|
|
306
319
|
}
|
|
307
320
|
|
|
308
321
|
function encodeCallParts(parts: string[]): string {
|
|
@@ -321,7 +334,7 @@ function addOptionalPrefix(encoded: string): string {
|
|
|
321
334
|
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
|
|
322
335
|
payload = encoded.slice(2, -1);
|
|
323
336
|
}
|
|
324
|
-
else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
337
|
+
else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
|
|
325
338
|
payload = encoded.slice(2, -1);
|
|
326
339
|
}
|
|
327
340
|
else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
|
|
@@ -334,7 +347,7 @@ function addOptionalPrefix(encoded: string): string {
|
|
|
334
347
|
}
|
|
335
348
|
|
|
336
349
|
function encodeBlockExpression(block: IRNode[]): string {
|
|
337
|
-
if (block.length === 0) return "
|
|
350
|
+
if (block.length === 0) return "un'";
|
|
338
351
|
if (block.length === 1) return encodeNode(block[0] as IRNode);
|
|
339
352
|
return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
|
|
340
353
|
}
|
|
@@ -351,9 +364,14 @@ function encodeConditionalElse(elseBranch: IRConditionalElse): string {
|
|
|
351
364
|
return encodeNode(nested);
|
|
352
365
|
}
|
|
353
366
|
|
|
367
|
+
function encodeDomainLookup(shortCode: string, tag: string): string {
|
|
368
|
+
return `${shortCode}${tag}`;
|
|
369
|
+
}
|
|
370
|
+
|
|
354
371
|
function encodeNavigation(node: Extract<IRNode, { type: "navigation" }>): string {
|
|
355
372
|
const domainRefs = activeEncodeOptions?.domainRefs;
|
|
356
|
-
|
|
373
|
+
const domainOpcodes = activeEncodeOptions?.domainOpcodes;
|
|
374
|
+
if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
|
|
357
375
|
const staticPath = [node.target.name];
|
|
358
376
|
for (const segment of node.segments) {
|
|
359
377
|
if (segment.type !== "static") break;
|
|
@@ -362,15 +380,18 @@ function encodeNavigation(node: Extract<IRNode, { type: "navigation" }>): string
|
|
|
362
380
|
|
|
363
381
|
for (let pathLength = staticPath.length; pathLength >= 1; pathLength -= 1) {
|
|
364
382
|
const dottedName = staticPath.slice(0, pathLength).join(".");
|
|
365
|
-
const
|
|
366
|
-
|
|
383
|
+
const refCode = domainRefs?.[dottedName];
|
|
384
|
+
const opcodeCode = domainOpcodes?.[dottedName];
|
|
385
|
+
const shortCode = refCode ?? opcodeCode;
|
|
386
|
+
if (shortCode === undefined) continue;
|
|
387
|
+
const tag = refCode !== undefined ? "'" : "%";
|
|
367
388
|
|
|
368
389
|
const consumedStaticSegments = pathLength - 1;
|
|
369
390
|
if (consumedStaticSegments === node.segments.length) {
|
|
370
|
-
return
|
|
391
|
+
return encodeDomainLookup(shortCode, tag);
|
|
371
392
|
}
|
|
372
393
|
|
|
373
|
-
const parts = [
|
|
394
|
+
const parts = [encodeDomainLookup(shortCode, tag)];
|
|
374
395
|
for (const segment of node.segments.slice(consumedStaticSegments)) {
|
|
375
396
|
if (segment.type === "static") parts.push(encodeBareOrLengthString(segment.key));
|
|
376
397
|
else parts.push(encodeNode(segment.key));
|
|
@@ -395,9 +416,12 @@ function encodeWhile(node: Extract<IRNode, { type: "while" }>): string {
|
|
|
395
416
|
|
|
396
417
|
function encodeFor(node: Extract<IRNode, { type: "for" }>): string {
|
|
397
418
|
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
398
|
-
if (node.binding.type === "binding:
|
|
419
|
+
if (node.binding.type === "binding:bareIn") {
|
|
399
420
|
return `>(${encodeNode(node.binding.source)}${body})`;
|
|
400
421
|
}
|
|
422
|
+
if (node.binding.type === "binding:bareOf") {
|
|
423
|
+
return `<(${encodeNode(node.binding.source)}${body})`;
|
|
424
|
+
}
|
|
401
425
|
if (node.binding.type === "binding:valueIn") {
|
|
402
426
|
return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
|
|
403
427
|
}
|
|
@@ -409,31 +433,50 @@ function encodeFor(node: Extract<IRNode, { type: "for" }>): string {
|
|
|
409
433
|
|
|
410
434
|
function encodeArrayComprehension(node: Extract<IRNode, { type: "arrayComprehension" }>): string {
|
|
411
435
|
const body = addOptionalPrefix(encodeNode(node.body));
|
|
412
|
-
if (node.binding.type === "binding:
|
|
436
|
+
if (node.binding.type === "binding:bareIn") {
|
|
413
437
|
return `>[${encodeNode(node.binding.source)}${body}]`;
|
|
414
438
|
}
|
|
439
|
+
if (node.binding.type === "binding:bareOf") {
|
|
440
|
+
return `<[${encodeNode(node.binding.source)}${body}]`;
|
|
441
|
+
}
|
|
415
442
|
if (node.binding.type === "binding:valueIn") {
|
|
416
443
|
return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
|
|
417
444
|
}
|
|
418
445
|
if (node.binding.type === "binding:keyValueIn") {
|
|
419
446
|
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
|
|
420
447
|
}
|
|
421
|
-
return
|
|
448
|
+
return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
|
|
422
449
|
}
|
|
423
450
|
|
|
424
451
|
function encodeObjectComprehension(node: Extract<IRNode, { type: "objectComprehension" }>): string {
|
|
425
452
|
const key = addOptionalPrefix(encodeNode(node.key));
|
|
426
453
|
const value = addOptionalPrefix(encodeNode(node.value));
|
|
427
|
-
if (node.binding.type === "binding:
|
|
454
|
+
if (node.binding.type === "binding:bareIn") {
|
|
428
455
|
return `>{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
429
456
|
}
|
|
457
|
+
if (node.binding.type === "binding:bareOf") {
|
|
458
|
+
return `<{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
459
|
+
}
|
|
430
460
|
if (node.binding.type === "binding:valueIn") {
|
|
431
461
|
return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
|
|
432
462
|
}
|
|
433
463
|
if (node.binding.type === "binding:keyValueIn") {
|
|
434
464
|
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
|
|
435
465
|
}
|
|
436
|
-
return
|
|
466
|
+
return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function encodeWhileArrayComprehension(node: Extract<IRNode, { type: "whileArrayComprehension" }>): string {
|
|
470
|
+
const cond = encodeNode(node.condition);
|
|
471
|
+
const body = addOptionalPrefix(encodeNode(node.body));
|
|
472
|
+
return `#[${cond}${body}]`;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function encodeWhileObjectComprehension(node: Extract<IRNode, { type: "whileObjectComprehension" }>): string {
|
|
476
|
+
const cond = encodeNode(node.condition);
|
|
477
|
+
const key = addOptionalPrefix(encodeNode(node.key));
|
|
478
|
+
const value = addOptionalPrefix(encodeNode(node.value));
|
|
479
|
+
return `#{${cond}${key}${value}}`;
|
|
437
480
|
}
|
|
438
481
|
|
|
439
482
|
let activeEncodeOptions: EncodeOptions | undefined;
|
|
@@ -444,7 +487,9 @@ function encodeNode(node: IRNode): string {
|
|
|
444
487
|
return encodeBlockExpression(node.body);
|
|
445
488
|
case "identifier": {
|
|
446
489
|
const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
|
|
447
|
-
if (domainRef !== undefined) return `${
|
|
490
|
+
if (domainRef !== undefined) return `${domainRef}'`;
|
|
491
|
+
const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
|
|
492
|
+
if (domainOpcode !== undefined) return `${domainOpcode}%`;
|
|
448
493
|
return `${node.name}$`;
|
|
449
494
|
}
|
|
450
495
|
case "self":
|
|
@@ -455,11 +500,11 @@ function encodeNode(node: IRNode): string {
|
|
|
455
500
|
return `${encodeUint(node.depth - 1)}@`;
|
|
456
501
|
}
|
|
457
502
|
case "boolean":
|
|
458
|
-
return node.value ? "
|
|
503
|
+
return node.value ? "tr'" : "fl'";
|
|
459
504
|
case "null":
|
|
460
|
-
return "
|
|
505
|
+
return "nl'";
|
|
461
506
|
case "undefined":
|
|
462
|
-
return "
|
|
507
|
+
return "un'";
|
|
463
508
|
case "number":
|
|
464
509
|
return encodeNumberNode(node);
|
|
465
510
|
case "string":
|
|
@@ -470,6 +515,8 @@ function encodeNode(node: IRNode): string {
|
|
|
470
515
|
}
|
|
471
516
|
case "arrayComprehension":
|
|
472
517
|
return encodeArrayComprehension(node);
|
|
518
|
+
case "whileArrayComprehension":
|
|
519
|
+
return encodeWhileArrayComprehension(node);
|
|
473
520
|
case "object": {
|
|
474
521
|
const body = node.entries
|
|
475
522
|
.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`)
|
|
@@ -478,6 +525,8 @@ function encodeNode(node: IRNode): string {
|
|
|
478
525
|
}
|
|
479
526
|
case "objectComprehension":
|
|
480
527
|
return encodeObjectComprehension(node);
|
|
528
|
+
case "whileObjectComprehension":
|
|
529
|
+
return encodeWhileObjectComprehension(node);
|
|
481
530
|
case "key":
|
|
482
531
|
return encodeBareOrLengthString(node.name);
|
|
483
532
|
case "group":
|
|
@@ -485,6 +534,10 @@ function encodeNode(node: IRNode): string {
|
|
|
485
534
|
case "unary":
|
|
486
535
|
if (node.op === "delete") return `~${encodeNode(node.value)}`;
|
|
487
536
|
if (node.op === "neg") return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
|
|
537
|
+
if (node.op === "logicalNot") {
|
|
538
|
+
const val = encodeNode(node.value);
|
|
539
|
+
return `!(${val}tr')`;
|
|
540
|
+
}
|
|
488
541
|
return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
|
|
489
542
|
case "binary":
|
|
490
543
|
if (node.op === "and") {
|
|
@@ -507,12 +560,18 @@ function encodeNode(node: IRNode): string {
|
|
|
507
560
|
.join("");
|
|
508
561
|
return `|(${body})`;
|
|
509
562
|
}
|
|
563
|
+
if (node.op === "nor") {
|
|
564
|
+
const left = encodeNode(node.left);
|
|
565
|
+
const right = addOptionalPrefix(encodeNode(node.right));
|
|
566
|
+
return `!(${left}${right})`;
|
|
567
|
+
}
|
|
510
568
|
return encodeCallParts([
|
|
511
569
|
encodeOpcode(BINARY_TO_OPCODE[node.op]),
|
|
512
570
|
encodeNode(node.left),
|
|
513
571
|
encodeNode(node.right),
|
|
514
572
|
]);
|
|
515
573
|
case "assign": {
|
|
574
|
+
if (node.op === ":=") return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
516
575
|
if (node.op === "=") return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
517
576
|
const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
|
|
518
577
|
if (!opcode) throw new Error(`Unsupported assignment op: ${node.op}`);
|
|
@@ -521,8 +580,14 @@ function encodeNode(node: IRNode): string {
|
|
|
521
580
|
}
|
|
522
581
|
case "navigation":
|
|
523
582
|
return encodeNavigation(node);
|
|
524
|
-
case "call":
|
|
583
|
+
case "call": {
|
|
584
|
+
// Keyword identifiers (boolean, number, string, array, object) are parsed
|
|
585
|
+
// as identifier nodes but must be encoded as opcode calls, not variable navigation.
|
|
586
|
+
if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
|
|
587
|
+
return encodeCallParts([encodeOpcode(node.callee.name as OpcodeName), ...node.args.map((arg) => encodeNode(arg))]);
|
|
588
|
+
}
|
|
525
589
|
return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
|
|
590
|
+
}
|
|
526
591
|
case "conditional": {
|
|
527
592
|
const opener = node.head === "when" ? "?(" : "!(";
|
|
528
593
|
const cond = encodeNode(node.condition);
|
|
@@ -530,6 +595,8 @@ function encodeNode(node: IRNode): string {
|
|
|
530
595
|
const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
|
|
531
596
|
return `${opener}${cond}${thenExpr}${elseExpr})`;
|
|
532
597
|
}
|
|
598
|
+
case "range":
|
|
599
|
+
return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
|
|
533
600
|
case "for":
|
|
534
601
|
return encodeFor(node);
|
|
535
602
|
case "while":
|
|
@@ -550,11 +617,28 @@ function collectLogicalChain(node: IRNode, op: "and" | "or"): IRNode[] {
|
|
|
550
617
|
return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
|
|
551
618
|
}
|
|
552
619
|
|
|
620
|
+
type ParseFailure = { message?: string; getRightmostFailurePosition?: () => number };
|
|
621
|
+
|
|
622
|
+
export function formatParseError(source: string, match: ParseFailure): string {
|
|
623
|
+
const message = match.message ?? "Parse failed";
|
|
624
|
+
const pos = match.getRightmostFailurePosition?.();
|
|
625
|
+
if (typeof pos !== "number" || !Number.isFinite(pos)) return message;
|
|
626
|
+
|
|
627
|
+
const safePos = Math.max(0, Math.min(source.length, pos));
|
|
628
|
+
const lineStart = source.lastIndexOf("\n", safePos - 1) + 1;
|
|
629
|
+
const lineEndIndex = source.indexOf("\n", safePos);
|
|
630
|
+
const lineEnd = lineEndIndex === -1 ? source.length : lineEndIndex;
|
|
631
|
+
const lineText = source.slice(lineStart, lineEnd);
|
|
632
|
+
const lineNumber = source.slice(0, lineStart).split("\n").length;
|
|
633
|
+
const columnNumber = safePos - lineStart + 1;
|
|
634
|
+
const caret = `${" ".repeat(Math.max(0, columnNumber - 1))}^`;
|
|
635
|
+
return `${message}\n ${lineText}\n ${caret}`;
|
|
636
|
+
}
|
|
637
|
+
|
|
553
638
|
export function parseToIR(source: string): IRNode {
|
|
554
639
|
const match = grammar.match(source);
|
|
555
640
|
if (!match.succeeded()) {
|
|
556
|
-
|
|
557
|
-
throw new Error(failure.message ?? "Parse failed");
|
|
641
|
+
throw new Error(formatParseError(source, match as ParseFailure));
|
|
558
642
|
}
|
|
559
643
|
return semantics(match).toIR() as IRNode;
|
|
560
644
|
}
|
|
@@ -690,81 +774,62 @@ export function parse(source: string): unknown {
|
|
|
690
774
|
return parseDataNode(parseToIR(source));
|
|
691
775
|
}
|
|
692
776
|
|
|
693
|
-
|
|
777
|
+
type DomainMaps = { domainRefs: Record<string, string>; domainOpcodes: Record<string, string> };
|
|
778
|
+
|
|
779
|
+
export function domainRefsFromConfig(config: unknown): DomainMaps {
|
|
694
780
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
695
781
|
throw new Error("Domain config must be an object");
|
|
696
782
|
}
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
mapConfigEntries(section as Record<string, unknown>, refs);
|
|
701
|
-
}
|
|
702
|
-
return refs;
|
|
703
|
-
}
|
|
783
|
+
const configObj = config as Record<string, unknown>;
|
|
784
|
+
const domainRefs: Record<string, string> = {};
|
|
785
|
+
const domainOpcodes: Record<string, string> = {};
|
|
704
786
|
|
|
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)`);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
let value = 0;
|
|
718
|
-
for (const char of refText) {
|
|
719
|
-
const digit = DOMAIN_DIGIT_INDEX.get(char);
|
|
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
|
-
}
|
|
787
|
+
const dataSection = configObj.data;
|
|
788
|
+
if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
|
|
789
|
+
mapConfigEntries(dataSection as Record<string, unknown>, domainRefs);
|
|
725
790
|
}
|
|
726
791
|
|
|
727
|
-
|
|
728
|
-
|
|
792
|
+
const functionsSection = configObj.functions;
|
|
793
|
+
if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
|
|
794
|
+
mapConfigEntries(functionsSection as Record<string, unknown>, domainOpcodes);
|
|
729
795
|
}
|
|
730
796
|
|
|
731
|
-
return
|
|
797
|
+
return { domainRefs, domainOpcodes };
|
|
732
798
|
}
|
|
733
799
|
|
|
734
|
-
function mapConfigEntries(entries: Record<string, unknown>, refs: Record<string,
|
|
800
|
+
function mapConfigEntries(entries: Record<string, unknown>, refs: Record<string, string>) {
|
|
735
801
|
const sourceKindByRoot = new Map<string, "explicit" | "implicit">();
|
|
736
802
|
for (const root of Object.keys(refs)) {
|
|
737
803
|
sourceKindByRoot.set(root, "explicit");
|
|
738
804
|
}
|
|
739
805
|
|
|
740
|
-
for (const [
|
|
806
|
+
for (const [shortCode, rawEntry] of Object.entries(entries)) {
|
|
741
807
|
const entry = rawEntry as RexDomainConfigEntry;
|
|
742
808
|
if (!entry || typeof entry !== "object") continue;
|
|
743
809
|
if (!Array.isArray(entry.names)) continue;
|
|
744
810
|
|
|
745
|
-
const refId = decodeDomainRefKey(refText);
|
|
746
811
|
for (const rawName of entry.names) {
|
|
747
812
|
if (typeof rawName !== "string") continue;
|
|
748
|
-
const
|
|
749
|
-
if (
|
|
750
|
-
throw new Error(`Conflicting refs for '${rawName}': ${
|
|
813
|
+
const existingRef = refs[rawName];
|
|
814
|
+
if (existingRef !== undefined && existingRef !== shortCode) {
|
|
815
|
+
throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
|
|
751
816
|
}
|
|
752
|
-
refs[rawName] =
|
|
817
|
+
refs[rawName] = shortCode;
|
|
753
818
|
|
|
754
819
|
const root = rawName.split(".")[0];
|
|
755
820
|
if (!root) continue;
|
|
756
821
|
const currentKind: "explicit" | "implicit" = rawName.includes(".") ? "implicit" : "explicit";
|
|
757
822
|
const existing = refs[root];
|
|
758
823
|
if (existing !== undefined) {
|
|
759
|
-
if (existing ===
|
|
824
|
+
if (existing === shortCode) continue;
|
|
760
825
|
const existingKind = sourceKindByRoot.get(root) ?? "explicit";
|
|
761
826
|
if (currentKind === "explicit") {
|
|
762
|
-
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${
|
|
827
|
+
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
|
|
763
828
|
}
|
|
764
829
|
if (existingKind === "explicit") continue;
|
|
765
830
|
continue;
|
|
766
831
|
}
|
|
767
|
-
refs[root] =
|
|
832
|
+
refs[root] = shortCode;
|
|
768
833
|
sourceKindByRoot.set(root, currentKind);
|
|
769
834
|
}
|
|
770
835
|
}
|
|
@@ -910,17 +975,10 @@ function gatherEncodedValueSpans(text: string): EncodedSpan[] {
|
|
|
910
975
|
return spans;
|
|
911
976
|
}
|
|
912
977
|
|
|
913
|
-
function buildPointerToken(pointerStart: number, targetStart: number): string | undefined {
|
|
914
|
-
|
|
978
|
+
function buildPointerToken(pointerStart: number, targetStart: number, occurrenceSize: number): string | undefined {
|
|
979
|
+
const offset = targetStart - pointerStart - occurrenceSize;
|
|
915
980
|
if (offset < 0) return undefined;
|
|
916
|
-
|
|
917
|
-
const prefix = encodeUint(offset);
|
|
918
|
-
const recalculated = targetStart - (pointerStart + prefix.length + 1);
|
|
919
|
-
if (recalculated === offset) return `${prefix}^`;
|
|
920
|
-
offset = recalculated;
|
|
921
|
-
if (offset < 0) return undefined;
|
|
922
|
-
}
|
|
923
|
-
return undefined;
|
|
981
|
+
return `${encodeUint(offset)}^`;
|
|
924
982
|
}
|
|
925
983
|
|
|
926
984
|
function buildDedupeCandidateTable(encoded: string, minBytes: number): Map<string, DedupeCandidate[]> {
|
|
@@ -962,10 +1020,10 @@ function dedupeLargeEncodedValues(encoded: string, minBytes = 4): string {
|
|
|
962
1020
|
|
|
963
1021
|
if (current.slice(occurrence.span.start, occurrence.span.end) !== value) continue;
|
|
964
1022
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1023
|
+
const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
|
|
1024
|
+
const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
|
|
1025
|
+
if (!pointerToken) continue;
|
|
1026
|
+
if (pointerToken.length >= occurrence.sizeBytes) continue;
|
|
969
1027
|
|
|
970
1028
|
current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
|
|
971
1029
|
replaced = true;
|
|
@@ -1053,6 +1111,22 @@ function dropBindingNames(env: OptimizeEnv, binding: IRBindingOrExpr) {
|
|
|
1053
1111
|
}
|
|
1054
1112
|
}
|
|
1055
1113
|
|
|
1114
|
+
function optimizeBinding(binding: IRBindingOrExpr, sourceEnv: OptimizeEnv, currentDepth: number): IRBinding {
|
|
1115
|
+
const source = optimizeNode(binding.source, sourceEnv, currentDepth);
|
|
1116
|
+
switch (binding.type) {
|
|
1117
|
+
case "binding:bareIn":
|
|
1118
|
+
return { type: "binding:bareIn", source };
|
|
1119
|
+
case "binding:bareOf":
|
|
1120
|
+
return { type: "binding:bareOf", source };
|
|
1121
|
+
case "binding:valueIn":
|
|
1122
|
+
return { type: "binding:valueIn", value: binding.value, source };
|
|
1123
|
+
case "binding:keyValueIn":
|
|
1124
|
+
return { type: "binding:keyValueIn", key: binding.key, value: binding.value, source };
|
|
1125
|
+
case "binding:keyOf":
|
|
1126
|
+
return { type: "binding:keyOf", key: binding.key, source };
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1056
1130
|
function collectReads(node: IRNode, out: Set<string>) {
|
|
1057
1131
|
switch (node.type) {
|
|
1058
1132
|
case "identifier":
|
|
@@ -1074,11 +1148,20 @@ function collectReads(node: IRNode, out: Set<string>) {
|
|
|
1074
1148
|
collectReads(node.binding.source, out);
|
|
1075
1149
|
collectReads(node.body, out);
|
|
1076
1150
|
return;
|
|
1151
|
+
case "whileArrayComprehension":
|
|
1152
|
+
collectReads(node.condition, out);
|
|
1153
|
+
collectReads(node.body, out);
|
|
1154
|
+
return;
|
|
1077
1155
|
case "objectComprehension":
|
|
1078
1156
|
collectReads(node.binding.source, out);
|
|
1079
1157
|
collectReads(node.key, out);
|
|
1080
1158
|
collectReads(node.value, out);
|
|
1081
1159
|
return;
|
|
1160
|
+
case "whileObjectComprehension":
|
|
1161
|
+
collectReads(node.condition, out);
|
|
1162
|
+
collectReads(node.key, out);
|
|
1163
|
+
collectReads(node.value, out);
|
|
1164
|
+
return;
|
|
1082
1165
|
case "unary":
|
|
1083
1166
|
collectReads(node.value, out);
|
|
1084
1167
|
return;
|
|
@@ -1109,6 +1192,10 @@ function collectReads(node: IRNode, out: Set<string>) {
|
|
|
1109
1192
|
collectReads(node.binding.source, out);
|
|
1110
1193
|
for (const part of node.body) collectReads(part, out);
|
|
1111
1194
|
return;
|
|
1195
|
+
case "range":
|
|
1196
|
+
collectReads(node.from, out);
|
|
1197
|
+
collectReads(node.to, out);
|
|
1198
|
+
return;
|
|
1112
1199
|
case "program":
|
|
1113
1200
|
for (const part of node.body) collectReads(part, out);
|
|
1114
1201
|
return;
|
|
@@ -1151,6 +1238,8 @@ function isPureNode(node: IRNode): boolean {
|
|
|
1151
1238
|
return node.op !== "delete" && isPureNode(node.value);
|
|
1152
1239
|
case "binary":
|
|
1153
1240
|
return isPureNode(node.left) && isPureNode(node.right);
|
|
1241
|
+
case "range":
|
|
1242
|
+
return isPureNode(node.from) && isPureNode(node.to);
|
|
1154
1243
|
default:
|
|
1155
1244
|
return false;
|
|
1156
1245
|
}
|
|
@@ -1223,6 +1312,8 @@ function hasIdentifierRead(node: IRNode, name: string, asPlace = false): boolean
|
|
|
1223
1312
|
return hasIdentifierRead(node.value, name, node.op === "delete");
|
|
1224
1313
|
case "binary":
|
|
1225
1314
|
return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
|
|
1315
|
+
case "range":
|
|
1316
|
+
return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
|
|
1226
1317
|
case "assign":
|
|
1227
1318
|
return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
|
|
1228
1319
|
default:
|
|
@@ -1245,6 +1336,8 @@ function countIdentifierReads(node: IRNode, name: string, asPlace = false): numb
|
|
|
1245
1336
|
return countIdentifierReads(node.value, name, node.op === "delete");
|
|
1246
1337
|
case "binary":
|
|
1247
1338
|
return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
|
|
1339
|
+
case "range":
|
|
1340
|
+
return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
|
|
1248
1341
|
case "assign":
|
|
1249
1342
|
return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
|
|
1250
1343
|
default:
|
|
@@ -1302,6 +1395,12 @@ function replaceIdentifier(node: IRNode, name: string, replacement: IRNode, asPl
|
|
|
1302
1395
|
place: replaceIdentifier(node.place, name, replacement, true),
|
|
1303
1396
|
value: replaceIdentifier(node.value, name, replacement),
|
|
1304
1397
|
} satisfies IRNode;
|
|
1398
|
+
case "range":
|
|
1399
|
+
return {
|
|
1400
|
+
type: "range",
|
|
1401
|
+
from: replaceIdentifier(node.from, name, replacement),
|
|
1402
|
+
to: replaceIdentifier(node.to, name, replacement),
|
|
1403
|
+
} satisfies IRNode;
|
|
1305
1404
|
default:
|
|
1306
1405
|
return node;
|
|
1307
1406
|
}
|
|
@@ -1435,6 +1534,9 @@ function foldUnary(op: Extract<IRNode, { type: "unary" }> ["op"], value: unknown
|
|
|
1435
1534
|
if (typeof value === "number") return ~value;
|
|
1436
1535
|
return undefined;
|
|
1437
1536
|
}
|
|
1537
|
+
if (op === "logicalNot") {
|
|
1538
|
+
return value === undefined ? true : undefined;
|
|
1539
|
+
}
|
|
1438
1540
|
return undefined;
|
|
1439
1541
|
}
|
|
1440
1542
|
|
|
@@ -1597,6 +1699,12 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1597
1699
|
}
|
|
1598
1700
|
return { type: "binary", op: node.op, left, right } satisfies IRNode;
|
|
1599
1701
|
}
|
|
1702
|
+
case "range":
|
|
1703
|
+
return {
|
|
1704
|
+
type: "range",
|
|
1705
|
+
from: optimizeNode(node.from, env, currentDepth),
|
|
1706
|
+
to: optimizeNode(node.to, env, currentDepth),
|
|
1707
|
+
} satisfies IRNode;
|
|
1600
1708
|
case "navigation": {
|
|
1601
1709
|
const target = optimizeNode(node.target, env, currentDepth);
|
|
1602
1710
|
const segments = node.segments.map((segment) => (segment.type === "static"
|
|
@@ -1706,31 +1814,7 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1706
1814
|
}
|
|
1707
1815
|
case "for": {
|
|
1708
1816
|
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
|
-
})();
|
|
1817
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1734
1818
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1735
1819
|
dropBindingNames(bodyEnv, binding);
|
|
1736
1820
|
return {
|
|
@@ -1741,26 +1825,7 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1741
1825
|
}
|
|
1742
1826
|
case "arrayComprehension": {
|
|
1743
1827
|
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);
|
|
1828
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1764
1829
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1765
1830
|
dropBindingNames(bodyEnv, binding);
|
|
1766
1831
|
return {
|
|
@@ -1769,28 +1834,15 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1769
1834
|
body: optimizeNode(node.body, bodyEnv, currentDepth + 1),
|
|
1770
1835
|
} satisfies IRNode;
|
|
1771
1836
|
}
|
|
1837
|
+
case "whileArrayComprehension":
|
|
1838
|
+
return {
|
|
1839
|
+
type: "whileArrayComprehension",
|
|
1840
|
+
condition: optimizeNode(node.condition, env, currentDepth),
|
|
1841
|
+
body: optimizeNode(node.body, env, currentDepth + 1),
|
|
1842
|
+
} satisfies IRNode;
|
|
1772
1843
|
case "objectComprehension": {
|
|
1773
1844
|
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);
|
|
1845
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1794
1846
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1795
1847
|
dropBindingNames(bodyEnv, binding);
|
|
1796
1848
|
return {
|
|
@@ -1800,13 +1852,21 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1800
1852
|
value: optimizeNode(node.value, bodyEnv, currentDepth + 1),
|
|
1801
1853
|
} satisfies IRNode;
|
|
1802
1854
|
}
|
|
1855
|
+
case "whileObjectComprehension":
|
|
1856
|
+
return {
|
|
1857
|
+
type: "whileObjectComprehension",
|
|
1858
|
+
condition: optimizeNode(node.condition, env, currentDepth),
|
|
1859
|
+
key: optimizeNode(node.key, env, currentDepth + 1),
|
|
1860
|
+
value: optimizeNode(node.value, env, currentDepth + 1),
|
|
1861
|
+
} satisfies IRNode;
|
|
1803
1862
|
default:
|
|
1804
1863
|
return node;
|
|
1805
1864
|
}
|
|
1806
1865
|
}
|
|
1807
1866
|
|
|
1808
1867
|
export function optimizeIR(node: IRNode): IRNode {
|
|
1809
|
-
|
|
1868
|
+
// Optimization is temporarily disabled pending correctness fixes.
|
|
1869
|
+
return node;
|
|
1810
1870
|
}
|
|
1811
1871
|
|
|
1812
1872
|
function collectLocalBindings(node: IRNode, locals: Set<string>) {
|
|
@@ -1848,6 +1908,10 @@ function collectLocalBindings(node: IRNode, locals: Set<string>) {
|
|
|
1848
1908
|
collectLocalBindings(node.left, locals);
|
|
1849
1909
|
collectLocalBindings(node.right, locals);
|
|
1850
1910
|
return;
|
|
1911
|
+
case "range":
|
|
1912
|
+
collectLocalBindings(node.from, locals);
|
|
1913
|
+
collectLocalBindings(node.to, locals);
|
|
1914
|
+
return;
|
|
1851
1915
|
case "conditional":
|
|
1852
1916
|
collectLocalBindings(node.condition, locals);
|
|
1853
1917
|
for (const part of node.thenBlock) collectLocalBindings(part, locals);
|
|
@@ -1861,11 +1925,20 @@ function collectLocalBindings(node: IRNode, locals: Set<string>) {
|
|
|
1861
1925
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1862
1926
|
collectLocalBindings(node.body, locals);
|
|
1863
1927
|
return;
|
|
1928
|
+
case "whileArrayComprehension":
|
|
1929
|
+
collectLocalBindings(node.condition, locals);
|
|
1930
|
+
collectLocalBindings(node.body, locals);
|
|
1931
|
+
return;
|
|
1864
1932
|
case "objectComprehension":
|
|
1865
1933
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1866
1934
|
collectLocalBindings(node.key, locals);
|
|
1867
1935
|
collectLocalBindings(node.value, locals);
|
|
1868
1936
|
return;
|
|
1937
|
+
case "whileObjectComprehension":
|
|
1938
|
+
collectLocalBindings(node.condition, locals);
|
|
1939
|
+
collectLocalBindings(node.key, locals);
|
|
1940
|
+
collectLocalBindings(node.value, locals);
|
|
1941
|
+
return;
|
|
1869
1942
|
default:
|
|
1870
1943
|
return;
|
|
1871
1944
|
}
|
|
@@ -1952,6 +2025,10 @@ function collectNameFrequencies(node: IRNode, locals: Set<string>, frequencies:
|
|
|
1952
2025
|
collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
|
|
1953
2026
|
collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
|
|
1954
2027
|
return;
|
|
2028
|
+
case "range":
|
|
2029
|
+
collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
|
|
2030
|
+
collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
|
|
2031
|
+
return;
|
|
1955
2032
|
case "conditional":
|
|
1956
2033
|
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1957
2034
|
for (const part of node.thenBlock) collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
@@ -1965,11 +2042,20 @@ function collectNameFrequencies(node: IRNode, locals: Set<string>, frequencies:
|
|
|
1965
2042
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1966
2043
|
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1967
2044
|
return;
|
|
2045
|
+
case "whileArrayComprehension":
|
|
2046
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
2047
|
+
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
2048
|
+
return;
|
|
1968
2049
|
case "objectComprehension":
|
|
1969
2050
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1970
2051
|
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1971
2052
|
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1972
2053
|
return;
|
|
2054
|
+
case "whileObjectComprehension":
|
|
2055
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
2056
|
+
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
2057
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
2058
|
+
return;
|
|
1973
2059
|
default:
|
|
1974
2060
|
return;
|
|
1975
2061
|
}
|
|
@@ -2046,6 +2132,12 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2046
2132
|
left: renameLocalNames(node.left, map),
|
|
2047
2133
|
right: renameLocalNames(node.right, map),
|
|
2048
2134
|
} satisfies IRNode;
|
|
2135
|
+
case "range":
|
|
2136
|
+
return {
|
|
2137
|
+
type: "range",
|
|
2138
|
+
from: renameLocalNames(node.from, map),
|
|
2139
|
+
to: renameLocalNames(node.to, map),
|
|
2140
|
+
} satisfies IRNode;
|
|
2049
2141
|
case "assign": {
|
|
2050
2142
|
const place = node.place.type === "identifier" && map.has(node.place.name)
|
|
2051
2143
|
? ({ type: "identifier", name: map.get(node.place.name) as string } satisfies IRNode)
|
|
@@ -2077,6 +2169,12 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2077
2169
|
binding: renameLocalNamesBinding(node.binding, map),
|
|
2078
2170
|
body: renameLocalNames(node.body, map),
|
|
2079
2171
|
} satisfies IRNode;
|
|
2172
|
+
case "whileArrayComprehension":
|
|
2173
|
+
return {
|
|
2174
|
+
type: "whileArrayComprehension",
|
|
2175
|
+
condition: renameLocalNames(node.condition, map),
|
|
2176
|
+
body: renameLocalNames(node.body, map),
|
|
2177
|
+
} satisfies IRNode;
|
|
2080
2178
|
case "objectComprehension":
|
|
2081
2179
|
return {
|
|
2082
2180
|
type: "objectComprehension",
|
|
@@ -2084,35 +2182,32 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2084
2182
|
key: renameLocalNames(node.key, map),
|
|
2085
2183
|
value: renameLocalNames(node.value, map),
|
|
2086
2184
|
} satisfies IRNode;
|
|
2185
|
+
case "whileObjectComprehension":
|
|
2186
|
+
return {
|
|
2187
|
+
type: "whileObjectComprehension",
|
|
2188
|
+
condition: renameLocalNames(node.condition, map),
|
|
2189
|
+
key: renameLocalNames(node.key, map),
|
|
2190
|
+
value: renameLocalNames(node.value, map),
|
|
2191
|
+
} satisfies IRNode;
|
|
2087
2192
|
default:
|
|
2088
2193
|
return node;
|
|
2089
2194
|
}
|
|
2090
2195
|
}
|
|
2091
2196
|
|
|
2092
2197
|
function renameLocalNamesBinding(binding: IRBindingOrExpr, map: Map<string, string>): IRBindingOrExpr {
|
|
2093
|
-
|
|
2094
|
-
|
|
2198
|
+
const source = renameLocalNames(binding.source, map);
|
|
2199
|
+
switch (binding.type) {
|
|
2200
|
+
case "binding:bareIn":
|
|
2201
|
+
return { type: "binding:bareIn", source };
|
|
2202
|
+
case "binding:bareOf":
|
|
2203
|
+
return { type: "binding:bareOf", source };
|
|
2204
|
+
case "binding:valueIn":
|
|
2205
|
+
return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
|
|
2206
|
+
case "binding:keyValueIn":
|
|
2207
|
+
return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
|
|
2208
|
+
case "binding:keyOf":
|
|
2209
|
+
return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
|
|
2095
2210
|
}
|
|
2096
|
-
if (binding.type === "binding:valueIn") {
|
|
2097
|
-
return {
|
|
2098
|
-
type: "binding:valueIn",
|
|
2099
|
-
value: map.get(binding.value) ?? binding.value,
|
|
2100
|
-
source: renameLocalNames(binding.source, map),
|
|
2101
|
-
} satisfies IRBinding;
|
|
2102
|
-
}
|
|
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
2211
|
}
|
|
2117
2212
|
|
|
2118
2213
|
function renameLocalNamesElse(elseBranch: IRConditionalElse, map: Map<string, string>): IRConditionalElse {
|
|
@@ -2163,9 +2258,9 @@ export function compile(source: string, options?: CompileOptions): string {
|
|
|
2163
2258
|
const ir = parseToIR(source);
|
|
2164
2259
|
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
2165
2260
|
if (options?.minifyNames) lowered = minifyLocalNamesIR(lowered);
|
|
2166
|
-
const
|
|
2261
|
+
const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
|
|
2167
2262
|
return encodeIR(lowered, {
|
|
2168
|
-
|
|
2263
|
+
...domainMaps,
|
|
2169
2264
|
dedupeValues: options?.dedupeValues,
|
|
2170
2265
|
dedupeMinBytes: options?.dedupeMinBytes,
|
|
2171
2266
|
});
|
|
@@ -2299,6 +2394,9 @@ semantics.addOperation("toIR", {
|
|
|
2299
2394
|
ExistenceExpr_or(left, _or, right) {
|
|
2300
2395
|
return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2301
2396
|
},
|
|
2397
|
+
ExistenceExpr_nor(left, _nor, right) {
|
|
2398
|
+
return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2399
|
+
},
|
|
2302
2400
|
|
|
2303
2401
|
BitExpr_and(left, _op, right) {
|
|
2304
2402
|
return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
@@ -2310,6 +2408,10 @@ semantics.addOperation("toIR", {
|
|
|
2310
2408
|
return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2311
2409
|
},
|
|
2312
2410
|
|
|
2411
|
+
RangeExpr_range(left, _op, right) {
|
|
2412
|
+
return { type: "range", from: left.toIR(), to: right.toIR() } satisfies IRNode;
|
|
2413
|
+
},
|
|
2414
|
+
|
|
2313
2415
|
CompareExpr_binary(left, op, right) {
|
|
2314
2416
|
const map: Record<string, Extract<IRNode, { type: "binary" }>["op"]> = {
|
|
2315
2417
|
"==": "eq",
|
|
@@ -2352,6 +2454,9 @@ semantics.addOperation("toIR", {
|
|
|
2352
2454
|
UnaryExpr_not(_op, value) {
|
|
2353
2455
|
return { type: "unary", op: "not", value: value.toIR() } satisfies IRNode;
|
|
2354
2456
|
},
|
|
2457
|
+
UnaryExpr_logicalNot(_not, value) {
|
|
2458
|
+
return { type: "unary", op: "logicalNot", value: value.toIR() } satisfies IRNode;
|
|
2459
|
+
},
|
|
2355
2460
|
UnaryExpr_delete(_del, place) {
|
|
2356
2461
|
return { type: "unary", op: "delete", value: place.toIR() } satisfies IRNode;
|
|
2357
2462
|
},
|
|
@@ -2408,13 +2513,6 @@ semantics.addOperation("toIR", {
|
|
|
2408
2513
|
return { type: "else", block: block.toIR() as IRNode[] } satisfies IRConditionalElse;
|
|
2409
2514
|
},
|
|
2410
2515
|
|
|
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
2516
|
WhileExpr(_while, condition, _do, block, _end) {
|
|
2419
2517
|
return {
|
|
2420
2518
|
type: "while",
|
|
@@ -2426,25 +2524,39 @@ semantics.addOperation("toIR", {
|
|
|
2426
2524
|
ForExpr(_for, binding, _do, block, _end) {
|
|
2427
2525
|
return {
|
|
2428
2526
|
type: "for",
|
|
2429
|
-
binding: binding.toIR() as
|
|
2527
|
+
binding: binding.toIR() as IRBinding,
|
|
2430
2528
|
body: block.toIR() as IRNode[],
|
|
2431
2529
|
} satisfies IRNode;
|
|
2432
2530
|
},
|
|
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
2531
|
|
|
2441
2532
|
Array_empty(_open, _close) {
|
|
2442
2533
|
return { type: "array", items: [] } satisfies IRNode;
|
|
2443
2534
|
},
|
|
2444
|
-
|
|
2535
|
+
Array_forComprehension(_open, body, _for, binding, _close) {
|
|
2536
|
+
return {
|
|
2537
|
+
type: "arrayComprehension",
|
|
2538
|
+
binding: binding.toIR() as IRBinding,
|
|
2539
|
+
body: body.toIR(),
|
|
2540
|
+
} satisfies IRNode;
|
|
2541
|
+
},
|
|
2542
|
+
Array_whileComprehension(_open, body, _while, condition, _close) {
|
|
2543
|
+
return {
|
|
2544
|
+
type: "whileArrayComprehension",
|
|
2545
|
+
condition: condition.toIR(),
|
|
2546
|
+
body: body.toIR(),
|
|
2547
|
+
} satisfies IRNode;
|
|
2548
|
+
},
|
|
2549
|
+
Array_inComprehension(_open, body, _in, source, _close) {
|
|
2445
2550
|
return {
|
|
2446
2551
|
type: "arrayComprehension",
|
|
2447
|
-
binding: binding.toIR()
|
|
2552
|
+
binding: { type: "binding:bareIn", source: source.toIR() } satisfies IRBinding,
|
|
2553
|
+
body: body.toIR(),
|
|
2554
|
+
} satisfies IRNode;
|
|
2555
|
+
},
|
|
2556
|
+
Array_ofComprehension(_open, body, _of, source, _close) {
|
|
2557
|
+
return {
|
|
2558
|
+
type: "arrayComprehension",
|
|
2559
|
+
binding: { type: "binding:bareOf", source: source.toIR() } satisfies IRBinding,
|
|
2448
2560
|
body: body.toIR(),
|
|
2449
2561
|
} satisfies IRNode;
|
|
2450
2562
|
},
|
|
@@ -2455,10 +2567,34 @@ semantics.addOperation("toIR", {
|
|
|
2455
2567
|
Object_empty(_open, _close) {
|
|
2456
2568
|
return { type: "object", entries: [] } satisfies IRNode;
|
|
2457
2569
|
},
|
|
2458
|
-
|
|
2570
|
+
Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
|
|
2459
2571
|
return {
|
|
2460
2572
|
type: "objectComprehension",
|
|
2461
|
-
binding: binding.toIR() as
|
|
2573
|
+
binding: binding.toIR() as IRBinding,
|
|
2574
|
+
key: key.toIR(),
|
|
2575
|
+
value: value.toIR(),
|
|
2576
|
+
} satisfies IRNode;
|
|
2577
|
+
},
|
|
2578
|
+
Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
|
|
2579
|
+
return {
|
|
2580
|
+
type: "whileObjectComprehension",
|
|
2581
|
+
condition: condition.toIR(),
|
|
2582
|
+
key: key.toIR(),
|
|
2583
|
+
value: value.toIR(),
|
|
2584
|
+
} satisfies IRNode;
|
|
2585
|
+
},
|
|
2586
|
+
Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
|
|
2587
|
+
return {
|
|
2588
|
+
type: "objectComprehension",
|
|
2589
|
+
binding: { type: "binding:bareIn", source: source.toIR() } satisfies IRBinding,
|
|
2590
|
+
key: key.toIR(),
|
|
2591
|
+
value: value.toIR(),
|
|
2592
|
+
} satisfies IRNode;
|
|
2593
|
+
},
|
|
2594
|
+
Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
|
|
2595
|
+
return {
|
|
2596
|
+
type: "objectComprehension",
|
|
2597
|
+
binding: { type: "binding:bareOf", source: source.toIR() } satisfies IRBinding,
|
|
2462
2598
|
key: key.toIR(),
|
|
2463
2599
|
value: value.toIR(),
|
|
2464
2600
|
} satisfies IRNode;
|
|
@@ -2492,6 +2628,40 @@ semantics.addOperation("toIR", {
|
|
|
2492
2628
|
source: source.toIR(),
|
|
2493
2629
|
} satisfies IRBinding;
|
|
2494
2630
|
},
|
|
2631
|
+
IterBinding_bareIn(_in, source) {
|
|
2632
|
+
return {
|
|
2633
|
+
type: "binding:bareIn",
|
|
2634
|
+
source: source.toIR(),
|
|
2635
|
+
} satisfies IRBinding;
|
|
2636
|
+
},
|
|
2637
|
+
IterBinding_bareOf(_of, source) {
|
|
2638
|
+
return {
|
|
2639
|
+
type: "binding:bareOf",
|
|
2640
|
+
source: source.toIR(),
|
|
2641
|
+
} satisfies IRBinding;
|
|
2642
|
+
},
|
|
2643
|
+
IterBindingComprehension_keyValueIn(key, _comma, value, _in, source) {
|
|
2644
|
+
return {
|
|
2645
|
+
type: "binding:keyValueIn",
|
|
2646
|
+
key: key.sourceString,
|
|
2647
|
+
value: value.sourceString,
|
|
2648
|
+
source: source.toIR(),
|
|
2649
|
+
} satisfies IRBinding;
|
|
2650
|
+
},
|
|
2651
|
+
IterBindingComprehension_valueIn(value, _in, source) {
|
|
2652
|
+
return {
|
|
2653
|
+
type: "binding:valueIn",
|
|
2654
|
+
value: value.sourceString,
|
|
2655
|
+
source: source.toIR(),
|
|
2656
|
+
} satisfies IRBinding;
|
|
2657
|
+
},
|
|
2658
|
+
IterBindingComprehension_keyOf(key, _of, source) {
|
|
2659
|
+
return {
|
|
2660
|
+
type: "binding:keyOf",
|
|
2661
|
+
key: key.sourceString,
|
|
2662
|
+
source: source.toIR(),
|
|
2663
|
+
} satisfies IRBinding;
|
|
2664
|
+
},
|
|
2495
2665
|
|
|
2496
2666
|
Pair(key, _colon, value) {
|
|
2497
2667
|
return { key: key.toIR(), value: value.toIR() };
|