@creationix/rex 0.3.1 → 0.4.1

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