@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-repl.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // rex-repl.ts
2
2
  import * as readline from "node:readline";
3
3
  import { createRequire as createRequire2 } from "node:module";
4
+ import { readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { resolve, dirname, basename } from "node:path";
6
+ import { homedir } from "node:os";
4
7
 
5
8
  // rex.ts
6
9
  import { createRequire } from "node:module";
@@ -14,31 +17,31 @@ function byteLength(value) {
14
17
  return Buffer.byteLength(value, "utf8");
15
18
  }
16
19
  var OPCODE_IDS = {
17
- do: 0,
18
- add: 1,
19
- sub: 2,
20
- mul: 3,
21
- div: 4,
22
- eq: 5,
23
- neq: 6,
24
- lt: 7,
25
- lte: 8,
26
- gt: 9,
27
- gte: 10,
28
- and: 11,
29
- or: 12,
30
- xor: 13,
31
- not: 14,
32
- boolean: 15,
33
- number: 16,
34
- string: 17,
35
- array: 18,
36
- object: 19,
37
- mod: 20,
38
- neg: 21
20
+ do: "",
21
+ add: "ad",
22
+ sub: "sb",
23
+ mul: "ml",
24
+ div: "dv",
25
+ eq: "eq",
26
+ neq: "nq",
27
+ lt: "lt",
28
+ lte: "le",
29
+ gt: "gt",
30
+ gte: "ge",
31
+ and: "an",
32
+ or: "or",
33
+ xor: "xr",
34
+ not: "nt",
35
+ boolean: "bt",
36
+ number: "nm",
37
+ string: "st",
38
+ array: "ar",
39
+ object: "ob",
40
+ mod: "md",
41
+ neg: "ng",
42
+ range: "rn"
39
43
  };
40
- var FIRST_NON_RESERVED_REF = 8;
41
- var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
44
+ var KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object"]);
42
45
  var BINARY_TO_OPCODE = {
43
46
  add: "add",
44
47
  sub: "sub",
@@ -167,11 +170,11 @@ function encodeDecimal(significand, power) {
167
170
  function encodeNumberNode(node) {
168
171
  const numberValue = node.value;
169
172
  if (Number.isNaN(numberValue))
170
- return "5'";
173
+ return "nan'";
171
174
  if (numberValue === Infinity)
172
- return "6'";
175
+ return "inf'";
173
176
  if (numberValue === -Infinity)
174
- return "7'";
177
+ return "nif'";
175
178
  if (Number.isInteger(numberValue)) {
176
179
  const { base, exp } = splitDecimal(numberValue);
177
180
  if (exp >= 0 && exp <= 4)
@@ -203,7 +206,7 @@ function encodeNumberNode(node) {
203
206
  return encodeDecimal(significand, power);
204
207
  }
205
208
  function encodeOpcode(opcode) {
206
- return `${encodeUint(OPCODE_IDS[opcode])}%`;
209
+ return `${OPCODE_IDS[opcode]}%`;
207
210
  }
208
211
  function encodeCallParts(parts) {
209
212
  return `(${parts.join("")})`;
@@ -220,7 +223,7 @@ function addOptionalPrefix(encoded) {
220
223
  let payload = encoded;
221
224
  if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
222
225
  payload = encoded.slice(2, -1);
223
- } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
226
+ } else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
224
227
  payload = encoded.slice(2, -1);
225
228
  } else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
226
229
  payload = encoded.slice(1, -1);
@@ -231,7 +234,7 @@ function addOptionalPrefix(encoded) {
231
234
  }
232
235
  function encodeBlockExpression(block) {
233
236
  if (block.length === 0)
234
- return "4'";
237
+ return "un'";
235
238
  if (block.length === 1)
236
239
  return encodeNode(block[0]);
237
240
  return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
@@ -248,9 +251,13 @@ function encodeConditionalElse(elseBranch) {
248
251
  };
249
252
  return encodeNode(nested);
250
253
  }
254
+ function encodeDomainLookup(shortCode, tag) {
255
+ return `${shortCode}${tag}`;
256
+ }
251
257
  function encodeNavigation(node) {
252
258
  const domainRefs = activeEncodeOptions?.domainRefs;
253
- if (domainRefs && node.target.type === "identifier") {
259
+ const domainOpcodes = activeEncodeOptions?.domainOpcodes;
260
+ if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
254
261
  const staticPath = [node.target.name];
255
262
  for (const segment of node.segments) {
256
263
  if (segment.type !== "static")
@@ -259,14 +266,17 @@ function encodeNavigation(node) {
259
266
  }
260
267
  for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
261
268
  const dottedName = staticPath.slice(0, pathLength).join(".");
262
- const domainRef = domainRefs[dottedName];
263
- if (domainRef === undefined)
269
+ const refCode = domainRefs?.[dottedName];
270
+ const opcodeCode = domainOpcodes?.[dottedName];
271
+ const shortCode = refCode ?? opcodeCode;
272
+ if (shortCode === undefined)
264
273
  continue;
274
+ const tag = refCode !== undefined ? "'" : "%";
265
275
  const consumedStaticSegments = pathLength - 1;
266
276
  if (consumedStaticSegments === node.segments.length) {
267
- return `${encodeUint(domainRef)}'`;
277
+ return encodeDomainLookup(shortCode, tag);
268
278
  }
269
- const parts2 = [`${encodeUint(domainRef)}'`];
279
+ const parts2 = [encodeDomainLookup(shortCode, tag)];
270
280
  for (const segment of node.segments.slice(consumedStaticSegments)) {
271
281
  if (segment.type === "static")
272
282
  parts2.push(encodeBareOrLengthString(segment.key));
@@ -292,9 +302,12 @@ function encodeWhile(node) {
292
302
  }
293
303
  function encodeFor(node) {
294
304
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
295
- if (node.binding.type === "binding:expr") {
305
+ if (node.binding.type === "binding:bareIn") {
296
306
  return `>(${encodeNode(node.binding.source)}${body})`;
297
307
  }
308
+ if (node.binding.type === "binding:bareOf") {
309
+ return `<(${encodeNode(node.binding.source)}${body})`;
310
+ }
298
311
  if (node.binding.type === "binding:valueIn") {
299
312
  return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
300
313
  }
@@ -305,30 +318,47 @@ function encodeFor(node) {
305
318
  }
306
319
  function encodeArrayComprehension(node) {
307
320
  const body = addOptionalPrefix(encodeNode(node.body));
308
- if (node.binding.type === "binding:expr") {
321
+ if (node.binding.type === "binding:bareIn") {
309
322
  return `>[${encodeNode(node.binding.source)}${body}]`;
310
323
  }
324
+ if (node.binding.type === "binding:bareOf") {
325
+ return `<[${encodeNode(node.binding.source)}${body}]`;
326
+ }
311
327
  if (node.binding.type === "binding:valueIn") {
312
328
  return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
313
329
  }
314
330
  if (node.binding.type === "binding:keyValueIn") {
315
331
  return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
316
332
  }
317
- return `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
333
+ return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
318
334
  }
319
335
  function encodeObjectComprehension(node) {
320
336
  const key = addOptionalPrefix(encodeNode(node.key));
321
337
  const value = addOptionalPrefix(encodeNode(node.value));
322
- if (node.binding.type === "binding:expr") {
338
+ if (node.binding.type === "binding:bareIn") {
323
339
  return `>{${encodeNode(node.binding.source)}${key}${value}}`;
324
340
  }
341
+ if (node.binding.type === "binding:bareOf") {
342
+ return `<{${encodeNode(node.binding.source)}${key}${value}}`;
343
+ }
325
344
  if (node.binding.type === "binding:valueIn") {
326
345
  return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
327
346
  }
328
347
  if (node.binding.type === "binding:keyValueIn") {
329
348
  return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
330
349
  }
331
- return `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
350
+ return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
351
+ }
352
+ function encodeWhileArrayComprehension(node) {
353
+ const cond = encodeNode(node.condition);
354
+ const body = addOptionalPrefix(encodeNode(node.body));
355
+ return `#[${cond}${body}]`;
356
+ }
357
+ function encodeWhileObjectComprehension(node) {
358
+ const cond = encodeNode(node.condition);
359
+ const key = addOptionalPrefix(encodeNode(node.key));
360
+ const value = addOptionalPrefix(encodeNode(node.value));
361
+ return `#{${cond}${key}${value}}`;
332
362
  }
333
363
  var activeEncodeOptions;
334
364
  function encodeNode(node) {
@@ -338,7 +368,10 @@ function encodeNode(node) {
338
368
  case "identifier": {
339
369
  const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
340
370
  if (domainRef !== undefined)
341
- return `${encodeUint(domainRef)}'`;
371
+ return `${domainRef}'`;
372
+ const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
373
+ if (domainOpcode !== undefined)
374
+ return `${domainOpcode}%`;
342
375
  return `${node.name}$`;
343
376
  }
344
377
  case "self":
@@ -351,11 +384,11 @@ function encodeNode(node) {
351
384
  return `${encodeUint(node.depth - 1)}@`;
352
385
  }
353
386
  case "boolean":
354
- return node.value ? "1'" : "2'";
387
+ return node.value ? "tr'" : "fl'";
355
388
  case "null":
356
- return "3'";
389
+ return "nl'";
357
390
  case "undefined":
358
- return "4'";
391
+ return "un'";
359
392
  case "number":
360
393
  return encodeNumberNode(node);
361
394
  case "string":
@@ -366,12 +399,16 @@ function encodeNode(node) {
366
399
  }
367
400
  case "arrayComprehension":
368
401
  return encodeArrayComprehension(node);
402
+ case "whileArrayComprehension":
403
+ return encodeWhileArrayComprehension(node);
369
404
  case "object": {
370
405
  const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
371
406
  return `{${body}}`;
372
407
  }
373
408
  case "objectComprehension":
374
409
  return encodeObjectComprehension(node);
410
+ case "whileObjectComprehension":
411
+ return encodeWhileObjectComprehension(node);
375
412
  case "key":
376
413
  return encodeBareOrLengthString(node.name);
377
414
  case "group":
@@ -381,6 +418,10 @@ function encodeNode(node) {
381
418
  return `~${encodeNode(node.value)}`;
382
419
  if (node.op === "neg")
383
420
  return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
421
+ if (node.op === "logicalNot") {
422
+ const val = encodeNode(node.value);
423
+ return `!(${val}tr')`;
424
+ }
384
425
  return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
385
426
  case "binary":
386
427
  if (node.op === "and") {
@@ -399,12 +440,19 @@ function encodeNode(node) {
399
440
  }).join("");
400
441
  return `|(${body})`;
401
442
  }
443
+ if (node.op === "nor") {
444
+ const left = encodeNode(node.left);
445
+ const right = addOptionalPrefix(encodeNode(node.right));
446
+ return `!(${left}${right})`;
447
+ }
402
448
  return encodeCallParts([
403
449
  encodeOpcode(BINARY_TO_OPCODE[node.op]),
404
450
  encodeNode(node.left),
405
451
  encodeNode(node.right)
406
452
  ]);
407
453
  case "assign": {
454
+ if (node.op === ":=")
455
+ return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
408
456
  if (node.op === "=")
409
457
  return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
410
458
  const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
@@ -415,8 +463,12 @@ function encodeNode(node) {
415
463
  }
416
464
  case "navigation":
417
465
  return encodeNavigation(node);
418
- case "call":
466
+ case "call": {
467
+ if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
468
+ return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
469
+ }
419
470
  return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
471
+ }
420
472
  case "conditional": {
421
473
  const opener = node.head === "when" ? "?(" : "!(";
422
474
  const cond = encodeNode(node.condition);
@@ -424,6 +476,8 @@ function encodeNode(node) {
424
476
  const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
425
477
  return `${opener}${cond}${thenExpr}${elseExpr})`;
426
478
  }
479
+ case "range":
480
+ return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
427
481
  case "for":
428
482
  return encodeFor(node);
429
483
  case "while":
@@ -443,11 +497,30 @@ function collectLogicalChain(node, op) {
443
497
  return [node];
444
498
  return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
445
499
  }
500
+ function formatParseError(source, match) {
501
+ const message = match.message ?? "Parse failed";
502
+ const pos = match.getRightmostFailurePosition?.();
503
+ if (typeof pos !== "number" || !Number.isFinite(pos))
504
+ return message;
505
+ const safePos = Math.max(0, Math.min(source.length, pos));
506
+ const lineStart = source.lastIndexOf(`
507
+ `, safePos - 1) + 1;
508
+ const lineEndIndex = source.indexOf(`
509
+ `, safePos);
510
+ const lineEnd = lineEndIndex === -1 ? source.length : lineEndIndex;
511
+ const lineText = source.slice(lineStart, lineEnd);
512
+ const lineNumber = source.slice(0, lineStart).split(`
513
+ `).length;
514
+ const columnNumber = safePos - lineStart + 1;
515
+ const caret = `${" ".repeat(Math.max(0, columnNumber - 1))}^`;
516
+ return `${message}
517
+ ${lineText}
518
+ ${caret}`;
519
+ }
446
520
  function parseToIR(source) {
447
521
  const match = grammar.match(source);
448
522
  if (!match.succeeded()) {
449
- const failure = match;
450
- throw new Error(failure.message ?? "Parse failed");
523
+ throw new Error(formatParseError(source, match));
451
524
  }
452
525
  return semantics(match).toIR();
453
526
  }
@@ -554,78 +627,55 @@ function domainRefsFromConfig(config) {
554
627
  if (!config || typeof config !== "object" || Array.isArray(config)) {
555
628
  throw new Error("Domain config must be an object");
556
629
  }
557
- const refs = {};
558
- for (const section of Object.values(config)) {
559
- if (!section || typeof section !== "object" || Array.isArray(section))
560
- continue;
561
- mapConfigEntries(section, refs);
562
- }
563
- return refs;
564
- }
565
- function decodeDomainRefKey(refText) {
566
- if (!refText)
567
- throw new Error("Domain ref key cannot be empty");
568
- if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
569
- throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
570
- }
571
- if (refText.length > 1 && refText[0] === "0") {
572
- throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
573
- }
574
- if (/^[1-9]$/.test(refText)) {
575
- throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
576
- }
577
- let value = 0;
578
- for (const char of refText) {
579
- const digit = DOMAIN_DIGIT_INDEX.get(char);
580
- if (digit === undefined)
581
- throw new Error(`Invalid domain ref key '${refText}'`);
582
- value = value * 64 + digit;
583
- if (value > Number.MAX_SAFE_INTEGER) {
584
- throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
585
- }
630
+ const configObj = config;
631
+ const domainRefs = {};
632
+ const domainOpcodes = {};
633
+ const dataSection = configObj.data;
634
+ if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
635
+ mapConfigEntries(dataSection, domainRefs);
586
636
  }
587
- if (value < FIRST_NON_RESERVED_REF) {
588
- throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
637
+ const functionsSection = configObj.functions;
638
+ if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
639
+ mapConfigEntries(functionsSection, domainOpcodes);
589
640
  }
590
- return value;
641
+ return { domainRefs, domainOpcodes };
591
642
  }
592
643
  function mapConfigEntries(entries, refs) {
593
644
  const sourceKindByRoot = new Map;
594
645
  for (const root of Object.keys(refs)) {
595
646
  sourceKindByRoot.set(root, "explicit");
596
647
  }
597
- for (const [refText, rawEntry] of Object.entries(entries)) {
648
+ for (const [shortCode, rawEntry] of Object.entries(entries)) {
598
649
  const entry = rawEntry;
599
650
  if (!entry || typeof entry !== "object")
600
651
  continue;
601
652
  if (!Array.isArray(entry.names))
602
653
  continue;
603
- const refId = decodeDomainRefKey(refText);
604
654
  for (const rawName of entry.names) {
605
655
  if (typeof rawName !== "string")
606
656
  continue;
607
- const existingNameRef = refs[rawName];
608
- if (existingNameRef !== undefined && existingNameRef !== refId) {
609
- throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
657
+ const existingRef = refs[rawName];
658
+ if (existingRef !== undefined && existingRef !== shortCode) {
659
+ throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
610
660
  }
611
- refs[rawName] = refId;
661
+ refs[rawName] = shortCode;
612
662
  const root = rawName.split(".")[0];
613
663
  if (!root)
614
664
  continue;
615
665
  const currentKind = rawName.includes(".") ? "implicit" : "explicit";
616
666
  const existing = refs[root];
617
667
  if (existing !== undefined) {
618
- if (existing === refId)
668
+ if (existing === shortCode)
619
669
  continue;
620
670
  const existingKind = sourceKindByRoot.get(root) ?? "explicit";
621
671
  if (currentKind === "explicit") {
622
- throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
672
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
623
673
  }
624
674
  if (existingKind === "explicit")
625
675
  continue;
626
676
  continue;
627
677
  }
628
- refs[root] = refId;
678
+ refs[root] = shortCode;
629
679
  sourceKindByRoot.set(root, currentKind);
630
680
  }
631
681
  }
@@ -771,20 +821,11 @@ function gatherEncodedValueSpans(text) {
771
821
  }
772
822
  return spans;
773
823
  }
774
- function buildPointerToken(pointerStart, targetStart) {
775
- let offset = targetStart - (pointerStart + 1);
824
+ function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
825
+ const offset = targetStart - pointerStart - occurrenceSize;
776
826
  if (offset < 0)
777
827
  return;
778
- for (let guard = 0;guard < 8; guard += 1) {
779
- const prefix = encodeUint(offset);
780
- const recalculated = targetStart - (pointerStart + prefix.length + 1);
781
- if (recalculated === offset)
782
- return `${prefix}^`;
783
- offset = recalculated;
784
- if (offset < 0)
785
- return;
786
- }
787
- return;
828
+ return `${encodeUint(offset)}^`;
788
829
  }
789
830
  function buildDedupeCandidateTable(encoded, minBytes) {
790
831
  const spans = gatherEncodedValueSpans(encoded);
@@ -826,7 +867,7 @@ function dedupeLargeEncodedValues(encoded, minBytes = 4) {
826
867
  if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
827
868
  continue;
828
869
  const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
829
- const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
870
+ const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
830
871
  if (!pointerToken)
831
872
  continue;
832
873
  if (pointerToken.length >= occurrence.sizeBytes)
@@ -855,819 +896,8 @@ function encodeIR(node, options) {
855
896
  activeEncodeOptions = previous;
856
897
  }
857
898
  }
858
- function cloneNode(node) {
859
- return structuredClone(node);
860
- }
861
- function emptyOptimizeEnv() {
862
- return { constants: {}, selfCaptures: {} };
863
- }
864
- function cloneOptimizeEnv(env) {
865
- return {
866
- constants: { ...env.constants },
867
- selfCaptures: { ...env.selfCaptures }
868
- };
869
- }
870
- function clearOptimizeEnv(env) {
871
- for (const key of Object.keys(env.constants))
872
- delete env.constants[key];
873
- for (const key of Object.keys(env.selfCaptures))
874
- delete env.selfCaptures[key];
875
- }
876
- function clearBinding(env, name) {
877
- delete env.constants[name];
878
- delete env.selfCaptures[name];
879
- }
880
- function selfTargetFromNode(node, currentDepth) {
881
- if (node.type === "self")
882
- return currentDepth;
883
- if (node.type === "selfDepth") {
884
- const target = currentDepth - (node.depth - 1);
885
- if (target >= 1)
886
- return target;
887
- }
888
- return;
889
- }
890
- function selfNodeFromTarget(targetDepth, currentDepth) {
891
- const relDepth = currentDepth - targetDepth + 1;
892
- if (!Number.isInteger(relDepth) || relDepth < 1)
893
- return;
894
- if (relDepth === 1)
895
- return { type: "self" };
896
- return { type: "selfDepth", depth: relDepth };
897
- }
898
- function dropBindingNames(env, binding) {
899
- if (binding.type === "binding:valueIn") {
900
- clearBinding(env, binding.value);
901
- return;
902
- }
903
- if (binding.type === "binding:keyValueIn") {
904
- clearBinding(env, binding.key);
905
- clearBinding(env, binding.value);
906
- return;
907
- }
908
- if (binding.type === "binding:keyOf") {
909
- clearBinding(env, binding.key);
910
- }
911
- }
912
- function collectReads(node, out) {
913
- switch (node.type) {
914
- case "identifier":
915
- out.add(node.name);
916
- return;
917
- case "group":
918
- collectReads(node.expression, out);
919
- return;
920
- case "array":
921
- for (const item of node.items)
922
- collectReads(item, out);
923
- return;
924
- case "object":
925
- for (const entry of node.entries) {
926
- collectReads(entry.key, out);
927
- collectReads(entry.value, out);
928
- }
929
- return;
930
- case "arrayComprehension":
931
- collectReads(node.binding.source, out);
932
- collectReads(node.body, out);
933
- return;
934
- case "objectComprehension":
935
- collectReads(node.binding.source, out);
936
- collectReads(node.key, out);
937
- collectReads(node.value, out);
938
- return;
939
- case "unary":
940
- collectReads(node.value, out);
941
- return;
942
- case "binary":
943
- collectReads(node.left, out);
944
- collectReads(node.right, out);
945
- return;
946
- case "assign":
947
- if (!(node.op === "=" && node.place.type === "identifier"))
948
- collectReads(node.place, out);
949
- collectReads(node.value, out);
950
- return;
951
- case "navigation":
952
- collectReads(node.target, out);
953
- for (const segment of node.segments) {
954
- if (segment.type === "dynamic")
955
- collectReads(segment.key, out);
956
- }
957
- return;
958
- case "call":
959
- collectReads(node.callee, out);
960
- for (const arg of node.args)
961
- collectReads(arg, out);
962
- return;
963
- case "conditional":
964
- collectReads(node.condition, out);
965
- for (const part of node.thenBlock)
966
- collectReads(part, out);
967
- if (node.elseBranch)
968
- collectReadsElse(node.elseBranch, out);
969
- return;
970
- case "for":
971
- collectReads(node.binding.source, out);
972
- for (const part of node.body)
973
- collectReads(part, out);
974
- return;
975
- case "program":
976
- for (const part of node.body)
977
- collectReads(part, out);
978
- return;
979
- default:
980
- return;
981
- }
982
- }
983
- function collectReadsElse(elseBranch, out) {
984
- if (elseBranch.type === "else") {
985
- for (const part of elseBranch.block)
986
- collectReads(part, out);
987
- return;
988
- }
989
- collectReads(elseBranch.condition, out);
990
- for (const part of elseBranch.thenBlock)
991
- collectReads(part, out);
992
- if (elseBranch.elseBranch)
993
- collectReadsElse(elseBranch.elseBranch, out);
994
- }
995
- function isPureNode(node) {
996
- switch (node.type) {
997
- case "identifier":
998
- case "self":
999
- case "selfDepth":
1000
- case "boolean":
1001
- case "null":
1002
- case "undefined":
1003
- case "number":
1004
- case "string":
1005
- case "key":
1006
- return true;
1007
- case "group":
1008
- return isPureNode(node.expression);
1009
- case "array":
1010
- return node.items.every((item) => isPureNode(item));
1011
- case "object":
1012
- return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
1013
- case "navigation":
1014
- return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
1015
- case "unary":
1016
- return node.op !== "delete" && isPureNode(node.value);
1017
- case "binary":
1018
- return isPureNode(node.left) && isPureNode(node.right);
1019
- default:
1020
- return false;
1021
- }
1022
- }
1023
- function eliminateDeadAssignments(block) {
1024
- const needed = new Set;
1025
- const out = [];
1026
- for (let index = block.length - 1;index >= 0; index -= 1) {
1027
- const node = block[index];
1028
- if (node.type === "conditional") {
1029
- let rewritten = node;
1030
- if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
1031
- const name = node.condition.place.name;
1032
- const branchReads = new Set;
1033
- for (const part of node.thenBlock)
1034
- collectReads(part, branchReads);
1035
- if (node.elseBranch)
1036
- collectReadsElse(node.elseBranch, branchReads);
1037
- if (!needed.has(name) && !branchReads.has(name)) {
1038
- rewritten = {
1039
- type: "conditional",
1040
- head: node.head,
1041
- condition: node.condition.value,
1042
- thenBlock: node.thenBlock,
1043
- elseBranch: node.elseBranch
1044
- };
1045
- }
1046
- }
1047
- collectReads(rewritten, needed);
1048
- out.push(rewritten);
1049
- continue;
1050
- }
1051
- if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
1052
- collectReads(node.value, needed);
1053
- const name = node.place.name;
1054
- const canDrop = !needed.has(name) && isPureNode(node.value);
1055
- needed.delete(name);
1056
- if (canDrop)
1057
- continue;
1058
- out.push(node);
1059
- continue;
1060
- }
1061
- collectReads(node, needed);
1062
- out.push(node);
1063
- }
1064
- out.reverse();
1065
- return out;
1066
- }
1067
- function hasIdentifierRead(node, name, asPlace = false) {
1068
- if (node.type === "identifier")
1069
- return !asPlace && node.name === name;
1070
- switch (node.type) {
1071
- case "group":
1072
- return hasIdentifierRead(node.expression, name);
1073
- case "array":
1074
- return node.items.some((item) => hasIdentifierRead(item, name));
1075
- case "object":
1076
- return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
1077
- case "navigation":
1078
- return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
1079
- case "unary":
1080
- return hasIdentifierRead(node.value, name, node.op === "delete");
1081
- case "binary":
1082
- return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1083
- case "assign":
1084
- return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1085
- default:
1086
- return false;
1087
- }
1088
- }
1089
- function countIdentifierReads(node, name, asPlace = false) {
1090
- if (node.type === "identifier")
1091
- return !asPlace && node.name === name ? 1 : 0;
1092
- switch (node.type) {
1093
- case "group":
1094
- return countIdentifierReads(node.expression, name);
1095
- case "array":
1096
- return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
1097
- case "object":
1098
- return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
1099
- case "navigation":
1100
- return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
1101
- case "unary":
1102
- return countIdentifierReads(node.value, name, node.op === "delete");
1103
- case "binary":
1104
- return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1105
- case "assign":
1106
- return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1107
- default:
1108
- return 0;
1109
- }
1110
- }
1111
- function replaceIdentifier(node, name, replacement, asPlace = false) {
1112
- if (node.type === "identifier") {
1113
- if (!asPlace && node.name === name)
1114
- return cloneNode(replacement);
1115
- return node;
1116
- }
1117
- switch (node.type) {
1118
- case "group":
1119
- return {
1120
- type: "group",
1121
- expression: replaceIdentifier(node.expression, name, replacement)
1122
- };
1123
- case "array":
1124
- return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
1125
- case "object":
1126
- return {
1127
- type: "object",
1128
- entries: node.entries.map((entry) => ({
1129
- key: replaceIdentifier(entry.key, name, replacement),
1130
- value: replaceIdentifier(entry.value, name, replacement)
1131
- }))
1132
- };
1133
- case "navigation":
1134
- return {
1135
- type: "navigation",
1136
- target: replaceIdentifier(node.target, name, replacement),
1137
- segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
1138
- };
1139
- case "unary":
1140
- return {
1141
- type: "unary",
1142
- op: node.op,
1143
- value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
1144
- };
1145
- case "binary":
1146
- return {
1147
- type: "binary",
1148
- op: node.op,
1149
- left: replaceIdentifier(node.left, name, replacement),
1150
- right: replaceIdentifier(node.right, name, replacement)
1151
- };
1152
- case "assign":
1153
- return {
1154
- type: "assign",
1155
- op: node.op,
1156
- place: replaceIdentifier(node.place, name, replacement, true),
1157
- value: replaceIdentifier(node.value, name, replacement)
1158
- };
1159
- default:
1160
- return node;
1161
- }
1162
- }
1163
- function isSafeInlineTargetNode(node) {
1164
- if (isPureNode(node))
1165
- return true;
1166
- if (node.type === "assign" && node.op === "=") {
1167
- return isPureNode(node.place) && isPureNode(node.value);
1168
- }
1169
- return false;
1170
- }
1171
- function inlineAdjacentPureAssignments(block) {
1172
- const out = [...block];
1173
- let changed = true;
1174
- while (changed) {
1175
- changed = false;
1176
- for (let index = 0;index < out.length - 1; index += 1) {
1177
- const current = out[index];
1178
- if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
1179
- continue;
1180
- if (!isPureNode(current.value))
1181
- continue;
1182
- const name = current.place.name;
1183
- if (hasIdentifierRead(current.value, name))
1184
- continue;
1185
- const next = out[index + 1];
1186
- if (!isSafeInlineTargetNode(next))
1187
- continue;
1188
- if (countIdentifierReads(next, name) !== 1)
1189
- continue;
1190
- out[index + 1] = replaceIdentifier(next, name, current.value);
1191
- out.splice(index, 1);
1192
- changed = true;
1193
- break;
1194
- }
1195
- }
1196
- return out;
1197
- }
1198
- function toNumberNode(value) {
1199
- let raw;
1200
- if (Number.isNaN(value))
1201
- raw = "nan";
1202
- else if (value === Infinity)
1203
- raw = "inf";
1204
- else if (value === -Infinity)
1205
- raw = "-inf";
1206
- else
1207
- raw = String(value);
1208
- return { type: "number", raw, value };
1209
- }
1210
- function toStringNode(value) {
1211
- return { type: "string", raw: JSON.stringify(value) };
1212
- }
1213
- function toLiteralNode(value) {
1214
- if (value === undefined)
1215
- return { type: "undefined" };
1216
- if (value === null)
1217
- return { type: "null" };
1218
- if (typeof value === "boolean")
1219
- return { type: "boolean", value };
1220
- if (typeof value === "number")
1221
- return toNumberNode(value);
1222
- if (typeof value === "string")
1223
- return toStringNode(value);
1224
- if (Array.isArray(value)) {
1225
- const items = [];
1226
- for (const item of value) {
1227
- const lowered = toLiteralNode(item);
1228
- if (!lowered)
1229
- return;
1230
- items.push(lowered);
1231
- }
1232
- return { type: "array", items };
1233
- }
1234
- if (value && typeof value === "object") {
1235
- const entries = [];
1236
- for (const [key, entryValue] of Object.entries(value)) {
1237
- const loweredValue = toLiteralNode(entryValue);
1238
- if (!loweredValue)
1239
- return;
1240
- entries.push({ key: { type: "key", name: key }, value: loweredValue });
1241
- }
1242
- return { type: "object", entries };
1243
- }
1244
- return;
1245
- }
1246
- function constValue(node) {
1247
- switch (node.type) {
1248
- case "undefined":
1249
- return;
1250
- case "null":
1251
- return null;
1252
- case "boolean":
1253
- return node.value;
1254
- case "number":
1255
- return node.value;
1256
- case "string":
1257
- return decodeStringLiteral(node.raw);
1258
- case "key":
1259
- return node.name;
1260
- case "array": {
1261
- const out = [];
1262
- for (const item of node.items) {
1263
- const value = constValue(item);
1264
- if (value === undefined && item.type !== "undefined")
1265
- return;
1266
- out.push(value);
1267
- }
1268
- return out;
1269
- }
1270
- case "object": {
1271
- const out = {};
1272
- for (const entry of node.entries) {
1273
- const key = constValue(entry.key);
1274
- if (key === undefined && entry.key.type !== "undefined")
1275
- return;
1276
- const value = constValue(entry.value);
1277
- if (value === undefined && entry.value.type !== "undefined")
1278
- return;
1279
- out[String(key)] = value;
1280
- }
1281
- return out;
1282
- }
1283
- default:
1284
- return;
1285
- }
1286
- }
1287
- function isDefinedValue(value) {
1288
- return value !== undefined;
1289
- }
1290
- function foldUnary(op, value) {
1291
- if (op === "neg") {
1292
- if (typeof value !== "number")
1293
- return;
1294
- return -value;
1295
- }
1296
- if (op === "not") {
1297
- if (typeof value === "boolean")
1298
- return !value;
1299
- if (typeof value === "number")
1300
- return ~value;
1301
- return;
1302
- }
1303
- return;
1304
- }
1305
- function foldBinary(op, left, right) {
1306
- if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
1307
- if (typeof left !== "number" || typeof right !== "number")
1308
- return;
1309
- if (op === "add")
1310
- return left + right;
1311
- if (op === "sub")
1312
- return left - right;
1313
- if (op === "mul")
1314
- return left * right;
1315
- if (op === "div")
1316
- return left / right;
1317
- return left % right;
1318
- }
1319
- if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
1320
- if (typeof left !== "number" || typeof right !== "number")
1321
- return;
1322
- if (op === "bitAnd")
1323
- return left & right;
1324
- if (op === "bitOr")
1325
- return left | right;
1326
- return left ^ right;
1327
- }
1328
- if (op === "eq")
1329
- return left === right ? left : undefined;
1330
- if (op === "neq")
1331
- return left !== right ? left : undefined;
1332
- if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
1333
- if (typeof left !== "number" || typeof right !== "number")
1334
- return;
1335
- if (op === "gt")
1336
- return left > right ? left : undefined;
1337
- if (op === "gte")
1338
- return left >= right ? left : undefined;
1339
- if (op === "lt")
1340
- return left < right ? left : undefined;
1341
- return left <= right ? left : undefined;
1342
- }
1343
- if (op === "and")
1344
- return isDefinedValue(left) ? right : undefined;
1345
- if (op === "or")
1346
- return isDefinedValue(left) ? left : right;
1347
- return;
1348
- }
1349
- function optimizeElse(elseBranch, env, currentDepth) {
1350
- if (!elseBranch)
1351
- return;
1352
- if (elseBranch.type === "else") {
1353
- return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
1354
- }
1355
- const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
1356
- const foldedCondition = constValue(optimizedCondition);
1357
- if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
1358
- const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
1359
- if (passes) {
1360
- return {
1361
- type: "else",
1362
- block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
1363
- };
1364
- }
1365
- return optimizeElse(elseBranch.elseBranch, env, currentDepth);
1366
- }
1367
- return {
1368
- type: "elseChain",
1369
- head: elseBranch.head,
1370
- condition: optimizedCondition,
1371
- thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
1372
- elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
1373
- };
1374
- }
1375
- function optimizeBlock(block, env, currentDepth) {
1376
- const out = [];
1377
- for (const node of block) {
1378
- const optimized = optimizeNode(node, env, currentDepth);
1379
- out.push(optimized);
1380
- if (optimized.type === "break" || optimized.type === "continue")
1381
- break;
1382
- if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
1383
- const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
1384
- if (selfTarget !== undefined) {
1385
- env.selfCaptures[optimized.place.name] = selfTarget;
1386
- delete env.constants[optimized.place.name];
1387
- continue;
1388
- }
1389
- const folded = constValue(optimized.value);
1390
- if (folded !== undefined || optimized.value.type === "undefined") {
1391
- env.constants[optimized.place.name] = cloneNode(optimized.value);
1392
- delete env.selfCaptures[optimized.place.name];
1393
- } else {
1394
- clearBinding(env, optimized.place.name);
1395
- }
1396
- continue;
1397
- }
1398
- if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
1399
- clearBinding(env, optimized.value.name);
1400
- continue;
1401
- }
1402
- if (optimized.type === "assign" && optimized.place.type === "identifier") {
1403
- clearBinding(env, optimized.place.name);
1404
- continue;
1405
- }
1406
- if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
1407
- clearOptimizeEnv(env);
1408
- }
1409
- }
1410
- return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
1411
- }
1412
- function optimizeNode(node, env, currentDepth, asPlace = false) {
1413
- switch (node.type) {
1414
- case "program": {
1415
- const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
1416
- if (body.length === 0)
1417
- return { type: "undefined" };
1418
- if (body.length === 1)
1419
- return body[0];
1420
- return { type: "program", body };
1421
- }
1422
- case "identifier": {
1423
- if (asPlace)
1424
- return node;
1425
- const selfTarget = env.selfCaptures[node.name];
1426
- if (selfTarget !== undefined) {
1427
- const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
1428
- if (rewritten)
1429
- return rewritten;
1430
- }
1431
- const replacement = env.constants[node.name];
1432
- return replacement ? cloneNode(replacement) : node;
1433
- }
1434
- case "group": {
1435
- return optimizeNode(node.expression, env, currentDepth);
1436
- }
1437
- case "array": {
1438
- return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
1439
- }
1440
- case "object": {
1441
- return {
1442
- type: "object",
1443
- entries: node.entries.map((entry) => ({
1444
- key: optimizeNode(entry.key, env, currentDepth),
1445
- value: optimizeNode(entry.value, env, currentDepth)
1446
- }))
1447
- };
1448
- }
1449
- case "unary": {
1450
- const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
1451
- const foldedValue = constValue(value);
1452
- if (foldedValue !== undefined || value.type === "undefined") {
1453
- const folded = foldUnary(node.op, foldedValue);
1454
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1455
- if (literal)
1456
- return literal;
1457
- }
1458
- return { type: "unary", op: node.op, value };
1459
- }
1460
- case "binary": {
1461
- const left = optimizeNode(node.left, env, currentDepth);
1462
- const right = optimizeNode(node.right, env, currentDepth);
1463
- const leftValue = constValue(left);
1464
- const rightValue = constValue(right);
1465
- if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
1466
- const folded = foldBinary(node.op, leftValue, rightValue);
1467
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1468
- if (literal)
1469
- return literal;
1470
- }
1471
- return { type: "binary", op: node.op, left, right };
1472
- }
1473
- case "navigation": {
1474
- const target = optimizeNode(node.target, env, currentDepth);
1475
- const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
1476
- const targetValue = constValue(target);
1477
- if (targetValue !== undefined || target.type === "undefined") {
1478
- let current = targetValue;
1479
- let foldable = true;
1480
- for (const segment of segments) {
1481
- if (!foldable)
1482
- break;
1483
- const key = segment.type === "static" ? segment.key : constValue(segment.key);
1484
- if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
1485
- foldable = false;
1486
- break;
1487
- }
1488
- if (current === null || current === undefined) {
1489
- current = undefined;
1490
- continue;
1491
- }
1492
- current = current[String(key)];
1493
- }
1494
- if (foldable) {
1495
- const literal = toLiteralNode(current);
1496
- if (literal)
1497
- return literal;
1498
- }
1499
- }
1500
- return {
1501
- type: "navigation",
1502
- target,
1503
- segments
1504
- };
1505
- }
1506
- case "call": {
1507
- return {
1508
- type: "call",
1509
- callee: optimizeNode(node.callee, env, currentDepth),
1510
- args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
1511
- };
1512
- }
1513
- case "assign": {
1514
- return {
1515
- type: "assign",
1516
- op: node.op,
1517
- place: optimizeNode(node.place, env, currentDepth, true),
1518
- value: optimizeNode(node.value, env, currentDepth)
1519
- };
1520
- }
1521
- case "conditional": {
1522
- const condition = optimizeNode(node.condition, env, currentDepth);
1523
- const thenEnv = cloneOptimizeEnv(env);
1524
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1525
- thenEnv.selfCaptures[condition.place.name] = currentDepth;
1526
- delete thenEnv.constants[condition.place.name];
1527
- }
1528
- const conditionValue = constValue(condition);
1529
- if (conditionValue !== undefined || condition.type === "undefined") {
1530
- const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1531
- if (passes) {
1532
- const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1533
- if (thenBlock2.length === 0)
1534
- return { type: "undefined" };
1535
- if (thenBlock2.length === 1)
1536
- return thenBlock2[0];
1537
- return { type: "program", body: thenBlock2 };
1538
- }
1539
- if (!node.elseBranch)
1540
- return { type: "undefined" };
1541
- const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1542
- if (!loweredElse)
1543
- return { type: "undefined" };
1544
- if (loweredElse.type === "else") {
1545
- if (loweredElse.block.length === 0)
1546
- return { type: "undefined" };
1547
- if (loweredElse.block.length === 1)
1548
- return loweredElse.block[0];
1549
- return { type: "program", body: loweredElse.block };
1550
- }
1551
- return {
1552
- type: "conditional",
1553
- head: loweredElse.head,
1554
- condition: loweredElse.condition,
1555
- thenBlock: loweredElse.thenBlock,
1556
- elseBranch: loweredElse.elseBranch
1557
- };
1558
- }
1559
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1560
- const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1561
- let finalCondition = condition;
1562
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1563
- const name = condition.place.name;
1564
- const reads = new Set;
1565
- for (const part of thenBlock)
1566
- collectReads(part, reads);
1567
- if (elseBranch)
1568
- collectReadsElse(elseBranch, reads);
1569
- if (!reads.has(name)) {
1570
- finalCondition = condition.value;
1571
- }
1572
- }
1573
- return {
1574
- type: "conditional",
1575
- head: node.head,
1576
- condition: finalCondition,
1577
- thenBlock,
1578
- elseBranch
1579
- };
1580
- }
1581
- case "for": {
1582
- const sourceEnv = cloneOptimizeEnv(env);
1583
- const binding = (() => {
1584
- if (node.binding.type === "binding:expr") {
1585
- return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
1586
- }
1587
- if (node.binding.type === "binding:valueIn") {
1588
- return {
1589
- type: "binding:valueIn",
1590
- value: node.binding.value,
1591
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1592
- };
1593
- }
1594
- if (node.binding.type === "binding:keyValueIn") {
1595
- return {
1596
- type: "binding:keyValueIn",
1597
- key: node.binding.key,
1598
- value: node.binding.value,
1599
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1600
- };
1601
- }
1602
- return {
1603
- type: "binding:keyOf",
1604
- key: node.binding.key,
1605
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1606
- };
1607
- })();
1608
- const bodyEnv = cloneOptimizeEnv(env);
1609
- dropBindingNames(bodyEnv, binding);
1610
- return {
1611
- type: "for",
1612
- binding,
1613
- body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
1614
- };
1615
- }
1616
- case "arrayComprehension": {
1617
- const sourceEnv = cloneOptimizeEnv(env);
1618
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1619
- type: "binding:valueIn",
1620
- value: node.binding.value,
1621
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1622
- } : node.binding.type === "binding:keyValueIn" ? {
1623
- type: "binding:keyValueIn",
1624
- key: node.binding.key,
1625
- value: node.binding.value,
1626
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1627
- } : {
1628
- type: "binding:keyOf",
1629
- key: node.binding.key,
1630
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1631
- };
1632
- const bodyEnv = cloneOptimizeEnv(env);
1633
- dropBindingNames(bodyEnv, binding);
1634
- return {
1635
- type: "arrayComprehension",
1636
- binding,
1637
- body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1638
- };
1639
- }
1640
- case "objectComprehension": {
1641
- const sourceEnv = cloneOptimizeEnv(env);
1642
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1643
- type: "binding:valueIn",
1644
- value: node.binding.value,
1645
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1646
- } : node.binding.type === "binding:keyValueIn" ? {
1647
- type: "binding:keyValueIn",
1648
- key: node.binding.key,
1649
- value: node.binding.value,
1650
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1651
- } : {
1652
- type: "binding:keyOf",
1653
- key: node.binding.key,
1654
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1655
- };
1656
- const bodyEnv = cloneOptimizeEnv(env);
1657
- dropBindingNames(bodyEnv, binding);
1658
- return {
1659
- type: "objectComprehension",
1660
- binding,
1661
- key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
1662
- value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1663
- };
1664
- }
1665
- default:
1666
- return node;
1667
- }
1668
- }
1669
899
  function optimizeIR(node) {
1670
- return optimizeNode(node, emptyOptimizeEnv(), 1);
900
+ return node;
1671
901
  }
1672
902
  function collectLocalBindings(node, locals) {
1673
903
  switch (node.type) {
@@ -1713,6 +943,10 @@ function collectLocalBindings(node, locals) {
1713
943
  collectLocalBindings(node.left, locals);
1714
944
  collectLocalBindings(node.right, locals);
1715
945
  return;
946
+ case "range":
947
+ collectLocalBindings(node.from, locals);
948
+ collectLocalBindings(node.to, locals);
949
+ return;
1716
950
  case "conditional":
1717
951
  collectLocalBindings(node.condition, locals);
1718
952
  for (const part of node.thenBlock)
@@ -1729,11 +963,20 @@ function collectLocalBindings(node, locals) {
1729
963
  collectLocalBindingFromBinding(node.binding, locals);
1730
964
  collectLocalBindings(node.body, locals);
1731
965
  return;
966
+ case "whileArrayComprehension":
967
+ collectLocalBindings(node.condition, locals);
968
+ collectLocalBindings(node.body, locals);
969
+ return;
1732
970
  case "objectComprehension":
1733
971
  collectLocalBindingFromBinding(node.binding, locals);
1734
972
  collectLocalBindings(node.key, locals);
1735
973
  collectLocalBindings(node.value, locals);
1736
974
  return;
975
+ case "whileObjectComprehension":
976
+ collectLocalBindings(node.condition, locals);
977
+ collectLocalBindings(node.key, locals);
978
+ collectLocalBindings(node.value, locals);
979
+ return;
1737
980
  default:
1738
981
  return;
1739
982
  }
@@ -1825,6 +1068,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1825
1068
  collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
1826
1069
  collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
1827
1070
  return;
1071
+ case "range":
1072
+ collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
1073
+ collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
1074
+ return;
1828
1075
  case "conditional":
1829
1076
  collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1830
1077
  for (const part of node.thenBlock)
@@ -1841,11 +1088,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1841
1088
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1842
1089
  collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1843
1090
  return;
1091
+ case "whileArrayComprehension":
1092
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1093
+ collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1094
+ return;
1844
1095
  case "objectComprehension":
1845
1096
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1846
1097
  collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1847
1098
  collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1848
1099
  return;
1100
+ case "whileObjectComprehension":
1101
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1102
+ collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1103
+ collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1104
+ return;
1849
1105
  default:
1850
1106
  return;
1851
1107
  }
@@ -1920,6 +1176,12 @@ function renameLocalNames(node, map) {
1920
1176
  left: renameLocalNames(node.left, map),
1921
1177
  right: renameLocalNames(node.right, map)
1922
1178
  };
1179
+ case "range":
1180
+ return {
1181
+ type: "range",
1182
+ from: renameLocalNames(node.from, map),
1183
+ to: renameLocalNames(node.to, map)
1184
+ };
1923
1185
  case "assign": {
1924
1186
  const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
1925
1187
  return {
@@ -1949,6 +1211,12 @@ function renameLocalNames(node, map) {
1949
1211
  binding: renameLocalNamesBinding(node.binding, map),
1950
1212
  body: renameLocalNames(node.body, map)
1951
1213
  };
1214
+ case "whileArrayComprehension":
1215
+ return {
1216
+ type: "whileArrayComprehension",
1217
+ condition: renameLocalNames(node.condition, map),
1218
+ body: renameLocalNames(node.body, map)
1219
+ };
1952
1220
  case "objectComprehension":
1953
1221
  return {
1954
1222
  type: "objectComprehension",
@@ -1956,34 +1224,31 @@ function renameLocalNames(node, map) {
1956
1224
  key: renameLocalNames(node.key, map),
1957
1225
  value: renameLocalNames(node.value, map)
1958
1226
  };
1227
+ case "whileObjectComprehension":
1228
+ return {
1229
+ type: "whileObjectComprehension",
1230
+ condition: renameLocalNames(node.condition, map),
1231
+ key: renameLocalNames(node.key, map),
1232
+ value: renameLocalNames(node.value, map)
1233
+ };
1959
1234
  default:
1960
1235
  return node;
1961
1236
  }
1962
1237
  }
1963
1238
  function renameLocalNamesBinding(binding, map) {
1964
- if (binding.type === "binding:expr") {
1965
- return { type: "binding:expr", source: renameLocalNames(binding.source, map) };
1966
- }
1967
- if (binding.type === "binding:valueIn") {
1968
- return {
1969
- type: "binding:valueIn",
1970
- value: map.get(binding.value) ?? binding.value,
1971
- source: renameLocalNames(binding.source, map)
1972
- };
1973
- }
1974
- if (binding.type === "binding:keyValueIn") {
1975
- return {
1976
- type: "binding:keyValueIn",
1977
- key: map.get(binding.key) ?? binding.key,
1978
- value: map.get(binding.value) ?? binding.value,
1979
- source: renameLocalNames(binding.source, map)
1980
- };
1239
+ const source = renameLocalNames(binding.source, map);
1240
+ switch (binding.type) {
1241
+ case "binding:bareIn":
1242
+ return { type: "binding:bareIn", source };
1243
+ case "binding:bareOf":
1244
+ return { type: "binding:bareOf", source };
1245
+ case "binding:valueIn":
1246
+ return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
1247
+ case "binding:keyValueIn":
1248
+ return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
1249
+ case "binding:keyOf":
1250
+ return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
1981
1251
  }
1982
- return {
1983
- type: "binding:keyOf",
1984
- key: map.get(binding.key) ?? binding.key,
1985
- source: renameLocalNames(binding.source, map)
1986
- };
1987
1252
  }
1988
1253
  function renameLocalNamesElse(elseBranch, map) {
1989
1254
  if (elseBranch.type === "else") {
@@ -2030,9 +1295,9 @@ function compile(source, options) {
2030
1295
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
2031
1296
  if (options?.minifyNames)
2032
1297
  lowered = minifyLocalNamesIR(lowered);
2033
- const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1298
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2034
1299
  return encodeIR(lowered, {
2035
- domainRefs,
1300
+ ...domainMaps,
2036
1301
  dedupeValues: options?.dedupeValues,
2037
1302
  dedupeMinBytes: options?.dedupeMinBytes
2038
1303
  });
@@ -2158,6 +1423,9 @@ semantics.addOperation("toIR", {
2158
1423
  ExistenceExpr_or(left, _or, right) {
2159
1424
  return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
2160
1425
  },
1426
+ ExistenceExpr_nor(left, _nor, right) {
1427
+ return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
1428
+ },
2161
1429
  BitExpr_and(left, _op, right) {
2162
1430
  return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
2163
1431
  },
@@ -2167,6 +1435,9 @@ semantics.addOperation("toIR", {
2167
1435
  BitExpr_or(left, _op, right) {
2168
1436
  return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
2169
1437
  },
1438
+ RangeExpr_range(left, _op, right) {
1439
+ return { type: "range", from: left.toIR(), to: right.toIR() };
1440
+ },
2170
1441
  CompareExpr_binary(left, op, right) {
2171
1442
  const map = {
2172
1443
  "==": "eq",
@@ -2207,6 +1478,9 @@ semantics.addOperation("toIR", {
2207
1478
  UnaryExpr_not(_op, value) {
2208
1479
  return { type: "unary", op: "not", value: value.toIR() };
2209
1480
  },
1481
+ UnaryExpr_logicalNot(_not, value) {
1482
+ return { type: "unary", op: "logicalNot", value: value.toIR() };
1483
+ },
2210
1484
  UnaryExpr_delete(_del, place) {
2211
1485
  return { type: "unary", op: "delete", value: place.toIR() };
2212
1486
  },
@@ -2260,14 +1534,6 @@ semantics.addOperation("toIR", {
2260
1534
  ConditionalElse_else(_else, block) {
2261
1535
  return { type: "else", block: block.toIR() };
2262
1536
  },
2263
- DoExpr(_do, block, _end) {
2264
- const body = block.toIR();
2265
- if (body.length === 0)
2266
- return { type: "undefined" };
2267
- if (body.length === 1)
2268
- return body[0];
2269
- return { type: "program", body };
2270
- },
2271
1537
  WhileExpr(_while, condition, _do, block, _end) {
2272
1538
  return {
2273
1539
  type: "while",
@@ -2282,30 +1548,44 @@ semantics.addOperation("toIR", {
2282
1548
  body: block.toIR()
2283
1549
  };
2284
1550
  },
2285
- BindingExpr(iterOrExpr) {
2286
- const node = iterOrExpr.toIR();
2287
- if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
2288
- return node;
2289
- }
2290
- return { type: "binding:expr", source: node };
2291
- },
2292
1551
  Array_empty(_open, _close) {
2293
1552
  return { type: "array", items: [] };
2294
1553
  },
2295
- Array_comprehension(_open, binding, _semi, body, _close) {
1554
+ Array_forComprehension(_open, body, _for, binding, _close) {
2296
1555
  return {
2297
1556
  type: "arrayComprehension",
2298
1557
  binding: binding.toIR(),
2299
1558
  body: body.toIR()
2300
1559
  };
2301
1560
  },
1561
+ Array_whileComprehension(_open, body, _while, condition, _close) {
1562
+ return {
1563
+ type: "whileArrayComprehension",
1564
+ condition: condition.toIR(),
1565
+ body: body.toIR()
1566
+ };
1567
+ },
1568
+ Array_inComprehension(_open, body, _in, source, _close) {
1569
+ return {
1570
+ type: "arrayComprehension",
1571
+ binding: { type: "binding:bareIn", source: source.toIR() },
1572
+ body: body.toIR()
1573
+ };
1574
+ },
1575
+ Array_ofComprehension(_open, body, _of, source, _close) {
1576
+ return {
1577
+ type: "arrayComprehension",
1578
+ binding: { type: "binding:bareOf", source: source.toIR() },
1579
+ body: body.toIR()
1580
+ };
1581
+ },
2302
1582
  Array_values(_open, items, _close) {
2303
1583
  return { type: "array", items: normalizeList(items.toIR()) };
2304
1584
  },
2305
1585
  Object_empty(_open, _close) {
2306
1586
  return { type: "object", entries: [] };
2307
1587
  },
2308
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
1588
+ Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
2309
1589
  return {
2310
1590
  type: "objectComprehension",
2311
1591
  binding: binding.toIR(),
@@ -2313,6 +1593,30 @@ semantics.addOperation("toIR", {
2313
1593
  value: value.toIR()
2314
1594
  };
2315
1595
  },
1596
+ Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
1597
+ return {
1598
+ type: "whileObjectComprehension",
1599
+ condition: condition.toIR(),
1600
+ key: key.toIR(),
1601
+ value: value.toIR()
1602
+ };
1603
+ },
1604
+ Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
1605
+ return {
1606
+ type: "objectComprehension",
1607
+ binding: { type: "binding:bareIn", source: source.toIR() },
1608
+ key: key.toIR(),
1609
+ value: value.toIR()
1610
+ };
1611
+ },
1612
+ Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
1613
+ return {
1614
+ type: "objectComprehension",
1615
+ binding: { type: "binding:bareOf", source: source.toIR() },
1616
+ key: key.toIR(),
1617
+ value: value.toIR()
1618
+ };
1619
+ },
2316
1620
  Object_pairs(_open, pairs, _close) {
2317
1621
  return {
2318
1622
  type: "object",
@@ -2341,6 +1645,40 @@ semantics.addOperation("toIR", {
2341
1645
  source: source.toIR()
2342
1646
  };
2343
1647
  },
1648
+ IterBinding_bareIn(_in, source) {
1649
+ return {
1650
+ type: "binding:bareIn",
1651
+ source: source.toIR()
1652
+ };
1653
+ },
1654
+ IterBinding_bareOf(_of, source) {
1655
+ return {
1656
+ type: "binding:bareOf",
1657
+ source: source.toIR()
1658
+ };
1659
+ },
1660
+ IterBindingComprehension_keyValueIn(key, _comma, value, _in, source) {
1661
+ return {
1662
+ type: "binding:keyValueIn",
1663
+ key: key.sourceString,
1664
+ value: value.sourceString,
1665
+ source: source.toIR()
1666
+ };
1667
+ },
1668
+ IterBindingComprehension_valueIn(value, _in, source) {
1669
+ return {
1670
+ type: "binding:valueIn",
1671
+ value: value.sourceString,
1672
+ source: source.toIR()
1673
+ };
1674
+ },
1675
+ IterBindingComprehension_keyOf(key, _of, source) {
1676
+ return {
1677
+ type: "binding:keyOf",
1678
+ key: key.sourceString,
1679
+ source: source.toIR()
1680
+ };
1681
+ },
2344
1682
  Pair(key, _colon, value) {
2345
1683
  return { key: key.toIR(), value: value.toIR() };
2346
1684
  },
@@ -2422,28 +1760,29 @@ semantics.addOperation("toIR", {
2422
1760
  var DIGITS2 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
2423
1761
  var digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
2424
1762
  var OPCODES = {
2425
- do: 0,
2426
- add: 1,
2427
- sub: 2,
2428
- mul: 3,
2429
- div: 4,
2430
- eq: 5,
2431
- neq: 6,
2432
- lt: 7,
2433
- lte: 8,
2434
- gt: 9,
2435
- gte: 10,
2436
- and: 11,
2437
- or: 12,
2438
- xor: 13,
2439
- not: 14,
2440
- boolean: 15,
2441
- number: 16,
2442
- string: 17,
2443
- array: 18,
2444
- object: 19,
2445
- mod: 20,
2446
- neg: 21
1763
+ do: "",
1764
+ add: "ad",
1765
+ sub: "sb",
1766
+ mul: "ml",
1767
+ div: "dv",
1768
+ eq: "eq",
1769
+ neq: "nq",
1770
+ lt: "lt",
1771
+ lte: "le",
1772
+ gt: "gt",
1773
+ gte: "ge",
1774
+ and: "an",
1775
+ or: "or",
1776
+ xor: "xr",
1777
+ not: "nt",
1778
+ boolean: "bt",
1779
+ number: "nm",
1780
+ string: "st",
1781
+ array: "ar",
1782
+ object: "ob",
1783
+ mod: "md",
1784
+ neg: "ng",
1785
+ range: "rn"
2447
1786
  };
2448
1787
  function decodePrefix(text, start, end) {
2449
1788
  let value = 0;
@@ -2467,7 +1806,6 @@ class CursorInterpreter {
2467
1806
  pos = 0;
2468
1807
  state;
2469
1808
  selfStack;
2470
- opcodeMarkers;
2471
1809
  pointerCache = new Map;
2472
1810
  gasLimit;
2473
1811
  gas = 0;
@@ -2482,28 +1820,22 @@ class CursorInterpreter {
2482
1820
  this.state = {
2483
1821
  vars: ctx.vars ?? {},
2484
1822
  refs: {
2485
- 0: ctx.refs?.[0],
2486
- 1: ctx.refs?.[1] ?? true,
2487
- 2: ctx.refs?.[2] ?? false,
2488
- 3: ctx.refs?.[3] ?? null,
2489
- 4: ctx.refs?.[4] ?? undefined,
2490
- 5: ctx.refs?.[5] ?? NaN,
2491
- 6: ctx.refs?.[6] ?? Infinity,
2492
- 7: ctx.refs?.[7] ?? -Infinity
1823
+ tr: true,
1824
+ fl: false,
1825
+ nl: null,
1826
+ un: undefined,
1827
+ nan: NaN,
1828
+ inf: Infinity,
1829
+ nif: -Infinity,
1830
+ ...ctx.refs
2493
1831
  }
2494
1832
  };
2495
1833
  this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
2496
1834
  this.gasLimit = ctx.gasLimit ?? 0;
2497
- this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
2498
- for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
2499
- const id = Number(idText);
2500
- if (Number.isInteger(id))
2501
- this.state.refs[id] = value;
2502
- }
2503
1835
  if (ctx.opcodes) {
2504
- for (const [idText, op] of Object.entries(ctx.opcodes)) {
1836
+ for (const [key, op] of Object.entries(ctx.opcodes)) {
2505
1837
  if (op)
2506
- this.customOpcodes.set(Number(idText), op);
1838
+ this.customOpcodes.set(key, op);
2507
1839
  }
2508
1840
  }
2509
1841
  }
@@ -2564,6 +1896,22 @@ class CursorInterpreter {
2564
1896
  const end = this.pos;
2565
1897
  return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
2566
1898
  }
1899
+ advanceByBytes(start, byteCount) {
1900
+ if (byteCount <= 0)
1901
+ return start;
1902
+ let bytes = 0;
1903
+ let index = start;
1904
+ for (const char of this.text.slice(start)) {
1905
+ const charBytes = Buffer.byteLength(char);
1906
+ if (bytes + charBytes > byteCount)
1907
+ break;
1908
+ bytes += charBytes;
1909
+ index += char.length;
1910
+ if (bytes === byteCount)
1911
+ return index;
1912
+ }
1913
+ throw new Error("String container overflows input");
1914
+ }
2567
1915
  ensure(char) {
2568
1916
  if (this.text[this.pos] !== char)
2569
1917
  throw new Error(`Expected '${char}' at ${this.pos}`);
@@ -2604,36 +1952,34 @@ class CursorInterpreter {
2604
1952
  const significand = this.evalValue();
2605
1953
  if (typeof significand !== "number")
2606
1954
  throw new Error("Decimal significand must be numeric");
2607
- return significand * 10 ** power;
1955
+ return parseFloat(`${significand}e${power}`);
2608
1956
  }
2609
1957
  case ":":
2610
1958
  this.pos += 1;
2611
1959
  return prefix.raw;
2612
1960
  case "%":
2613
1961
  this.pos += 1;
2614
- return this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
1962
+ return { __opcode: prefix.raw };
2615
1963
  case "@":
2616
1964
  this.pos += 1;
2617
1965
  return this.readSelf(prefix.value);
2618
1966
  case "'":
2619
1967
  this.pos += 1;
2620
- return this.state.refs[prefix.value];
1968
+ return this.state.refs[prefix.raw];
2621
1969
  case "$":
2622
1970
  this.pos += 1;
2623
1971
  return this.state.vars[prefix.raw];
2624
1972
  case ",": {
2625
1973
  this.pos += 1;
2626
1974
  const start = this.pos;
2627
- const end = start + prefix.value;
2628
- if (end > this.text.length)
2629
- throw new Error("String container overflows input");
1975
+ const end = this.advanceByBytes(start, prefix.value);
2630
1976
  const value = this.text.slice(start, end);
2631
1977
  this.pos = end;
2632
1978
  return value;
2633
1979
  }
2634
1980
  case "^": {
2635
1981
  this.pos += 1;
2636
- const target = this.pos + prefix.value;
1982
+ const target = this.advanceByBytes(this.pos, prefix.value);
2637
1983
  if (this.pointerCache.has(target))
2638
1984
  return this.pointerCache.get(target);
2639
1985
  const save = this.pos;
@@ -2650,6 +1996,14 @@ class CursorInterpreter {
2650
1996
  this.writePlace(place, value);
2651
1997
  return value;
2652
1998
  }
1999
+ case "/": {
2000
+ this.pos += 1;
2001
+ const place = this.readPlace();
2002
+ const oldValue = this.readPlaceValue(place);
2003
+ const newValue = this.evalValue();
2004
+ this.writePlace(place, newValue);
2005
+ return oldValue;
2006
+ }
2653
2007
  case "~": {
2654
2008
  this.pos += 1;
2655
2009
  const place = this.readPlace();
@@ -2677,7 +2031,7 @@ class CursorInterpreter {
2677
2031
  case "<":
2678
2032
  return this.evalLoopLike(tag);
2679
2033
  case "#":
2680
- return this.evalWhileLoop();
2034
+ return this.evalWhileLike();
2681
2035
  default:
2682
2036
  throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
2683
2037
  }
@@ -2699,7 +2053,9 @@ class CursorInterpreter {
2699
2053
  }
2700
2054
  this.ensure(")");
2701
2055
  if (typeof callee === "object" && callee && "__opcode" in callee) {
2702
- return this.applyOpcode(callee.__opcode, args);
2056
+ const marker = callee;
2057
+ const opArgs = marker.__receiver !== undefined ? [marker.__receiver, ...args] : args;
2058
+ return this.applyOpcode(marker.__opcode, opArgs);
2703
2059
  }
2704
2060
  return this.navigate(callee, args);
2705
2061
  }
@@ -2890,15 +2246,11 @@ class CursorInterpreter {
2890
2246
  return entries.map(([key]) => ({ key, value: key }));
2891
2247
  return entries.map(([key, value]) => ({ key, value }));
2892
2248
  }
2893
- if (typeof iterable === "number" && Number.isFinite(iterable) && iterable > 0) {
2894
- const out = [];
2895
- for (let index = 0;index < Math.floor(iterable); index += 1) {
2896
- if (keysOnly)
2897
- out.push({ key: index, value: index });
2898
- else
2899
- out.push({ key: index, value: index + 1 });
2900
- }
2901
- return out;
2249
+ if (typeof iterable === "string") {
2250
+ const entries = Array.from(iterable);
2251
+ if (keysOnly)
2252
+ return entries.map((_value, index) => ({ key: index, value: index }));
2253
+ return entries.map((value, index) => ({ key: index, value }));
2902
2254
  }
2903
2255
  return [];
2904
2256
  }
@@ -2943,21 +2295,32 @@ class CursorInterpreter {
2943
2295
  }
2944
2296
  return last;
2945
2297
  }
2946
- evalWhileLoop() {
2298
+ evalWhileLike() {
2299
+ this.pos += 1;
2300
+ const open = this.text[this.pos];
2301
+ if (!open || !"([{".includes(open))
2302
+ throw new Error(`Expected opener after '#' at ${this.pos}`);
2303
+ const close = open === "(" ? ")" : open === "[" ? "]" : "}";
2947
2304
  this.pos += 1;
2948
- this.ensure("(");
2949
2305
  const condStart = this.pos;
2950
2306
  const condValue = this.evalValue();
2951
2307
  const bodyStart = this.pos;
2952
- const bodyValueCount = 1;
2308
+ const bodyValueCount = open === "{" ? 2 : 1;
2953
2309
  let cursor = bodyStart;
2954
2310
  for (let index = 0;index < bodyValueCount; index += 1) {
2955
2311
  cursor = this.skipValueFrom(cursor);
2956
2312
  }
2957
2313
  const bodyEnd = cursor;
2958
2314
  this.pos = bodyEnd;
2959
- this.ensure(")");
2315
+ this.ensure(close);
2960
2316
  const afterClose = this.pos;
2317
+ if (open === "[")
2318
+ return this.evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
2319
+ if (open === "{")
2320
+ return this.evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
2321
+ return this.evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue);
2322
+ }
2323
+ evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue) {
2961
2324
  let last = undefined;
2962
2325
  let currentCond = condValue;
2963
2326
  while (isDefined(currentCond)) {
@@ -2978,6 +2341,58 @@ class CursorInterpreter {
2978
2341
  this.pos = afterClose;
2979
2342
  return last;
2980
2343
  }
2344
+ evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
2345
+ const out = [];
2346
+ let currentCond = condValue;
2347
+ while (isDefined(currentCond)) {
2348
+ this.tick();
2349
+ this.selfStack.push(currentCond);
2350
+ const value = this.evalBodySlice(bodyStart, bodyEnd);
2351
+ this.selfStack.pop();
2352
+ const control = this.handleLoopControl(value);
2353
+ if (control) {
2354
+ if (control.depth > 1)
2355
+ return { kind: control.kind, depth: control.depth - 1 };
2356
+ if (control.kind === "break")
2357
+ break;
2358
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2359
+ continue;
2360
+ }
2361
+ if (isDefined(value))
2362
+ out.push(value);
2363
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2364
+ }
2365
+ this.pos = afterClose;
2366
+ return out;
2367
+ }
2368
+ evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
2369
+ const result = {};
2370
+ let currentCond = condValue;
2371
+ while (isDefined(currentCond)) {
2372
+ this.tick();
2373
+ this.selfStack.push(currentCond);
2374
+ const save = this.pos;
2375
+ this.pos = bodyStart;
2376
+ const key = this.evalValue();
2377
+ const value = this.evalValue();
2378
+ this.pos = save;
2379
+ this.selfStack.pop();
2380
+ const control = this.handleLoopControl(value);
2381
+ if (control) {
2382
+ if (control.depth > 1)
2383
+ return { kind: control.kind, depth: control.depth - 1 };
2384
+ if (control.kind === "break")
2385
+ break;
2386
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2387
+ continue;
2388
+ }
2389
+ if (isDefined(value))
2390
+ result[String(key)] = value;
2391
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2392
+ }
2393
+ this.pos = afterClose;
2394
+ return result;
2395
+ }
2981
2396
  evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
2982
2397
  const items = this.iterate(iterable, keysOnly);
2983
2398
  const out = [];
@@ -3048,10 +2463,19 @@ class CursorInterpreter {
3048
2463
  case OPCODES.add:
3049
2464
  if (args[0] === undefined || args[1] === undefined)
3050
2465
  return;
3051
- if (typeof args[0] === "string" || typeof args[1] === "string") {
3052
- return String(args[0]) + String(args[1]);
2466
+ if (Array.isArray(args[0]) && Array.isArray(args[1])) {
2467
+ return [...args[0], ...args[1]];
3053
2468
  }
3054
- return Number(args[0]) + Number(args[1]);
2469
+ if (args[0] && args[1] && typeof args[0] === "object" && typeof args[1] === "object" && !Array.isArray(args[0]) && !Array.isArray(args[1])) {
2470
+ return { ...args[0], ...args[1] };
2471
+ }
2472
+ if (typeof args[0] === "string" && typeof args[1] === "string") {
2473
+ return args[0] + args[1];
2474
+ }
2475
+ if (typeof args[0] === "number" && typeof args[1] === "number") {
2476
+ return args[0] + args[1];
2477
+ }
2478
+ return;
3055
2479
  case OPCODES.sub:
3056
2480
  if (args[0] === undefined || args[1] === undefined)
3057
2481
  return;
@@ -3120,6 +2544,98 @@ class CursorInterpreter {
3120
2544
  return Array.isArray(args[0]) ? args[0] : undefined;
3121
2545
  case OPCODES.object:
3122
2546
  return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
2547
+ case OPCODES.range: {
2548
+ const from = Number(args[0]);
2549
+ const to = Number(args[1]);
2550
+ const step = to >= from ? 1 : -1;
2551
+ const out = [];
2552
+ for (let v = from;step > 0 ? v <= to : v >= to; v += step)
2553
+ out.push(v);
2554
+ return out;
2555
+ }
2556
+ case "array:push": {
2557
+ const target = args[0];
2558
+ if (!Array.isArray(target))
2559
+ return;
2560
+ const next = target.slice();
2561
+ for (let i = 1;i < args.length; i += 1)
2562
+ next.push(args[i]);
2563
+ return next;
2564
+ }
2565
+ case "array:pop": {
2566
+ const target = args[0];
2567
+ if (!Array.isArray(target) || target.length === 0)
2568
+ return;
2569
+ return target[target.length - 1];
2570
+ }
2571
+ case "array:unshift": {
2572
+ const target = args[0];
2573
+ if (!Array.isArray(target))
2574
+ return;
2575
+ const next = target.slice();
2576
+ for (let i = args.length - 1;i >= 1; i -= 1)
2577
+ next.unshift(args[i]);
2578
+ return next;
2579
+ }
2580
+ case "array:shift": {
2581
+ const target = args[0];
2582
+ if (!Array.isArray(target) || target.length === 0)
2583
+ return;
2584
+ return target[0];
2585
+ }
2586
+ case "array:slice": {
2587
+ const target = args[0];
2588
+ if (!Array.isArray(target))
2589
+ return;
2590
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2591
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2592
+ return target.slice(start, end);
2593
+ }
2594
+ case "array:join": {
2595
+ const target = args[0];
2596
+ if (!Array.isArray(target))
2597
+ return;
2598
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : ",";
2599
+ return target.map((item) => String(item)).join(sep);
2600
+ }
2601
+ case "string:split": {
2602
+ const target = args[0];
2603
+ if (typeof target !== "string")
2604
+ return;
2605
+ if (args.length < 2 || args[1] === undefined)
2606
+ return [target];
2607
+ return target.split(String(args[1]));
2608
+ }
2609
+ case "string:join": {
2610
+ const target = args[0];
2611
+ if (typeof target !== "string")
2612
+ return;
2613
+ const parts = Array.from(target);
2614
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2615
+ return parts.join(sep);
2616
+ }
2617
+ case "string:slice": {
2618
+ const target = args[0];
2619
+ if (typeof target !== "string")
2620
+ return;
2621
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2622
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2623
+ return Array.from(target).slice(start, end).join("");
2624
+ }
2625
+ case "string:starts-with": {
2626
+ const target = args[0];
2627
+ if (typeof target !== "string")
2628
+ return;
2629
+ const prefix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2630
+ return target.startsWith(prefix);
2631
+ }
2632
+ case "string:ends-with": {
2633
+ const target = args[0];
2634
+ if (typeof target !== "string")
2635
+ return;
2636
+ const suffix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2637
+ return target.endsWith(suffix);
2638
+ }
3123
2639
  default:
3124
2640
  throw new Error(`Unknown opcode ${id}`);
3125
2641
  }
@@ -3129,10 +2645,103 @@ class CursorInterpreter {
3129
2645
  for (const key of keys) {
3130
2646
  if (current === undefined || current === null)
3131
2647
  return;
3132
- current = current[String(key)];
2648
+ current = this.readProperty(current, key);
2649
+ if (current === undefined)
2650
+ return;
3133
2651
  }
3134
2652
  return current;
3135
2653
  }
2654
+ readProperty(target, key) {
2655
+ if (typeof key === "string" && key === "size") {
2656
+ if (Array.isArray(target))
2657
+ return target.length;
2658
+ if (typeof target === "string")
2659
+ return Array.from(target).length;
2660
+ }
2661
+ const index = this.parseIndexKey(key);
2662
+ if (Array.isArray(target)) {
2663
+ if (index !== undefined)
2664
+ return target[index];
2665
+ if (typeof key === "string")
2666
+ return this.resolveArrayMethod(target, key);
2667
+ return;
2668
+ }
2669
+ if (typeof target === "string") {
2670
+ if (index !== undefined)
2671
+ return Array.from(target)[index];
2672
+ if (typeof key === "string")
2673
+ return this.resolveStringMethod(target, key);
2674
+ return;
2675
+ }
2676
+ if (this.isPlainObject(target)) {
2677
+ const prop = String(key);
2678
+ if (!Object.prototype.hasOwnProperty.call(target, prop))
2679
+ return;
2680
+ return target[prop];
2681
+ }
2682
+ return;
2683
+ }
2684
+ resolveArrayMethod(target, key) {
2685
+ switch (key) {
2686
+ case "push":
2687
+ return { __opcode: "array:push", __receiver: target };
2688
+ case "pop":
2689
+ return { __opcode: "array:pop", __receiver: target };
2690
+ case "unshift":
2691
+ return { __opcode: "array:unshift", __receiver: target };
2692
+ case "shift":
2693
+ return { __opcode: "array:shift", __receiver: target };
2694
+ case "slice":
2695
+ return { __opcode: "array:slice", __receiver: target };
2696
+ case "join":
2697
+ return { __opcode: "array:join", __receiver: target };
2698
+ default:
2699
+ return;
2700
+ }
2701
+ }
2702
+ resolveStringMethod(target, key) {
2703
+ switch (key) {
2704
+ case "split":
2705
+ return { __opcode: "string:split", __receiver: target };
2706
+ case "join":
2707
+ return { __opcode: "string:join", __receiver: target };
2708
+ case "slice":
2709
+ return { __opcode: "string:slice", __receiver: target };
2710
+ case "starts-with":
2711
+ return { __opcode: "string:starts-with", __receiver: target };
2712
+ case "ends-with":
2713
+ return { __opcode: "string:ends-with", __receiver: target };
2714
+ default:
2715
+ return;
2716
+ }
2717
+ }
2718
+ canWriteProperty(target, key) {
2719
+ const index = this.parseIndexKey(key);
2720
+ if (Array.isArray(target)) {
2721
+ if (index === undefined)
2722
+ return;
2723
+ return { kind: "array", index };
2724
+ }
2725
+ if (this.isPlainObject(target))
2726
+ return { kind: "object" };
2727
+ return;
2728
+ }
2729
+ parseIndexKey(key) {
2730
+ if (typeof key === "number" && Number.isInteger(key) && key >= 0)
2731
+ return key;
2732
+ if (typeof key !== "string" || key.length === 0)
2733
+ return;
2734
+ if (!/^(0|[1-9]\d*)$/.test(key))
2735
+ return;
2736
+ const index = Number(key);
2737
+ return Number.isSafeInteger(index) ? index : undefined;
2738
+ }
2739
+ isPlainObject(value) {
2740
+ if (!value || typeof value !== "object")
2741
+ return false;
2742
+ const proto = Object.getPrototypeOf(value);
2743
+ return proto === Object.prototype || proto === null;
2744
+ }
3136
2745
  readPlace() {
3137
2746
  this.skipNonCode();
3138
2747
  const direct = this.readRootVarOrRefIfPresent();
@@ -3187,13 +2796,13 @@ class CursorInterpreter {
3187
2796
  }
3188
2797
  this.pos += 1;
3189
2798
  return {
3190
- root: tag === "$" ? prefix.raw : prefix.value,
2799
+ root: prefix.raw,
3191
2800
  isRef: tag === "'"
3192
2801
  };
3193
2802
  }
3194
2803
  writePlace(place, value) {
3195
2804
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3196
- const rootKey = String(place.root);
2805
+ const rootKey = place.root;
3197
2806
  if (place.keys.length === 0) {
3198
2807
  rootTable[rootKey] = value;
3199
2808
  return;
@@ -3204,17 +2813,48 @@ class CursorInterpreter {
3204
2813
  rootTable[rootKey] = target;
3205
2814
  }
3206
2815
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3207
- const key = String(place.keys[index]);
3208
- const next = target[key];
2816
+ const key = place.keys[index];
2817
+ const access2 = this.canWriteProperty(target, key);
2818
+ if (!access2)
2819
+ return;
2820
+ if (access2.kind === "array") {
2821
+ const next2 = target[access2.index];
2822
+ if (!next2 || typeof next2 !== "object")
2823
+ target[access2.index] = {};
2824
+ target = target[access2.index];
2825
+ continue;
2826
+ }
2827
+ const prop = String(key);
2828
+ const next = target[prop];
3209
2829
  if (!next || typeof next !== "object")
3210
- target[key] = {};
3211
- target = target[key];
2830
+ target[prop] = {};
2831
+ target = target[prop];
2832
+ }
2833
+ const lastKey = place.keys[place.keys.length - 1];
2834
+ const access = this.canWriteProperty(target, lastKey);
2835
+ if (!access)
2836
+ return;
2837
+ if (access.kind === "array") {
2838
+ target[access.index] = value;
2839
+ return;
3212
2840
  }
3213
- target[String(place.keys[place.keys.length - 1])] = value;
2841
+ target[String(lastKey)] = value;
2842
+ }
2843
+ readPlaceValue(place) {
2844
+ const rootTable = place.isRef ? this.state.refs : this.state.vars;
2845
+ let current = rootTable[place.root];
2846
+ for (const key of place.keys) {
2847
+ if (current === undefined || current === null)
2848
+ return;
2849
+ current = this.readProperty(current, key);
2850
+ if (current === undefined)
2851
+ return;
2852
+ }
2853
+ return current;
3214
2854
  }
3215
2855
  deletePlace(place) {
3216
2856
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3217
- const rootKey = String(place.root);
2857
+ const rootKey = place.root;
3218
2858
  if (place.keys.length === 0) {
3219
2859
  delete rootTable[rootKey];
3220
2860
  return;
@@ -3223,11 +2863,29 @@ class CursorInterpreter {
3223
2863
  if (!target || typeof target !== "object")
3224
2864
  return;
3225
2865
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3226
- target = target[String(place.keys[index])];
2866
+ const key = place.keys[index];
2867
+ const access2 = this.canWriteProperty(target, key);
2868
+ if (!access2)
2869
+ return;
2870
+ if (access2.kind === "array") {
2871
+ target = target[access2.index];
2872
+ if (!target || typeof target !== "object")
2873
+ return;
2874
+ continue;
2875
+ }
2876
+ target = target[String(key)];
3227
2877
  if (!target || typeof target !== "object")
3228
2878
  return;
3229
2879
  }
3230
- delete target[String(place.keys[place.keys.length - 1])];
2880
+ const lastKey = place.keys[place.keys.length - 1];
2881
+ const access = this.canWriteProperty(target, lastKey);
2882
+ if (!access)
2883
+ return;
2884
+ if (access.kind === "array") {
2885
+ delete target[access.index];
2886
+ return;
2887
+ }
2888
+ delete target[String(lastKey)];
3231
2889
  }
3232
2890
  skipValue() {
3233
2891
  this.pos = this.skipValueFrom(this.pos);
@@ -3243,12 +2901,12 @@ class CursorInterpreter {
3243
2901
  return startPos;
3244
2902
  }
3245
2903
  if (tag === ",") {
3246
- this.pos += 1 + prefix.value;
2904
+ this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
3247
2905
  const end2 = this.pos;
3248
2906
  this.pos = save;
3249
2907
  return end2;
3250
2908
  }
3251
- if (tag === "=") {
2909
+ if (tag === "=" || tag === "/") {
3252
2910
  this.pos += 1;
3253
2911
  this.skipValue();
3254
2912
  this.skipValue();
@@ -3283,7 +2941,8 @@ class CursorInterpreter {
3283
2941
  if (opener && "([{".includes(opener)) {
3284
2942
  const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
3285
2943
  if (prefix.value > 0) {
3286
- this.pos += 1 + prefix.value + 1;
2944
+ const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
2945
+ this.pos = bodyEnd + 1;
3287
2946
  const end3 = this.pos;
3288
2947
  this.pos = save;
3289
2948
  return end3;
@@ -3315,19 +2974,63 @@ function evaluateRexc(text, ctx = {}) {
3315
2974
  // rex-repl.ts
3316
2975
  var req = createRequire2(import.meta.url);
3317
2976
  var { version } = req("./package.json");
3318
- var C = {
3319
- reset: "\x1B[0m",
3320
- bold: "\x1B[1m",
3321
- dim: "\x1B[2m",
3322
- red: "\x1B[31m",
3323
- green: "\x1B[32m",
3324
- yellow: "\x1B[33m",
3325
- blue: "\x1B[34m",
3326
- cyan: "\x1B[36m",
3327
- gray: "\x1B[90m",
3328
- boldBlue: "\x1B[1;34m"
3329
- };
3330
- var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
2977
+ function createColors(enabled) {
2978
+ if (!enabled) {
2979
+ return {
2980
+ reset: "",
2981
+ bold: "",
2982
+ dim: "",
2983
+ red: "",
2984
+ green: "",
2985
+ yellow: "",
2986
+ blue: "",
2987
+ magenta: "",
2988
+ cyan: "",
2989
+ gray: "",
2990
+ keyword: ""
2991
+ };
2992
+ }
2993
+ return {
2994
+ reset: "\x1B[0m",
2995
+ bold: "\x1B[1m",
2996
+ dim: "\x1B[2m",
2997
+ red: "\x1B[38;5;203m",
2998
+ green: "\x1B[38;5;114m",
2999
+ yellow: "\x1B[38;5;179m",
3000
+ blue: "\x1B[38;5;75m",
3001
+ magenta: "\x1B[38;5;141m",
3002
+ cyan: "\x1B[38;5;81m",
3003
+ gray: "\x1B[38;5;245m",
3004
+ keyword: "\x1B[1;38;5;208m"
3005
+ };
3006
+ }
3007
+ var colorEnabled = process.stdout.isTTY;
3008
+ var C = createColors(colorEnabled);
3009
+ function setColorEnabled(enabled) {
3010
+ colorEnabled = enabled;
3011
+ C = createColors(enabled);
3012
+ }
3013
+ function formatJson(value, indent = 2) {
3014
+ const normalized = normalizeJsonValue(value, false);
3015
+ const text = JSON.stringify(normalized, null, indent);
3016
+ return text ?? "null";
3017
+ }
3018
+ function normalizeJsonValue(value, inArray) {
3019
+ if (value === undefined)
3020
+ return inArray ? null : undefined;
3021
+ if (value === null || typeof value !== "object")
3022
+ return value;
3023
+ if (Array.isArray(value))
3024
+ return value.map((item) => normalizeJsonValue(item, true));
3025
+ const out = {};
3026
+ for (const [key, val] of Object.entries(value)) {
3027
+ const normalized = normalizeJsonValue(val, false);
3028
+ if (normalized !== undefined)
3029
+ out[key] = normalized;
3030
+ }
3031
+ return out;
3032
+ }
3033
+ var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<objKey>\b[A-Za-z_][A-Za-z0-9_-]*\b)(?=\s*:)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|nor|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)|(?<identifier>\b[A-Za-z_][A-Za-z0-9_.-]*\b)/g;
3331
3034
  function highlightLine(line) {
3332
3035
  let result = "";
3333
3036
  let lastIndex = 0;
@@ -3340,14 +3043,18 @@ function highlightLine(line) {
3340
3043
  result += C.gray + text + C.reset;
3341
3044
  } else if (g.dstring || g.sstring) {
3342
3045
  result += C.green + text + C.reset;
3046
+ } else if (g.objKey) {
3047
+ result += C.magenta + text + C.reset;
3343
3048
  } else if (g.keyword) {
3344
- result += C.boldBlue + text + C.reset;
3049
+ result += C.keyword + text + C.reset;
3345
3050
  } else if (g.literal) {
3346
3051
  result += C.yellow + text + C.reset;
3347
3052
  } else if (g.typePred) {
3348
3053
  result += C.cyan + text + C.reset;
3349
3054
  } else if (g.num) {
3350
3055
  result += C.cyan + text + C.reset;
3056
+ } else if (g.identifier) {
3057
+ result += C.blue + text + C.reset;
3351
3058
  } else {
3352
3059
  result += text;
3353
3060
  }
@@ -3410,7 +3117,7 @@ function highlightRexc(text) {
3410
3117
  i++;
3411
3118
  break;
3412
3119
  case "%":
3413
- out += C.boldBlue + prefix + tag + C.reset;
3120
+ out += C.keyword + prefix + tag + C.reset;
3414
3121
  i++;
3415
3122
  break;
3416
3123
  case "$":
@@ -3436,6 +3143,7 @@ function highlightRexc(text) {
3436
3143
  break;
3437
3144
  }
3438
3145
  case "=":
3146
+ case "/":
3439
3147
  case "~":
3440
3148
  out += C.red + prefix + tag + C.reset;
3441
3149
  i++;
@@ -3447,11 +3155,11 @@ function highlightRexc(text) {
3447
3155
  case ">":
3448
3156
  case "<":
3449
3157
  case "#":
3450
- out += C.boldBlue + prefix + tag + C.reset;
3158
+ out += C.keyword + prefix + tag + C.reset;
3451
3159
  i++;
3452
3160
  break;
3453
3161
  case ";":
3454
- out += C.boldBlue + prefix + tag + C.reset;
3162
+ out += C.keyword + prefix + tag + C.reset;
3455
3163
  i++;
3456
3164
  break;
3457
3165
  case "^":
@@ -3475,6 +3183,23 @@ function highlightRexc(text) {
3475
3183
  }
3476
3184
  return out;
3477
3185
  }
3186
+ function highlightAuto(text, hint) {
3187
+ if (hint === "rexc")
3188
+ return highlightRexc(text);
3189
+ if (hint === "rex")
3190
+ return text.split(`
3191
+ `).map((line) => highlightLine(line)).join(`
3192
+ `);
3193
+ try {
3194
+ const match = grammar.match(text);
3195
+ if (match.succeeded()) {
3196
+ return text.split(`
3197
+ `).map((line) => highlightLine(line)).join(`
3198
+ `);
3199
+ }
3200
+ } catch {}
3201
+ return highlightRexc(text);
3202
+ }
3478
3203
  var JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3479
3204
  function highlightJSON(json) {
3480
3205
  let result = "";
@@ -3539,23 +3264,18 @@ function isIncomplete(buffer) {
3539
3264
  const trimmed = buffer.trimEnd();
3540
3265
  if (/[+\-*/%&|^=<>]$/.test(trimmed))
3541
3266
  return true;
3542
- if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
3267
+ if (/\b(?:and|or|nor|do|in|of)\s*$/.test(trimmed))
3543
3268
  return true;
3544
3269
  return false;
3545
3270
  }
3546
- function formatResult(value) {
3547
- let text;
3548
- try {
3549
- text = stringify(value, { maxWidth: 60 });
3550
- } catch {
3551
- text = String(value);
3552
- }
3553
- return `${C.gray}→${C.reset} ${highlightLine(text)}`;
3554
- }
3555
- function formatVarState(vars) {
3271
+ function formatVarState(vars, format) {
3556
3272
  const entries = Object.entries(vars);
3557
3273
  if (entries.length === 0)
3558
3274
  return "";
3275
+ if (format === "json") {
3276
+ const rendered = highlightJSON(formatJson(vars, 2));
3277
+ return `${C.dim} vars:${C.reset} ${rendered}`;
3278
+ }
3559
3279
  const MAX_LINE = 70;
3560
3280
  const MAX_VALUE = 30;
3561
3281
  const parts = [];
@@ -3580,6 +3300,18 @@ function formatVarState(vars) {
3580
3300
  }
3581
3301
  return `${C.dim} ${parts.join(", ")}${C.reset}`;
3582
3302
  }
3303
+ function renderValue(value, format, kind) {
3304
+ if (format === "json") {
3305
+ return highlightJSON(formatJson(value, 2));
3306
+ }
3307
+ if (kind === "source") {
3308
+ return highlightAuto(String(value ?? ""));
3309
+ }
3310
+ if (kind === "rexc") {
3311
+ return highlightRexc(String(value ?? ""));
3312
+ }
3313
+ return highlightLine(stringify(value, { maxWidth: 120 }));
3314
+ }
3583
3315
  var KEYWORDS = [
3584
3316
  "when",
3585
3317
  "unless",
@@ -3591,6 +3323,7 @@ var KEYWORDS = [
3591
3323
  "of",
3592
3324
  "and",
3593
3325
  "or",
3326
+ "nor",
3594
3327
  "else",
3595
3328
  "break",
3596
3329
  "continue",
@@ -3608,8 +3341,35 @@ var KEYWORDS = [
3608
3341
  "array",
3609
3342
  "boolean"
3610
3343
  ];
3344
+ var DOT_COMMANDS = [
3345
+ ".help",
3346
+ ".file",
3347
+ ".cat",
3348
+ ".expr",
3349
+ ".source",
3350
+ ".vars",
3351
+ ".vars!",
3352
+ ".clear",
3353
+ ".ir",
3354
+ ".rexc",
3355
+ ".opt",
3356
+ ".json",
3357
+ ".color",
3358
+ ".exit"
3359
+ ];
3611
3360
  function completer(state) {
3612
3361
  return (line) => {
3362
+ if (line.startsWith(".file ")) {
3363
+ return completeFilePath(line, 6);
3364
+ }
3365
+ if (line.startsWith(".cat ")) {
3366
+ return completeFilePath(line, 5);
3367
+ }
3368
+ if (line.startsWith(".") && !line.includes(" ")) {
3369
+ const partial2 = line.trim();
3370
+ const matches = DOT_COMMANDS.filter((cmd) => cmd.startsWith(partial2));
3371
+ return [matches, line];
3372
+ }
3613
3373
  const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
3614
3374
  const partial = match ? match[0] : "";
3615
3375
  if (!partial)
@@ -3620,45 +3380,92 @@ function completer(state) {
3620
3380
  return [hits, partial];
3621
3381
  };
3622
3382
  }
3623
- function handleDotCommand(cmd, state, rl) {
3383
+ function completeFilePath(line, prefixLength) {
3384
+ const raw = line.slice(prefixLength);
3385
+ const trimmed = raw.trimStart();
3386
+ const quote = trimmed.startsWith('"') || trimmed.startsWith("'") ? trimmed[0] : "";
3387
+ const pathPart = quote ? trimmed.slice(1) : trimmed;
3388
+ const endsWithSlash = pathPart.endsWith("/");
3389
+ const rawDir = endsWithSlash ? pathPart.slice(0, -1) : pathPart;
3390
+ const baseName = endsWithSlash ? "" : rawDir.includes("/") ? basename(rawDir) : rawDir;
3391
+ const dirPart = endsWithSlash ? rawDir || "." : rawDir.includes("/") ? dirname(rawDir) : ".";
3392
+ const dirPath = resolve(dirPart);
3393
+ let entries = [];
3394
+ try {
3395
+ entries = readdirSync(dirPath);
3396
+ } catch {
3397
+ return [[], ""];
3398
+ }
3399
+ const prefix = dirPart === "." ? "" : `${dirPart}/`;
3400
+ const matches = entries.filter((entry) => entry.startsWith(baseName)).map((entry) => {
3401
+ const fullPath = resolve(dirPath, entry);
3402
+ let suffix = "";
3403
+ try {
3404
+ if (statSync(fullPath).isDirectory())
3405
+ suffix = "/";
3406
+ } catch {
3407
+ suffix = "";
3408
+ }
3409
+ return `${quote}${prefix}${entry}${suffix}`;
3410
+ });
3411
+ return [matches, trimmed];
3412
+ }
3413
+ function stripQuotes(value) {
3414
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3415
+ return value.slice(1, -1);
3416
+ }
3417
+ return value;
3418
+ }
3419
+ async function handleDotCommand(cmd, state, rl, runSource, updatePromptStyles) {
3624
3420
  function toggleLabel(on) {
3625
3421
  return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
3626
3422
  }
3627
3423
  switch (cmd) {
3628
3424
  case ".help":
3629
3425
  console.log([
3630
- `${C.boldBlue}Rex REPL Commands:${C.reset}`,
3631
- " .help Show this help message",
3632
- " .vars Show all current variables",
3633
- " .clear Clear all variables",
3634
- " .ir Toggle showing IR JSON after parsing",
3635
- " .rexc Toggle showing compiled rexc before execution",
3636
- " .opt Toggle IR optimizations",
3637
- " .exit Exit the REPL",
3426
+ `${C.keyword}Rex REPL Commands:${C.reset}`,
3427
+ " .help Show this help message",
3428
+ " .file <path> Load and execute a Rex file",
3429
+ " .cat <path> Print a Rex/rexc file with highlighting",
3430
+ " .expr Toggle showing expression results",
3431
+ " .source Toggle showing input source",
3432
+ " .vars Toggle showing variable summary",
3433
+ " .vars! Show all current variables",
3434
+ " .clear Clear all variables",
3435
+ " .ir Toggle showing IR JSON after parsing",
3436
+ " .rexc Toggle showing compiled rexc before execution",
3437
+ " .opt Toggle IR optimizations",
3438
+ " .json Toggle JSON output format",
3439
+ " .color Toggle ANSI color output",
3440
+ " .exit Exit the REPL",
3638
3441
  "",
3639
3442
  "Enter Rex expressions to evaluate them.",
3640
3443
  "Multi-line: open brackets or do/end blocks continue on the next line.",
3641
3444
  "Ctrl-C cancels multi-line input.",
3642
- "Ctrl-D exits."
3445
+ "Ctrl-D exits.",
3446
+ "",
3447
+ "Outputs are printed as labeled blocks when enabled."
3643
3448
  ].join(`
3644
3449
  `));
3645
- return true;
3646
- case ".ir":
3647
- state.showIR = !state.showIR;
3648
- console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3649
- return true;
3650
- case ".rexc":
3651
- state.showRexc = !state.showRexc;
3652
- console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3653
- return true;
3654
- case ".opt":
3655
- state.optimize = !state.optimize;
3656
- console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3657
- return true;
3658
- case ".vars": {
3450
+ return "handled";
3451
+ case ".expr":
3452
+ state.showExpr = !state.showExpr;
3453
+ console.log(`${C.dim} Expression output: ${toggleLabel(state.showExpr)}${C.reset}`);
3454
+ return "handled";
3455
+ case ".source":
3456
+ state.showSource = !state.showSource;
3457
+ console.log(`${C.dim} Source output: ${toggleLabel(state.showSource)}${C.reset}`);
3458
+ return "handled";
3459
+ case ".vars":
3460
+ state.showVars = !state.showVars;
3461
+ console.log(`${C.dim} Variable summary: ${toggleLabel(state.showVars)}${C.reset}`);
3462
+ return "handled";
3463
+ case ".vars!": {
3659
3464
  const entries = Object.entries(state.vars);
3660
3465
  if (entries.length === 0) {
3661
3466
  console.log(`${C.dim} (no variables)${C.reset}`);
3467
+ } else if (state.outputFormat === "json") {
3468
+ console.log(highlightJSON(formatJson(state.vars, 2)));
3662
3469
  } else {
3663
3470
  for (const [key, val] of entries) {
3664
3471
  let valStr;
@@ -3670,43 +3477,121 @@ function handleDotCommand(cmd, state, rl) {
3670
3477
  console.log(` ${key} = ${highlightLine(valStr)}`);
3671
3478
  }
3672
3479
  }
3673
- return true;
3480
+ return "handled";
3674
3481
  }
3482
+ case ".ir":
3483
+ state.showIR = !state.showIR;
3484
+ console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3485
+ return "handled";
3486
+ case ".rexc":
3487
+ state.showRexc = !state.showRexc;
3488
+ console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3489
+ return "handled";
3490
+ case ".opt":
3491
+ state.optimize = !state.optimize;
3492
+ console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3493
+ return "handled";
3494
+ case ".json":
3495
+ state.outputFormat = state.outputFormat === "json" ? "rex" : "json";
3496
+ console.log(`${C.dim} Output format: ${state.outputFormat}${C.reset}`);
3497
+ return "handled";
3498
+ case ".color":
3499
+ setColorEnabled(!colorEnabled);
3500
+ updatePromptStyles();
3501
+ console.log(`${C.dim} Color output: ${toggleLabel(colorEnabled)}${C.reset}`);
3502
+ return "handled";
3675
3503
  case ".clear":
3676
3504
  state.vars = {};
3677
3505
  state.refs = {};
3678
3506
  console.log(`${C.dim} Variables cleared.${C.reset}`);
3679
- return true;
3507
+ return "handled";
3680
3508
  case ".exit":
3681
3509
  rl.close();
3682
- return true;
3510
+ return "handled-noprompt";
3683
3511
  default:
3512
+ if (cmd.startsWith(".cat ")) {
3513
+ const rawPath = cmd.slice(5).trim();
3514
+ if (!rawPath) {
3515
+ console.log(`${C.red} Missing file path. Usage: .cat <path>${C.reset}`);
3516
+ return "handled";
3517
+ }
3518
+ const filePath = resolve(stripQuotes(rawPath));
3519
+ try {
3520
+ const source = readFileSync(filePath, "utf8");
3521
+ const hint = filePath.endsWith(".rexc") ? "rexc" : filePath.endsWith(".rex") ? "rex" : undefined;
3522
+ console.log(highlightAuto(source, hint));
3523
+ } catch (error) {
3524
+ const message = error instanceof Error ? error.message : String(error);
3525
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3526
+ }
3527
+ return "handled";
3528
+ }
3529
+ if (cmd.startsWith(".file ")) {
3530
+ const rawPath = cmd.slice(6).trim();
3531
+ if (!rawPath) {
3532
+ console.log(`${C.red} Missing file path. Usage: .file <path>${C.reset}`);
3533
+ return "handled";
3534
+ }
3535
+ const filePath = resolve(stripQuotes(rawPath));
3536
+ try {
3537
+ const source = readFileSync(filePath, "utf8");
3538
+ runSource(source);
3539
+ } catch (error) {
3540
+ const message = error instanceof Error ? error.message : String(error);
3541
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3542
+ }
3543
+ return "handled";
3544
+ }
3684
3545
  if (cmd.startsWith(".")) {
3685
3546
  console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
3686
- return true;
3547
+ return "handled";
3687
3548
  }
3688
- return false;
3549
+ return "unhandled";
3689
3550
  }
3690
3551
  }
3691
3552
  var GAS_LIMIT = 1e7;
3553
+ var HISTORY_LIMIT = 1000;
3554
+ var HISTORY_PATH = resolve(homedir(), ".rex_history");
3692
3555
  async function startRepl() {
3693
- const state = { vars: {}, refs: {}, showIR: false, showRexc: false, optimize: false };
3556
+ const state = {
3557
+ vars: {},
3558
+ refs: {},
3559
+ showIR: false,
3560
+ showRexc: false,
3561
+ optimize: false,
3562
+ showExpr: true,
3563
+ showVars: false,
3564
+ showSource: false,
3565
+ outputFormat: "rex"
3566
+ };
3694
3567
  let multiLineBuffer = "";
3695
3568
  const PRIMARY_PROMPT = "rex> ";
3696
3569
  const CONT_PROMPT = "... ";
3697
- const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
3698
- const STYLED_CONT = `${C.dim}...${C.reset} `;
3699
3570
  let currentPrompt = PRIMARY_PROMPT;
3700
- let styledPrompt = STYLED_PRIMARY;
3701
- console.log(`${C.boldBlue}Rex${C.reset} v${version} type ${C.dim}.help${C.reset} for commands`);
3571
+ let styledPrompt = "";
3572
+ let styledPrimary = "";
3573
+ let styledCont = "";
3574
+ function updatePromptStyles() {
3575
+ styledPrimary = `${C.keyword}rex${C.reset}> `;
3576
+ styledCont = `${C.dim}...${C.reset} `;
3577
+ styledPrompt = currentPrompt === PRIMARY_PROMPT ? styledPrimary : styledCont;
3578
+ }
3579
+ updatePromptStyles();
3580
+ console.log(`${C.keyword}Rex${C.reset} v${version} — type ${C.dim}.help${C.reset} for commands`);
3702
3581
  const rl = readline.createInterface({
3703
3582
  input: process.stdin,
3704
3583
  output: process.stdout,
3705
3584
  prompt: PRIMARY_PROMPT,
3706
- historySize: 500,
3585
+ historySize: HISTORY_LIMIT,
3707
3586
  completer: completer(state),
3708
3587
  terminal: true
3709
3588
  });
3589
+ try {
3590
+ const historyText = readFileSync(HISTORY_PATH, "utf8");
3591
+ const lines = historyText.split(/\r?\n/).filter((line) => line.trim().length > 0);
3592
+ const recent = lines.slice(-HISTORY_LIMIT);
3593
+ rl.history = recent.reverse();
3594
+ } catch {}
3710
3595
  process.stdin.on("keypress", () => {
3711
3596
  process.nextTick(() => {
3712
3597
  if (!rl.line && rl.line !== "")
@@ -3728,7 +3613,7 @@ async function startRepl() {
3728
3613
  if (multiLineBuffer) {
3729
3614
  multiLineBuffer = "";
3730
3615
  currentPrompt = PRIMARY_PROMPT;
3731
- styledPrompt = STYLED_PRIMARY;
3616
+ styledPrompt = styledPrimary;
3732
3617
  rl.setPrompt(PRIMARY_PROMPT);
3733
3618
  process.stdout.write(`
3734
3619
  `);
@@ -3740,17 +3625,70 @@ async function startRepl() {
3740
3625
  });
3741
3626
  function resetPrompt() {
3742
3627
  currentPrompt = PRIMARY_PROMPT;
3743
- styledPrompt = STYLED_PRIMARY;
3628
+ styledPrompt = styledPrimary;
3744
3629
  rl.setPrompt(PRIMARY_PROMPT);
3745
3630
  rl.prompt();
3746
3631
  }
3747
- rl.on("line", (line) => {
3632
+ function runSource(source) {
3633
+ const match = grammar.match(source);
3634
+ if (!match.succeeded()) {
3635
+ const message = formatParseError(source, match);
3636
+ console.log(`${C.red} ${message}${C.reset}`);
3637
+ return;
3638
+ }
3639
+ try {
3640
+ const outputs = {};
3641
+ if (state.showSource)
3642
+ outputs.source = source;
3643
+ const isRex = grammar.match(source).succeeded();
3644
+ if (!isRex && state.showIR) {
3645
+ console.log(`${C.red} IR output is only available for Rex source.${C.reset}`);
3646
+ }
3647
+ const rexc = isRex ? compile(source, { optimize: state.optimize }) : source;
3648
+ if (state.showIR && isRex) {
3649
+ const ir = parseToIR(source);
3650
+ const lowered = state.optimize ? optimizeIR(ir) : ir;
3651
+ outputs.ir = lowered;
3652
+ }
3653
+ if (state.showRexc)
3654
+ outputs.rexc = rexc;
3655
+ const result = evaluateRexc(rexc, {
3656
+ vars: { ...state.vars },
3657
+ refs: { ...state.refs },
3658
+ gasLimit: GAS_LIMIT
3659
+ });
3660
+ state.vars = result.state.vars;
3661
+ state.refs = result.state.refs;
3662
+ if (state.showExpr)
3663
+ outputs.result = result.value;
3664
+ if (state.showVars)
3665
+ outputs.vars = state.vars;
3666
+ const order = ["source", "ir", "rexc", "vars", "result"];
3667
+ for (const key of order) {
3668
+ const value = outputs[key];
3669
+ if (value === undefined)
3670
+ continue;
3671
+ console.log(`${C.gray} ${key}:${C.reset} ${renderValue(value, state.outputFormat, key)}`);
3672
+ }
3673
+ } catch (error) {
3674
+ const message = error instanceof Error ? error.message : String(error);
3675
+ if (message.includes("Gas limit exceeded")) {
3676
+ console.log(`${C.yellow} ${message}${C.reset}`);
3677
+ } else {
3678
+ console.log(`${C.red} Error: ${message}${C.reset}`);
3679
+ }
3680
+ }
3681
+ }
3682
+ rl.on("line", async (line) => {
3748
3683
  const trimmed = line.trim();
3749
3684
  if (!multiLineBuffer && trimmed.startsWith(".")) {
3750
- if (handleDotCommand(trimmed, state, rl)) {
3685
+ const result = await handleDotCommand(trimmed, state, rl, runSource, updatePromptStyles);
3686
+ if (result === "handled") {
3751
3687
  rl.prompt();
3752
3688
  return;
3753
3689
  }
3690
+ if (result === "handled-noprompt")
3691
+ return;
3754
3692
  }
3755
3693
  multiLineBuffer += (multiLineBuffer ? `
3756
3694
  ` : "") + line;
@@ -3761,60 +3699,35 @@ async function startRepl() {
3761
3699
  }
3762
3700
  if (isIncomplete(multiLineBuffer)) {
3763
3701
  currentPrompt = CONT_PROMPT;
3764
- styledPrompt = STYLED_CONT;
3702
+ styledPrompt = styledCont;
3765
3703
  rl.setPrompt(CONT_PROMPT);
3766
3704
  rl.prompt();
3767
3705
  return;
3768
3706
  }
3769
3707
  const source = multiLineBuffer;
3770
3708
  multiLineBuffer = "";
3771
- const match = grammar.match(source);
3772
- if (!match.succeeded()) {
3773
- console.log(`${C.red} ${match.message}${C.reset}`);
3774
- resetPrompt();
3775
- return;
3776
- }
3777
- try {
3778
- const ir = parseToIR(source);
3779
- const lowered = state.optimize ? optimizeIR(ir) : ir;
3780
- if (state.showIR) {
3781
- console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3782
- }
3783
- const rexc = compile(source, { optimize: state.optimize });
3784
- if (state.showRexc) {
3785
- console.log(`${C.dim} rexc:${C.reset} ${highlightRexc(rexc)}`);
3786
- }
3787
- const result = evaluateRexc(rexc, {
3788
- vars: { ...state.vars },
3789
- refs: { ...state.refs },
3790
- gasLimit: GAS_LIMIT
3791
- });
3792
- state.vars = result.state.vars;
3793
- state.refs = result.state.refs;
3794
- console.log(formatResult(result.value));
3795
- const varLine = formatVarState(state.vars);
3796
- if (varLine)
3797
- console.log(varLine);
3798
- } catch (error) {
3799
- const message = error instanceof Error ? error.message : String(error);
3800
- if (message.includes("Gas limit exceeded")) {
3801
- console.log(`${C.yellow} ${message}${C.reset}`);
3802
- } else {
3803
- console.log(`${C.red} Error: ${message}${C.reset}`);
3804
- }
3805
- }
3709
+ runSource(source);
3806
3710
  resetPrompt();
3807
3711
  });
3808
3712
  rl.on("close", () => {
3713
+ try {
3714
+ const history = rl.history ?? [];
3715
+ const trimmed = history.slice().reverse().filter((line) => line.trim().length > 0).slice(-HISTORY_LIMIT);
3716
+ writeFileSync(HISTORY_PATH, `${trimmed.join(`
3717
+ `)}
3718
+ `, "utf8");
3719
+ } catch {}
3809
3720
  process.exit(0);
3810
3721
  });
3811
3722
  rl.prompt();
3812
3723
  }
3813
3724
  export {
3814
3725
  startRepl,
3726
+ setColorEnabled,
3815
3727
  isIncomplete,
3816
3728
  highlightRexc,
3817
3729
  highlightLine,
3818
3730
  highlightJSON,
3731
+ highlightAuto,
3819
3732
  formatVarState
3820
3733
  };