@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/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 | { type: "binding:expr"; source: IRNode };
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: 0,
103
- add: 1,
104
- sub: 2,
105
- mul: 3,
106
- div: 4,
107
- eq: 5,
108
- neq: 6,
109
- lt: 7,
110
- lte: 8,
111
- gt: 9,
112
- gte: 10,
113
- and: 11,
114
- or: 12,
115
- xor: 13,
116
- not: 14,
117
- boolean: 15,
118
- number: 16,
119
- string: 17,
120
- array: 18,
121
- object: 19,
122
- mod: 20,
123
- neg: 21,
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, number>;
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 FIRST_NON_RESERVED_REF = 5;
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 (!Number.isFinite(numberValue)) throw new Error(`Cannot encode non-finite number: ${node.raw}`);
256
- if (Number.isInteger(numberValue)) return encodeInt(numberValue);
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 `${encodeZigzag(power)}*${encodeInt(significand)}`;
315
+ return encodeDecimal(significand, power);
280
316
  }
281
317
 
282
318
  function encodeOpcode(opcode: OpcodeName): string {
283
- return `${encodeUint(OPCODE_IDS[opcode])}%`;
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 "4'";
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
- if (domainRefs && node.target.type === "identifier") {
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 domainRef = domainRefs[dottedName];
344
- if (domainRef === undefined) continue;
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 `${encodeUint(domainRef)}'`;
392
+ return encodeDomainLookup(shortCode, tag);
349
393
  }
350
394
 
351
- const parts = [`${encodeUint(domainRef)}'`];
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:expr") {
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:expr") {
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 `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
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:expr") {
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 `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
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 `${encodeUint(domainRef)}'`;
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 ? "1'" : "2'";
504
+ return node.value ? "tr'" : "fl'";
437
505
  case "null":
438
- return "3'";
506
+ return "nl'";
439
507
  case "undefined":
440
- return "4'";
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 (!Number.isFinite(value)) throw new Error("Rex stringify() cannot encode non-finite numbers");
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]) => `${isBareKeyName(key) ? key : stringifyString(key)}: ${stringifyInline(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 = isBareKeyName(key) ? key : stringifyString(key);
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
- export function domainRefsFromConfig(config: unknown): Record<string, number> {
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 refs: Record<string, number> = {};
663
- for (const section of Object.values(config)) {
664
- if (!section || typeof section !== "object" || Array.isArray(section)) continue;
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
- function decodeDomainRefKey(refText: string): number {
671
- if (!refText) throw new Error("Domain ref key cannot be empty");
672
- if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
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
- let value = 0;
683
- for (const char of refText) {
684
- const digit = DOMAIN_DIGIT_INDEX.get(char);
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
- if (value < FIRST_NON_RESERVED_REF) {
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, number>) {
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 [refText, rawEntry] of Object.entries(entries)) {
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 existingNameRef = refs[rawName];
714
- if (existingNameRef !== undefined && existingNameRef !== refId) {
715
- throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
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] = refId;
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 === refId) continue;
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 ${refId}`);
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] = refId;
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
- return { type: "number", raw: String(value), value } satisfies IRNode;
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" && Number.isFinite(value)) return toNumberNode(value);
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.type === "binding:expr"
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.type === "binding:expr"
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
- if (binding.type === "binding:expr") {
2054
- return { type: "binding:expr", source: renameLocalNames(binding.source, map) } satisfies IRBindingOrExpr;
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 domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2251
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2127
2252
  return encodeIR(lowered, {
2128
- domainRefs,
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 IRBindingOrExpr,
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
- Array_comprehension(_open, binding, _semi, body, _close) {
2525
+ Array_forComprehension(_open, body, _for, binding, _close) {
2402
2526
  return {
2403
2527
  type: "arrayComprehension",
2404
- binding: binding.toIR() as IRBindingOrExpr,
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
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
2560
+ Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
2416
2561
  return {
2417
2562
  type: "objectComprehension",
2418
- binding: binding.toIR() as IRBindingOrExpr,
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;