@creationix/rex 0.3.0 → 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 +3 -2
- package/rex-cli.js +730 -285
- package/rex-cli.ts +34 -13
- package/rex-repl.js +695 -270
- package/rex-repl.ts +39 -5
- package/rex.js +370 -188
- package/rex.ohm +73 -25
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +31 -8
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +390 -206
- package/rexc-interpreter.ts +277 -90
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",
|
|
@@ -250,10 +264,32 @@ function encodeBareOrLengthString(value: string): string {
|
|
|
250
264
|
return `${encodeUint(byteLength(value))},${value}`;
|
|
251
265
|
}
|
|
252
266
|
|
|
267
|
+
const DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
|
|
268
|
+
|
|
269
|
+
function splitDecimal(num: number): { base: number; exp: number } {
|
|
270
|
+
const match = num.toExponential().match(DEC_PARTS);
|
|
271
|
+
if (!match) throw new Error(`Failed to split decimal for ${num}`);
|
|
272
|
+
const [, b1, b2 = "", e1] = match as RegExpMatchArray;
|
|
273
|
+
const base = Number.parseInt(b1 + b2, 10);
|
|
274
|
+
const exp = Number.parseInt(e1!, 10) - b2.length;
|
|
275
|
+
return { base, exp };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function encodeDecimal(significand: number, power: number): string {
|
|
279
|
+
return `${encodeZigzag(power)}*${encodeInt(significand)}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
253
282
|
function encodeNumberNode(node: Extract<IRNode, { type: "number" }>): string {
|
|
254
283
|
const numberValue = node.value;
|
|
255
|
-
if (
|
|
256
|
-
if (
|
|
284
|
+
if (Number.isNaN(numberValue)) return "nan'";
|
|
285
|
+
if (numberValue === Infinity) return "inf'";
|
|
286
|
+
if (numberValue === -Infinity) return "nif'";
|
|
287
|
+
|
|
288
|
+
if (Number.isInteger(numberValue)) {
|
|
289
|
+
const { base, exp } = splitDecimal(numberValue);
|
|
290
|
+
if (exp >= 0 && exp <= 4) return encodeInt(numberValue);
|
|
291
|
+
return encodeDecimal(base, exp);
|
|
292
|
+
}
|
|
257
293
|
|
|
258
294
|
const raw = node.raw.toLowerCase();
|
|
259
295
|
const sign = raw.startsWith("-") ? -1 : 1;
|
|
@@ -276,11 +312,11 @@ function encodeNumberNode(node: Extract<IRNode, { type: "number" }>): string {
|
|
|
276
312
|
significand /= 10;
|
|
277
313
|
power += 1;
|
|
278
314
|
}
|
|
279
|
-
return
|
|
315
|
+
return encodeDecimal(significand, power);
|
|
280
316
|
}
|
|
281
317
|
|
|
282
318
|
function encodeOpcode(opcode: OpcodeName): string {
|
|
283
|
-
return `${
|
|
319
|
+
return `${OPCODE_IDS[opcode]}%`;
|
|
284
320
|
}
|
|
285
321
|
|
|
286
322
|
function encodeCallParts(parts: string[]): string {
|
|
@@ -299,7 +335,7 @@ function addOptionalPrefix(encoded: string): string {
|
|
|
299
335
|
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
|
|
300
336
|
payload = encoded.slice(2, -1);
|
|
301
337
|
}
|
|
302
|
-
else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
338
|
+
else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
|
|
303
339
|
payload = encoded.slice(2, -1);
|
|
304
340
|
}
|
|
305
341
|
else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
|
|
@@ -312,7 +348,7 @@ function addOptionalPrefix(encoded: string): string {
|
|
|
312
348
|
}
|
|
313
349
|
|
|
314
350
|
function encodeBlockExpression(block: IRNode[]): string {
|
|
315
|
-
if (block.length === 0) return "
|
|
351
|
+
if (block.length === 0) return "un'";
|
|
316
352
|
if (block.length === 1) return encodeNode(block[0] as IRNode);
|
|
317
353
|
return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
|
|
318
354
|
}
|
|
@@ -329,9 +365,14 @@ function encodeConditionalElse(elseBranch: IRConditionalElse): string {
|
|
|
329
365
|
return encodeNode(nested);
|
|
330
366
|
}
|
|
331
367
|
|
|
368
|
+
function encodeDomainLookup(shortCode: string, tag: string): string {
|
|
369
|
+
return `${shortCode}${tag}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
332
372
|
function encodeNavigation(node: Extract<IRNode, { type: "navigation" }>): string {
|
|
333
373
|
const domainRefs = activeEncodeOptions?.domainRefs;
|
|
334
|
-
|
|
374
|
+
const domainOpcodes = activeEncodeOptions?.domainOpcodes;
|
|
375
|
+
if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
|
|
335
376
|
const staticPath = [node.target.name];
|
|
336
377
|
for (const segment of node.segments) {
|
|
337
378
|
if (segment.type !== "static") break;
|
|
@@ -340,15 +381,18 @@ function encodeNavigation(node: Extract<IRNode, { type: "navigation" }>): string
|
|
|
340
381
|
|
|
341
382
|
for (let pathLength = staticPath.length; pathLength >= 1; pathLength -= 1) {
|
|
342
383
|
const dottedName = staticPath.slice(0, pathLength).join(".");
|
|
343
|
-
const
|
|
344
|
-
|
|
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 ? "'" : "%";
|
|
345
389
|
|
|
346
390
|
const consumedStaticSegments = pathLength - 1;
|
|
347
391
|
if (consumedStaticSegments === node.segments.length) {
|
|
348
|
-
return
|
|
392
|
+
return encodeDomainLookup(shortCode, tag);
|
|
349
393
|
}
|
|
350
394
|
|
|
351
|
-
const parts = [
|
|
395
|
+
const parts = [encodeDomainLookup(shortCode, tag)];
|
|
352
396
|
for (const segment of node.segments.slice(consumedStaticSegments)) {
|
|
353
397
|
if (segment.type === "static") parts.push(encodeBareOrLengthString(segment.key));
|
|
354
398
|
else parts.push(encodeNode(segment.key));
|
|
@@ -373,9 +417,12 @@ function encodeWhile(node: Extract<IRNode, { type: "while" }>): string {
|
|
|
373
417
|
|
|
374
418
|
function encodeFor(node: Extract<IRNode, { type: "for" }>): string {
|
|
375
419
|
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
376
|
-
if (node.binding.type === "binding:
|
|
420
|
+
if (node.binding.type === "binding:bareIn") {
|
|
377
421
|
return `>(${encodeNode(node.binding.source)}${body})`;
|
|
378
422
|
}
|
|
423
|
+
if (node.binding.type === "binding:bareOf") {
|
|
424
|
+
return `<(${encodeNode(node.binding.source)}${body})`;
|
|
425
|
+
}
|
|
379
426
|
if (node.binding.type === "binding:valueIn") {
|
|
380
427
|
return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
|
|
381
428
|
}
|
|
@@ -387,31 +434,50 @@ function encodeFor(node: Extract<IRNode, { type: "for" }>): string {
|
|
|
387
434
|
|
|
388
435
|
function encodeArrayComprehension(node: Extract<IRNode, { type: "arrayComprehension" }>): string {
|
|
389
436
|
const body = addOptionalPrefix(encodeNode(node.body));
|
|
390
|
-
if (node.binding.type === "binding:
|
|
437
|
+
if (node.binding.type === "binding:bareIn") {
|
|
391
438
|
return `>[${encodeNode(node.binding.source)}${body}]`;
|
|
392
439
|
}
|
|
440
|
+
if (node.binding.type === "binding:bareOf") {
|
|
441
|
+
return `<[${encodeNode(node.binding.source)}${body}]`;
|
|
442
|
+
}
|
|
393
443
|
if (node.binding.type === "binding:valueIn") {
|
|
394
444
|
return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
|
|
395
445
|
}
|
|
396
446
|
if (node.binding.type === "binding:keyValueIn") {
|
|
397
447
|
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
|
|
398
448
|
}
|
|
399
|
-
return
|
|
449
|
+
return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
|
|
400
450
|
}
|
|
401
451
|
|
|
402
452
|
function encodeObjectComprehension(node: Extract<IRNode, { type: "objectComprehension" }>): string {
|
|
403
453
|
const key = addOptionalPrefix(encodeNode(node.key));
|
|
404
454
|
const value = addOptionalPrefix(encodeNode(node.value));
|
|
405
|
-
if (node.binding.type === "binding:
|
|
455
|
+
if (node.binding.type === "binding:bareIn") {
|
|
406
456
|
return `>{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
407
457
|
}
|
|
458
|
+
if (node.binding.type === "binding:bareOf") {
|
|
459
|
+
return `<{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
460
|
+
}
|
|
408
461
|
if (node.binding.type === "binding:valueIn") {
|
|
409
462
|
return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
|
|
410
463
|
}
|
|
411
464
|
if (node.binding.type === "binding:keyValueIn") {
|
|
412
465
|
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
|
|
413
466
|
}
|
|
414
|
-
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}}`;
|
|
415
481
|
}
|
|
416
482
|
|
|
417
483
|
let activeEncodeOptions: EncodeOptions | undefined;
|
|
@@ -422,7 +488,9 @@ function encodeNode(node: IRNode): string {
|
|
|
422
488
|
return encodeBlockExpression(node.body);
|
|
423
489
|
case "identifier": {
|
|
424
490
|
const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
|
|
425
|
-
if (domainRef !== undefined) return `${
|
|
491
|
+
if (domainRef !== undefined) return `${domainRef}'`;
|
|
492
|
+
const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
|
|
493
|
+
if (domainOpcode !== undefined) return `${domainOpcode}%`;
|
|
426
494
|
return `${node.name}$`;
|
|
427
495
|
}
|
|
428
496
|
case "self":
|
|
@@ -433,11 +501,11 @@ function encodeNode(node: IRNode): string {
|
|
|
433
501
|
return `${encodeUint(node.depth - 1)}@`;
|
|
434
502
|
}
|
|
435
503
|
case "boolean":
|
|
436
|
-
return node.value ? "
|
|
504
|
+
return node.value ? "tr'" : "fl'";
|
|
437
505
|
case "null":
|
|
438
|
-
return "
|
|
506
|
+
return "nl'";
|
|
439
507
|
case "undefined":
|
|
440
|
-
return "
|
|
508
|
+
return "un'";
|
|
441
509
|
case "number":
|
|
442
510
|
return encodeNumberNode(node);
|
|
443
511
|
case "string":
|
|
@@ -448,6 +516,8 @@ function encodeNode(node: IRNode): string {
|
|
|
448
516
|
}
|
|
449
517
|
case "arrayComprehension":
|
|
450
518
|
return encodeArrayComprehension(node);
|
|
519
|
+
case "whileArrayComprehension":
|
|
520
|
+
return encodeWhileArrayComprehension(node);
|
|
451
521
|
case "object": {
|
|
452
522
|
const body = node.entries
|
|
453
523
|
.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`)
|
|
@@ -456,6 +526,8 @@ function encodeNode(node: IRNode): string {
|
|
|
456
526
|
}
|
|
457
527
|
case "objectComprehension":
|
|
458
528
|
return encodeObjectComprehension(node);
|
|
529
|
+
case "whileObjectComprehension":
|
|
530
|
+
return encodeWhileObjectComprehension(node);
|
|
459
531
|
case "key":
|
|
460
532
|
return encodeBareOrLengthString(node.name);
|
|
461
533
|
case "group":
|
|
@@ -463,6 +535,10 @@ function encodeNode(node: IRNode): string {
|
|
|
463
535
|
case "unary":
|
|
464
536
|
if (node.op === "delete") return `~${encodeNode(node.value)}`;
|
|
465
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
|
+
}
|
|
466
542
|
return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
|
|
467
543
|
case "binary":
|
|
468
544
|
if (node.op === "and") {
|
|
@@ -485,12 +561,18 @@ function encodeNode(node: IRNode): string {
|
|
|
485
561
|
.join("");
|
|
486
562
|
return `|(${body})`;
|
|
487
563
|
}
|
|
564
|
+
if (node.op === "nor") {
|
|
565
|
+
const left = encodeNode(node.left);
|
|
566
|
+
const right = addOptionalPrefix(encodeNode(node.right));
|
|
567
|
+
return `!(${left}${right})`;
|
|
568
|
+
}
|
|
488
569
|
return encodeCallParts([
|
|
489
570
|
encodeOpcode(BINARY_TO_OPCODE[node.op]),
|
|
490
571
|
encodeNode(node.left),
|
|
491
572
|
encodeNode(node.right),
|
|
492
573
|
]);
|
|
493
574
|
case "assign": {
|
|
575
|
+
if (node.op === ":=") return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
494
576
|
if (node.op === "=") return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
495
577
|
const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
|
|
496
578
|
if (!opcode) throw new Error(`Unsupported assignment op: ${node.op}`);
|
|
@@ -499,8 +581,14 @@ function encodeNode(node: IRNode): string {
|
|
|
499
581
|
}
|
|
500
582
|
case "navigation":
|
|
501
583
|
return encodeNavigation(node);
|
|
502
|
-
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
|
+
}
|
|
503
590
|
return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
|
|
591
|
+
}
|
|
504
592
|
case "conditional": {
|
|
505
593
|
const opener = node.head === "when" ? "?(" : "!(";
|
|
506
594
|
const cond = encodeNode(node.condition);
|
|
@@ -508,6 +596,8 @@ function encodeNode(node: IRNode): string {
|
|
|
508
596
|
const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
|
|
509
597
|
return `${opener}${cond}${thenExpr}${elseExpr})`;
|
|
510
598
|
}
|
|
599
|
+
case "range":
|
|
600
|
+
return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
|
|
511
601
|
case "for":
|
|
512
602
|
return encodeFor(node);
|
|
513
603
|
case "while":
|
|
@@ -587,6 +677,17 @@ function isBareKeyName(key: string): boolean {
|
|
|
587
677
|
return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
|
|
588
678
|
}
|
|
589
679
|
|
|
680
|
+
function isNumericKey(key: string): boolean {
|
|
681
|
+
if (key === "") return false;
|
|
682
|
+
return String(Number(key)) === key && Number.isFinite(Number(key));
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function stringifyKey(key: string): string {
|
|
686
|
+
if (isBareKeyName(key)) return key;
|
|
687
|
+
if (isNumericKey(key)) return key;
|
|
688
|
+
return stringifyString(key);
|
|
689
|
+
}
|
|
690
|
+
|
|
590
691
|
function stringifyString(value: string): string {
|
|
591
692
|
return JSON.stringify(value);
|
|
592
693
|
}
|
|
@@ -596,7 +697,9 @@ function stringifyInline(value: unknown): string {
|
|
|
596
697
|
if (value === null) return "null";
|
|
597
698
|
if (typeof value === "boolean") return value ? "true" : "false";
|
|
598
699
|
if (typeof value === "number") {
|
|
599
|
-
if (
|
|
700
|
+
if (Number.isNaN(value)) return "nan";
|
|
701
|
+
if (value === Infinity) return "inf";
|
|
702
|
+
if (value === -Infinity) return "-inf";
|
|
600
703
|
return String(value);
|
|
601
704
|
}
|
|
602
705
|
if (typeof value === "string") return stringifyString(value);
|
|
@@ -608,7 +711,7 @@ function stringifyInline(value: unknown): string {
|
|
|
608
711
|
const entries = Object.entries(value);
|
|
609
712
|
if (entries.length === 0) return "{}";
|
|
610
713
|
const body = entries
|
|
611
|
-
.map(([key, item]) => `${
|
|
714
|
+
.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`)
|
|
612
715
|
.join(" ");
|
|
613
716
|
return `{${body}}`;
|
|
614
717
|
}
|
|
@@ -641,7 +744,7 @@ function stringifyPretty(value: unknown, depth: number, indentSize: number, maxW
|
|
|
641
744
|
const entries = Object.entries(value);
|
|
642
745
|
if (entries.length === 0) return "{}";
|
|
643
746
|
const lines = entries.map(([key, item]) => {
|
|
644
|
-
const keyText =
|
|
747
|
+
const keyText = stringifyKey(key);
|
|
645
748
|
const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
|
|
646
749
|
return `${childIndent}${keyText}: ${rendered}`;
|
|
647
750
|
});
|
|
@@ -655,81 +758,62 @@ export function parse(source: string): unknown {
|
|
|
655
758
|
return parseDataNode(parseToIR(source));
|
|
656
759
|
}
|
|
657
760
|
|
|
658
|
-
|
|
761
|
+
type DomainMaps = { domainRefs: Record<string, string>; domainOpcodes: Record<string, string> };
|
|
762
|
+
|
|
763
|
+
export function domainRefsFromConfig(config: unknown): DomainMaps {
|
|
659
764
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
660
765
|
throw new Error("Domain config must be an object");
|
|
661
766
|
}
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
mapConfigEntries(section as Record<string, unknown>, refs);
|
|
666
|
-
}
|
|
667
|
-
return refs;
|
|
668
|
-
}
|
|
767
|
+
const configObj = config as Record<string, unknown>;
|
|
768
|
+
const domainRefs: Record<string, string> = {};
|
|
769
|
+
const domainOpcodes: Record<string, string> = {};
|
|
669
770
|
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
|
|
673
|
-
throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
|
|
674
|
-
}
|
|
675
|
-
if (refText.length > 1 && refText[0] === "0") {
|
|
676
|
-
throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
|
|
677
|
-
}
|
|
678
|
-
if (/^[1-9]$/.test(refText)) {
|
|
679
|
-
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);
|
|
680
774
|
}
|
|
681
775
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (digit === undefined) throw new Error(`Invalid domain ref key '${refText}'`);
|
|
686
|
-
value = value * 64 + digit;
|
|
687
|
-
if (value > Number.MAX_SAFE_INTEGER) {
|
|
688
|
-
throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
|
|
689
|
-
}
|
|
776
|
+
const functionsSection = configObj.functions;
|
|
777
|
+
if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
|
|
778
|
+
mapConfigEntries(functionsSection as Record<string, unknown>, domainOpcodes);
|
|
690
779
|
}
|
|
691
780
|
|
|
692
|
-
|
|
693
|
-
throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return value;
|
|
781
|
+
return { domainRefs, domainOpcodes };
|
|
697
782
|
}
|
|
698
783
|
|
|
699
|
-
function mapConfigEntries(entries: Record<string, unknown>, refs: Record<string,
|
|
784
|
+
function mapConfigEntries(entries: Record<string, unknown>, refs: Record<string, string>) {
|
|
700
785
|
const sourceKindByRoot = new Map<string, "explicit" | "implicit">();
|
|
701
786
|
for (const root of Object.keys(refs)) {
|
|
702
787
|
sourceKindByRoot.set(root, "explicit");
|
|
703
788
|
}
|
|
704
789
|
|
|
705
|
-
for (const [
|
|
790
|
+
for (const [shortCode, rawEntry] of Object.entries(entries)) {
|
|
706
791
|
const entry = rawEntry as RexDomainConfigEntry;
|
|
707
792
|
if (!entry || typeof entry !== "object") continue;
|
|
708
793
|
if (!Array.isArray(entry.names)) continue;
|
|
709
794
|
|
|
710
|
-
const refId = decodeDomainRefKey(refText);
|
|
711
795
|
for (const rawName of entry.names) {
|
|
712
796
|
if (typeof rawName !== "string") continue;
|
|
713
|
-
const
|
|
714
|
-
if (
|
|
715
|
-
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}`);
|
|
716
800
|
}
|
|
717
|
-
refs[rawName] =
|
|
801
|
+
refs[rawName] = shortCode;
|
|
718
802
|
|
|
719
803
|
const root = rawName.split(".")[0];
|
|
720
804
|
if (!root) continue;
|
|
721
805
|
const currentKind: "explicit" | "implicit" = rawName.includes(".") ? "implicit" : "explicit";
|
|
722
806
|
const existing = refs[root];
|
|
723
807
|
if (existing !== undefined) {
|
|
724
|
-
if (existing ===
|
|
808
|
+
if (existing === shortCode) continue;
|
|
725
809
|
const existingKind = sourceKindByRoot.get(root) ?? "explicit";
|
|
726
810
|
if (currentKind === "explicit") {
|
|
727
|
-
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${
|
|
811
|
+
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
|
|
728
812
|
}
|
|
729
813
|
if (existingKind === "explicit") continue;
|
|
730
814
|
continue;
|
|
731
815
|
}
|
|
732
|
-
refs[root] =
|
|
816
|
+
refs[root] = shortCode;
|
|
733
817
|
sourceKindByRoot.set(root, currentKind);
|
|
734
818
|
}
|
|
735
819
|
}
|
|
@@ -1018,6 +1102,22 @@ function dropBindingNames(env: OptimizeEnv, binding: IRBindingOrExpr) {
|
|
|
1018
1102
|
}
|
|
1019
1103
|
}
|
|
1020
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
|
+
|
|
1021
1121
|
function collectReads(node: IRNode, out: Set<string>) {
|
|
1022
1122
|
switch (node.type) {
|
|
1023
1123
|
case "identifier":
|
|
@@ -1039,11 +1139,20 @@ function collectReads(node: IRNode, out: Set<string>) {
|
|
|
1039
1139
|
collectReads(node.binding.source, out);
|
|
1040
1140
|
collectReads(node.body, out);
|
|
1041
1141
|
return;
|
|
1142
|
+
case "whileArrayComprehension":
|
|
1143
|
+
collectReads(node.condition, out);
|
|
1144
|
+
collectReads(node.body, out);
|
|
1145
|
+
return;
|
|
1042
1146
|
case "objectComprehension":
|
|
1043
1147
|
collectReads(node.binding.source, out);
|
|
1044
1148
|
collectReads(node.key, out);
|
|
1045
1149
|
collectReads(node.value, out);
|
|
1046
1150
|
return;
|
|
1151
|
+
case "whileObjectComprehension":
|
|
1152
|
+
collectReads(node.condition, out);
|
|
1153
|
+
collectReads(node.key, out);
|
|
1154
|
+
collectReads(node.value, out);
|
|
1155
|
+
return;
|
|
1047
1156
|
case "unary":
|
|
1048
1157
|
collectReads(node.value, out);
|
|
1049
1158
|
return;
|
|
@@ -1074,6 +1183,10 @@ function collectReads(node: IRNode, out: Set<string>) {
|
|
|
1074
1183
|
collectReads(node.binding.source, out);
|
|
1075
1184
|
for (const part of node.body) collectReads(part, out);
|
|
1076
1185
|
return;
|
|
1186
|
+
case "range":
|
|
1187
|
+
collectReads(node.from, out);
|
|
1188
|
+
collectReads(node.to, out);
|
|
1189
|
+
return;
|
|
1077
1190
|
case "program":
|
|
1078
1191
|
for (const part of node.body) collectReads(part, out);
|
|
1079
1192
|
return;
|
|
@@ -1116,6 +1229,8 @@ function isPureNode(node: IRNode): boolean {
|
|
|
1116
1229
|
return node.op !== "delete" && isPureNode(node.value);
|
|
1117
1230
|
case "binary":
|
|
1118
1231
|
return isPureNode(node.left) && isPureNode(node.right);
|
|
1232
|
+
case "range":
|
|
1233
|
+
return isPureNode(node.from) && isPureNode(node.to);
|
|
1119
1234
|
default:
|
|
1120
1235
|
return false;
|
|
1121
1236
|
}
|
|
@@ -1188,6 +1303,8 @@ function hasIdentifierRead(node: IRNode, name: string, asPlace = false): boolean
|
|
|
1188
1303
|
return hasIdentifierRead(node.value, name, node.op === "delete");
|
|
1189
1304
|
case "binary":
|
|
1190
1305
|
return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
|
|
1306
|
+
case "range":
|
|
1307
|
+
return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
|
|
1191
1308
|
case "assign":
|
|
1192
1309
|
return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
|
|
1193
1310
|
default:
|
|
@@ -1210,6 +1327,8 @@ function countIdentifierReads(node: IRNode, name: string, asPlace = false): numb
|
|
|
1210
1327
|
return countIdentifierReads(node.value, name, node.op === "delete");
|
|
1211
1328
|
case "binary":
|
|
1212
1329
|
return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
|
|
1330
|
+
case "range":
|
|
1331
|
+
return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
|
|
1213
1332
|
case "assign":
|
|
1214
1333
|
return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
|
|
1215
1334
|
default:
|
|
@@ -1267,6 +1386,12 @@ function replaceIdentifier(node: IRNode, name: string, replacement: IRNode, asPl
|
|
|
1267
1386
|
place: replaceIdentifier(node.place, name, replacement, true),
|
|
1268
1387
|
value: replaceIdentifier(node.value, name, replacement),
|
|
1269
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;
|
|
1270
1395
|
default:
|
|
1271
1396
|
return node;
|
|
1272
1397
|
}
|
|
@@ -1308,7 +1433,12 @@ function inlineAdjacentPureAssignments(block: IRNode[]): IRNode[] {
|
|
|
1308
1433
|
}
|
|
1309
1434
|
|
|
1310
1435
|
function toNumberNode(value: number): IRNode {
|
|
1311
|
-
|
|
1436
|
+
let raw: string;
|
|
1437
|
+
if (Number.isNaN(value)) raw = "nan";
|
|
1438
|
+
else if (value === Infinity) raw = "inf";
|
|
1439
|
+
else if (value === -Infinity) raw = "-inf";
|
|
1440
|
+
else raw = String(value);
|
|
1441
|
+
return { type: "number", raw, value } satisfies IRNode;
|
|
1312
1442
|
}
|
|
1313
1443
|
|
|
1314
1444
|
function toStringNode(value: string): IRNode {
|
|
@@ -1319,7 +1449,7 @@ function toLiteralNode(value: unknown): IRNode | undefined {
|
|
|
1319
1449
|
if (value === undefined) return { type: "undefined" } satisfies IRNode;
|
|
1320
1450
|
if (value === null) return { type: "null" } satisfies IRNode;
|
|
1321
1451
|
if (typeof value === "boolean") return { type: "boolean", value } satisfies IRNode;
|
|
1322
|
-
if (typeof value === "number"
|
|
1452
|
+
if (typeof value === "number") return toNumberNode(value);
|
|
1323
1453
|
if (typeof value === "string") return toStringNode(value);
|
|
1324
1454
|
if (Array.isArray(value)) {
|
|
1325
1455
|
const items: IRNode[] = [];
|
|
@@ -1395,6 +1525,9 @@ function foldUnary(op: Extract<IRNode, { type: "unary" }> ["op"], value: unknown
|
|
|
1395
1525
|
if (typeof value === "number") return ~value;
|
|
1396
1526
|
return undefined;
|
|
1397
1527
|
}
|
|
1528
|
+
if (op === "logicalNot") {
|
|
1529
|
+
return value === undefined ? true : undefined;
|
|
1530
|
+
}
|
|
1398
1531
|
return undefined;
|
|
1399
1532
|
}
|
|
1400
1533
|
|
|
@@ -1557,6 +1690,12 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1557
1690
|
}
|
|
1558
1691
|
return { type: "binary", op: node.op, left, right } satisfies IRNode;
|
|
1559
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;
|
|
1560
1699
|
case "navigation": {
|
|
1561
1700
|
const target = optimizeNode(node.target, env, currentDepth);
|
|
1562
1701
|
const segments = node.segments.map((segment) => (segment.type === "static"
|
|
@@ -1666,31 +1805,7 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1666
1805
|
}
|
|
1667
1806
|
case "for": {
|
|
1668
1807
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1669
|
-
const binding = (
|
|
1670
|
-
if (node.binding.type === "binding:expr") {
|
|
1671
|
-
return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } satisfies IRBindingOrExpr;
|
|
1672
|
-
}
|
|
1673
|
-
if (node.binding.type === "binding:valueIn") {
|
|
1674
|
-
return {
|
|
1675
|
-
type: "binding:valueIn",
|
|
1676
|
-
value: node.binding.value,
|
|
1677
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1678
|
-
} satisfies IRBinding;
|
|
1679
|
-
}
|
|
1680
|
-
if (node.binding.type === "binding:keyValueIn") {
|
|
1681
|
-
return {
|
|
1682
|
-
type: "binding:keyValueIn",
|
|
1683
|
-
key: node.binding.key,
|
|
1684
|
-
value: node.binding.value,
|
|
1685
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1686
|
-
} satisfies IRBinding;
|
|
1687
|
-
}
|
|
1688
|
-
return {
|
|
1689
|
-
type: "binding:keyOf",
|
|
1690
|
-
key: node.binding.key,
|
|
1691
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1692
|
-
} satisfies IRBinding;
|
|
1693
|
-
})();
|
|
1808
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1694
1809
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1695
1810
|
dropBindingNames(bodyEnv, binding);
|
|
1696
1811
|
return {
|
|
@@ -1701,26 +1816,7 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1701
1816
|
}
|
|
1702
1817
|
case "arrayComprehension": {
|
|
1703
1818
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1704
|
-
const binding = node.binding
|
|
1705
|
-
? ({ type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } satisfies IRBindingOrExpr)
|
|
1706
|
-
: node.binding.type === "binding:valueIn"
|
|
1707
|
-
? ({
|
|
1708
|
-
type: "binding:valueIn",
|
|
1709
|
-
value: node.binding.value,
|
|
1710
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1711
|
-
} satisfies IRBinding)
|
|
1712
|
-
: node.binding.type === "binding:keyValueIn"
|
|
1713
|
-
? ({
|
|
1714
|
-
type: "binding:keyValueIn",
|
|
1715
|
-
key: node.binding.key,
|
|
1716
|
-
value: node.binding.value,
|
|
1717
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1718
|
-
} satisfies IRBinding)
|
|
1719
|
-
: ({
|
|
1720
|
-
type: "binding:keyOf",
|
|
1721
|
-
key: node.binding.key,
|
|
1722
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1723
|
-
} satisfies IRBinding);
|
|
1819
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1724
1820
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1725
1821
|
dropBindingNames(bodyEnv, binding);
|
|
1726
1822
|
return {
|
|
@@ -1729,28 +1825,15 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1729
1825
|
body: optimizeNode(node.body, bodyEnv, currentDepth + 1),
|
|
1730
1826
|
} satisfies IRNode;
|
|
1731
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;
|
|
1732
1834
|
case "objectComprehension": {
|
|
1733
1835
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1734
|
-
const binding = node.binding
|
|
1735
|
-
? ({ type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } satisfies IRBindingOrExpr)
|
|
1736
|
-
: node.binding.type === "binding:valueIn"
|
|
1737
|
-
? ({
|
|
1738
|
-
type: "binding:valueIn",
|
|
1739
|
-
value: node.binding.value,
|
|
1740
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1741
|
-
} satisfies IRBinding)
|
|
1742
|
-
: node.binding.type === "binding:keyValueIn"
|
|
1743
|
-
? ({
|
|
1744
|
-
type: "binding:keyValueIn",
|
|
1745
|
-
key: node.binding.key,
|
|
1746
|
-
value: node.binding.value,
|
|
1747
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1748
|
-
} satisfies IRBinding)
|
|
1749
|
-
: ({
|
|
1750
|
-
type: "binding:keyOf",
|
|
1751
|
-
key: node.binding.key,
|
|
1752
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth),
|
|
1753
|
-
} satisfies IRBinding);
|
|
1836
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1754
1837
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1755
1838
|
dropBindingNames(bodyEnv, binding);
|
|
1756
1839
|
return {
|
|
@@ -1760,6 +1843,13 @@ function optimizeNode(node: IRNode, env: OptimizeEnv, currentDepth: number, asPl
|
|
|
1760
1843
|
value: optimizeNode(node.value, bodyEnv, currentDepth + 1),
|
|
1761
1844
|
} satisfies IRNode;
|
|
1762
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;
|
|
1763
1853
|
default:
|
|
1764
1854
|
return node;
|
|
1765
1855
|
}
|
|
@@ -1808,6 +1898,10 @@ function collectLocalBindings(node: IRNode, locals: Set<string>) {
|
|
|
1808
1898
|
collectLocalBindings(node.left, locals);
|
|
1809
1899
|
collectLocalBindings(node.right, locals);
|
|
1810
1900
|
return;
|
|
1901
|
+
case "range":
|
|
1902
|
+
collectLocalBindings(node.from, locals);
|
|
1903
|
+
collectLocalBindings(node.to, locals);
|
|
1904
|
+
return;
|
|
1811
1905
|
case "conditional":
|
|
1812
1906
|
collectLocalBindings(node.condition, locals);
|
|
1813
1907
|
for (const part of node.thenBlock) collectLocalBindings(part, locals);
|
|
@@ -1821,11 +1915,20 @@ function collectLocalBindings(node: IRNode, locals: Set<string>) {
|
|
|
1821
1915
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1822
1916
|
collectLocalBindings(node.body, locals);
|
|
1823
1917
|
return;
|
|
1918
|
+
case "whileArrayComprehension":
|
|
1919
|
+
collectLocalBindings(node.condition, locals);
|
|
1920
|
+
collectLocalBindings(node.body, locals);
|
|
1921
|
+
return;
|
|
1824
1922
|
case "objectComprehension":
|
|
1825
1923
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1826
1924
|
collectLocalBindings(node.key, locals);
|
|
1827
1925
|
collectLocalBindings(node.value, locals);
|
|
1828
1926
|
return;
|
|
1927
|
+
case "whileObjectComprehension":
|
|
1928
|
+
collectLocalBindings(node.condition, locals);
|
|
1929
|
+
collectLocalBindings(node.key, locals);
|
|
1930
|
+
collectLocalBindings(node.value, locals);
|
|
1931
|
+
return;
|
|
1829
1932
|
default:
|
|
1830
1933
|
return;
|
|
1831
1934
|
}
|
|
@@ -1912,6 +2015,10 @@ function collectNameFrequencies(node: IRNode, locals: Set<string>, frequencies:
|
|
|
1912
2015
|
collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
|
|
1913
2016
|
collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
|
|
1914
2017
|
return;
|
|
2018
|
+
case "range":
|
|
2019
|
+
collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
|
|
2020
|
+
collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
|
|
2021
|
+
return;
|
|
1915
2022
|
case "conditional":
|
|
1916
2023
|
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1917
2024
|
for (const part of node.thenBlock) collectNameFrequencies(part, locals, frequencies, order, nextOrder);
|
|
@@ -1925,11 +2032,20 @@ function collectNameFrequencies(node: IRNode, locals: Set<string>, frequencies:
|
|
|
1925
2032
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1926
2033
|
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1927
2034
|
return;
|
|
2035
|
+
case "whileArrayComprehension":
|
|
2036
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
2037
|
+
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
2038
|
+
return;
|
|
1928
2039
|
case "objectComprehension":
|
|
1929
2040
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1930
2041
|
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1931
2042
|
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1932
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;
|
|
1933
2049
|
default:
|
|
1934
2050
|
return;
|
|
1935
2051
|
}
|
|
@@ -2006,6 +2122,12 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2006
2122
|
left: renameLocalNames(node.left, map),
|
|
2007
2123
|
right: renameLocalNames(node.right, map),
|
|
2008
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;
|
|
2009
2131
|
case "assign": {
|
|
2010
2132
|
const place = node.place.type === "identifier" && map.has(node.place.name)
|
|
2011
2133
|
? ({ type: "identifier", name: map.get(node.place.name) as string } satisfies IRNode)
|
|
@@ -2037,6 +2159,12 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2037
2159
|
binding: renameLocalNamesBinding(node.binding, map),
|
|
2038
2160
|
body: renameLocalNames(node.body, map),
|
|
2039
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;
|
|
2040
2168
|
case "objectComprehension":
|
|
2041
2169
|
return {
|
|
2042
2170
|
type: "objectComprehension",
|
|
@@ -2044,35 +2172,32 @@ function renameLocalNames(node: IRNode, map: Map<string, string>): IRNode {
|
|
|
2044
2172
|
key: renameLocalNames(node.key, map),
|
|
2045
2173
|
value: renameLocalNames(node.value, map),
|
|
2046
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;
|
|
2047
2182
|
default:
|
|
2048
2183
|
return node;
|
|
2049
2184
|
}
|
|
2050
2185
|
}
|
|
2051
2186
|
|
|
2052
2187
|
function renameLocalNamesBinding(binding: IRBindingOrExpr, map: Map<string, string>): IRBindingOrExpr {
|
|
2053
|
-
|
|
2054
|
-
|
|
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 };
|
|
2055
2200
|
}
|
|
2056
|
-
if (binding.type === "binding:valueIn") {
|
|
2057
|
-
return {
|
|
2058
|
-
type: "binding:valueIn",
|
|
2059
|
-
value: map.get(binding.value) ?? binding.value,
|
|
2060
|
-
source: renameLocalNames(binding.source, map),
|
|
2061
|
-
} satisfies IRBinding;
|
|
2062
|
-
}
|
|
2063
|
-
if (binding.type === "binding:keyValueIn") {
|
|
2064
|
-
return {
|
|
2065
|
-
type: "binding:keyValueIn",
|
|
2066
|
-
key: map.get(binding.key) ?? binding.key,
|
|
2067
|
-
value: map.get(binding.value) ?? binding.value,
|
|
2068
|
-
source: renameLocalNames(binding.source, map),
|
|
2069
|
-
} satisfies IRBinding;
|
|
2070
|
-
}
|
|
2071
|
-
return {
|
|
2072
|
-
type: "binding:keyOf",
|
|
2073
|
-
key: map.get(binding.key) ?? binding.key,
|
|
2074
|
-
source: renameLocalNames(binding.source, map),
|
|
2075
|
-
} satisfies IRBinding;
|
|
2076
2201
|
}
|
|
2077
2202
|
|
|
2078
2203
|
function renameLocalNamesElse(elseBranch: IRConditionalElse, map: Map<string, string>): IRConditionalElse {
|
|
@@ -2123,9 +2248,9 @@ export function compile(source: string, options?: CompileOptions): string {
|
|
|
2123
2248
|
const ir = parseToIR(source);
|
|
2124
2249
|
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
2125
2250
|
if (options?.minifyNames) lowered = minifyLocalNamesIR(lowered);
|
|
2126
|
-
const
|
|
2251
|
+
const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
|
|
2127
2252
|
return encodeIR(lowered, {
|
|
2128
|
-
|
|
2253
|
+
...domainMaps,
|
|
2129
2254
|
dedupeValues: options?.dedupeValues,
|
|
2130
2255
|
dedupeMinBytes: options?.dedupeMinBytes,
|
|
2131
2256
|
});
|
|
@@ -2137,6 +2262,9 @@ type IRPostfixStep =
|
|
|
2137
2262
|
| { kind: "call"; args: IRNode[] };
|
|
2138
2263
|
|
|
2139
2264
|
function parseNumber(raw: string) {
|
|
2265
|
+
if (raw === "nan") return NaN;
|
|
2266
|
+
if (raw === "inf") return Infinity;
|
|
2267
|
+
if (raw === "-inf") return -Infinity;
|
|
2140
2268
|
if (/^-?0x/i.test(raw)) return parseInt(raw, 16);
|
|
2141
2269
|
if (/^-?0b/i.test(raw)) {
|
|
2142
2270
|
const isNegative = raw.startsWith("-");
|
|
@@ -2256,6 +2384,9 @@ semantics.addOperation("toIR", {
|
|
|
2256
2384
|
ExistenceExpr_or(left, _or, right) {
|
|
2257
2385
|
return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2258
2386
|
},
|
|
2387
|
+
ExistenceExpr_nor(left, _nor, right) {
|
|
2388
|
+
return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2389
|
+
},
|
|
2259
2390
|
|
|
2260
2391
|
BitExpr_and(left, _op, right) {
|
|
2261
2392
|
return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
@@ -2267,6 +2398,10 @@ semantics.addOperation("toIR", {
|
|
|
2267
2398
|
return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() } satisfies IRNode;
|
|
2268
2399
|
},
|
|
2269
2400
|
|
|
2401
|
+
RangeExpr_range(left, _op, right) {
|
|
2402
|
+
return { type: "range", from: left.toIR(), to: right.toIR() } satisfies IRNode;
|
|
2403
|
+
},
|
|
2404
|
+
|
|
2270
2405
|
CompareExpr_binary(left, op, right) {
|
|
2271
2406
|
const map: Record<string, Extract<IRNode, { type: "binary" }>["op"]> = {
|
|
2272
2407
|
"==": "eq",
|
|
@@ -2309,6 +2444,9 @@ semantics.addOperation("toIR", {
|
|
|
2309
2444
|
UnaryExpr_not(_op, value) {
|
|
2310
2445
|
return { type: "unary", op: "not", value: value.toIR() } satisfies IRNode;
|
|
2311
2446
|
},
|
|
2447
|
+
UnaryExpr_logicalNot(_not, value) {
|
|
2448
|
+
return { type: "unary", op: "logicalNot", value: value.toIR() } satisfies IRNode;
|
|
2449
|
+
},
|
|
2312
2450
|
UnaryExpr_delete(_del, place) {
|
|
2313
2451
|
return { type: "unary", op: "delete", value: place.toIR() } satisfies IRNode;
|
|
2314
2452
|
},
|
|
@@ -2365,13 +2503,6 @@ semantics.addOperation("toIR", {
|
|
|
2365
2503
|
return { type: "else", block: block.toIR() as IRNode[] } satisfies IRConditionalElse;
|
|
2366
2504
|
},
|
|
2367
2505
|
|
|
2368
|
-
DoExpr(_do, block, _end) {
|
|
2369
|
-
const body = block.toIR() as IRNode[];
|
|
2370
|
-
if (body.length === 0) return { type: "undefined" } satisfies IRNode;
|
|
2371
|
-
if (body.length === 1) return body[0] as IRNode;
|
|
2372
|
-
return { type: "program", body } satisfies IRNode;
|
|
2373
|
-
},
|
|
2374
|
-
|
|
2375
2506
|
WhileExpr(_while, condition, _do, block, _end) {
|
|
2376
2507
|
return {
|
|
2377
2508
|
type: "while",
|
|
@@ -2383,25 +2514,39 @@ semantics.addOperation("toIR", {
|
|
|
2383
2514
|
ForExpr(_for, binding, _do, block, _end) {
|
|
2384
2515
|
return {
|
|
2385
2516
|
type: "for",
|
|
2386
|
-
binding: binding.toIR() as
|
|
2517
|
+
binding: binding.toIR() as IRBinding,
|
|
2387
2518
|
body: block.toIR() as IRNode[],
|
|
2388
2519
|
} satisfies IRNode;
|
|
2389
2520
|
},
|
|
2390
|
-
BindingExpr(iterOrExpr) {
|
|
2391
|
-
const node = iterOrExpr.toIR();
|
|
2392
|
-
if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
|
|
2393
|
-
return node as IRBinding;
|
|
2394
|
-
}
|
|
2395
|
-
return { type: "binding:expr", source: node as IRNode } satisfies IRBindingOrExpr;
|
|
2396
|
-
},
|
|
2397
2521
|
|
|
2398
2522
|
Array_empty(_open, _close) {
|
|
2399
2523
|
return { type: "array", items: [] } satisfies IRNode;
|
|
2400
2524
|
},
|
|
2401
|
-
|
|
2525
|
+
Array_forComprehension(_open, body, _for, binding, _close) {
|
|
2402
2526
|
return {
|
|
2403
2527
|
type: "arrayComprehension",
|
|
2404
|
-
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,
|
|
2405
2550
|
body: body.toIR(),
|
|
2406
2551
|
} satisfies IRNode;
|
|
2407
2552
|
},
|
|
@@ -2412,10 +2557,34 @@ semantics.addOperation("toIR", {
|
|
|
2412
2557
|
Object_empty(_open, _close) {
|
|
2413
2558
|
return { type: "object", entries: [] } satisfies IRNode;
|
|
2414
2559
|
},
|
|
2415
|
-
|
|
2560
|
+
Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
|
|
2416
2561
|
return {
|
|
2417
2562
|
type: "objectComprehension",
|
|
2418
|
-
binding: binding.toIR() as
|
|
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) {
|
|
2585
|
+
return {
|
|
2586
|
+
type: "objectComprehension",
|
|
2587
|
+
binding: { type: "binding:bareOf", source: source.toIR() } satisfies IRBinding,
|
|
2419
2588
|
key: key.toIR(),
|
|
2420
2589
|
value: value.toIR(),
|
|
2421
2590
|
} satisfies IRNode;
|
|
@@ -2449,6 +2618,18 @@ semantics.addOperation("toIR", {
|
|
|
2449
2618
|
source: source.toIR(),
|
|
2450
2619
|
} satisfies IRBinding;
|
|
2451
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
|
+
},
|
|
2452
2633
|
|
|
2453
2634
|
Pair(key, _colon, value) {
|
|
2454
2635
|
return { key: key.toIR(), value: value.toIR() };
|
|
@@ -2514,6 +2695,9 @@ semantics.addOperation("toIR", {
|
|
|
2514
2695
|
BooleanKw(_kw) {
|
|
2515
2696
|
return { type: "identifier", name: "boolean" } satisfies IRNode;
|
|
2516
2697
|
},
|
|
2698
|
+
SizeKw(_kw) {
|
|
2699
|
+
return { type: "identifier", name: "size" } satisfies IRNode;
|
|
2700
|
+
},
|
|
2517
2701
|
|
|
2518
2702
|
identifier(_a, _b) {
|
|
2519
2703
|
return { type: "identifier", name: this.sourceString } satisfies IRNode;
|