@creationix/rex 0.3.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/rex.js CHANGED
@@ -10,31 +10,31 @@ 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"
35
36
  };
36
- var FIRST_NON_RESERVED_REF = 8;
37
- var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
37
+ var KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object"]);
38
38
  var BINARY_TO_OPCODE = {
39
39
  add: "add",
40
40
  sub: "sub",
@@ -163,11 +163,11 @@ function encodeDecimal(significand, power) {
163
163
  function encodeNumberNode(node) {
164
164
  const numberValue = node.value;
165
165
  if (Number.isNaN(numberValue))
166
- return "5'";
166
+ return "nan'";
167
167
  if (numberValue === Infinity)
168
- return "6'";
168
+ return "inf'";
169
169
  if (numberValue === -Infinity)
170
- return "7'";
170
+ return "nif'";
171
171
  if (Number.isInteger(numberValue)) {
172
172
  const { base, exp } = splitDecimal(numberValue);
173
173
  if (exp >= 0 && exp <= 4)
@@ -199,7 +199,7 @@ function encodeNumberNode(node) {
199
199
  return encodeDecimal(significand, power);
200
200
  }
201
201
  function encodeOpcode(opcode) {
202
- return `${encodeUint(OPCODE_IDS[opcode])}%`;
202
+ return `${OPCODE_IDS[opcode]}%`;
203
203
  }
204
204
  function encodeCallParts(parts) {
205
205
  return `(${parts.join("")})`;
@@ -216,7 +216,7 @@ function addOptionalPrefix(encoded) {
216
216
  let payload = encoded;
217
217
  if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
218
218
  payload = encoded.slice(2, -1);
219
- } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
219
+ } else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
220
220
  payload = encoded.slice(2, -1);
221
221
  } else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
222
222
  payload = encoded.slice(1, -1);
@@ -227,7 +227,7 @@ function addOptionalPrefix(encoded) {
227
227
  }
228
228
  function encodeBlockExpression(block) {
229
229
  if (block.length === 0)
230
- return "4'";
230
+ return "un'";
231
231
  if (block.length === 1)
232
232
  return encodeNode(block[0]);
233
233
  return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
@@ -244,9 +244,13 @@ function encodeConditionalElse(elseBranch) {
244
244
  };
245
245
  return encodeNode(nested);
246
246
  }
247
+ function encodeDomainLookup(shortCode, tag) {
248
+ return `${shortCode}${tag}`;
249
+ }
247
250
  function encodeNavigation(node) {
248
251
  const domainRefs = activeEncodeOptions?.domainRefs;
249
- if (domainRefs && node.target.type === "identifier") {
252
+ const domainOpcodes = activeEncodeOptions?.domainOpcodes;
253
+ if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
250
254
  const staticPath = [node.target.name];
251
255
  for (const segment of node.segments) {
252
256
  if (segment.type !== "static")
@@ -255,14 +259,17 @@ function encodeNavigation(node) {
255
259
  }
256
260
  for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
257
261
  const dottedName = staticPath.slice(0, pathLength).join(".");
258
- const domainRef = domainRefs[dottedName];
259
- if (domainRef === undefined)
262
+ const refCode = domainRefs?.[dottedName];
263
+ const opcodeCode = domainOpcodes?.[dottedName];
264
+ const shortCode = refCode ?? opcodeCode;
265
+ if (shortCode === undefined)
260
266
  continue;
267
+ const tag = refCode !== undefined ? "'" : "%";
261
268
  const consumedStaticSegments = pathLength - 1;
262
269
  if (consumedStaticSegments === node.segments.length) {
263
- return `${encodeUint(domainRef)}'`;
270
+ return encodeDomainLookup(shortCode, tag);
264
271
  }
265
- const parts2 = [`${encodeUint(domainRef)}'`];
272
+ const parts2 = [encodeDomainLookup(shortCode, tag)];
266
273
  for (const segment of node.segments.slice(consumedStaticSegments)) {
267
274
  if (segment.type === "static")
268
275
  parts2.push(encodeBareOrLengthString(segment.key));
@@ -288,9 +295,12 @@ function encodeWhile(node) {
288
295
  }
289
296
  function encodeFor(node) {
290
297
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
291
- if (node.binding.type === "binding:expr") {
298
+ if (node.binding.type === "binding:bareIn") {
292
299
  return `>(${encodeNode(node.binding.source)}${body})`;
293
300
  }
301
+ if (node.binding.type === "binding:bareOf") {
302
+ return `<(${encodeNode(node.binding.source)}${body})`;
303
+ }
294
304
  if (node.binding.type === "binding:valueIn") {
295
305
  return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
296
306
  }
@@ -301,30 +311,47 @@ function encodeFor(node) {
301
311
  }
302
312
  function encodeArrayComprehension(node) {
303
313
  const body = addOptionalPrefix(encodeNode(node.body));
304
- if (node.binding.type === "binding:expr") {
314
+ if (node.binding.type === "binding:bareIn") {
305
315
  return `>[${encodeNode(node.binding.source)}${body}]`;
306
316
  }
317
+ if (node.binding.type === "binding:bareOf") {
318
+ return `<[${encodeNode(node.binding.source)}${body}]`;
319
+ }
307
320
  if (node.binding.type === "binding:valueIn") {
308
321
  return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
309
322
  }
310
323
  if (node.binding.type === "binding:keyValueIn") {
311
324
  return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
312
325
  }
313
- return `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
326
+ return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
314
327
  }
315
328
  function encodeObjectComprehension(node) {
316
329
  const key = addOptionalPrefix(encodeNode(node.key));
317
330
  const value = addOptionalPrefix(encodeNode(node.value));
318
- if (node.binding.type === "binding:expr") {
331
+ if (node.binding.type === "binding:bareIn") {
319
332
  return `>{${encodeNode(node.binding.source)}${key}${value}}`;
320
333
  }
334
+ if (node.binding.type === "binding:bareOf") {
335
+ return `<{${encodeNode(node.binding.source)}${key}${value}}`;
336
+ }
321
337
  if (node.binding.type === "binding:valueIn") {
322
338
  return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
323
339
  }
324
340
  if (node.binding.type === "binding:keyValueIn") {
325
341
  return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
326
342
  }
327
- return `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
343
+ return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
344
+ }
345
+ function encodeWhileArrayComprehension(node) {
346
+ const cond = encodeNode(node.condition);
347
+ const body = addOptionalPrefix(encodeNode(node.body));
348
+ return `#[${cond}${body}]`;
349
+ }
350
+ function encodeWhileObjectComprehension(node) {
351
+ const cond = encodeNode(node.condition);
352
+ const key = addOptionalPrefix(encodeNode(node.key));
353
+ const value = addOptionalPrefix(encodeNode(node.value));
354
+ return `#{${cond}${key}${value}}`;
328
355
  }
329
356
  var activeEncodeOptions;
330
357
  function encodeNode(node) {
@@ -334,7 +361,10 @@ function encodeNode(node) {
334
361
  case "identifier": {
335
362
  const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
336
363
  if (domainRef !== undefined)
337
- return `${encodeUint(domainRef)}'`;
364
+ return `${domainRef}'`;
365
+ const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
366
+ if (domainOpcode !== undefined)
367
+ return `${domainOpcode}%`;
338
368
  return `${node.name}$`;
339
369
  }
340
370
  case "self":
@@ -347,11 +377,11 @@ function encodeNode(node) {
347
377
  return `${encodeUint(node.depth - 1)}@`;
348
378
  }
349
379
  case "boolean":
350
- return node.value ? "1'" : "2'";
380
+ return node.value ? "tr'" : "fl'";
351
381
  case "null":
352
- return "3'";
382
+ return "nl'";
353
383
  case "undefined":
354
- return "4'";
384
+ return "un'";
355
385
  case "number":
356
386
  return encodeNumberNode(node);
357
387
  case "string":
@@ -362,12 +392,16 @@ function encodeNode(node) {
362
392
  }
363
393
  case "arrayComprehension":
364
394
  return encodeArrayComprehension(node);
395
+ case "whileArrayComprehension":
396
+ return encodeWhileArrayComprehension(node);
365
397
  case "object": {
366
398
  const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
367
399
  return `{${body}}`;
368
400
  }
369
401
  case "objectComprehension":
370
402
  return encodeObjectComprehension(node);
403
+ case "whileObjectComprehension":
404
+ return encodeWhileObjectComprehension(node);
371
405
  case "key":
372
406
  return encodeBareOrLengthString(node.name);
373
407
  case "group":
@@ -377,6 +411,10 @@ function encodeNode(node) {
377
411
  return `~${encodeNode(node.value)}`;
378
412
  if (node.op === "neg")
379
413
  return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
414
+ if (node.op === "logicalNot") {
415
+ const val = encodeNode(node.value);
416
+ return `!(${val}tr')`;
417
+ }
380
418
  return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
381
419
  case "binary":
382
420
  if (node.op === "and") {
@@ -395,12 +433,19 @@ function encodeNode(node) {
395
433
  }).join("");
396
434
  return `|(${body})`;
397
435
  }
436
+ if (node.op === "nor") {
437
+ const left = encodeNode(node.left);
438
+ const right = addOptionalPrefix(encodeNode(node.right));
439
+ return `!(${left}${right})`;
440
+ }
398
441
  return encodeCallParts([
399
442
  encodeOpcode(BINARY_TO_OPCODE[node.op]),
400
443
  encodeNode(node.left),
401
444
  encodeNode(node.right)
402
445
  ]);
403
446
  case "assign": {
447
+ if (node.op === ":=")
448
+ return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
404
449
  if (node.op === "=")
405
450
  return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
406
451
  const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
@@ -411,8 +456,12 @@ function encodeNode(node) {
411
456
  }
412
457
  case "navigation":
413
458
  return encodeNavigation(node);
414
- case "call":
459
+ case "call": {
460
+ if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
461
+ return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
462
+ }
415
463
  return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
464
+ }
416
465
  case "conditional": {
417
466
  const opener = node.head === "when" ? "?(" : "!(";
418
467
  const cond = encodeNode(node.condition);
@@ -420,6 +469,8 @@ function encodeNode(node) {
420
469
  const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
421
470
  return `${opener}${cond}${thenExpr}${elseExpr})`;
422
471
  }
472
+ case "range":
473
+ return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
423
474
  case "for":
424
475
  return encodeFor(node);
425
476
  case "while":
@@ -439,11 +490,30 @@ function collectLogicalChain(node, op) {
439
490
  return [node];
440
491
  return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
441
492
  }
493
+ function formatParseError(source, match) {
494
+ const message = match.message ?? "Parse failed";
495
+ const pos = match.getRightmostFailurePosition?.();
496
+ if (typeof pos !== "number" || !Number.isFinite(pos))
497
+ return message;
498
+ const safePos = Math.max(0, Math.min(source.length, pos));
499
+ const lineStart = source.lastIndexOf(`
500
+ `, safePos - 1) + 1;
501
+ const lineEndIndex = source.indexOf(`
502
+ `, safePos);
503
+ const lineEnd = lineEndIndex === -1 ? source.length : lineEndIndex;
504
+ const lineText = source.slice(lineStart, lineEnd);
505
+ const lineNumber = source.slice(0, lineStart).split(`
506
+ `).length;
507
+ const columnNumber = safePos - lineStart + 1;
508
+ const caret = `${" ".repeat(Math.max(0, columnNumber - 1))}^`;
509
+ return `${message}
510
+ ${lineText}
511
+ ${caret}`;
512
+ }
442
513
  function parseToIR(source) {
443
514
  const match = grammar.match(source);
444
515
  if (!match.succeeded()) {
445
- const failure = match;
446
- throw new Error(failure.message ?? "Parse failed");
516
+ throw new Error(formatParseError(source, match));
447
517
  }
448
518
  return semantics(match).toIR();
449
519
  }
@@ -595,78 +665,55 @@ function domainRefsFromConfig(config) {
595
665
  if (!config || typeof config !== "object" || Array.isArray(config)) {
596
666
  throw new Error("Domain config must be an object");
597
667
  }
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)`);
668
+ const configObj = config;
669
+ const domainRefs = {};
670
+ const domainOpcodes = {};
671
+ const dataSection = configObj.data;
672
+ if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
673
+ mapConfigEntries(dataSection, domainRefs);
614
674
  }
615
- if (/^[1-9]$/.test(refText)) {
616
- throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
675
+ const functionsSection = configObj.functions;
676
+ if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
677
+ mapConfigEntries(functionsSection, domainOpcodes);
617
678
  }
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;
679
+ return { domainRefs, domainOpcodes };
632
680
  }
633
681
  function mapConfigEntries(entries, refs) {
634
682
  const sourceKindByRoot = new Map;
635
683
  for (const root of Object.keys(refs)) {
636
684
  sourceKindByRoot.set(root, "explicit");
637
685
  }
638
- for (const [refText, rawEntry] of Object.entries(entries)) {
686
+ for (const [shortCode, rawEntry] of Object.entries(entries)) {
639
687
  const entry = rawEntry;
640
688
  if (!entry || typeof entry !== "object")
641
689
  continue;
642
690
  if (!Array.isArray(entry.names))
643
691
  continue;
644
- const refId = decodeDomainRefKey(refText);
645
692
  for (const rawName of entry.names) {
646
693
  if (typeof rawName !== "string")
647
694
  continue;
648
- const existingNameRef = refs[rawName];
649
- if (existingNameRef !== undefined && existingNameRef !== refId) {
650
- throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
695
+ const existingRef = refs[rawName];
696
+ if (existingRef !== undefined && existingRef !== shortCode) {
697
+ throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
651
698
  }
652
- refs[rawName] = refId;
699
+ refs[rawName] = shortCode;
653
700
  const root = rawName.split(".")[0];
654
701
  if (!root)
655
702
  continue;
656
703
  const currentKind = rawName.includes(".") ? "implicit" : "explicit";
657
704
  const existing = refs[root];
658
705
  if (existing !== undefined) {
659
- if (existing === refId)
706
+ if (existing === shortCode)
660
707
  continue;
661
708
  const existingKind = sourceKindByRoot.get(root) ?? "explicit";
662
709
  if (currentKind === "explicit") {
663
- throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
710
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
664
711
  }
665
712
  if (existingKind === "explicit")
666
713
  continue;
667
714
  continue;
668
715
  }
669
- refs[root] = refId;
716
+ refs[root] = shortCode;
670
717
  sourceKindByRoot.set(root, currentKind);
671
718
  }
672
719
  }
@@ -812,20 +859,11 @@ function gatherEncodedValueSpans(text) {
812
859
  }
813
860
  return spans;
814
861
  }
815
- function buildPointerToken(pointerStart, targetStart) {
816
- let offset = targetStart - (pointerStart + 1);
862
+ function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
863
+ const offset = targetStart - pointerStart - occurrenceSize;
817
864
  if (offset < 0)
818
865
  return;
819
- for (let guard = 0;guard < 8; guard += 1) {
820
- const prefix = encodeUint(offset);
821
- const recalculated = targetStart - (pointerStart + prefix.length + 1);
822
- if (recalculated === offset)
823
- return `${prefix}^`;
824
- offset = recalculated;
825
- if (offset < 0)
826
- return;
827
- }
828
- return;
866
+ return `${encodeUint(offset)}^`;
829
867
  }
830
868
  function buildDedupeCandidateTable(encoded, minBytes) {
831
869
  const spans = gatherEncodedValueSpans(encoded);
@@ -867,7 +905,7 @@ function dedupeLargeEncodedValues(encoded, minBytes = 4) {
867
905
  if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
868
906
  continue;
869
907
  const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
870
- const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
908
+ const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
871
909
  if (!pointerToken)
872
910
  continue;
873
911
  if (pointerToken.length >= occurrence.sizeBytes)
@@ -896,819 +934,8 @@ function encodeIR(node, options) {
896
934
  activeEncodeOptions = previous;
897
935
  }
898
936
  }
899
- function cloneNode(node) {
900
- return structuredClone(node);
901
- }
902
- function emptyOptimizeEnv() {
903
- return { constants: {}, selfCaptures: {} };
904
- }
905
- function cloneOptimizeEnv(env) {
906
- return {
907
- constants: { ...env.constants },
908
- selfCaptures: { ...env.selfCaptures }
909
- };
910
- }
911
- function clearOptimizeEnv(env) {
912
- for (const key of Object.keys(env.constants))
913
- delete env.constants[key];
914
- for (const key of Object.keys(env.selfCaptures))
915
- delete env.selfCaptures[key];
916
- }
917
- function clearBinding(env, name) {
918
- delete env.constants[name];
919
- delete env.selfCaptures[name];
920
- }
921
- function selfTargetFromNode(node, currentDepth) {
922
- if (node.type === "self")
923
- return currentDepth;
924
- if (node.type === "selfDepth") {
925
- const target = currentDepth - (node.depth - 1);
926
- if (target >= 1)
927
- return target;
928
- }
929
- return;
930
- }
931
- function selfNodeFromTarget(targetDepth, currentDepth) {
932
- const relDepth = currentDepth - targetDepth + 1;
933
- if (!Number.isInteger(relDepth) || relDepth < 1)
934
- return;
935
- if (relDepth === 1)
936
- return { type: "self" };
937
- return { type: "selfDepth", depth: relDepth };
938
- }
939
- function dropBindingNames(env, binding) {
940
- if (binding.type === "binding:valueIn") {
941
- clearBinding(env, binding.value);
942
- return;
943
- }
944
- if (binding.type === "binding:keyValueIn") {
945
- clearBinding(env, binding.key);
946
- clearBinding(env, binding.value);
947
- return;
948
- }
949
- if (binding.type === "binding:keyOf") {
950
- clearBinding(env, binding.key);
951
- }
952
- }
953
- function collectReads(node, out) {
954
- switch (node.type) {
955
- case "identifier":
956
- out.add(node.name);
957
- return;
958
- case "group":
959
- collectReads(node.expression, out);
960
- return;
961
- case "array":
962
- for (const item of node.items)
963
- collectReads(item, out);
964
- return;
965
- case "object":
966
- for (const entry of node.entries) {
967
- collectReads(entry.key, out);
968
- collectReads(entry.value, out);
969
- }
970
- return;
971
- case "arrayComprehension":
972
- collectReads(node.binding.source, out);
973
- collectReads(node.body, out);
974
- return;
975
- case "objectComprehension":
976
- collectReads(node.binding.source, out);
977
- collectReads(node.key, out);
978
- collectReads(node.value, out);
979
- return;
980
- case "unary":
981
- collectReads(node.value, out);
982
- return;
983
- case "binary":
984
- collectReads(node.left, out);
985
- collectReads(node.right, out);
986
- return;
987
- case "assign":
988
- if (!(node.op === "=" && node.place.type === "identifier"))
989
- collectReads(node.place, out);
990
- collectReads(node.value, out);
991
- return;
992
- case "navigation":
993
- collectReads(node.target, out);
994
- for (const segment of node.segments) {
995
- if (segment.type === "dynamic")
996
- collectReads(segment.key, out);
997
- }
998
- return;
999
- case "call":
1000
- collectReads(node.callee, out);
1001
- for (const arg of node.args)
1002
- collectReads(arg, out);
1003
- return;
1004
- case "conditional":
1005
- collectReads(node.condition, out);
1006
- for (const part of node.thenBlock)
1007
- collectReads(part, out);
1008
- if (node.elseBranch)
1009
- collectReadsElse(node.elseBranch, out);
1010
- return;
1011
- case "for":
1012
- collectReads(node.binding.source, out);
1013
- for (const part of node.body)
1014
- collectReads(part, out);
1015
- return;
1016
- case "program":
1017
- for (const part of node.body)
1018
- collectReads(part, out);
1019
- return;
1020
- default:
1021
- return;
1022
- }
1023
- }
1024
- function collectReadsElse(elseBranch, out) {
1025
- if (elseBranch.type === "else") {
1026
- for (const part of elseBranch.block)
1027
- collectReads(part, out);
1028
- return;
1029
- }
1030
- collectReads(elseBranch.condition, out);
1031
- for (const part of elseBranch.thenBlock)
1032
- collectReads(part, out);
1033
- if (elseBranch.elseBranch)
1034
- collectReadsElse(elseBranch.elseBranch, out);
1035
- }
1036
- function isPureNode(node) {
1037
- switch (node.type) {
1038
- case "identifier":
1039
- case "self":
1040
- case "selfDepth":
1041
- case "boolean":
1042
- case "null":
1043
- case "undefined":
1044
- case "number":
1045
- case "string":
1046
- case "key":
1047
- return true;
1048
- case "group":
1049
- return isPureNode(node.expression);
1050
- case "array":
1051
- return node.items.every((item) => isPureNode(item));
1052
- case "object":
1053
- return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
1054
- case "navigation":
1055
- return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
1056
- case "unary":
1057
- return node.op !== "delete" && isPureNode(node.value);
1058
- case "binary":
1059
- return isPureNode(node.left) && isPureNode(node.right);
1060
- default:
1061
- return false;
1062
- }
1063
- }
1064
- function eliminateDeadAssignments(block) {
1065
- const needed = new Set;
1066
- const out = [];
1067
- for (let index = block.length - 1;index >= 0; index -= 1) {
1068
- const node = block[index];
1069
- if (node.type === "conditional") {
1070
- let rewritten = node;
1071
- if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
1072
- const name = node.condition.place.name;
1073
- const branchReads = new Set;
1074
- for (const part of node.thenBlock)
1075
- collectReads(part, branchReads);
1076
- if (node.elseBranch)
1077
- collectReadsElse(node.elseBranch, branchReads);
1078
- if (!needed.has(name) && !branchReads.has(name)) {
1079
- rewritten = {
1080
- type: "conditional",
1081
- head: node.head,
1082
- condition: node.condition.value,
1083
- thenBlock: node.thenBlock,
1084
- elseBranch: node.elseBranch
1085
- };
1086
- }
1087
- }
1088
- collectReads(rewritten, needed);
1089
- out.push(rewritten);
1090
- continue;
1091
- }
1092
- if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
1093
- collectReads(node.value, needed);
1094
- const name = node.place.name;
1095
- const canDrop = !needed.has(name) && isPureNode(node.value);
1096
- needed.delete(name);
1097
- if (canDrop)
1098
- continue;
1099
- out.push(node);
1100
- continue;
1101
- }
1102
- collectReads(node, needed);
1103
- out.push(node);
1104
- }
1105
- out.reverse();
1106
- return out;
1107
- }
1108
- function hasIdentifierRead(node, name, asPlace = false) {
1109
- if (node.type === "identifier")
1110
- return !asPlace && node.name === name;
1111
- switch (node.type) {
1112
- case "group":
1113
- return hasIdentifierRead(node.expression, name);
1114
- case "array":
1115
- return node.items.some((item) => hasIdentifierRead(item, name));
1116
- case "object":
1117
- return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
1118
- case "navigation":
1119
- return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
1120
- case "unary":
1121
- return hasIdentifierRead(node.value, name, node.op === "delete");
1122
- case "binary":
1123
- return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1124
- case "assign":
1125
- return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1126
- default:
1127
- return false;
1128
- }
1129
- }
1130
- function countIdentifierReads(node, name, asPlace = false) {
1131
- if (node.type === "identifier")
1132
- return !asPlace && node.name === name ? 1 : 0;
1133
- switch (node.type) {
1134
- case "group":
1135
- return countIdentifierReads(node.expression, name);
1136
- case "array":
1137
- return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
1138
- case "object":
1139
- return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
1140
- case "navigation":
1141
- return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
1142
- case "unary":
1143
- return countIdentifierReads(node.value, name, node.op === "delete");
1144
- case "binary":
1145
- return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1146
- case "assign":
1147
- return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1148
- default:
1149
- return 0;
1150
- }
1151
- }
1152
- function replaceIdentifier(node, name, replacement, asPlace = false) {
1153
- if (node.type === "identifier") {
1154
- if (!asPlace && node.name === name)
1155
- return cloneNode(replacement);
1156
- return node;
1157
- }
1158
- switch (node.type) {
1159
- case "group":
1160
- return {
1161
- type: "group",
1162
- expression: replaceIdentifier(node.expression, name, replacement)
1163
- };
1164
- case "array":
1165
- return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
1166
- case "object":
1167
- return {
1168
- type: "object",
1169
- entries: node.entries.map((entry) => ({
1170
- key: replaceIdentifier(entry.key, name, replacement),
1171
- value: replaceIdentifier(entry.value, name, replacement)
1172
- }))
1173
- };
1174
- case "navigation":
1175
- return {
1176
- type: "navigation",
1177
- target: replaceIdentifier(node.target, name, replacement),
1178
- segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
1179
- };
1180
- case "unary":
1181
- return {
1182
- type: "unary",
1183
- op: node.op,
1184
- value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
1185
- };
1186
- case "binary":
1187
- return {
1188
- type: "binary",
1189
- op: node.op,
1190
- left: replaceIdentifier(node.left, name, replacement),
1191
- right: replaceIdentifier(node.right, name, replacement)
1192
- };
1193
- case "assign":
1194
- return {
1195
- type: "assign",
1196
- op: node.op,
1197
- place: replaceIdentifier(node.place, name, replacement, true),
1198
- value: replaceIdentifier(node.value, name, replacement)
1199
- };
1200
- default:
1201
- return node;
1202
- }
1203
- }
1204
- function isSafeInlineTargetNode(node) {
1205
- if (isPureNode(node))
1206
- return true;
1207
- if (node.type === "assign" && node.op === "=") {
1208
- return isPureNode(node.place) && isPureNode(node.value);
1209
- }
1210
- return false;
1211
- }
1212
- function inlineAdjacentPureAssignments(block) {
1213
- const out = [...block];
1214
- let changed = true;
1215
- while (changed) {
1216
- changed = false;
1217
- for (let index = 0;index < out.length - 1; index += 1) {
1218
- const current = out[index];
1219
- if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
1220
- continue;
1221
- if (!isPureNode(current.value))
1222
- continue;
1223
- const name = current.place.name;
1224
- if (hasIdentifierRead(current.value, name))
1225
- continue;
1226
- const next = out[index + 1];
1227
- if (!isSafeInlineTargetNode(next))
1228
- continue;
1229
- if (countIdentifierReads(next, name) !== 1)
1230
- continue;
1231
- out[index + 1] = replaceIdentifier(next, name, current.value);
1232
- out.splice(index, 1);
1233
- changed = true;
1234
- break;
1235
- }
1236
- }
1237
- return out;
1238
- }
1239
- function toNumberNode(value) {
1240
- let raw;
1241
- if (Number.isNaN(value))
1242
- raw = "nan";
1243
- else if (value === Infinity)
1244
- raw = "inf";
1245
- else if (value === -Infinity)
1246
- raw = "-inf";
1247
- else
1248
- raw = String(value);
1249
- return { type: "number", raw, value };
1250
- }
1251
- function toStringNode(value) {
1252
- return { type: "string", raw: JSON.stringify(value) };
1253
- }
1254
- function toLiteralNode(value) {
1255
- if (value === undefined)
1256
- return { type: "undefined" };
1257
- if (value === null)
1258
- return { type: "null" };
1259
- if (typeof value === "boolean")
1260
- return { type: "boolean", value };
1261
- if (typeof value === "number")
1262
- return toNumberNode(value);
1263
- if (typeof value === "string")
1264
- return toStringNode(value);
1265
- if (Array.isArray(value)) {
1266
- const items = [];
1267
- for (const item of value) {
1268
- const lowered = toLiteralNode(item);
1269
- if (!lowered)
1270
- return;
1271
- items.push(lowered);
1272
- }
1273
- return { type: "array", items };
1274
- }
1275
- if (value && typeof value === "object") {
1276
- const entries = [];
1277
- for (const [key, entryValue] of Object.entries(value)) {
1278
- const loweredValue = toLiteralNode(entryValue);
1279
- if (!loweredValue)
1280
- return;
1281
- entries.push({ key: { type: "key", name: key }, value: loweredValue });
1282
- }
1283
- return { type: "object", entries };
1284
- }
1285
- return;
1286
- }
1287
- function constValue(node) {
1288
- switch (node.type) {
1289
- case "undefined":
1290
- return;
1291
- case "null":
1292
- return null;
1293
- case "boolean":
1294
- return node.value;
1295
- case "number":
1296
- return node.value;
1297
- case "string":
1298
- return decodeStringLiteral(node.raw);
1299
- case "key":
1300
- return node.name;
1301
- case "array": {
1302
- const out = [];
1303
- for (const item of node.items) {
1304
- const value = constValue(item);
1305
- if (value === undefined && item.type !== "undefined")
1306
- return;
1307
- out.push(value);
1308
- }
1309
- return out;
1310
- }
1311
- case "object": {
1312
- const out = {};
1313
- for (const entry of node.entries) {
1314
- const key = constValue(entry.key);
1315
- if (key === undefined && entry.key.type !== "undefined")
1316
- return;
1317
- const value = constValue(entry.value);
1318
- if (value === undefined && entry.value.type !== "undefined")
1319
- return;
1320
- out[String(key)] = value;
1321
- }
1322
- return out;
1323
- }
1324
- default:
1325
- return;
1326
- }
1327
- }
1328
- function isDefinedValue(value) {
1329
- return value !== undefined;
1330
- }
1331
- function foldUnary(op, value) {
1332
- if (op === "neg") {
1333
- if (typeof value !== "number")
1334
- return;
1335
- return -value;
1336
- }
1337
- if (op === "not") {
1338
- if (typeof value === "boolean")
1339
- return !value;
1340
- if (typeof value === "number")
1341
- return ~value;
1342
- return;
1343
- }
1344
- return;
1345
- }
1346
- function foldBinary(op, left, right) {
1347
- if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
1348
- if (typeof left !== "number" || typeof right !== "number")
1349
- return;
1350
- if (op === "add")
1351
- return left + right;
1352
- if (op === "sub")
1353
- return left - right;
1354
- if (op === "mul")
1355
- return left * right;
1356
- if (op === "div")
1357
- return left / right;
1358
- return left % right;
1359
- }
1360
- if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
1361
- if (typeof left !== "number" || typeof right !== "number")
1362
- return;
1363
- if (op === "bitAnd")
1364
- return left & right;
1365
- if (op === "bitOr")
1366
- return left | right;
1367
- return left ^ right;
1368
- }
1369
- if (op === "eq")
1370
- return left === right ? left : undefined;
1371
- if (op === "neq")
1372
- return left !== right ? left : undefined;
1373
- if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
1374
- if (typeof left !== "number" || typeof right !== "number")
1375
- return;
1376
- if (op === "gt")
1377
- return left > right ? left : undefined;
1378
- if (op === "gte")
1379
- return left >= right ? left : undefined;
1380
- if (op === "lt")
1381
- return left < right ? left : undefined;
1382
- return left <= right ? left : undefined;
1383
- }
1384
- if (op === "and")
1385
- return isDefinedValue(left) ? right : undefined;
1386
- if (op === "or")
1387
- return isDefinedValue(left) ? left : right;
1388
- return;
1389
- }
1390
- function optimizeElse(elseBranch, env, currentDepth) {
1391
- if (!elseBranch)
1392
- return;
1393
- if (elseBranch.type === "else") {
1394
- return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
1395
- }
1396
- const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
1397
- const foldedCondition = constValue(optimizedCondition);
1398
- if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
1399
- const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
1400
- if (passes) {
1401
- return {
1402
- type: "else",
1403
- block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
1404
- };
1405
- }
1406
- return optimizeElse(elseBranch.elseBranch, env, currentDepth);
1407
- }
1408
- return {
1409
- type: "elseChain",
1410
- head: elseBranch.head,
1411
- condition: optimizedCondition,
1412
- thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
1413
- elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
1414
- };
1415
- }
1416
- function optimizeBlock(block, env, currentDepth) {
1417
- const out = [];
1418
- for (const node of block) {
1419
- const optimized = optimizeNode(node, env, currentDepth);
1420
- out.push(optimized);
1421
- if (optimized.type === "break" || optimized.type === "continue")
1422
- break;
1423
- if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
1424
- const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
1425
- if (selfTarget !== undefined) {
1426
- env.selfCaptures[optimized.place.name] = selfTarget;
1427
- delete env.constants[optimized.place.name];
1428
- continue;
1429
- }
1430
- const folded = constValue(optimized.value);
1431
- if (folded !== undefined || optimized.value.type === "undefined") {
1432
- env.constants[optimized.place.name] = cloneNode(optimized.value);
1433
- delete env.selfCaptures[optimized.place.name];
1434
- } else {
1435
- clearBinding(env, optimized.place.name);
1436
- }
1437
- continue;
1438
- }
1439
- if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
1440
- clearBinding(env, optimized.value.name);
1441
- continue;
1442
- }
1443
- if (optimized.type === "assign" && optimized.place.type === "identifier") {
1444
- clearBinding(env, optimized.place.name);
1445
- continue;
1446
- }
1447
- if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
1448
- clearOptimizeEnv(env);
1449
- }
1450
- }
1451
- return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
1452
- }
1453
- function optimizeNode(node, env, currentDepth, asPlace = false) {
1454
- switch (node.type) {
1455
- case "program": {
1456
- const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
1457
- if (body.length === 0)
1458
- return { type: "undefined" };
1459
- if (body.length === 1)
1460
- return body[0];
1461
- return { type: "program", body };
1462
- }
1463
- case "identifier": {
1464
- if (asPlace)
1465
- return node;
1466
- const selfTarget = env.selfCaptures[node.name];
1467
- if (selfTarget !== undefined) {
1468
- const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
1469
- if (rewritten)
1470
- return rewritten;
1471
- }
1472
- const replacement = env.constants[node.name];
1473
- return replacement ? cloneNode(replacement) : node;
1474
- }
1475
- case "group": {
1476
- return optimizeNode(node.expression, env, currentDepth);
1477
- }
1478
- case "array": {
1479
- return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
1480
- }
1481
- case "object": {
1482
- return {
1483
- type: "object",
1484
- entries: node.entries.map((entry) => ({
1485
- key: optimizeNode(entry.key, env, currentDepth),
1486
- value: optimizeNode(entry.value, env, currentDepth)
1487
- }))
1488
- };
1489
- }
1490
- case "unary": {
1491
- const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
1492
- const foldedValue = constValue(value);
1493
- if (foldedValue !== undefined || value.type === "undefined") {
1494
- const folded = foldUnary(node.op, foldedValue);
1495
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1496
- if (literal)
1497
- return literal;
1498
- }
1499
- return { type: "unary", op: node.op, value };
1500
- }
1501
- case "binary": {
1502
- const left = optimizeNode(node.left, env, currentDepth);
1503
- const right = optimizeNode(node.right, env, currentDepth);
1504
- const leftValue = constValue(left);
1505
- const rightValue = constValue(right);
1506
- if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
1507
- const folded = foldBinary(node.op, leftValue, rightValue);
1508
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1509
- if (literal)
1510
- return literal;
1511
- }
1512
- return { type: "binary", op: node.op, left, right };
1513
- }
1514
- case "navigation": {
1515
- const target = optimizeNode(node.target, env, currentDepth);
1516
- const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
1517
- const targetValue = constValue(target);
1518
- if (targetValue !== undefined || target.type === "undefined") {
1519
- let current = targetValue;
1520
- let foldable = true;
1521
- for (const segment of segments) {
1522
- if (!foldable)
1523
- break;
1524
- const key = segment.type === "static" ? segment.key : constValue(segment.key);
1525
- if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
1526
- foldable = false;
1527
- break;
1528
- }
1529
- if (current === null || current === undefined) {
1530
- current = undefined;
1531
- continue;
1532
- }
1533
- current = current[String(key)];
1534
- }
1535
- if (foldable) {
1536
- const literal = toLiteralNode(current);
1537
- if (literal)
1538
- return literal;
1539
- }
1540
- }
1541
- return {
1542
- type: "navigation",
1543
- target,
1544
- segments
1545
- };
1546
- }
1547
- case "call": {
1548
- return {
1549
- type: "call",
1550
- callee: optimizeNode(node.callee, env, currentDepth),
1551
- args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
1552
- };
1553
- }
1554
- case "assign": {
1555
- return {
1556
- type: "assign",
1557
- op: node.op,
1558
- place: optimizeNode(node.place, env, currentDepth, true),
1559
- value: optimizeNode(node.value, env, currentDepth)
1560
- };
1561
- }
1562
- case "conditional": {
1563
- const condition = optimizeNode(node.condition, env, currentDepth);
1564
- const thenEnv = cloneOptimizeEnv(env);
1565
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1566
- thenEnv.selfCaptures[condition.place.name] = currentDepth;
1567
- delete thenEnv.constants[condition.place.name];
1568
- }
1569
- const conditionValue = constValue(condition);
1570
- if (conditionValue !== undefined || condition.type === "undefined") {
1571
- const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1572
- if (passes) {
1573
- const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1574
- if (thenBlock2.length === 0)
1575
- return { type: "undefined" };
1576
- if (thenBlock2.length === 1)
1577
- return thenBlock2[0];
1578
- return { type: "program", body: thenBlock2 };
1579
- }
1580
- if (!node.elseBranch)
1581
- return { type: "undefined" };
1582
- const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1583
- if (!loweredElse)
1584
- return { type: "undefined" };
1585
- if (loweredElse.type === "else") {
1586
- if (loweredElse.block.length === 0)
1587
- return { type: "undefined" };
1588
- if (loweredElse.block.length === 1)
1589
- return loweredElse.block[0];
1590
- return { type: "program", body: loweredElse.block };
1591
- }
1592
- return {
1593
- type: "conditional",
1594
- head: loweredElse.head,
1595
- condition: loweredElse.condition,
1596
- thenBlock: loweredElse.thenBlock,
1597
- elseBranch: loweredElse.elseBranch
1598
- };
1599
- }
1600
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1601
- const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1602
- let finalCondition = condition;
1603
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1604
- const name = condition.place.name;
1605
- const reads = new Set;
1606
- for (const part of thenBlock)
1607
- collectReads(part, reads);
1608
- if (elseBranch)
1609
- collectReadsElse(elseBranch, reads);
1610
- if (!reads.has(name)) {
1611
- finalCondition = condition.value;
1612
- }
1613
- }
1614
- return {
1615
- type: "conditional",
1616
- head: node.head,
1617
- condition: finalCondition,
1618
- thenBlock,
1619
- elseBranch
1620
- };
1621
- }
1622
- case "for": {
1623
- 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
- })();
1649
- const bodyEnv = cloneOptimizeEnv(env);
1650
- dropBindingNames(bodyEnv, binding);
1651
- return {
1652
- type: "for",
1653
- binding,
1654
- body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
1655
- };
1656
- }
1657
- case "arrayComprehension": {
1658
- 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
- };
1673
- const bodyEnv = cloneOptimizeEnv(env);
1674
- dropBindingNames(bodyEnv, binding);
1675
- return {
1676
- type: "arrayComprehension",
1677
- binding,
1678
- body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1679
- };
1680
- }
1681
- case "objectComprehension": {
1682
- 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
- };
1697
- const bodyEnv = cloneOptimizeEnv(env);
1698
- dropBindingNames(bodyEnv, binding);
1699
- return {
1700
- type: "objectComprehension",
1701
- binding,
1702
- key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
1703
- value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1704
- };
1705
- }
1706
- default:
1707
- return node;
1708
- }
1709
- }
1710
937
  function optimizeIR(node) {
1711
- return optimizeNode(node, emptyOptimizeEnv(), 1);
938
+ return node;
1712
939
  }
1713
940
  function collectLocalBindings(node, locals) {
1714
941
  switch (node.type) {
@@ -1754,6 +981,10 @@ function collectLocalBindings(node, locals) {
1754
981
  collectLocalBindings(node.left, locals);
1755
982
  collectLocalBindings(node.right, locals);
1756
983
  return;
984
+ case "range":
985
+ collectLocalBindings(node.from, locals);
986
+ collectLocalBindings(node.to, locals);
987
+ return;
1757
988
  case "conditional":
1758
989
  collectLocalBindings(node.condition, locals);
1759
990
  for (const part of node.thenBlock)
@@ -1770,11 +1001,20 @@ function collectLocalBindings(node, locals) {
1770
1001
  collectLocalBindingFromBinding(node.binding, locals);
1771
1002
  collectLocalBindings(node.body, locals);
1772
1003
  return;
1004
+ case "whileArrayComprehension":
1005
+ collectLocalBindings(node.condition, locals);
1006
+ collectLocalBindings(node.body, locals);
1007
+ return;
1773
1008
  case "objectComprehension":
1774
1009
  collectLocalBindingFromBinding(node.binding, locals);
1775
1010
  collectLocalBindings(node.key, locals);
1776
1011
  collectLocalBindings(node.value, locals);
1777
1012
  return;
1013
+ case "whileObjectComprehension":
1014
+ collectLocalBindings(node.condition, locals);
1015
+ collectLocalBindings(node.key, locals);
1016
+ collectLocalBindings(node.value, locals);
1017
+ return;
1778
1018
  default:
1779
1019
  return;
1780
1020
  }
@@ -1866,6 +1106,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1866
1106
  collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
1867
1107
  collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
1868
1108
  return;
1109
+ case "range":
1110
+ collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
1111
+ collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
1112
+ return;
1869
1113
  case "conditional":
1870
1114
  collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1871
1115
  for (const part of node.thenBlock)
@@ -1882,11 +1126,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1882
1126
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1883
1127
  collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1884
1128
  return;
1129
+ case "whileArrayComprehension":
1130
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1131
+ collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1132
+ return;
1885
1133
  case "objectComprehension":
1886
1134
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1887
1135
  collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1888
1136
  collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1889
1137
  return;
1138
+ case "whileObjectComprehension":
1139
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1140
+ collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1141
+ collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1142
+ return;
1890
1143
  default:
1891
1144
  return;
1892
1145
  }
@@ -1961,6 +1214,12 @@ function renameLocalNames(node, map) {
1961
1214
  left: renameLocalNames(node.left, map),
1962
1215
  right: renameLocalNames(node.right, map)
1963
1216
  };
1217
+ case "range":
1218
+ return {
1219
+ type: "range",
1220
+ from: renameLocalNames(node.from, map),
1221
+ to: renameLocalNames(node.to, map)
1222
+ };
1964
1223
  case "assign": {
1965
1224
  const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
1966
1225
  return {
@@ -1990,6 +1249,12 @@ function renameLocalNames(node, map) {
1990
1249
  binding: renameLocalNamesBinding(node.binding, map),
1991
1250
  body: renameLocalNames(node.body, map)
1992
1251
  };
1252
+ case "whileArrayComprehension":
1253
+ return {
1254
+ type: "whileArrayComprehension",
1255
+ condition: renameLocalNames(node.condition, map),
1256
+ body: renameLocalNames(node.body, map)
1257
+ };
1993
1258
  case "objectComprehension":
1994
1259
  return {
1995
1260
  type: "objectComprehension",
@@ -1997,34 +1262,31 @@ function renameLocalNames(node, map) {
1997
1262
  key: renameLocalNames(node.key, map),
1998
1263
  value: renameLocalNames(node.value, map)
1999
1264
  };
1265
+ case "whileObjectComprehension":
1266
+ return {
1267
+ type: "whileObjectComprehension",
1268
+ condition: renameLocalNames(node.condition, map),
1269
+ key: renameLocalNames(node.key, map),
1270
+ value: renameLocalNames(node.value, map)
1271
+ };
2000
1272
  default:
2001
1273
  return node;
2002
1274
  }
2003
1275
  }
2004
1276
  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
- };
1277
+ const source = renameLocalNames(binding.source, map);
1278
+ switch (binding.type) {
1279
+ case "binding:bareIn":
1280
+ return { type: "binding:bareIn", source };
1281
+ case "binding:bareOf":
1282
+ return { type: "binding:bareOf", source };
1283
+ case "binding:valueIn":
1284
+ return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
1285
+ case "binding:keyValueIn":
1286
+ return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
1287
+ case "binding:keyOf":
1288
+ return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
2014
1289
  }
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
- };
2022
- }
2023
- return {
2024
- type: "binding:keyOf",
2025
- key: map.get(binding.key) ?? binding.key,
2026
- source: renameLocalNames(binding.source, map)
2027
- };
2028
1290
  }
2029
1291
  function renameLocalNamesElse(elseBranch, map) {
2030
1292
  if (elseBranch.type === "else") {
@@ -2071,9 +1333,9 @@ function compile(source, options) {
2071
1333
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
2072
1334
  if (options?.minifyNames)
2073
1335
  lowered = minifyLocalNamesIR(lowered);
2074
- const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1336
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2075
1337
  return encodeIR(lowered, {
2076
- domainRefs,
1338
+ ...domainMaps,
2077
1339
  dedupeValues: options?.dedupeValues,
2078
1340
  dedupeMinBytes: options?.dedupeMinBytes
2079
1341
  });
@@ -2199,6 +1461,9 @@ semantics.addOperation("toIR", {
2199
1461
  ExistenceExpr_or(left, _or, right) {
2200
1462
  return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
2201
1463
  },
1464
+ ExistenceExpr_nor(left, _nor, right) {
1465
+ return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
1466
+ },
2202
1467
  BitExpr_and(left, _op, right) {
2203
1468
  return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
2204
1469
  },
@@ -2208,6 +1473,9 @@ semantics.addOperation("toIR", {
2208
1473
  BitExpr_or(left, _op, right) {
2209
1474
  return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
2210
1475
  },
1476
+ RangeExpr_range(left, _op, right) {
1477
+ return { type: "range", from: left.toIR(), to: right.toIR() };
1478
+ },
2211
1479
  CompareExpr_binary(left, op, right) {
2212
1480
  const map = {
2213
1481
  "==": "eq",
@@ -2248,6 +1516,9 @@ semantics.addOperation("toIR", {
2248
1516
  UnaryExpr_not(_op, value) {
2249
1517
  return { type: "unary", op: "not", value: value.toIR() };
2250
1518
  },
1519
+ UnaryExpr_logicalNot(_not, value) {
1520
+ return { type: "unary", op: "logicalNot", value: value.toIR() };
1521
+ },
2251
1522
  UnaryExpr_delete(_del, place) {
2252
1523
  return { type: "unary", op: "delete", value: place.toIR() };
2253
1524
  },
@@ -2301,14 +1572,6 @@ semantics.addOperation("toIR", {
2301
1572
  ConditionalElse_else(_else, block) {
2302
1573
  return { type: "else", block: block.toIR() };
2303
1574
  },
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
1575
  WhileExpr(_while, condition, _do, block, _end) {
2313
1576
  return {
2314
1577
  type: "while",
@@ -2323,30 +1586,44 @@ semantics.addOperation("toIR", {
2323
1586
  body: block.toIR()
2324
1587
  };
2325
1588
  },
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
1589
  Array_empty(_open, _close) {
2334
1590
  return { type: "array", items: [] };
2335
1591
  },
2336
- Array_comprehension(_open, binding, _semi, body, _close) {
1592
+ Array_forComprehension(_open, body, _for, binding, _close) {
2337
1593
  return {
2338
1594
  type: "arrayComprehension",
2339
1595
  binding: binding.toIR(),
2340
1596
  body: body.toIR()
2341
1597
  };
2342
1598
  },
1599
+ Array_whileComprehension(_open, body, _while, condition, _close) {
1600
+ return {
1601
+ type: "whileArrayComprehension",
1602
+ condition: condition.toIR(),
1603
+ body: body.toIR()
1604
+ };
1605
+ },
1606
+ Array_inComprehension(_open, body, _in, source, _close) {
1607
+ return {
1608
+ type: "arrayComprehension",
1609
+ binding: { type: "binding:bareIn", source: source.toIR() },
1610
+ body: body.toIR()
1611
+ };
1612
+ },
1613
+ Array_ofComprehension(_open, body, _of, source, _close) {
1614
+ return {
1615
+ type: "arrayComprehension",
1616
+ binding: { type: "binding:bareOf", source: source.toIR() },
1617
+ body: body.toIR()
1618
+ };
1619
+ },
2343
1620
  Array_values(_open, items, _close) {
2344
1621
  return { type: "array", items: normalizeList(items.toIR()) };
2345
1622
  },
2346
1623
  Object_empty(_open, _close) {
2347
1624
  return { type: "object", entries: [] };
2348
1625
  },
2349
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
1626
+ Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
2350
1627
  return {
2351
1628
  type: "objectComprehension",
2352
1629
  binding: binding.toIR(),
@@ -2354,6 +1631,30 @@ semantics.addOperation("toIR", {
2354
1631
  value: value.toIR()
2355
1632
  };
2356
1633
  },
1634
+ Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
1635
+ return {
1636
+ type: "whileObjectComprehension",
1637
+ condition: condition.toIR(),
1638
+ key: key.toIR(),
1639
+ value: value.toIR()
1640
+ };
1641
+ },
1642
+ Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
1643
+ return {
1644
+ type: "objectComprehension",
1645
+ binding: { type: "binding:bareIn", source: source.toIR() },
1646
+ key: key.toIR(),
1647
+ value: value.toIR()
1648
+ };
1649
+ },
1650
+ Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
1651
+ return {
1652
+ type: "objectComprehension",
1653
+ binding: { type: "binding:bareOf", source: source.toIR() },
1654
+ key: key.toIR(),
1655
+ value: value.toIR()
1656
+ };
1657
+ },
2357
1658
  Object_pairs(_open, pairs, _close) {
2358
1659
  return {
2359
1660
  type: "object",
@@ -2382,6 +1683,40 @@ semantics.addOperation("toIR", {
2382
1683
  source: source.toIR()
2383
1684
  };
2384
1685
  },
1686
+ IterBinding_bareIn(_in, source) {
1687
+ return {
1688
+ type: "binding:bareIn",
1689
+ source: source.toIR()
1690
+ };
1691
+ },
1692
+ IterBinding_bareOf(_of, source) {
1693
+ return {
1694
+ type: "binding:bareOf",
1695
+ source: source.toIR()
1696
+ };
1697
+ },
1698
+ IterBindingComprehension_keyValueIn(key, _comma, value, _in, source) {
1699
+ return {
1700
+ type: "binding:keyValueIn",
1701
+ key: key.sourceString,
1702
+ value: value.sourceString,
1703
+ source: source.toIR()
1704
+ };
1705
+ },
1706
+ IterBindingComprehension_valueIn(value, _in, source) {
1707
+ return {
1708
+ type: "binding:valueIn",
1709
+ value: value.sourceString,
1710
+ source: source.toIR()
1711
+ };
1712
+ },
1713
+ IterBindingComprehension_keyOf(key, _of, source) {
1714
+ return {
1715
+ type: "binding:keyOf",
1716
+ key: key.sourceString,
1717
+ source: source.toIR()
1718
+ };
1719
+ },
2385
1720
  Pair(key, _colon, value) {
2386
1721
  return { key: key.toIR(), value: value.toIR() };
2387
1722
  },
@@ -2467,6 +1802,7 @@ export {
2467
1802
  optimizeIR,
2468
1803
  minifyLocalNamesIR,
2469
1804
  grammar,
1805
+ formatParseError,
2470
1806
  encodeIR,
2471
1807
  domainRefsFromConfig,
2472
1808
  rex_default as default,