@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.js CHANGED
@@ -10,31 +10,32 @@ function byteLength(value) {
10
10
  return Buffer.byteLength(value, "utf8");
11
11
  }
12
12
  var OPCODE_IDS = {
13
- do: 0,
14
- add: 1,
15
- sub: 2,
16
- mul: 3,
17
- div: 4,
18
- eq: 5,
19
- neq: 6,
20
- lt: 7,
21
- lte: 8,
22
- gt: 9,
23
- gte: 10,
24
- and: 11,
25
- or: 12,
26
- xor: 13,
27
- not: 14,
28
- boolean: 15,
29
- number: 16,
30
- string: 17,
31
- array: 18,
32
- object: 19,
33
- mod: 20,
34
- neg: 21
13
+ do: "",
14
+ add: "ad",
15
+ sub: "sb",
16
+ mul: "ml",
17
+ div: "dv",
18
+ eq: "eq",
19
+ neq: "nq",
20
+ lt: "lt",
21
+ lte: "le",
22
+ gt: "gt",
23
+ gte: "ge",
24
+ and: "an",
25
+ or: "or",
26
+ xor: "xr",
27
+ not: "nt",
28
+ boolean: "bt",
29
+ number: "nm",
30
+ string: "st",
31
+ array: "ar",
32
+ object: "ob",
33
+ mod: "md",
34
+ neg: "ng",
35
+ range: "rn",
36
+ size: "sz"
35
37
  };
36
- var FIRST_NON_RESERVED_REF = 8;
37
- var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
38
+ var KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object", "size"]);
38
39
  var BINARY_TO_OPCODE = {
39
40
  add: "add",
40
41
  sub: "sub",
@@ -163,11 +164,11 @@ function encodeDecimal(significand, power) {
163
164
  function encodeNumberNode(node) {
164
165
  const numberValue = node.value;
165
166
  if (Number.isNaN(numberValue))
166
- return "5'";
167
+ return "nan'";
167
168
  if (numberValue === Infinity)
168
- return "6'";
169
+ return "inf'";
169
170
  if (numberValue === -Infinity)
170
- return "7'";
171
+ return "nif'";
171
172
  if (Number.isInteger(numberValue)) {
172
173
  const { base, exp } = splitDecimal(numberValue);
173
174
  if (exp >= 0 && exp <= 4)
@@ -199,7 +200,7 @@ function encodeNumberNode(node) {
199
200
  return encodeDecimal(significand, power);
200
201
  }
201
202
  function encodeOpcode(opcode) {
202
- return `${encodeUint(OPCODE_IDS[opcode])}%`;
203
+ return `${OPCODE_IDS[opcode]}%`;
203
204
  }
204
205
  function encodeCallParts(parts) {
205
206
  return `(${parts.join("")})`;
@@ -216,7 +217,7 @@ function addOptionalPrefix(encoded) {
216
217
  let payload = encoded;
217
218
  if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
218
219
  payload = encoded.slice(2, -1);
219
- } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
220
+ } else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
220
221
  payload = encoded.slice(2, -1);
221
222
  } else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
222
223
  payload = encoded.slice(1, -1);
@@ -227,7 +228,7 @@ function addOptionalPrefix(encoded) {
227
228
  }
228
229
  function encodeBlockExpression(block) {
229
230
  if (block.length === 0)
230
- return "4'";
231
+ return "un'";
231
232
  if (block.length === 1)
232
233
  return encodeNode(block[0]);
233
234
  return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
@@ -244,9 +245,13 @@ function encodeConditionalElse(elseBranch) {
244
245
  };
245
246
  return encodeNode(nested);
246
247
  }
248
+ function encodeDomainLookup(shortCode, tag) {
249
+ return `${shortCode}${tag}`;
250
+ }
247
251
  function encodeNavigation(node) {
248
252
  const domainRefs = activeEncodeOptions?.domainRefs;
249
- if (domainRefs && node.target.type === "identifier") {
253
+ const domainOpcodes = activeEncodeOptions?.domainOpcodes;
254
+ if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
250
255
  const staticPath = [node.target.name];
251
256
  for (const segment of node.segments) {
252
257
  if (segment.type !== "static")
@@ -255,14 +260,17 @@ function encodeNavigation(node) {
255
260
  }
256
261
  for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
257
262
  const dottedName = staticPath.slice(0, pathLength).join(".");
258
- const domainRef = domainRefs[dottedName];
259
- if (domainRef === undefined)
263
+ const refCode = domainRefs?.[dottedName];
264
+ const opcodeCode = domainOpcodes?.[dottedName];
265
+ const shortCode = refCode ?? opcodeCode;
266
+ if (shortCode === undefined)
260
267
  continue;
268
+ const tag = refCode !== undefined ? "'" : "%";
261
269
  const consumedStaticSegments = pathLength - 1;
262
270
  if (consumedStaticSegments === node.segments.length) {
263
- return `${encodeUint(domainRef)}'`;
271
+ return encodeDomainLookup(shortCode, tag);
264
272
  }
265
- const parts2 = [`${encodeUint(domainRef)}'`];
273
+ const parts2 = [encodeDomainLookup(shortCode, tag)];
266
274
  for (const segment of node.segments.slice(consumedStaticSegments)) {
267
275
  if (segment.type === "static")
268
276
  parts2.push(encodeBareOrLengthString(segment.key));
@@ -288,9 +296,12 @@ function encodeWhile(node) {
288
296
  }
289
297
  function encodeFor(node) {
290
298
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
291
- if (node.binding.type === "binding:expr") {
299
+ if (node.binding.type === "binding:bareIn") {
292
300
  return `>(${encodeNode(node.binding.source)}${body})`;
293
301
  }
302
+ if (node.binding.type === "binding:bareOf") {
303
+ return `<(${encodeNode(node.binding.source)}${body})`;
304
+ }
294
305
  if (node.binding.type === "binding:valueIn") {
295
306
  return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
296
307
  }
@@ -301,30 +312,47 @@ function encodeFor(node) {
301
312
  }
302
313
  function encodeArrayComprehension(node) {
303
314
  const body = addOptionalPrefix(encodeNode(node.body));
304
- if (node.binding.type === "binding:expr") {
315
+ if (node.binding.type === "binding:bareIn") {
305
316
  return `>[${encodeNode(node.binding.source)}${body}]`;
306
317
  }
318
+ if (node.binding.type === "binding:bareOf") {
319
+ return `<[${encodeNode(node.binding.source)}${body}]`;
320
+ }
307
321
  if (node.binding.type === "binding:valueIn") {
308
322
  return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
309
323
  }
310
324
  if (node.binding.type === "binding:keyValueIn") {
311
325
  return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
312
326
  }
313
- return `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
327
+ return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
314
328
  }
315
329
  function encodeObjectComprehension(node) {
316
330
  const key = addOptionalPrefix(encodeNode(node.key));
317
331
  const value = addOptionalPrefix(encodeNode(node.value));
318
- if (node.binding.type === "binding:expr") {
332
+ if (node.binding.type === "binding:bareIn") {
319
333
  return `>{${encodeNode(node.binding.source)}${key}${value}}`;
320
334
  }
335
+ if (node.binding.type === "binding:bareOf") {
336
+ return `<{${encodeNode(node.binding.source)}${key}${value}}`;
337
+ }
321
338
  if (node.binding.type === "binding:valueIn") {
322
339
  return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
323
340
  }
324
341
  if (node.binding.type === "binding:keyValueIn") {
325
342
  return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
326
343
  }
327
- return `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
344
+ return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
345
+ }
346
+ function encodeWhileArrayComprehension(node) {
347
+ const cond = encodeNode(node.condition);
348
+ const body = addOptionalPrefix(encodeNode(node.body));
349
+ return `#[${cond}${body}]`;
350
+ }
351
+ function encodeWhileObjectComprehension(node) {
352
+ const cond = encodeNode(node.condition);
353
+ const key = addOptionalPrefix(encodeNode(node.key));
354
+ const value = addOptionalPrefix(encodeNode(node.value));
355
+ return `#{${cond}${key}${value}}`;
328
356
  }
329
357
  var activeEncodeOptions;
330
358
  function encodeNode(node) {
@@ -334,7 +362,10 @@ function encodeNode(node) {
334
362
  case "identifier": {
335
363
  const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
336
364
  if (domainRef !== undefined)
337
- return `${encodeUint(domainRef)}'`;
365
+ return `${domainRef}'`;
366
+ const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
367
+ if (domainOpcode !== undefined)
368
+ return `${domainOpcode}%`;
338
369
  return `${node.name}$`;
339
370
  }
340
371
  case "self":
@@ -347,11 +378,11 @@ function encodeNode(node) {
347
378
  return `${encodeUint(node.depth - 1)}@`;
348
379
  }
349
380
  case "boolean":
350
- return node.value ? "1'" : "2'";
381
+ return node.value ? "tr'" : "fl'";
351
382
  case "null":
352
- return "3'";
383
+ return "nl'";
353
384
  case "undefined":
354
- return "4'";
385
+ return "un'";
355
386
  case "number":
356
387
  return encodeNumberNode(node);
357
388
  case "string":
@@ -362,12 +393,16 @@ function encodeNode(node) {
362
393
  }
363
394
  case "arrayComprehension":
364
395
  return encodeArrayComprehension(node);
396
+ case "whileArrayComprehension":
397
+ return encodeWhileArrayComprehension(node);
365
398
  case "object": {
366
399
  const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
367
400
  return `{${body}}`;
368
401
  }
369
402
  case "objectComprehension":
370
403
  return encodeObjectComprehension(node);
404
+ case "whileObjectComprehension":
405
+ return encodeWhileObjectComprehension(node);
371
406
  case "key":
372
407
  return encodeBareOrLengthString(node.name);
373
408
  case "group":
@@ -377,6 +412,10 @@ function encodeNode(node) {
377
412
  return `~${encodeNode(node.value)}`;
378
413
  if (node.op === "neg")
379
414
  return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
415
+ if (node.op === "logicalNot") {
416
+ const val = encodeNode(node.value);
417
+ return `!(${val}tr')`;
418
+ }
380
419
  return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
381
420
  case "binary":
382
421
  if (node.op === "and") {
@@ -395,12 +434,19 @@ function encodeNode(node) {
395
434
  }).join("");
396
435
  return `|(${body})`;
397
436
  }
437
+ if (node.op === "nor") {
438
+ const left = encodeNode(node.left);
439
+ const right = addOptionalPrefix(encodeNode(node.right));
440
+ return `!(${left}${right})`;
441
+ }
398
442
  return encodeCallParts([
399
443
  encodeOpcode(BINARY_TO_OPCODE[node.op]),
400
444
  encodeNode(node.left),
401
445
  encodeNode(node.right)
402
446
  ]);
403
447
  case "assign": {
448
+ if (node.op === ":=")
449
+ return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
404
450
  if (node.op === "=")
405
451
  return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
406
452
  const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
@@ -411,8 +457,12 @@ function encodeNode(node) {
411
457
  }
412
458
  case "navigation":
413
459
  return encodeNavigation(node);
414
- case "call":
460
+ case "call": {
461
+ if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
462
+ return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
463
+ }
415
464
  return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
465
+ }
416
466
  case "conditional": {
417
467
  const opener = node.head === "when" ? "?(" : "!(";
418
468
  const cond = encodeNode(node.condition);
@@ -420,6 +470,8 @@ function encodeNode(node) {
420
470
  const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
421
471
  return `${opener}${cond}${thenExpr}${elseExpr})`;
422
472
  }
473
+ case "range":
474
+ return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
423
475
  case "for":
424
476
  return encodeFor(node);
425
477
  case "while":
@@ -595,78 +647,55 @@ function domainRefsFromConfig(config) {
595
647
  if (!config || typeof config !== "object" || Array.isArray(config)) {
596
648
  throw new Error("Domain config must be an object");
597
649
  }
598
- const refs = {};
599
- for (const section of Object.values(config)) {
600
- if (!section || typeof section !== "object" || Array.isArray(section))
601
- continue;
602
- mapConfigEntries(section, refs);
603
- }
604
- return refs;
605
- }
606
- function decodeDomainRefKey(refText) {
607
- if (!refText)
608
- throw new Error("Domain ref key cannot be empty");
609
- if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
610
- throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
611
- }
612
- if (refText.length > 1 && refText[0] === "0") {
613
- throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
650
+ const configObj = config;
651
+ const domainRefs = {};
652
+ const domainOpcodes = {};
653
+ const dataSection = configObj.data;
654
+ if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
655
+ mapConfigEntries(dataSection, domainRefs);
614
656
  }
615
- if (/^[1-9]$/.test(refText)) {
616
- throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
657
+ const functionsSection = configObj.functions;
658
+ if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
659
+ mapConfigEntries(functionsSection, domainOpcodes);
617
660
  }
618
- let value = 0;
619
- for (const char of refText) {
620
- const digit = DOMAIN_DIGIT_INDEX.get(char);
621
- if (digit === undefined)
622
- throw new Error(`Invalid domain ref key '${refText}'`);
623
- value = value * 64 + digit;
624
- if (value > Number.MAX_SAFE_INTEGER) {
625
- throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
626
- }
627
- }
628
- if (value < FIRST_NON_RESERVED_REF) {
629
- throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
630
- }
631
- return value;
661
+ return { domainRefs, domainOpcodes };
632
662
  }
633
663
  function mapConfigEntries(entries, refs) {
634
664
  const sourceKindByRoot = new Map;
635
665
  for (const root of Object.keys(refs)) {
636
666
  sourceKindByRoot.set(root, "explicit");
637
667
  }
638
- for (const [refText, rawEntry] of Object.entries(entries)) {
668
+ for (const [shortCode, rawEntry] of Object.entries(entries)) {
639
669
  const entry = rawEntry;
640
670
  if (!entry || typeof entry !== "object")
641
671
  continue;
642
672
  if (!Array.isArray(entry.names))
643
673
  continue;
644
- const refId = decodeDomainRefKey(refText);
645
674
  for (const rawName of entry.names) {
646
675
  if (typeof rawName !== "string")
647
676
  continue;
648
- const existingNameRef = refs[rawName];
649
- if (existingNameRef !== undefined && existingNameRef !== refId) {
650
- throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
677
+ const existingRef = refs[rawName];
678
+ if (existingRef !== undefined && existingRef !== shortCode) {
679
+ throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
651
680
  }
652
- refs[rawName] = refId;
681
+ refs[rawName] = shortCode;
653
682
  const root = rawName.split(".")[0];
654
683
  if (!root)
655
684
  continue;
656
685
  const currentKind = rawName.includes(".") ? "implicit" : "explicit";
657
686
  const existing = refs[root];
658
687
  if (existing !== undefined) {
659
- if (existing === refId)
688
+ if (existing === shortCode)
660
689
  continue;
661
690
  const existingKind = sourceKindByRoot.get(root) ?? "explicit";
662
691
  if (currentKind === "explicit") {
663
- throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
692
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
664
693
  }
665
694
  if (existingKind === "explicit")
666
695
  continue;
667
696
  continue;
668
697
  }
669
- refs[root] = refId;
698
+ refs[root] = shortCode;
670
699
  sourceKindByRoot.set(root, currentKind);
671
700
  }
672
701
  }
@@ -950,6 +979,21 @@ function dropBindingNames(env, binding) {
950
979
  clearBinding(env, binding.key);
951
980
  }
952
981
  }
982
+ function optimizeBinding(binding, sourceEnv, currentDepth) {
983
+ const source = optimizeNode(binding.source, sourceEnv, currentDepth);
984
+ switch (binding.type) {
985
+ case "binding:bareIn":
986
+ return { type: "binding:bareIn", source };
987
+ case "binding:bareOf":
988
+ return { type: "binding:bareOf", source };
989
+ case "binding:valueIn":
990
+ return { type: "binding:valueIn", value: binding.value, source };
991
+ case "binding:keyValueIn":
992
+ return { type: "binding:keyValueIn", key: binding.key, value: binding.value, source };
993
+ case "binding:keyOf":
994
+ return { type: "binding:keyOf", key: binding.key, source };
995
+ }
996
+ }
953
997
  function collectReads(node, out) {
954
998
  switch (node.type) {
955
999
  case "identifier":
@@ -972,11 +1016,20 @@ function collectReads(node, out) {
972
1016
  collectReads(node.binding.source, out);
973
1017
  collectReads(node.body, out);
974
1018
  return;
1019
+ case "whileArrayComprehension":
1020
+ collectReads(node.condition, out);
1021
+ collectReads(node.body, out);
1022
+ return;
975
1023
  case "objectComprehension":
976
1024
  collectReads(node.binding.source, out);
977
1025
  collectReads(node.key, out);
978
1026
  collectReads(node.value, out);
979
1027
  return;
1028
+ case "whileObjectComprehension":
1029
+ collectReads(node.condition, out);
1030
+ collectReads(node.key, out);
1031
+ collectReads(node.value, out);
1032
+ return;
980
1033
  case "unary":
981
1034
  collectReads(node.value, out);
982
1035
  return;
@@ -1013,6 +1066,10 @@ function collectReads(node, out) {
1013
1066
  for (const part of node.body)
1014
1067
  collectReads(part, out);
1015
1068
  return;
1069
+ case "range":
1070
+ collectReads(node.from, out);
1071
+ collectReads(node.to, out);
1072
+ return;
1016
1073
  case "program":
1017
1074
  for (const part of node.body)
1018
1075
  collectReads(part, out);
@@ -1057,6 +1114,8 @@ function isPureNode(node) {
1057
1114
  return node.op !== "delete" && isPureNode(node.value);
1058
1115
  case "binary":
1059
1116
  return isPureNode(node.left) && isPureNode(node.right);
1117
+ case "range":
1118
+ return isPureNode(node.from) && isPureNode(node.to);
1060
1119
  default:
1061
1120
  return false;
1062
1121
  }
@@ -1121,6 +1180,8 @@ function hasIdentifierRead(node, name, asPlace = false) {
1121
1180
  return hasIdentifierRead(node.value, name, node.op === "delete");
1122
1181
  case "binary":
1123
1182
  return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1183
+ case "range":
1184
+ return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
1124
1185
  case "assign":
1125
1186
  return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1126
1187
  default:
@@ -1143,6 +1204,8 @@ function countIdentifierReads(node, name, asPlace = false) {
1143
1204
  return countIdentifierReads(node.value, name, node.op === "delete");
1144
1205
  case "binary":
1145
1206
  return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1207
+ case "range":
1208
+ return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
1146
1209
  case "assign":
1147
1210
  return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1148
1211
  default:
@@ -1197,6 +1260,12 @@ function replaceIdentifier(node, name, replacement, asPlace = false) {
1197
1260
  place: replaceIdentifier(node.place, name, replacement, true),
1198
1261
  value: replaceIdentifier(node.value, name, replacement)
1199
1262
  };
1263
+ case "range":
1264
+ return {
1265
+ type: "range",
1266
+ from: replaceIdentifier(node.from, name, replacement),
1267
+ to: replaceIdentifier(node.to, name, replacement)
1268
+ };
1200
1269
  default:
1201
1270
  return node;
1202
1271
  }
@@ -1341,6 +1410,9 @@ function foldUnary(op, value) {
1341
1410
  return ~value;
1342
1411
  return;
1343
1412
  }
1413
+ if (op === "logicalNot") {
1414
+ return value === undefined ? true : undefined;
1415
+ }
1344
1416
  return;
1345
1417
  }
1346
1418
  function foldBinary(op, left, right) {
@@ -1511,6 +1583,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1511
1583
  }
1512
1584
  return { type: "binary", op: node.op, left, right };
1513
1585
  }
1586
+ case "range":
1587
+ return {
1588
+ type: "range",
1589
+ from: optimizeNode(node.from, env, currentDepth),
1590
+ to: optimizeNode(node.to, env, currentDepth)
1591
+ };
1514
1592
  case "navigation": {
1515
1593
  const target = optimizeNode(node.target, env, currentDepth);
1516
1594
  const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
@@ -1621,31 +1699,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1621
1699
  }
1622
1700
  case "for": {
1623
1701
  const sourceEnv = cloneOptimizeEnv(env);
1624
- const binding = (() => {
1625
- if (node.binding.type === "binding:expr") {
1626
- return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
1627
- }
1628
- if (node.binding.type === "binding:valueIn") {
1629
- return {
1630
- type: "binding:valueIn",
1631
- value: node.binding.value,
1632
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1633
- };
1634
- }
1635
- if (node.binding.type === "binding:keyValueIn") {
1636
- return {
1637
- type: "binding:keyValueIn",
1638
- key: node.binding.key,
1639
- value: node.binding.value,
1640
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1641
- };
1642
- }
1643
- return {
1644
- type: "binding:keyOf",
1645
- key: node.binding.key,
1646
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1647
- };
1648
- })();
1702
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1649
1703
  const bodyEnv = cloneOptimizeEnv(env);
1650
1704
  dropBindingNames(bodyEnv, binding);
1651
1705
  return {
@@ -1656,20 +1710,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1656
1710
  }
1657
1711
  case "arrayComprehension": {
1658
1712
  const sourceEnv = cloneOptimizeEnv(env);
1659
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1660
- type: "binding:valueIn",
1661
- value: node.binding.value,
1662
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1663
- } : node.binding.type === "binding:keyValueIn" ? {
1664
- type: "binding:keyValueIn",
1665
- key: node.binding.key,
1666
- value: node.binding.value,
1667
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1668
- } : {
1669
- type: "binding:keyOf",
1670
- key: node.binding.key,
1671
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1672
- };
1713
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1673
1714
  const bodyEnv = cloneOptimizeEnv(env);
1674
1715
  dropBindingNames(bodyEnv, binding);
1675
1716
  return {
@@ -1678,22 +1719,15 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1678
1719
  body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1679
1720
  };
1680
1721
  }
1722
+ case "whileArrayComprehension":
1723
+ return {
1724
+ type: "whileArrayComprehension",
1725
+ condition: optimizeNode(node.condition, env, currentDepth),
1726
+ body: optimizeNode(node.body, env, currentDepth + 1)
1727
+ };
1681
1728
  case "objectComprehension": {
1682
1729
  const sourceEnv = cloneOptimizeEnv(env);
1683
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1684
- type: "binding:valueIn",
1685
- value: node.binding.value,
1686
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1687
- } : node.binding.type === "binding:keyValueIn" ? {
1688
- type: "binding:keyValueIn",
1689
- key: node.binding.key,
1690
- value: node.binding.value,
1691
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1692
- } : {
1693
- type: "binding:keyOf",
1694
- key: node.binding.key,
1695
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1696
- };
1730
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1697
1731
  const bodyEnv = cloneOptimizeEnv(env);
1698
1732
  dropBindingNames(bodyEnv, binding);
1699
1733
  return {
@@ -1703,6 +1737,13 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1703
1737
  value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1704
1738
  };
1705
1739
  }
1740
+ case "whileObjectComprehension":
1741
+ return {
1742
+ type: "whileObjectComprehension",
1743
+ condition: optimizeNode(node.condition, env, currentDepth),
1744
+ key: optimizeNode(node.key, env, currentDepth + 1),
1745
+ value: optimizeNode(node.value, env, currentDepth + 1)
1746
+ };
1706
1747
  default:
1707
1748
  return node;
1708
1749
  }
@@ -1754,6 +1795,10 @@ function collectLocalBindings(node, locals) {
1754
1795
  collectLocalBindings(node.left, locals);
1755
1796
  collectLocalBindings(node.right, locals);
1756
1797
  return;
1798
+ case "range":
1799
+ collectLocalBindings(node.from, locals);
1800
+ collectLocalBindings(node.to, locals);
1801
+ return;
1757
1802
  case "conditional":
1758
1803
  collectLocalBindings(node.condition, locals);
1759
1804
  for (const part of node.thenBlock)
@@ -1770,11 +1815,20 @@ function collectLocalBindings(node, locals) {
1770
1815
  collectLocalBindingFromBinding(node.binding, locals);
1771
1816
  collectLocalBindings(node.body, locals);
1772
1817
  return;
1818
+ case "whileArrayComprehension":
1819
+ collectLocalBindings(node.condition, locals);
1820
+ collectLocalBindings(node.body, locals);
1821
+ return;
1773
1822
  case "objectComprehension":
1774
1823
  collectLocalBindingFromBinding(node.binding, locals);
1775
1824
  collectLocalBindings(node.key, locals);
1776
1825
  collectLocalBindings(node.value, locals);
1777
1826
  return;
1827
+ case "whileObjectComprehension":
1828
+ collectLocalBindings(node.condition, locals);
1829
+ collectLocalBindings(node.key, locals);
1830
+ collectLocalBindings(node.value, locals);
1831
+ return;
1778
1832
  default:
1779
1833
  return;
1780
1834
  }
@@ -1866,6 +1920,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1866
1920
  collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
1867
1921
  collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
1868
1922
  return;
1923
+ case "range":
1924
+ collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
1925
+ collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
1926
+ return;
1869
1927
  case "conditional":
1870
1928
  collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1871
1929
  for (const part of node.thenBlock)
@@ -1882,11 +1940,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1882
1940
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1883
1941
  collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1884
1942
  return;
1943
+ case "whileArrayComprehension":
1944
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1945
+ collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1946
+ return;
1885
1947
  case "objectComprehension":
1886
1948
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1887
1949
  collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1888
1950
  collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1889
1951
  return;
1952
+ case "whileObjectComprehension":
1953
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1954
+ collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1955
+ collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1956
+ return;
1890
1957
  default:
1891
1958
  return;
1892
1959
  }
@@ -1961,6 +2028,12 @@ function renameLocalNames(node, map) {
1961
2028
  left: renameLocalNames(node.left, map),
1962
2029
  right: renameLocalNames(node.right, map)
1963
2030
  };
2031
+ case "range":
2032
+ return {
2033
+ type: "range",
2034
+ from: renameLocalNames(node.from, map),
2035
+ to: renameLocalNames(node.to, map)
2036
+ };
1964
2037
  case "assign": {
1965
2038
  const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
1966
2039
  return {
@@ -1990,6 +2063,12 @@ function renameLocalNames(node, map) {
1990
2063
  binding: renameLocalNamesBinding(node.binding, map),
1991
2064
  body: renameLocalNames(node.body, map)
1992
2065
  };
2066
+ case "whileArrayComprehension":
2067
+ return {
2068
+ type: "whileArrayComprehension",
2069
+ condition: renameLocalNames(node.condition, map),
2070
+ body: renameLocalNames(node.body, map)
2071
+ };
1993
2072
  case "objectComprehension":
1994
2073
  return {
1995
2074
  type: "objectComprehension",
@@ -1997,34 +2076,31 @@ function renameLocalNames(node, map) {
1997
2076
  key: renameLocalNames(node.key, map),
1998
2077
  value: renameLocalNames(node.value, map)
1999
2078
  };
2079
+ case "whileObjectComprehension":
2080
+ return {
2081
+ type: "whileObjectComprehension",
2082
+ condition: renameLocalNames(node.condition, map),
2083
+ key: renameLocalNames(node.key, map),
2084
+ value: renameLocalNames(node.value, map)
2085
+ };
2000
2086
  default:
2001
2087
  return node;
2002
2088
  }
2003
2089
  }
2004
2090
  function renameLocalNamesBinding(binding, map) {
2005
- if (binding.type === "binding:expr") {
2006
- return { type: "binding:expr", source: renameLocalNames(binding.source, map) };
2007
- }
2008
- if (binding.type === "binding:valueIn") {
2009
- return {
2010
- type: "binding:valueIn",
2011
- value: map.get(binding.value) ?? binding.value,
2012
- source: renameLocalNames(binding.source, map)
2013
- };
2014
- }
2015
- if (binding.type === "binding:keyValueIn") {
2016
- return {
2017
- type: "binding:keyValueIn",
2018
- key: map.get(binding.key) ?? binding.key,
2019
- value: map.get(binding.value) ?? binding.value,
2020
- source: renameLocalNames(binding.source, map)
2021
- };
2091
+ const source = renameLocalNames(binding.source, map);
2092
+ switch (binding.type) {
2093
+ case "binding:bareIn":
2094
+ return { type: "binding:bareIn", source };
2095
+ case "binding:bareOf":
2096
+ return { type: "binding:bareOf", source };
2097
+ case "binding:valueIn":
2098
+ return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
2099
+ case "binding:keyValueIn":
2100
+ return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
2101
+ case "binding:keyOf":
2102
+ return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
2022
2103
  }
2023
- return {
2024
- type: "binding:keyOf",
2025
- key: map.get(binding.key) ?? binding.key,
2026
- source: renameLocalNames(binding.source, map)
2027
- };
2028
2104
  }
2029
2105
  function renameLocalNamesElse(elseBranch, map) {
2030
2106
  if (elseBranch.type === "else") {
@@ -2071,9 +2147,9 @@ function compile(source, options) {
2071
2147
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
2072
2148
  if (options?.minifyNames)
2073
2149
  lowered = minifyLocalNamesIR(lowered);
2074
- const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2150
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2075
2151
  return encodeIR(lowered, {
2076
- domainRefs,
2152
+ ...domainMaps,
2077
2153
  dedupeValues: options?.dedupeValues,
2078
2154
  dedupeMinBytes: options?.dedupeMinBytes
2079
2155
  });
@@ -2199,6 +2275,9 @@ semantics.addOperation("toIR", {
2199
2275
  ExistenceExpr_or(left, _or, right) {
2200
2276
  return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
2201
2277
  },
2278
+ ExistenceExpr_nor(left, _nor, right) {
2279
+ return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
2280
+ },
2202
2281
  BitExpr_and(left, _op, right) {
2203
2282
  return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
2204
2283
  },
@@ -2208,6 +2287,9 @@ semantics.addOperation("toIR", {
2208
2287
  BitExpr_or(left, _op, right) {
2209
2288
  return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
2210
2289
  },
2290
+ RangeExpr_range(left, _op, right) {
2291
+ return { type: "range", from: left.toIR(), to: right.toIR() };
2292
+ },
2211
2293
  CompareExpr_binary(left, op, right) {
2212
2294
  const map = {
2213
2295
  "==": "eq",
@@ -2248,6 +2330,9 @@ semantics.addOperation("toIR", {
2248
2330
  UnaryExpr_not(_op, value) {
2249
2331
  return { type: "unary", op: "not", value: value.toIR() };
2250
2332
  },
2333
+ UnaryExpr_logicalNot(_not, value) {
2334
+ return { type: "unary", op: "logicalNot", value: value.toIR() };
2335
+ },
2251
2336
  UnaryExpr_delete(_del, place) {
2252
2337
  return { type: "unary", op: "delete", value: place.toIR() };
2253
2338
  },
@@ -2301,14 +2386,6 @@ semantics.addOperation("toIR", {
2301
2386
  ConditionalElse_else(_else, block) {
2302
2387
  return { type: "else", block: block.toIR() };
2303
2388
  },
2304
- DoExpr(_do, block, _end) {
2305
- const body = block.toIR();
2306
- if (body.length === 0)
2307
- return { type: "undefined" };
2308
- if (body.length === 1)
2309
- return body[0];
2310
- return { type: "program", body };
2311
- },
2312
2389
  WhileExpr(_while, condition, _do, block, _end) {
2313
2390
  return {
2314
2391
  type: "while",
@@ -2323,30 +2400,44 @@ semantics.addOperation("toIR", {
2323
2400
  body: block.toIR()
2324
2401
  };
2325
2402
  },
2326
- BindingExpr(iterOrExpr) {
2327
- const node = iterOrExpr.toIR();
2328
- if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
2329
- return node;
2330
- }
2331
- return { type: "binding:expr", source: node };
2332
- },
2333
2403
  Array_empty(_open, _close) {
2334
2404
  return { type: "array", items: [] };
2335
2405
  },
2336
- Array_comprehension(_open, binding, _semi, body, _close) {
2406
+ Array_forComprehension(_open, body, _for, binding, _close) {
2337
2407
  return {
2338
2408
  type: "arrayComprehension",
2339
2409
  binding: binding.toIR(),
2340
2410
  body: body.toIR()
2341
2411
  };
2342
2412
  },
2413
+ Array_whileComprehension(_open, body, _while, condition, _close) {
2414
+ return {
2415
+ type: "whileArrayComprehension",
2416
+ condition: condition.toIR(),
2417
+ body: body.toIR()
2418
+ };
2419
+ },
2420
+ Array_inComprehension(_open, body, _in, source, _close) {
2421
+ return {
2422
+ type: "arrayComprehension",
2423
+ binding: { type: "binding:bareIn", source: source.toIR() },
2424
+ body: body.toIR()
2425
+ };
2426
+ },
2427
+ Array_ofComprehension(_open, body, _of, source, _close) {
2428
+ return {
2429
+ type: "arrayComprehension",
2430
+ binding: { type: "binding:bareOf", source: source.toIR() },
2431
+ body: body.toIR()
2432
+ };
2433
+ },
2343
2434
  Array_values(_open, items, _close) {
2344
2435
  return { type: "array", items: normalizeList(items.toIR()) };
2345
2436
  },
2346
2437
  Object_empty(_open, _close) {
2347
2438
  return { type: "object", entries: [] };
2348
2439
  },
2349
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
2440
+ Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
2350
2441
  return {
2351
2442
  type: "objectComprehension",
2352
2443
  binding: binding.toIR(),
@@ -2354,6 +2445,30 @@ semantics.addOperation("toIR", {
2354
2445
  value: value.toIR()
2355
2446
  };
2356
2447
  },
2448
+ Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
2449
+ return {
2450
+ type: "whileObjectComprehension",
2451
+ condition: condition.toIR(),
2452
+ key: key.toIR(),
2453
+ value: value.toIR()
2454
+ };
2455
+ },
2456
+ Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
2457
+ return {
2458
+ type: "objectComprehension",
2459
+ binding: { type: "binding:bareIn", source: source.toIR() },
2460
+ key: key.toIR(),
2461
+ value: value.toIR()
2462
+ };
2463
+ },
2464
+ Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
2465
+ return {
2466
+ type: "objectComprehension",
2467
+ binding: { type: "binding:bareOf", source: source.toIR() },
2468
+ key: key.toIR(),
2469
+ value: value.toIR()
2470
+ };
2471
+ },
2357
2472
  Object_pairs(_open, pairs, _close) {
2358
2473
  return {
2359
2474
  type: "object",
@@ -2382,6 +2497,18 @@ semantics.addOperation("toIR", {
2382
2497
  source: source.toIR()
2383
2498
  };
2384
2499
  },
2500
+ IterBinding_bareIn(_in, source) {
2501
+ return {
2502
+ type: "binding:bareIn",
2503
+ source: source.toIR()
2504
+ };
2505
+ },
2506
+ IterBinding_bareOf(_of, source) {
2507
+ return {
2508
+ type: "binding:bareOf",
2509
+ source: source.toIR()
2510
+ };
2511
+ },
2385
2512
  Pair(key, _colon, value) {
2386
2513
  return { key: key.toIR(), value: value.toIR() };
2387
2514
  },
@@ -2445,6 +2572,9 @@ semantics.addOperation("toIR", {
2445
2572
  BooleanKw(_kw) {
2446
2573
  return { type: "identifier", name: "boolean" };
2447
2574
  },
2575
+ SizeKw(_kw) {
2576
+ return { type: "identifier", name: "size" };
2577
+ },
2448
2578
  identifier(_a, _b) {
2449
2579
  return { type: "identifier", name: this.sourceString };
2450
2580
  },