@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-cli.js CHANGED
@@ -115,11 +115,11 @@ function encodeDecimal(significand, power) {
115
115
  function encodeNumberNode(node) {
116
116
  const numberValue = node.value;
117
117
  if (Number.isNaN(numberValue))
118
- return "5'";
118
+ return "nan'";
119
119
  if (numberValue === Infinity)
120
- return "6'";
120
+ return "inf'";
121
121
  if (numberValue === -Infinity)
122
- return "7'";
122
+ return "nif'";
123
123
  if (Number.isInteger(numberValue)) {
124
124
  const { base, exp } = splitDecimal(numberValue);
125
125
  if (exp >= 0 && exp <= 4)
@@ -151,7 +151,7 @@ function encodeNumberNode(node) {
151
151
  return encodeDecimal(significand, power);
152
152
  }
153
153
  function encodeOpcode(opcode) {
154
- return `${encodeUint(OPCODE_IDS[opcode])}%`;
154
+ return `${OPCODE_IDS[opcode]}%`;
155
155
  }
156
156
  function encodeCallParts(parts) {
157
157
  return `(${parts.join("")})`;
@@ -168,7 +168,7 @@ function addOptionalPrefix(encoded) {
168
168
  let payload = encoded;
169
169
  if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
170
170
  payload = encoded.slice(2, -1);
171
- } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
171
+ } else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
172
172
  payload = encoded.slice(2, -1);
173
173
  } else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
174
174
  payload = encoded.slice(1, -1);
@@ -179,7 +179,7 @@ function addOptionalPrefix(encoded) {
179
179
  }
180
180
  function encodeBlockExpression(block) {
181
181
  if (block.length === 0)
182
- return "4'";
182
+ return "un'";
183
183
  if (block.length === 1)
184
184
  return encodeNode(block[0]);
185
185
  return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
@@ -196,9 +196,13 @@ function encodeConditionalElse(elseBranch) {
196
196
  };
197
197
  return encodeNode(nested);
198
198
  }
199
+ function encodeDomainLookup(shortCode, tag) {
200
+ return `${shortCode}${tag}`;
201
+ }
199
202
  function encodeNavigation(node) {
200
203
  const domainRefs = activeEncodeOptions?.domainRefs;
201
- if (domainRefs && node.target.type === "identifier") {
204
+ const domainOpcodes = activeEncodeOptions?.domainOpcodes;
205
+ if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
202
206
  const staticPath = [node.target.name];
203
207
  for (const segment of node.segments) {
204
208
  if (segment.type !== "static")
@@ -207,14 +211,17 @@ function encodeNavigation(node) {
207
211
  }
208
212
  for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
209
213
  const dottedName = staticPath.slice(0, pathLength).join(".");
210
- const domainRef = domainRefs[dottedName];
211
- if (domainRef === undefined)
214
+ const refCode = domainRefs?.[dottedName];
215
+ const opcodeCode = domainOpcodes?.[dottedName];
216
+ const shortCode = refCode ?? opcodeCode;
217
+ if (shortCode === undefined)
212
218
  continue;
219
+ const tag = refCode !== undefined ? "'" : "%";
213
220
  const consumedStaticSegments = pathLength - 1;
214
221
  if (consumedStaticSegments === node.segments.length) {
215
- return `${encodeUint(domainRef)}'`;
222
+ return encodeDomainLookup(shortCode, tag);
216
223
  }
217
- const parts2 = [`${encodeUint(domainRef)}'`];
224
+ const parts2 = [encodeDomainLookup(shortCode, tag)];
218
225
  for (const segment of node.segments.slice(consumedStaticSegments)) {
219
226
  if (segment.type === "static")
220
227
  parts2.push(encodeBareOrLengthString(segment.key));
@@ -240,9 +247,12 @@ function encodeWhile(node) {
240
247
  }
241
248
  function encodeFor(node) {
242
249
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
243
- if (node.binding.type === "binding:expr") {
250
+ if (node.binding.type === "binding:bareIn") {
244
251
  return `>(${encodeNode(node.binding.source)}${body})`;
245
252
  }
253
+ if (node.binding.type === "binding:bareOf") {
254
+ return `<(${encodeNode(node.binding.source)}${body})`;
255
+ }
246
256
  if (node.binding.type === "binding:valueIn") {
247
257
  return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
248
258
  }
@@ -253,30 +263,47 @@ function encodeFor(node) {
253
263
  }
254
264
  function encodeArrayComprehension(node) {
255
265
  const body = addOptionalPrefix(encodeNode(node.body));
256
- if (node.binding.type === "binding:expr") {
266
+ if (node.binding.type === "binding:bareIn") {
257
267
  return `>[${encodeNode(node.binding.source)}${body}]`;
258
268
  }
269
+ if (node.binding.type === "binding:bareOf") {
270
+ return `<[${encodeNode(node.binding.source)}${body}]`;
271
+ }
259
272
  if (node.binding.type === "binding:valueIn") {
260
273
  return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
261
274
  }
262
275
  if (node.binding.type === "binding:keyValueIn") {
263
276
  return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
264
277
  }
265
- return `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
278
+ return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
266
279
  }
267
280
  function encodeObjectComprehension(node) {
268
281
  const key = addOptionalPrefix(encodeNode(node.key));
269
282
  const value = addOptionalPrefix(encodeNode(node.value));
270
- if (node.binding.type === "binding:expr") {
283
+ if (node.binding.type === "binding:bareIn") {
271
284
  return `>{${encodeNode(node.binding.source)}${key}${value}}`;
272
285
  }
286
+ if (node.binding.type === "binding:bareOf") {
287
+ return `<{${encodeNode(node.binding.source)}${key}${value}}`;
288
+ }
273
289
  if (node.binding.type === "binding:valueIn") {
274
290
  return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
275
291
  }
276
292
  if (node.binding.type === "binding:keyValueIn") {
277
293
  return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
278
294
  }
279
- return `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
295
+ return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
296
+ }
297
+ function encodeWhileArrayComprehension(node) {
298
+ const cond = encodeNode(node.condition);
299
+ const body = addOptionalPrefix(encodeNode(node.body));
300
+ return `#[${cond}${body}]`;
301
+ }
302
+ function encodeWhileObjectComprehension(node) {
303
+ const cond = encodeNode(node.condition);
304
+ const key = addOptionalPrefix(encodeNode(node.key));
305
+ const value = addOptionalPrefix(encodeNode(node.value));
306
+ return `#{${cond}${key}${value}}`;
280
307
  }
281
308
  function encodeNode(node) {
282
309
  switch (node.type) {
@@ -285,7 +312,10 @@ function encodeNode(node) {
285
312
  case "identifier": {
286
313
  const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
287
314
  if (domainRef !== undefined)
288
- return `${encodeUint(domainRef)}'`;
315
+ return `${domainRef}'`;
316
+ const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
317
+ if (domainOpcode !== undefined)
318
+ return `${domainOpcode}%`;
289
319
  return `${node.name}$`;
290
320
  }
291
321
  case "self":
@@ -298,11 +328,11 @@ function encodeNode(node) {
298
328
  return `${encodeUint(node.depth - 1)}@`;
299
329
  }
300
330
  case "boolean":
301
- return node.value ? "1'" : "2'";
331
+ return node.value ? "tr'" : "fl'";
302
332
  case "null":
303
- return "3'";
333
+ return "nl'";
304
334
  case "undefined":
305
- return "4'";
335
+ return "un'";
306
336
  case "number":
307
337
  return encodeNumberNode(node);
308
338
  case "string":
@@ -313,12 +343,16 @@ function encodeNode(node) {
313
343
  }
314
344
  case "arrayComprehension":
315
345
  return encodeArrayComprehension(node);
346
+ case "whileArrayComprehension":
347
+ return encodeWhileArrayComprehension(node);
316
348
  case "object": {
317
349
  const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
318
350
  return `{${body}}`;
319
351
  }
320
352
  case "objectComprehension":
321
353
  return encodeObjectComprehension(node);
354
+ case "whileObjectComprehension":
355
+ return encodeWhileObjectComprehension(node);
322
356
  case "key":
323
357
  return encodeBareOrLengthString(node.name);
324
358
  case "group":
@@ -328,6 +362,10 @@ function encodeNode(node) {
328
362
  return `~${encodeNode(node.value)}`;
329
363
  if (node.op === "neg")
330
364
  return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
365
+ if (node.op === "logicalNot") {
366
+ const val = encodeNode(node.value);
367
+ return `!(${val}tr')`;
368
+ }
331
369
  return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
332
370
  case "binary":
333
371
  if (node.op === "and") {
@@ -346,12 +384,19 @@ function encodeNode(node) {
346
384
  }).join("");
347
385
  return `|(${body})`;
348
386
  }
387
+ if (node.op === "nor") {
388
+ const left = encodeNode(node.left);
389
+ const right = addOptionalPrefix(encodeNode(node.right));
390
+ return `!(${left}${right})`;
391
+ }
349
392
  return encodeCallParts([
350
393
  encodeOpcode(BINARY_TO_OPCODE[node.op]),
351
394
  encodeNode(node.left),
352
395
  encodeNode(node.right)
353
396
  ]);
354
397
  case "assign": {
398
+ if (node.op === ":=")
399
+ return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
355
400
  if (node.op === "=")
356
401
  return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
357
402
  const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
@@ -362,8 +407,12 @@ function encodeNode(node) {
362
407
  }
363
408
  case "navigation":
364
409
  return encodeNavigation(node);
365
- case "call":
410
+ case "call": {
411
+ if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
412
+ return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
413
+ }
366
414
  return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
415
+ }
367
416
  case "conditional": {
368
417
  const opener = node.head === "when" ? "?(" : "!(";
369
418
  const cond = encodeNode(node.condition);
@@ -371,6 +420,8 @@ function encodeNode(node) {
371
420
  const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
372
421
  return `${opener}${cond}${thenExpr}${elseExpr})`;
373
422
  }
423
+ case "range":
424
+ return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
374
425
  case "for":
375
426
  return encodeFor(node);
376
427
  case "while":
@@ -390,11 +441,30 @@ function collectLogicalChain(node, op) {
390
441
  return [node];
391
442
  return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
392
443
  }
444
+ function formatParseError(source, match) {
445
+ const message = match.message ?? "Parse failed";
446
+ const pos = match.getRightmostFailurePosition?.();
447
+ if (typeof pos !== "number" || !Number.isFinite(pos))
448
+ return message;
449
+ const safePos = Math.max(0, Math.min(source.length, pos));
450
+ const lineStart = source.lastIndexOf(`
451
+ `, safePos - 1) + 1;
452
+ const lineEndIndex = source.indexOf(`
453
+ `, safePos);
454
+ const lineEnd = lineEndIndex === -1 ? source.length : lineEndIndex;
455
+ const lineText = source.slice(lineStart, lineEnd);
456
+ const lineNumber = source.slice(0, lineStart).split(`
457
+ `).length;
458
+ const columnNumber = safePos - lineStart + 1;
459
+ const caret = `${" ".repeat(Math.max(0, columnNumber - 1))}^`;
460
+ return `${message}
461
+ ${lineText}
462
+ ${caret}`;
463
+ }
393
464
  function parseToIR(source) {
394
465
  const match = grammar.match(source);
395
466
  if (!match.succeeded()) {
396
- const failure = match;
397
- throw new Error(failure.message ?? "Parse failed");
467
+ throw new Error(formatParseError(source, match));
398
468
  }
399
469
  return semantics(match).toIR();
400
470
  }
@@ -546,78 +616,55 @@ function domainRefsFromConfig(config) {
546
616
  if (!config || typeof config !== "object" || Array.isArray(config)) {
547
617
  throw new Error("Domain config must be an object");
548
618
  }
549
- const refs = {};
550
- for (const section of Object.values(config)) {
551
- if (!section || typeof section !== "object" || Array.isArray(section))
552
- continue;
553
- mapConfigEntries(section, refs);
554
- }
555
- return refs;
556
- }
557
- function decodeDomainRefKey(refText) {
558
- if (!refText)
559
- throw new Error("Domain ref key cannot be empty");
560
- if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
561
- throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
619
+ const configObj = config;
620
+ const domainRefs = {};
621
+ const domainOpcodes = {};
622
+ const dataSection = configObj.data;
623
+ if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
624
+ mapConfigEntries(dataSection, domainRefs);
562
625
  }
563
- if (refText.length > 1 && refText[0] === "0") {
564
- throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
626
+ const functionsSection = configObj.functions;
627
+ if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
628
+ mapConfigEntries(functionsSection, domainOpcodes);
565
629
  }
566
- if (/^[1-9]$/.test(refText)) {
567
- throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
568
- }
569
- let value = 0;
570
- for (const char of refText) {
571
- const digit = DOMAIN_DIGIT_INDEX.get(char);
572
- if (digit === undefined)
573
- throw new Error(`Invalid domain ref key '${refText}'`);
574
- value = value * 64 + digit;
575
- if (value > Number.MAX_SAFE_INTEGER) {
576
- throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
577
- }
578
- }
579
- if (value < FIRST_NON_RESERVED_REF) {
580
- throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
581
- }
582
- return value;
630
+ return { domainRefs, domainOpcodes };
583
631
  }
584
632
  function mapConfigEntries(entries, refs) {
585
633
  const sourceKindByRoot = new Map;
586
634
  for (const root of Object.keys(refs)) {
587
635
  sourceKindByRoot.set(root, "explicit");
588
636
  }
589
- for (const [refText, rawEntry] of Object.entries(entries)) {
637
+ for (const [shortCode, rawEntry] of Object.entries(entries)) {
590
638
  const entry = rawEntry;
591
639
  if (!entry || typeof entry !== "object")
592
640
  continue;
593
641
  if (!Array.isArray(entry.names))
594
642
  continue;
595
- const refId = decodeDomainRefKey(refText);
596
643
  for (const rawName of entry.names) {
597
644
  if (typeof rawName !== "string")
598
645
  continue;
599
- const existingNameRef = refs[rawName];
600
- if (existingNameRef !== undefined && existingNameRef !== refId) {
601
- throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
646
+ const existingRef = refs[rawName];
647
+ if (existingRef !== undefined && existingRef !== shortCode) {
648
+ throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
602
649
  }
603
- refs[rawName] = refId;
650
+ refs[rawName] = shortCode;
604
651
  const root = rawName.split(".")[0];
605
652
  if (!root)
606
653
  continue;
607
654
  const currentKind = rawName.includes(".") ? "implicit" : "explicit";
608
655
  const existing = refs[root];
609
656
  if (existing !== undefined) {
610
- if (existing === refId)
657
+ if (existing === shortCode)
611
658
  continue;
612
659
  const existingKind = sourceKindByRoot.get(root) ?? "explicit";
613
660
  if (currentKind === "explicit") {
614
- throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
661
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
615
662
  }
616
663
  if (existingKind === "explicit")
617
664
  continue;
618
665
  continue;
619
666
  }
620
- refs[root] = refId;
667
+ refs[root] = shortCode;
621
668
  sourceKindByRoot.set(root, currentKind);
622
669
  }
623
670
  }
@@ -761,20 +808,11 @@ function gatherEncodedValueSpans(text) {
761
808
  }
762
809
  return spans;
763
810
  }
764
- function buildPointerToken(pointerStart, targetStart) {
765
- let offset = targetStart - (pointerStart + 1);
811
+ function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
812
+ const offset = targetStart - pointerStart - occurrenceSize;
766
813
  if (offset < 0)
767
814
  return;
768
- for (let guard = 0;guard < 8; guard += 1) {
769
- const prefix = encodeUint(offset);
770
- const recalculated = targetStart - (pointerStart + prefix.length + 1);
771
- if (recalculated === offset)
772
- return `${prefix}^`;
773
- offset = recalculated;
774
- if (offset < 0)
775
- return;
776
- }
777
- return;
815
+ return `${encodeUint(offset)}^`;
778
816
  }
779
817
  function buildDedupeCandidateTable(encoded, minBytes) {
780
818
  const spans = gatherEncodedValueSpans(encoded);
@@ -810,854 +848,43 @@ function dedupeLargeEncodedValues(encoded, minBytes = 4) {
810
848
  continue;
811
849
  const canonical = occurrences[occurrences.length - 1];
812
850
  for (let index = occurrences.length - 2;index >= 0; index -= 1) {
813
- const occurrence = occurrences[index];
814
- if (occurrence.span.end > canonical.span.start)
815
- continue;
816
- if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
817
- continue;
818
- const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
819
- const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
820
- if (!pointerToken)
821
- continue;
822
- if (pointerToken.length >= occurrence.sizeBytes)
823
- continue;
824
- current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
825
- replaced = true;
826
- break;
827
- }
828
- if (replaced)
829
- break;
830
- }
831
- if (!replaced)
832
- return current;
833
- }
834
- }
835
- function encodeIR(node, options) {
836
- const previous = activeEncodeOptions;
837
- activeEncodeOptions = options;
838
- try {
839
- const encoded = encodeNode(node);
840
- if (options?.dedupeValues) {
841
- return dedupeLargeEncodedValues(encoded, options.dedupeMinBytes ?? 4);
842
- }
843
- return encoded;
844
- } finally {
845
- activeEncodeOptions = previous;
846
- }
847
- }
848
- function cloneNode(node) {
849
- return structuredClone(node);
850
- }
851
- function emptyOptimizeEnv() {
852
- return { constants: {}, selfCaptures: {} };
853
- }
854
- function cloneOptimizeEnv(env) {
855
- return {
856
- constants: { ...env.constants },
857
- selfCaptures: { ...env.selfCaptures }
858
- };
859
- }
860
- function clearOptimizeEnv(env) {
861
- for (const key of Object.keys(env.constants))
862
- delete env.constants[key];
863
- for (const key of Object.keys(env.selfCaptures))
864
- delete env.selfCaptures[key];
865
- }
866
- function clearBinding(env, name) {
867
- delete env.constants[name];
868
- delete env.selfCaptures[name];
869
- }
870
- function selfTargetFromNode(node, currentDepth) {
871
- if (node.type === "self")
872
- return currentDepth;
873
- if (node.type === "selfDepth") {
874
- const target = currentDepth - (node.depth - 1);
875
- if (target >= 1)
876
- return target;
877
- }
878
- return;
879
- }
880
- function selfNodeFromTarget(targetDepth, currentDepth) {
881
- const relDepth = currentDepth - targetDepth + 1;
882
- if (!Number.isInteger(relDepth) || relDepth < 1)
883
- return;
884
- if (relDepth === 1)
885
- return { type: "self" };
886
- return { type: "selfDepth", depth: relDepth };
887
- }
888
- function dropBindingNames(env, binding) {
889
- if (binding.type === "binding:valueIn") {
890
- clearBinding(env, binding.value);
891
- return;
892
- }
893
- if (binding.type === "binding:keyValueIn") {
894
- clearBinding(env, binding.key);
895
- clearBinding(env, binding.value);
896
- return;
897
- }
898
- if (binding.type === "binding:keyOf") {
899
- clearBinding(env, binding.key);
900
- }
901
- }
902
- function collectReads(node, out) {
903
- switch (node.type) {
904
- case "identifier":
905
- out.add(node.name);
906
- return;
907
- case "group":
908
- collectReads(node.expression, out);
909
- return;
910
- case "array":
911
- for (const item of node.items)
912
- collectReads(item, out);
913
- return;
914
- case "object":
915
- for (const entry of node.entries) {
916
- collectReads(entry.key, out);
917
- collectReads(entry.value, out);
918
- }
919
- return;
920
- case "arrayComprehension":
921
- collectReads(node.binding.source, out);
922
- collectReads(node.body, out);
923
- return;
924
- case "objectComprehension":
925
- collectReads(node.binding.source, out);
926
- collectReads(node.key, out);
927
- collectReads(node.value, out);
928
- return;
929
- case "unary":
930
- collectReads(node.value, out);
931
- return;
932
- case "binary":
933
- collectReads(node.left, out);
934
- collectReads(node.right, out);
935
- return;
936
- case "assign":
937
- if (!(node.op === "=" && node.place.type === "identifier"))
938
- collectReads(node.place, out);
939
- collectReads(node.value, out);
940
- return;
941
- case "navigation":
942
- collectReads(node.target, out);
943
- for (const segment of node.segments) {
944
- if (segment.type === "dynamic")
945
- collectReads(segment.key, out);
946
- }
947
- return;
948
- case "call":
949
- collectReads(node.callee, out);
950
- for (const arg of node.args)
951
- collectReads(arg, out);
952
- return;
953
- case "conditional":
954
- collectReads(node.condition, out);
955
- for (const part of node.thenBlock)
956
- collectReads(part, out);
957
- if (node.elseBranch)
958
- collectReadsElse(node.elseBranch, out);
959
- return;
960
- case "for":
961
- collectReads(node.binding.source, out);
962
- for (const part of node.body)
963
- collectReads(part, out);
964
- return;
965
- case "program":
966
- for (const part of node.body)
967
- collectReads(part, out);
968
- return;
969
- default:
970
- return;
971
- }
972
- }
973
- function collectReadsElse(elseBranch, out) {
974
- if (elseBranch.type === "else") {
975
- for (const part of elseBranch.block)
976
- collectReads(part, out);
977
- return;
978
- }
979
- collectReads(elseBranch.condition, out);
980
- for (const part of elseBranch.thenBlock)
981
- collectReads(part, out);
982
- if (elseBranch.elseBranch)
983
- collectReadsElse(elseBranch.elseBranch, out);
984
- }
985
- function isPureNode(node) {
986
- switch (node.type) {
987
- case "identifier":
988
- case "self":
989
- case "selfDepth":
990
- case "boolean":
991
- case "null":
992
- case "undefined":
993
- case "number":
994
- case "string":
995
- case "key":
996
- return true;
997
- case "group":
998
- return isPureNode(node.expression);
999
- case "array":
1000
- return node.items.every((item) => isPureNode(item));
1001
- case "object":
1002
- return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
1003
- case "navigation":
1004
- return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
1005
- case "unary":
1006
- return node.op !== "delete" && isPureNode(node.value);
1007
- case "binary":
1008
- return isPureNode(node.left) && isPureNode(node.right);
1009
- default:
1010
- return false;
1011
- }
1012
- }
1013
- function eliminateDeadAssignments(block) {
1014
- const needed = new Set;
1015
- const out = [];
1016
- for (let index = block.length - 1;index >= 0; index -= 1) {
1017
- const node = block[index];
1018
- if (node.type === "conditional") {
1019
- let rewritten = node;
1020
- if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
1021
- const name = node.condition.place.name;
1022
- const branchReads = new Set;
1023
- for (const part of node.thenBlock)
1024
- collectReads(part, branchReads);
1025
- if (node.elseBranch)
1026
- collectReadsElse(node.elseBranch, branchReads);
1027
- if (!needed.has(name) && !branchReads.has(name)) {
1028
- rewritten = {
1029
- type: "conditional",
1030
- head: node.head,
1031
- condition: node.condition.value,
1032
- thenBlock: node.thenBlock,
1033
- elseBranch: node.elseBranch
1034
- };
1035
- }
1036
- }
1037
- collectReads(rewritten, needed);
1038
- out.push(rewritten);
1039
- continue;
1040
- }
1041
- if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
1042
- collectReads(node.value, needed);
1043
- const name = node.place.name;
1044
- const canDrop = !needed.has(name) && isPureNode(node.value);
1045
- needed.delete(name);
1046
- if (canDrop)
1047
- continue;
1048
- out.push(node);
1049
- continue;
1050
- }
1051
- collectReads(node, needed);
1052
- out.push(node);
1053
- }
1054
- out.reverse();
1055
- return out;
1056
- }
1057
- function hasIdentifierRead(node, name, asPlace = false) {
1058
- if (node.type === "identifier")
1059
- return !asPlace && node.name === name;
1060
- switch (node.type) {
1061
- case "group":
1062
- return hasIdentifierRead(node.expression, name);
1063
- case "array":
1064
- return node.items.some((item) => hasIdentifierRead(item, name));
1065
- case "object":
1066
- return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
1067
- case "navigation":
1068
- return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
1069
- case "unary":
1070
- return hasIdentifierRead(node.value, name, node.op === "delete");
1071
- case "binary":
1072
- return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1073
- case "assign":
1074
- return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1075
- default:
1076
- return false;
1077
- }
1078
- }
1079
- function countIdentifierReads(node, name, asPlace = false) {
1080
- if (node.type === "identifier")
1081
- return !asPlace && node.name === name ? 1 : 0;
1082
- switch (node.type) {
1083
- case "group":
1084
- return countIdentifierReads(node.expression, name);
1085
- case "array":
1086
- return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
1087
- case "object":
1088
- return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
1089
- case "navigation":
1090
- return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
1091
- case "unary":
1092
- return countIdentifierReads(node.value, name, node.op === "delete");
1093
- case "binary":
1094
- return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1095
- case "assign":
1096
- return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1097
- default:
1098
- return 0;
1099
- }
1100
- }
1101
- function replaceIdentifier(node, name, replacement, asPlace = false) {
1102
- if (node.type === "identifier") {
1103
- if (!asPlace && node.name === name)
1104
- return cloneNode(replacement);
1105
- return node;
1106
- }
1107
- switch (node.type) {
1108
- case "group":
1109
- return {
1110
- type: "group",
1111
- expression: replaceIdentifier(node.expression, name, replacement)
1112
- };
1113
- case "array":
1114
- return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
1115
- case "object":
1116
- return {
1117
- type: "object",
1118
- entries: node.entries.map((entry) => ({
1119
- key: replaceIdentifier(entry.key, name, replacement),
1120
- value: replaceIdentifier(entry.value, name, replacement)
1121
- }))
1122
- };
1123
- case "navigation":
1124
- return {
1125
- type: "navigation",
1126
- target: replaceIdentifier(node.target, name, replacement),
1127
- segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
1128
- };
1129
- case "unary":
1130
- return {
1131
- type: "unary",
1132
- op: node.op,
1133
- value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
1134
- };
1135
- case "binary":
1136
- return {
1137
- type: "binary",
1138
- op: node.op,
1139
- left: replaceIdentifier(node.left, name, replacement),
1140
- right: replaceIdentifier(node.right, name, replacement)
1141
- };
1142
- case "assign":
1143
- return {
1144
- type: "assign",
1145
- op: node.op,
1146
- place: replaceIdentifier(node.place, name, replacement, true),
1147
- value: replaceIdentifier(node.value, name, replacement)
1148
- };
1149
- default:
1150
- return node;
1151
- }
1152
- }
1153
- function isSafeInlineTargetNode(node) {
1154
- if (isPureNode(node))
1155
- return true;
1156
- if (node.type === "assign" && node.op === "=") {
1157
- return isPureNode(node.place) && isPureNode(node.value);
1158
- }
1159
- return false;
1160
- }
1161
- function inlineAdjacentPureAssignments(block) {
1162
- const out = [...block];
1163
- let changed = true;
1164
- while (changed) {
1165
- changed = false;
1166
- for (let index = 0;index < out.length - 1; index += 1) {
1167
- const current = out[index];
1168
- if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
1169
- continue;
1170
- if (!isPureNode(current.value))
1171
- continue;
1172
- const name = current.place.name;
1173
- if (hasIdentifierRead(current.value, name))
1174
- continue;
1175
- const next = out[index + 1];
1176
- if (!isSafeInlineTargetNode(next))
1177
- continue;
1178
- if (countIdentifierReads(next, name) !== 1)
1179
- continue;
1180
- out[index + 1] = replaceIdentifier(next, name, current.value);
1181
- out.splice(index, 1);
1182
- changed = true;
1183
- break;
1184
- }
1185
- }
1186
- return out;
1187
- }
1188
- function toNumberNode(value) {
1189
- let raw;
1190
- if (Number.isNaN(value))
1191
- raw = "nan";
1192
- else if (value === Infinity)
1193
- raw = "inf";
1194
- else if (value === -Infinity)
1195
- raw = "-inf";
1196
- else
1197
- raw = String(value);
1198
- return { type: "number", raw, value };
1199
- }
1200
- function toStringNode(value) {
1201
- return { type: "string", raw: JSON.stringify(value) };
1202
- }
1203
- function toLiteralNode(value) {
1204
- if (value === undefined)
1205
- return { type: "undefined" };
1206
- if (value === null)
1207
- return { type: "null" };
1208
- if (typeof value === "boolean")
1209
- return { type: "boolean", value };
1210
- if (typeof value === "number")
1211
- return toNumberNode(value);
1212
- if (typeof value === "string")
1213
- return toStringNode(value);
1214
- if (Array.isArray(value)) {
1215
- const items = [];
1216
- for (const item of value) {
1217
- const lowered = toLiteralNode(item);
1218
- if (!lowered)
1219
- return;
1220
- items.push(lowered);
1221
- }
1222
- return { type: "array", items };
1223
- }
1224
- if (value && typeof value === "object") {
1225
- const entries = [];
1226
- for (const [key, entryValue] of Object.entries(value)) {
1227
- const loweredValue = toLiteralNode(entryValue);
1228
- if (!loweredValue)
1229
- return;
1230
- entries.push({ key: { type: "key", name: key }, value: loweredValue });
1231
- }
1232
- return { type: "object", entries };
1233
- }
1234
- return;
1235
- }
1236
- function constValue(node) {
1237
- switch (node.type) {
1238
- case "undefined":
1239
- return;
1240
- case "null":
1241
- return null;
1242
- case "boolean":
1243
- return node.value;
1244
- case "number":
1245
- return node.value;
1246
- case "string":
1247
- return decodeStringLiteral(node.raw);
1248
- case "key":
1249
- return node.name;
1250
- case "array": {
1251
- const out = [];
1252
- for (const item of node.items) {
1253
- const value = constValue(item);
1254
- if (value === undefined && item.type !== "undefined")
1255
- return;
1256
- out.push(value);
1257
- }
1258
- return out;
1259
- }
1260
- case "object": {
1261
- const out = {};
1262
- for (const entry of node.entries) {
1263
- const key = constValue(entry.key);
1264
- if (key === undefined && entry.key.type !== "undefined")
1265
- return;
1266
- const value = constValue(entry.value);
1267
- if (value === undefined && entry.value.type !== "undefined")
1268
- return;
1269
- out[String(key)] = value;
1270
- }
1271
- return out;
1272
- }
1273
- default:
1274
- return;
1275
- }
1276
- }
1277
- function isDefinedValue(value) {
1278
- return value !== undefined;
1279
- }
1280
- function foldUnary(op, value) {
1281
- if (op === "neg") {
1282
- if (typeof value !== "number")
1283
- return;
1284
- return -value;
1285
- }
1286
- if (op === "not") {
1287
- if (typeof value === "boolean")
1288
- return !value;
1289
- if (typeof value === "number")
1290
- return ~value;
1291
- return;
1292
- }
1293
- return;
1294
- }
1295
- function foldBinary(op, left, right) {
1296
- if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
1297
- if (typeof left !== "number" || typeof right !== "number")
1298
- return;
1299
- if (op === "add")
1300
- return left + right;
1301
- if (op === "sub")
1302
- return left - right;
1303
- if (op === "mul")
1304
- return left * right;
1305
- if (op === "div")
1306
- return left / right;
1307
- return left % right;
1308
- }
1309
- if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
1310
- if (typeof left !== "number" || typeof right !== "number")
1311
- return;
1312
- if (op === "bitAnd")
1313
- return left & right;
1314
- if (op === "bitOr")
1315
- return left | right;
1316
- return left ^ right;
1317
- }
1318
- if (op === "eq")
1319
- return left === right ? left : undefined;
1320
- if (op === "neq")
1321
- return left !== right ? left : undefined;
1322
- if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
1323
- if (typeof left !== "number" || typeof right !== "number")
1324
- return;
1325
- if (op === "gt")
1326
- return left > right ? left : undefined;
1327
- if (op === "gte")
1328
- return left >= right ? left : undefined;
1329
- if (op === "lt")
1330
- return left < right ? left : undefined;
1331
- return left <= right ? left : undefined;
1332
- }
1333
- if (op === "and")
1334
- return isDefinedValue(left) ? right : undefined;
1335
- if (op === "or")
1336
- return isDefinedValue(left) ? left : right;
1337
- return;
1338
- }
1339
- function optimizeElse(elseBranch, env, currentDepth) {
1340
- if (!elseBranch)
1341
- return;
1342
- if (elseBranch.type === "else") {
1343
- return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
1344
- }
1345
- const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
1346
- const foldedCondition = constValue(optimizedCondition);
1347
- if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
1348
- const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
1349
- if (passes) {
1350
- return {
1351
- type: "else",
1352
- block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
1353
- };
1354
- }
1355
- return optimizeElse(elseBranch.elseBranch, env, currentDepth);
1356
- }
1357
- return {
1358
- type: "elseChain",
1359
- head: elseBranch.head,
1360
- condition: optimizedCondition,
1361
- thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
1362
- elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
1363
- };
1364
- }
1365
- function optimizeBlock(block, env, currentDepth) {
1366
- const out = [];
1367
- for (const node of block) {
1368
- const optimized = optimizeNode(node, env, currentDepth);
1369
- out.push(optimized);
1370
- if (optimized.type === "break" || optimized.type === "continue")
1371
- break;
1372
- if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
1373
- const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
1374
- if (selfTarget !== undefined) {
1375
- env.selfCaptures[optimized.place.name] = selfTarget;
1376
- delete env.constants[optimized.place.name];
1377
- continue;
1378
- }
1379
- const folded = constValue(optimized.value);
1380
- if (folded !== undefined || optimized.value.type === "undefined") {
1381
- env.constants[optimized.place.name] = cloneNode(optimized.value);
1382
- delete env.selfCaptures[optimized.place.name];
1383
- } else {
1384
- clearBinding(env, optimized.place.name);
1385
- }
1386
- continue;
1387
- }
1388
- if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
1389
- clearBinding(env, optimized.value.name);
1390
- continue;
1391
- }
1392
- if (optimized.type === "assign" && optimized.place.type === "identifier") {
1393
- clearBinding(env, optimized.place.name);
1394
- continue;
1395
- }
1396
- if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
1397
- clearOptimizeEnv(env);
1398
- }
1399
- }
1400
- return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
1401
- }
1402
- function optimizeNode(node, env, currentDepth, asPlace = false) {
1403
- switch (node.type) {
1404
- case "program": {
1405
- const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
1406
- if (body.length === 0)
1407
- return { type: "undefined" };
1408
- if (body.length === 1)
1409
- return body[0];
1410
- return { type: "program", body };
1411
- }
1412
- case "identifier": {
1413
- if (asPlace)
1414
- return node;
1415
- const selfTarget = env.selfCaptures[node.name];
1416
- if (selfTarget !== undefined) {
1417
- const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
1418
- if (rewritten)
1419
- return rewritten;
1420
- }
1421
- const replacement = env.constants[node.name];
1422
- return replacement ? cloneNode(replacement) : node;
1423
- }
1424
- case "group": {
1425
- return optimizeNode(node.expression, env, currentDepth);
1426
- }
1427
- case "array": {
1428
- return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
1429
- }
1430
- case "object": {
1431
- return {
1432
- type: "object",
1433
- entries: node.entries.map((entry) => ({
1434
- key: optimizeNode(entry.key, env, currentDepth),
1435
- value: optimizeNode(entry.value, env, currentDepth)
1436
- }))
1437
- };
1438
- }
1439
- case "unary": {
1440
- const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
1441
- const foldedValue = constValue(value);
1442
- if (foldedValue !== undefined || value.type === "undefined") {
1443
- const folded = foldUnary(node.op, foldedValue);
1444
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1445
- if (literal)
1446
- return literal;
1447
- }
1448
- return { type: "unary", op: node.op, value };
1449
- }
1450
- case "binary": {
1451
- const left = optimizeNode(node.left, env, currentDepth);
1452
- const right = optimizeNode(node.right, env, currentDepth);
1453
- const leftValue = constValue(left);
1454
- const rightValue = constValue(right);
1455
- if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
1456
- const folded = foldBinary(node.op, leftValue, rightValue);
1457
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1458
- if (literal)
1459
- return literal;
1460
- }
1461
- return { type: "binary", op: node.op, left, right };
1462
- }
1463
- case "navigation": {
1464
- const target = optimizeNode(node.target, env, currentDepth);
1465
- const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
1466
- const targetValue = constValue(target);
1467
- if (targetValue !== undefined || target.type === "undefined") {
1468
- let current = targetValue;
1469
- let foldable = true;
1470
- for (const segment of segments) {
1471
- if (!foldable)
1472
- break;
1473
- const key = segment.type === "static" ? segment.key : constValue(segment.key);
1474
- if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
1475
- foldable = false;
1476
- break;
1477
- }
1478
- if (current === null || current === undefined) {
1479
- current = undefined;
1480
- continue;
1481
- }
1482
- current = current[String(key)];
1483
- }
1484
- if (foldable) {
1485
- const literal = toLiteralNode(current);
1486
- if (literal)
1487
- return literal;
1488
- }
1489
- }
1490
- return {
1491
- type: "navigation",
1492
- target,
1493
- segments
1494
- };
1495
- }
1496
- case "call": {
1497
- return {
1498
- type: "call",
1499
- callee: optimizeNode(node.callee, env, currentDepth),
1500
- args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
1501
- };
1502
- }
1503
- case "assign": {
1504
- return {
1505
- type: "assign",
1506
- op: node.op,
1507
- place: optimizeNode(node.place, env, currentDepth, true),
1508
- value: optimizeNode(node.value, env, currentDepth)
1509
- };
1510
- }
1511
- case "conditional": {
1512
- const condition = optimizeNode(node.condition, env, currentDepth);
1513
- const thenEnv = cloneOptimizeEnv(env);
1514
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1515
- thenEnv.selfCaptures[condition.place.name] = currentDepth;
1516
- delete thenEnv.constants[condition.place.name];
1517
- }
1518
- const conditionValue = constValue(condition);
1519
- if (conditionValue !== undefined || condition.type === "undefined") {
1520
- const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1521
- if (passes) {
1522
- const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1523
- if (thenBlock2.length === 0)
1524
- return { type: "undefined" };
1525
- if (thenBlock2.length === 1)
1526
- return thenBlock2[0];
1527
- return { type: "program", body: thenBlock2 };
1528
- }
1529
- if (!node.elseBranch)
1530
- return { type: "undefined" };
1531
- const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1532
- if (!loweredElse)
1533
- return { type: "undefined" };
1534
- if (loweredElse.type === "else") {
1535
- if (loweredElse.block.length === 0)
1536
- return { type: "undefined" };
1537
- if (loweredElse.block.length === 1)
1538
- return loweredElse.block[0];
1539
- return { type: "program", body: loweredElse.block };
1540
- }
1541
- return {
1542
- type: "conditional",
1543
- head: loweredElse.head,
1544
- condition: loweredElse.condition,
1545
- thenBlock: loweredElse.thenBlock,
1546
- elseBranch: loweredElse.elseBranch
1547
- };
1548
- }
1549
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1550
- const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1551
- let finalCondition = condition;
1552
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1553
- const name = condition.place.name;
1554
- const reads = new Set;
1555
- for (const part of thenBlock)
1556
- collectReads(part, reads);
1557
- if (elseBranch)
1558
- collectReadsElse(elseBranch, reads);
1559
- if (!reads.has(name)) {
1560
- finalCondition = condition.value;
1561
- }
851
+ const occurrence = occurrences[index];
852
+ if (occurrence.span.end > canonical.span.start)
853
+ continue;
854
+ if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
855
+ continue;
856
+ const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
857
+ const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
858
+ if (!pointerToken)
859
+ continue;
860
+ if (pointerToken.length >= occurrence.sizeBytes)
861
+ continue;
862
+ current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
863
+ replaced = true;
864
+ break;
1562
865
  }
1563
- return {
1564
- type: "conditional",
1565
- head: node.head,
1566
- condition: finalCondition,
1567
- thenBlock,
1568
- elseBranch
1569
- };
1570
- }
1571
- case "for": {
1572
- const sourceEnv = cloneOptimizeEnv(env);
1573
- const binding = (() => {
1574
- if (node.binding.type === "binding:expr") {
1575
- return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
1576
- }
1577
- if (node.binding.type === "binding:valueIn") {
1578
- return {
1579
- type: "binding:valueIn",
1580
- value: node.binding.value,
1581
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1582
- };
1583
- }
1584
- if (node.binding.type === "binding:keyValueIn") {
1585
- return {
1586
- type: "binding:keyValueIn",
1587
- key: node.binding.key,
1588
- value: node.binding.value,
1589
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1590
- };
1591
- }
1592
- return {
1593
- type: "binding:keyOf",
1594
- key: node.binding.key,
1595
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1596
- };
1597
- })();
1598
- const bodyEnv = cloneOptimizeEnv(env);
1599
- dropBindingNames(bodyEnv, binding);
1600
- return {
1601
- type: "for",
1602
- binding,
1603
- body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
1604
- };
1605
- }
1606
- case "arrayComprehension": {
1607
- const sourceEnv = cloneOptimizeEnv(env);
1608
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1609
- type: "binding:valueIn",
1610
- value: node.binding.value,
1611
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1612
- } : node.binding.type === "binding:keyValueIn" ? {
1613
- type: "binding:keyValueIn",
1614
- key: node.binding.key,
1615
- value: node.binding.value,
1616
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1617
- } : {
1618
- type: "binding:keyOf",
1619
- key: node.binding.key,
1620
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1621
- };
1622
- const bodyEnv = cloneOptimizeEnv(env);
1623
- dropBindingNames(bodyEnv, binding);
1624
- return {
1625
- type: "arrayComprehension",
1626
- binding,
1627
- body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1628
- };
866
+ if (replaced)
867
+ break;
1629
868
  }
1630
- case "objectComprehension": {
1631
- const sourceEnv = cloneOptimizeEnv(env);
1632
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1633
- type: "binding:valueIn",
1634
- value: node.binding.value,
1635
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1636
- } : node.binding.type === "binding:keyValueIn" ? {
1637
- type: "binding:keyValueIn",
1638
- key: node.binding.key,
1639
- value: node.binding.value,
1640
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1641
- } : {
1642
- type: "binding:keyOf",
1643
- key: node.binding.key,
1644
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1645
- };
1646
- const bodyEnv = cloneOptimizeEnv(env);
1647
- dropBindingNames(bodyEnv, binding);
1648
- return {
1649
- type: "objectComprehension",
1650
- binding,
1651
- key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
1652
- value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1653
- };
869
+ if (!replaced)
870
+ return current;
871
+ }
872
+ }
873
+ function encodeIR(node, options) {
874
+ const previous = activeEncodeOptions;
875
+ activeEncodeOptions = options;
876
+ try {
877
+ const encoded = encodeNode(node);
878
+ if (options?.dedupeValues) {
879
+ return dedupeLargeEncodedValues(encoded, options.dedupeMinBytes ?? 4);
1654
880
  }
1655
- default:
1656
- return node;
881
+ return encoded;
882
+ } finally {
883
+ activeEncodeOptions = previous;
1657
884
  }
1658
885
  }
1659
886
  function optimizeIR(node) {
1660
- return optimizeNode(node, emptyOptimizeEnv(), 1);
887
+ return node;
1661
888
  }
1662
889
  function collectLocalBindings(node, locals) {
1663
890
  switch (node.type) {
@@ -1703,6 +930,10 @@ function collectLocalBindings(node, locals) {
1703
930
  collectLocalBindings(node.left, locals);
1704
931
  collectLocalBindings(node.right, locals);
1705
932
  return;
933
+ case "range":
934
+ collectLocalBindings(node.from, locals);
935
+ collectLocalBindings(node.to, locals);
936
+ return;
1706
937
  case "conditional":
1707
938
  collectLocalBindings(node.condition, locals);
1708
939
  for (const part of node.thenBlock)
@@ -1719,11 +950,20 @@ function collectLocalBindings(node, locals) {
1719
950
  collectLocalBindingFromBinding(node.binding, locals);
1720
951
  collectLocalBindings(node.body, locals);
1721
952
  return;
953
+ case "whileArrayComprehension":
954
+ collectLocalBindings(node.condition, locals);
955
+ collectLocalBindings(node.body, locals);
956
+ return;
1722
957
  case "objectComprehension":
1723
958
  collectLocalBindingFromBinding(node.binding, locals);
1724
959
  collectLocalBindings(node.key, locals);
1725
960
  collectLocalBindings(node.value, locals);
1726
961
  return;
962
+ case "whileObjectComprehension":
963
+ collectLocalBindings(node.condition, locals);
964
+ collectLocalBindings(node.key, locals);
965
+ collectLocalBindings(node.value, locals);
966
+ return;
1727
967
  default:
1728
968
  return;
1729
969
  }
@@ -1815,6 +1055,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1815
1055
  collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
1816
1056
  collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
1817
1057
  return;
1058
+ case "range":
1059
+ collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
1060
+ collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
1061
+ return;
1818
1062
  case "conditional":
1819
1063
  collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1820
1064
  for (const part of node.thenBlock)
@@ -1831,11 +1075,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1831
1075
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1832
1076
  collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1833
1077
  return;
1078
+ case "whileArrayComprehension":
1079
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1080
+ collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1081
+ return;
1834
1082
  case "objectComprehension":
1835
1083
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1836
1084
  collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1837
1085
  collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1838
1086
  return;
1087
+ case "whileObjectComprehension":
1088
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1089
+ collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1090
+ collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1091
+ return;
1839
1092
  default:
1840
1093
  return;
1841
1094
  }
@@ -1910,6 +1163,12 @@ function renameLocalNames(node, map) {
1910
1163
  left: renameLocalNames(node.left, map),
1911
1164
  right: renameLocalNames(node.right, map)
1912
1165
  };
1166
+ case "range":
1167
+ return {
1168
+ type: "range",
1169
+ from: renameLocalNames(node.from, map),
1170
+ to: renameLocalNames(node.to, map)
1171
+ };
1913
1172
  case "assign": {
1914
1173
  const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
1915
1174
  return {
@@ -1939,6 +1198,12 @@ function renameLocalNames(node, map) {
1939
1198
  binding: renameLocalNamesBinding(node.binding, map),
1940
1199
  body: renameLocalNames(node.body, map)
1941
1200
  };
1201
+ case "whileArrayComprehension":
1202
+ return {
1203
+ type: "whileArrayComprehension",
1204
+ condition: renameLocalNames(node.condition, map),
1205
+ body: renameLocalNames(node.body, map)
1206
+ };
1942
1207
  case "objectComprehension":
1943
1208
  return {
1944
1209
  type: "objectComprehension",
@@ -1946,34 +1211,31 @@ function renameLocalNames(node, map) {
1946
1211
  key: renameLocalNames(node.key, map),
1947
1212
  value: renameLocalNames(node.value, map)
1948
1213
  };
1214
+ case "whileObjectComprehension":
1215
+ return {
1216
+ type: "whileObjectComprehension",
1217
+ condition: renameLocalNames(node.condition, map),
1218
+ key: renameLocalNames(node.key, map),
1219
+ value: renameLocalNames(node.value, map)
1220
+ };
1949
1221
  default:
1950
1222
  return node;
1951
1223
  }
1952
1224
  }
1953
1225
  function renameLocalNamesBinding(binding, map) {
1954
- if (binding.type === "binding:expr") {
1955
- return { type: "binding:expr", source: renameLocalNames(binding.source, map) };
1956
- }
1957
- if (binding.type === "binding:valueIn") {
1958
- return {
1959
- type: "binding:valueIn",
1960
- value: map.get(binding.value) ?? binding.value,
1961
- source: renameLocalNames(binding.source, map)
1962
- };
1226
+ const source = renameLocalNames(binding.source, map);
1227
+ switch (binding.type) {
1228
+ case "binding:bareIn":
1229
+ return { type: "binding:bareIn", source };
1230
+ case "binding:bareOf":
1231
+ return { type: "binding:bareOf", source };
1232
+ case "binding:valueIn":
1233
+ return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
1234
+ case "binding:keyValueIn":
1235
+ return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
1236
+ case "binding:keyOf":
1237
+ return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
1963
1238
  }
1964
- if (binding.type === "binding:keyValueIn") {
1965
- return {
1966
- type: "binding:keyValueIn",
1967
- key: map.get(binding.key) ?? binding.key,
1968
- value: map.get(binding.value) ?? binding.value,
1969
- source: renameLocalNames(binding.source, map)
1970
- };
1971
- }
1972
- return {
1973
- type: "binding:keyOf",
1974
- key: map.get(binding.key) ?? binding.key,
1975
- source: renameLocalNames(binding.source, map)
1976
- };
1977
1239
  }
1978
1240
  function renameLocalNamesElse(elseBranch, map) {
1979
1241
  if (elseBranch.type === "else") {
@@ -2020,9 +1282,9 @@ function compile(source, options) {
2020
1282
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
2021
1283
  if (options?.minifyNames)
2022
1284
  lowered = minifyLocalNamesIR(lowered);
2023
- const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1285
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2024
1286
  return encodeIR(lowered, {
2025
- domainRefs,
1287
+ ...domainMaps,
2026
1288
  dedupeValues: options?.dedupeValues,
2027
1289
  dedupeMinBytes: options?.dedupeMinBytes
2028
1290
  });
@@ -2105,7 +1367,7 @@ function buildPostfix(base, steps) {
2105
1367
  flushSegments();
2106
1368
  return current;
2107
1369
  }
2108
- var require2, rexGrammarModule, rexGrammar, grammar, semantics, DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", OPCODE_IDS, FIRST_NON_RESERVED_REF = 8, DOMAIN_DIGIT_INDEX, BINARY_TO_OPCODE, ASSIGN_COMPOUND_TO_OPCODE, DEC_PARTS, activeEncodeOptions, DIGIT_SET, DIGIT_INDEX;
1370
+ var require2, rexGrammarModule, rexGrammar, grammar, semantics, DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", OPCODE_IDS, KEYWORD_OPCODES, BINARY_TO_OPCODE, ASSIGN_COMPOUND_TO_OPCODE, DEC_PARTS, activeEncodeOptions, DIGIT_SET, DIGIT_INDEX;
2109
1371
  var init_rex = __esm(() => {
2110
1372
  require2 = createRequire(import.meta.url);
2111
1373
  rexGrammarModule = require2("./rex.ohm-bundle.cjs");
@@ -2113,30 +1375,31 @@ var init_rex = __esm(() => {
2113
1375
  grammar = rexGrammar;
2114
1376
  semantics = rexGrammar.createSemantics();
2115
1377
  OPCODE_IDS = {
2116
- do: 0,
2117
- add: 1,
2118
- sub: 2,
2119
- mul: 3,
2120
- div: 4,
2121
- eq: 5,
2122
- neq: 6,
2123
- lt: 7,
2124
- lte: 8,
2125
- gt: 9,
2126
- gte: 10,
2127
- and: 11,
2128
- or: 12,
2129
- xor: 13,
2130
- not: 14,
2131
- boolean: 15,
2132
- number: 16,
2133
- string: 17,
2134
- array: 18,
2135
- object: 19,
2136
- mod: 20,
2137
- neg: 21
1378
+ do: "",
1379
+ add: "ad",
1380
+ sub: "sb",
1381
+ mul: "ml",
1382
+ div: "dv",
1383
+ eq: "eq",
1384
+ neq: "nq",
1385
+ lt: "lt",
1386
+ lte: "le",
1387
+ gt: "gt",
1388
+ gte: "ge",
1389
+ and: "an",
1390
+ or: "or",
1391
+ xor: "xr",
1392
+ not: "nt",
1393
+ boolean: "bt",
1394
+ number: "nm",
1395
+ string: "st",
1396
+ array: "ar",
1397
+ object: "ob",
1398
+ mod: "md",
1399
+ neg: "ng",
1400
+ range: "rn"
2138
1401
  };
2139
- DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
1402
+ KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object"]);
2140
1403
  BINARY_TO_OPCODE = {
2141
1404
  add: "add",
2142
1405
  sub: "sub",
@@ -2211,6 +1474,9 @@ var init_rex = __esm(() => {
2211
1474
  ExistenceExpr_or(left, _or, right) {
2212
1475
  return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
2213
1476
  },
1477
+ ExistenceExpr_nor(left, _nor, right) {
1478
+ return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
1479
+ },
2214
1480
  BitExpr_and(left, _op, right) {
2215
1481
  return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
2216
1482
  },
@@ -2220,6 +1486,9 @@ var init_rex = __esm(() => {
2220
1486
  BitExpr_or(left, _op, right) {
2221
1487
  return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
2222
1488
  },
1489
+ RangeExpr_range(left, _op, right) {
1490
+ return { type: "range", from: left.toIR(), to: right.toIR() };
1491
+ },
2223
1492
  CompareExpr_binary(left, op, right) {
2224
1493
  const map = {
2225
1494
  "==": "eq",
@@ -2260,6 +1529,9 @@ var init_rex = __esm(() => {
2260
1529
  UnaryExpr_not(_op, value) {
2261
1530
  return { type: "unary", op: "not", value: value.toIR() };
2262
1531
  },
1532
+ UnaryExpr_logicalNot(_not, value) {
1533
+ return { type: "unary", op: "logicalNot", value: value.toIR() };
1534
+ },
2263
1535
  UnaryExpr_delete(_del, place) {
2264
1536
  return { type: "unary", op: "delete", value: place.toIR() };
2265
1537
  },
@@ -2313,14 +1585,6 @@ var init_rex = __esm(() => {
2313
1585
  ConditionalElse_else(_else, block) {
2314
1586
  return { type: "else", block: block.toIR() };
2315
1587
  },
2316
- DoExpr(_do, block, _end) {
2317
- const body = block.toIR();
2318
- if (body.length === 0)
2319
- return { type: "undefined" };
2320
- if (body.length === 1)
2321
- return body[0];
2322
- return { type: "program", body };
2323
- },
2324
1588
  WhileExpr(_while, condition, _do, block, _end) {
2325
1589
  return {
2326
1590
  type: "while",
@@ -2335,30 +1599,44 @@ var init_rex = __esm(() => {
2335
1599
  body: block.toIR()
2336
1600
  };
2337
1601
  },
2338
- BindingExpr(iterOrExpr) {
2339
- const node = iterOrExpr.toIR();
2340
- if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
2341
- return node;
2342
- }
2343
- return { type: "binding:expr", source: node };
2344
- },
2345
1602
  Array_empty(_open, _close) {
2346
1603
  return { type: "array", items: [] };
2347
1604
  },
2348
- Array_comprehension(_open, binding, _semi, body, _close) {
1605
+ Array_forComprehension(_open, body, _for, binding, _close) {
2349
1606
  return {
2350
1607
  type: "arrayComprehension",
2351
1608
  binding: binding.toIR(),
2352
1609
  body: body.toIR()
2353
1610
  };
2354
1611
  },
1612
+ Array_whileComprehension(_open, body, _while, condition, _close) {
1613
+ return {
1614
+ type: "whileArrayComprehension",
1615
+ condition: condition.toIR(),
1616
+ body: body.toIR()
1617
+ };
1618
+ },
1619
+ Array_inComprehension(_open, body, _in, source, _close) {
1620
+ return {
1621
+ type: "arrayComprehension",
1622
+ binding: { type: "binding:bareIn", source: source.toIR() },
1623
+ body: body.toIR()
1624
+ };
1625
+ },
1626
+ Array_ofComprehension(_open, body, _of, source, _close) {
1627
+ return {
1628
+ type: "arrayComprehension",
1629
+ binding: { type: "binding:bareOf", source: source.toIR() },
1630
+ body: body.toIR()
1631
+ };
1632
+ },
2355
1633
  Array_values(_open, items, _close) {
2356
1634
  return { type: "array", items: normalizeList(items.toIR()) };
2357
1635
  },
2358
1636
  Object_empty(_open, _close) {
2359
1637
  return { type: "object", entries: [] };
2360
1638
  },
2361
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
1639
+ Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
2362
1640
  return {
2363
1641
  type: "objectComprehension",
2364
1642
  binding: binding.toIR(),
@@ -2366,6 +1644,30 @@ var init_rex = __esm(() => {
2366
1644
  value: value.toIR()
2367
1645
  };
2368
1646
  },
1647
+ Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
1648
+ return {
1649
+ type: "whileObjectComprehension",
1650
+ condition: condition.toIR(),
1651
+ key: key.toIR(),
1652
+ value: value.toIR()
1653
+ };
1654
+ },
1655
+ Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
1656
+ return {
1657
+ type: "objectComprehension",
1658
+ binding: { type: "binding:bareIn", source: source.toIR() },
1659
+ key: key.toIR(),
1660
+ value: value.toIR()
1661
+ };
1662
+ },
1663
+ Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
1664
+ return {
1665
+ type: "objectComprehension",
1666
+ binding: { type: "binding:bareOf", source: source.toIR() },
1667
+ key: key.toIR(),
1668
+ value: value.toIR()
1669
+ };
1670
+ },
2369
1671
  Object_pairs(_open, pairs, _close) {
2370
1672
  return {
2371
1673
  type: "object",
@@ -2394,6 +1696,40 @@ var init_rex = __esm(() => {
2394
1696
  source: source.toIR()
2395
1697
  };
2396
1698
  },
1699
+ IterBinding_bareIn(_in, source) {
1700
+ return {
1701
+ type: "binding:bareIn",
1702
+ source: source.toIR()
1703
+ };
1704
+ },
1705
+ IterBinding_bareOf(_of, source) {
1706
+ return {
1707
+ type: "binding:bareOf",
1708
+ source: source.toIR()
1709
+ };
1710
+ },
1711
+ IterBindingComprehension_keyValueIn(key, _comma, value, _in, source) {
1712
+ return {
1713
+ type: "binding:keyValueIn",
1714
+ key: key.sourceString,
1715
+ value: value.sourceString,
1716
+ source: source.toIR()
1717
+ };
1718
+ },
1719
+ IterBindingComprehension_valueIn(value, _in, source) {
1720
+ return {
1721
+ type: "binding:valueIn",
1722
+ value: value.sourceString,
1723
+ source: source.toIR()
1724
+ };
1725
+ },
1726
+ IterBindingComprehension_keyOf(key, _of, source) {
1727
+ return {
1728
+ type: "binding:keyOf",
1729
+ key: key.sourceString,
1730
+ source: source.toIR()
1731
+ };
1732
+ },
2397
1733
  Pair(key, _colon, value) {
2398
1734
  return { key: key.toIR(), value: value.toIR() };
2399
1735
  },
@@ -2495,7 +1831,6 @@ class CursorInterpreter {
2495
1831
  pos = 0;
2496
1832
  state;
2497
1833
  selfStack;
2498
- opcodeMarkers;
2499
1834
  pointerCache = new Map;
2500
1835
  gasLimit;
2501
1836
  gas = 0;
@@ -2510,28 +1845,22 @@ class CursorInterpreter {
2510
1845
  this.state = {
2511
1846
  vars: ctx.vars ?? {},
2512
1847
  refs: {
2513
- 0: ctx.refs?.[0],
2514
- 1: ctx.refs?.[1] ?? true,
2515
- 2: ctx.refs?.[2] ?? false,
2516
- 3: ctx.refs?.[3] ?? null,
2517
- 4: ctx.refs?.[4] ?? undefined,
2518
- 5: ctx.refs?.[5] ?? NaN,
2519
- 6: ctx.refs?.[6] ?? Infinity,
2520
- 7: ctx.refs?.[7] ?? -Infinity
1848
+ tr: true,
1849
+ fl: false,
1850
+ nl: null,
1851
+ un: undefined,
1852
+ nan: NaN,
1853
+ inf: Infinity,
1854
+ nif: -Infinity,
1855
+ ...ctx.refs
2521
1856
  }
2522
1857
  };
2523
1858
  this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
2524
1859
  this.gasLimit = ctx.gasLimit ?? 0;
2525
- this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
2526
- for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
2527
- const id = Number(idText);
2528
- if (Number.isInteger(id))
2529
- this.state.refs[id] = value;
2530
- }
2531
1860
  if (ctx.opcodes) {
2532
- for (const [idText, op] of Object.entries(ctx.opcodes)) {
1861
+ for (const [key, op] of Object.entries(ctx.opcodes)) {
2533
1862
  if (op)
2534
- this.customOpcodes.set(Number(idText), op);
1863
+ this.customOpcodes.set(key, op);
2535
1864
  }
2536
1865
  }
2537
1866
  }
@@ -2592,6 +1921,22 @@ class CursorInterpreter {
2592
1921
  const end = this.pos;
2593
1922
  return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
2594
1923
  }
1924
+ advanceByBytes(start, byteCount) {
1925
+ if (byteCount <= 0)
1926
+ return start;
1927
+ let bytes = 0;
1928
+ let index = start;
1929
+ for (const char of this.text.slice(start)) {
1930
+ const charBytes = Buffer.byteLength(char);
1931
+ if (bytes + charBytes > byteCount)
1932
+ break;
1933
+ bytes += charBytes;
1934
+ index += char.length;
1935
+ if (bytes === byteCount)
1936
+ return index;
1937
+ }
1938
+ throw new Error("String container overflows input");
1939
+ }
2595
1940
  ensure(char) {
2596
1941
  if (this.text[this.pos] !== char)
2597
1942
  throw new Error(`Expected '${char}' at ${this.pos}`);
@@ -2632,36 +1977,34 @@ class CursorInterpreter {
2632
1977
  const significand = this.evalValue();
2633
1978
  if (typeof significand !== "number")
2634
1979
  throw new Error("Decimal significand must be numeric");
2635
- return significand * 10 ** power;
1980
+ return parseFloat(`${significand}e${power}`);
2636
1981
  }
2637
1982
  case ":":
2638
1983
  this.pos += 1;
2639
1984
  return prefix.raw;
2640
1985
  case "%":
2641
1986
  this.pos += 1;
2642
- return this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
1987
+ return { __opcode: prefix.raw };
2643
1988
  case "@":
2644
1989
  this.pos += 1;
2645
1990
  return this.readSelf(prefix.value);
2646
1991
  case "'":
2647
1992
  this.pos += 1;
2648
- return this.state.refs[prefix.value];
1993
+ return this.state.refs[prefix.raw];
2649
1994
  case "$":
2650
1995
  this.pos += 1;
2651
1996
  return this.state.vars[prefix.raw];
2652
1997
  case ",": {
2653
1998
  this.pos += 1;
2654
1999
  const start = this.pos;
2655
- const end = start + prefix.value;
2656
- if (end > this.text.length)
2657
- throw new Error("String container overflows input");
2000
+ const end = this.advanceByBytes(start, prefix.value);
2658
2001
  const value = this.text.slice(start, end);
2659
2002
  this.pos = end;
2660
2003
  return value;
2661
2004
  }
2662
2005
  case "^": {
2663
2006
  this.pos += 1;
2664
- const target = this.pos + prefix.value;
2007
+ const target = this.advanceByBytes(this.pos, prefix.value);
2665
2008
  if (this.pointerCache.has(target))
2666
2009
  return this.pointerCache.get(target);
2667
2010
  const save = this.pos;
@@ -2678,6 +2021,14 @@ class CursorInterpreter {
2678
2021
  this.writePlace(place, value);
2679
2022
  return value;
2680
2023
  }
2024
+ case "/": {
2025
+ this.pos += 1;
2026
+ const place = this.readPlace();
2027
+ const oldValue = this.readPlaceValue(place);
2028
+ const newValue = this.evalValue();
2029
+ this.writePlace(place, newValue);
2030
+ return oldValue;
2031
+ }
2681
2032
  case "~": {
2682
2033
  this.pos += 1;
2683
2034
  const place = this.readPlace();
@@ -2705,7 +2056,7 @@ class CursorInterpreter {
2705
2056
  case "<":
2706
2057
  return this.evalLoopLike(tag);
2707
2058
  case "#":
2708
- return this.evalWhileLoop();
2059
+ return this.evalWhileLike();
2709
2060
  default:
2710
2061
  throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
2711
2062
  }
@@ -2727,7 +2078,9 @@ class CursorInterpreter {
2727
2078
  }
2728
2079
  this.ensure(")");
2729
2080
  if (typeof callee === "object" && callee && "__opcode" in callee) {
2730
- return this.applyOpcode(callee.__opcode, args);
2081
+ const marker = callee;
2082
+ const opArgs = marker.__receiver !== undefined ? [marker.__receiver, ...args] : args;
2083
+ return this.applyOpcode(marker.__opcode, opArgs);
2731
2084
  }
2732
2085
  return this.navigate(callee, args);
2733
2086
  }
@@ -2918,15 +2271,11 @@ class CursorInterpreter {
2918
2271
  return entries.map(([key]) => ({ key, value: key }));
2919
2272
  return entries.map(([key, value]) => ({ key, value }));
2920
2273
  }
2921
- if (typeof iterable === "number" && Number.isFinite(iterable) && iterable > 0) {
2922
- const out = [];
2923
- for (let index = 0;index < Math.floor(iterable); index += 1) {
2924
- if (keysOnly)
2925
- out.push({ key: index, value: index });
2926
- else
2927
- out.push({ key: index, value: index + 1 });
2928
- }
2929
- return out;
2274
+ if (typeof iterable === "string") {
2275
+ const entries = Array.from(iterable);
2276
+ if (keysOnly)
2277
+ return entries.map((_value, index) => ({ key: index, value: index }));
2278
+ return entries.map((value, index) => ({ key: index, value }));
2930
2279
  }
2931
2280
  return [];
2932
2281
  }
@@ -2971,21 +2320,32 @@ class CursorInterpreter {
2971
2320
  }
2972
2321
  return last;
2973
2322
  }
2974
- evalWhileLoop() {
2323
+ evalWhileLike() {
2324
+ this.pos += 1;
2325
+ const open = this.text[this.pos];
2326
+ if (!open || !"([{".includes(open))
2327
+ throw new Error(`Expected opener after '#' at ${this.pos}`);
2328
+ const close = open === "(" ? ")" : open === "[" ? "]" : "}";
2975
2329
  this.pos += 1;
2976
- this.ensure("(");
2977
2330
  const condStart = this.pos;
2978
2331
  const condValue = this.evalValue();
2979
2332
  const bodyStart = this.pos;
2980
- const bodyValueCount = 1;
2333
+ const bodyValueCount = open === "{" ? 2 : 1;
2981
2334
  let cursor = bodyStart;
2982
2335
  for (let index = 0;index < bodyValueCount; index += 1) {
2983
2336
  cursor = this.skipValueFrom(cursor);
2984
2337
  }
2985
2338
  const bodyEnd = cursor;
2986
2339
  this.pos = bodyEnd;
2987
- this.ensure(")");
2340
+ this.ensure(close);
2988
2341
  const afterClose = this.pos;
2342
+ if (open === "[")
2343
+ return this.evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
2344
+ if (open === "{")
2345
+ return this.evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
2346
+ return this.evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue);
2347
+ }
2348
+ evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue) {
2989
2349
  let last = undefined;
2990
2350
  let currentCond = condValue;
2991
2351
  while (isDefined(currentCond)) {
@@ -3006,6 +2366,58 @@ class CursorInterpreter {
3006
2366
  this.pos = afterClose;
3007
2367
  return last;
3008
2368
  }
2369
+ evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
2370
+ const out = [];
2371
+ let currentCond = condValue;
2372
+ while (isDefined(currentCond)) {
2373
+ this.tick();
2374
+ this.selfStack.push(currentCond);
2375
+ const value = this.evalBodySlice(bodyStart, bodyEnd);
2376
+ this.selfStack.pop();
2377
+ const control = this.handleLoopControl(value);
2378
+ if (control) {
2379
+ if (control.depth > 1)
2380
+ return { kind: control.kind, depth: control.depth - 1 };
2381
+ if (control.kind === "break")
2382
+ break;
2383
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2384
+ continue;
2385
+ }
2386
+ if (isDefined(value))
2387
+ out.push(value);
2388
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2389
+ }
2390
+ this.pos = afterClose;
2391
+ return out;
2392
+ }
2393
+ evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
2394
+ const result = {};
2395
+ let currentCond = condValue;
2396
+ while (isDefined(currentCond)) {
2397
+ this.tick();
2398
+ this.selfStack.push(currentCond);
2399
+ const save = this.pos;
2400
+ this.pos = bodyStart;
2401
+ const key = this.evalValue();
2402
+ const value = this.evalValue();
2403
+ this.pos = save;
2404
+ this.selfStack.pop();
2405
+ const control = this.handleLoopControl(value);
2406
+ if (control) {
2407
+ if (control.depth > 1)
2408
+ return { kind: control.kind, depth: control.depth - 1 };
2409
+ if (control.kind === "break")
2410
+ break;
2411
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2412
+ continue;
2413
+ }
2414
+ if (isDefined(value))
2415
+ result[String(key)] = value;
2416
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2417
+ }
2418
+ this.pos = afterClose;
2419
+ return result;
2420
+ }
3009
2421
  evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
3010
2422
  const items = this.iterate(iterable, keysOnly);
3011
2423
  const out = [];
@@ -3076,10 +2488,19 @@ class CursorInterpreter {
3076
2488
  case OPCODES.add:
3077
2489
  if (args[0] === undefined || args[1] === undefined)
3078
2490
  return;
3079
- if (typeof args[0] === "string" || typeof args[1] === "string") {
3080
- return String(args[0]) + String(args[1]);
2491
+ if (Array.isArray(args[0]) && Array.isArray(args[1])) {
2492
+ return [...args[0], ...args[1]];
2493
+ }
2494
+ if (args[0] && args[1] && typeof args[0] === "object" && typeof args[1] === "object" && !Array.isArray(args[0]) && !Array.isArray(args[1])) {
2495
+ return { ...args[0], ...args[1] };
3081
2496
  }
3082
- return Number(args[0]) + Number(args[1]);
2497
+ if (typeof args[0] === "string" && typeof args[1] === "string") {
2498
+ return args[0] + args[1];
2499
+ }
2500
+ if (typeof args[0] === "number" && typeof args[1] === "number") {
2501
+ return args[0] + args[1];
2502
+ }
2503
+ return;
3083
2504
  case OPCODES.sub:
3084
2505
  if (args[0] === undefined || args[1] === undefined)
3085
2506
  return;
@@ -3148,6 +2569,98 @@ class CursorInterpreter {
3148
2569
  return Array.isArray(args[0]) ? args[0] : undefined;
3149
2570
  case OPCODES.object:
3150
2571
  return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
2572
+ case OPCODES.range: {
2573
+ const from = Number(args[0]);
2574
+ const to = Number(args[1]);
2575
+ const step = to >= from ? 1 : -1;
2576
+ const out = [];
2577
+ for (let v = from;step > 0 ? v <= to : v >= to; v += step)
2578
+ out.push(v);
2579
+ return out;
2580
+ }
2581
+ case "array:push": {
2582
+ const target = args[0];
2583
+ if (!Array.isArray(target))
2584
+ return;
2585
+ const next = target.slice();
2586
+ for (let i = 1;i < args.length; i += 1)
2587
+ next.push(args[i]);
2588
+ return next;
2589
+ }
2590
+ case "array:pop": {
2591
+ const target = args[0];
2592
+ if (!Array.isArray(target) || target.length === 0)
2593
+ return;
2594
+ return target[target.length - 1];
2595
+ }
2596
+ case "array:unshift": {
2597
+ const target = args[0];
2598
+ if (!Array.isArray(target))
2599
+ return;
2600
+ const next = target.slice();
2601
+ for (let i = args.length - 1;i >= 1; i -= 1)
2602
+ next.unshift(args[i]);
2603
+ return next;
2604
+ }
2605
+ case "array:shift": {
2606
+ const target = args[0];
2607
+ if (!Array.isArray(target) || target.length === 0)
2608
+ return;
2609
+ return target[0];
2610
+ }
2611
+ case "array:slice": {
2612
+ const target = args[0];
2613
+ if (!Array.isArray(target))
2614
+ return;
2615
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2616
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2617
+ return target.slice(start, end);
2618
+ }
2619
+ case "array:join": {
2620
+ const target = args[0];
2621
+ if (!Array.isArray(target))
2622
+ return;
2623
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : ",";
2624
+ return target.map((item) => String(item)).join(sep);
2625
+ }
2626
+ case "string:split": {
2627
+ const target = args[0];
2628
+ if (typeof target !== "string")
2629
+ return;
2630
+ if (args.length < 2 || args[1] === undefined)
2631
+ return [target];
2632
+ return target.split(String(args[1]));
2633
+ }
2634
+ case "string:join": {
2635
+ const target = args[0];
2636
+ if (typeof target !== "string")
2637
+ return;
2638
+ const parts = Array.from(target);
2639
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2640
+ return parts.join(sep);
2641
+ }
2642
+ case "string:slice": {
2643
+ const target = args[0];
2644
+ if (typeof target !== "string")
2645
+ return;
2646
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2647
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2648
+ return Array.from(target).slice(start, end).join("");
2649
+ }
2650
+ case "string:starts-with": {
2651
+ const target = args[0];
2652
+ if (typeof target !== "string")
2653
+ return;
2654
+ const prefix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2655
+ return target.startsWith(prefix);
2656
+ }
2657
+ case "string:ends-with": {
2658
+ const target = args[0];
2659
+ if (typeof target !== "string")
2660
+ return;
2661
+ const suffix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2662
+ return target.endsWith(suffix);
2663
+ }
3151
2664
  default:
3152
2665
  throw new Error(`Unknown opcode ${id}`);
3153
2666
  }
@@ -3157,10 +2670,103 @@ class CursorInterpreter {
3157
2670
  for (const key of keys) {
3158
2671
  if (current === undefined || current === null)
3159
2672
  return;
3160
- current = current[String(key)];
2673
+ current = this.readProperty(current, key);
2674
+ if (current === undefined)
2675
+ return;
3161
2676
  }
3162
2677
  return current;
3163
2678
  }
2679
+ readProperty(target, key) {
2680
+ if (typeof key === "string" && key === "size") {
2681
+ if (Array.isArray(target))
2682
+ return target.length;
2683
+ if (typeof target === "string")
2684
+ return Array.from(target).length;
2685
+ }
2686
+ const index = this.parseIndexKey(key);
2687
+ if (Array.isArray(target)) {
2688
+ if (index !== undefined)
2689
+ return target[index];
2690
+ if (typeof key === "string")
2691
+ return this.resolveArrayMethod(target, key);
2692
+ return;
2693
+ }
2694
+ if (typeof target === "string") {
2695
+ if (index !== undefined)
2696
+ return Array.from(target)[index];
2697
+ if (typeof key === "string")
2698
+ return this.resolveStringMethod(target, key);
2699
+ return;
2700
+ }
2701
+ if (this.isPlainObject(target)) {
2702
+ const prop = String(key);
2703
+ if (!Object.prototype.hasOwnProperty.call(target, prop))
2704
+ return;
2705
+ return target[prop];
2706
+ }
2707
+ return;
2708
+ }
2709
+ resolveArrayMethod(target, key) {
2710
+ switch (key) {
2711
+ case "push":
2712
+ return { __opcode: "array:push", __receiver: target };
2713
+ case "pop":
2714
+ return { __opcode: "array:pop", __receiver: target };
2715
+ case "unshift":
2716
+ return { __opcode: "array:unshift", __receiver: target };
2717
+ case "shift":
2718
+ return { __opcode: "array:shift", __receiver: target };
2719
+ case "slice":
2720
+ return { __opcode: "array:slice", __receiver: target };
2721
+ case "join":
2722
+ return { __opcode: "array:join", __receiver: target };
2723
+ default:
2724
+ return;
2725
+ }
2726
+ }
2727
+ resolveStringMethod(target, key) {
2728
+ switch (key) {
2729
+ case "split":
2730
+ return { __opcode: "string:split", __receiver: target };
2731
+ case "join":
2732
+ return { __opcode: "string:join", __receiver: target };
2733
+ case "slice":
2734
+ return { __opcode: "string:slice", __receiver: target };
2735
+ case "starts-with":
2736
+ return { __opcode: "string:starts-with", __receiver: target };
2737
+ case "ends-with":
2738
+ return { __opcode: "string:ends-with", __receiver: target };
2739
+ default:
2740
+ return;
2741
+ }
2742
+ }
2743
+ canWriteProperty(target, key) {
2744
+ const index = this.parseIndexKey(key);
2745
+ if (Array.isArray(target)) {
2746
+ if (index === undefined)
2747
+ return;
2748
+ return { kind: "array", index };
2749
+ }
2750
+ if (this.isPlainObject(target))
2751
+ return { kind: "object" };
2752
+ return;
2753
+ }
2754
+ parseIndexKey(key) {
2755
+ if (typeof key === "number" && Number.isInteger(key) && key >= 0)
2756
+ return key;
2757
+ if (typeof key !== "string" || key.length === 0)
2758
+ return;
2759
+ if (!/^(0|[1-9]\d*)$/.test(key))
2760
+ return;
2761
+ const index = Number(key);
2762
+ return Number.isSafeInteger(index) ? index : undefined;
2763
+ }
2764
+ isPlainObject(value) {
2765
+ if (!value || typeof value !== "object")
2766
+ return false;
2767
+ const proto = Object.getPrototypeOf(value);
2768
+ return proto === Object.prototype || proto === null;
2769
+ }
3164
2770
  readPlace() {
3165
2771
  this.skipNonCode();
3166
2772
  const direct = this.readRootVarOrRefIfPresent();
@@ -3215,13 +2821,13 @@ class CursorInterpreter {
3215
2821
  }
3216
2822
  this.pos += 1;
3217
2823
  return {
3218
- root: tag === "$" ? prefix.raw : prefix.value,
2824
+ root: prefix.raw,
3219
2825
  isRef: tag === "'"
3220
2826
  };
3221
2827
  }
3222
2828
  writePlace(place, value) {
3223
2829
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3224
- const rootKey = String(place.root);
2830
+ const rootKey = place.root;
3225
2831
  if (place.keys.length === 0) {
3226
2832
  rootTable[rootKey] = value;
3227
2833
  return;
@@ -3232,17 +2838,48 @@ class CursorInterpreter {
3232
2838
  rootTable[rootKey] = target;
3233
2839
  }
3234
2840
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3235
- const key = String(place.keys[index]);
3236
- const next = target[key];
2841
+ const key = place.keys[index];
2842
+ const access2 = this.canWriteProperty(target, key);
2843
+ if (!access2)
2844
+ return;
2845
+ if (access2.kind === "array") {
2846
+ const next2 = target[access2.index];
2847
+ if (!next2 || typeof next2 !== "object")
2848
+ target[access2.index] = {};
2849
+ target = target[access2.index];
2850
+ continue;
2851
+ }
2852
+ const prop = String(key);
2853
+ const next = target[prop];
3237
2854
  if (!next || typeof next !== "object")
3238
- target[key] = {};
3239
- target = target[key];
2855
+ target[prop] = {};
2856
+ target = target[prop];
2857
+ }
2858
+ const lastKey = place.keys[place.keys.length - 1];
2859
+ const access = this.canWriteProperty(target, lastKey);
2860
+ if (!access)
2861
+ return;
2862
+ if (access.kind === "array") {
2863
+ target[access.index] = value;
2864
+ return;
2865
+ }
2866
+ target[String(lastKey)] = value;
2867
+ }
2868
+ readPlaceValue(place) {
2869
+ const rootTable = place.isRef ? this.state.refs : this.state.vars;
2870
+ let current = rootTable[place.root];
2871
+ for (const key of place.keys) {
2872
+ if (current === undefined || current === null)
2873
+ return;
2874
+ current = this.readProperty(current, key);
2875
+ if (current === undefined)
2876
+ return;
3240
2877
  }
3241
- target[String(place.keys[place.keys.length - 1])] = value;
2878
+ return current;
3242
2879
  }
3243
2880
  deletePlace(place) {
3244
2881
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3245
- const rootKey = String(place.root);
2882
+ const rootKey = place.root;
3246
2883
  if (place.keys.length === 0) {
3247
2884
  delete rootTable[rootKey];
3248
2885
  return;
@@ -3251,11 +2888,29 @@ class CursorInterpreter {
3251
2888
  if (!target || typeof target !== "object")
3252
2889
  return;
3253
2890
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3254
- target = target[String(place.keys[index])];
2891
+ const key = place.keys[index];
2892
+ const access2 = this.canWriteProperty(target, key);
2893
+ if (!access2)
2894
+ return;
2895
+ if (access2.kind === "array") {
2896
+ target = target[access2.index];
2897
+ if (!target || typeof target !== "object")
2898
+ return;
2899
+ continue;
2900
+ }
2901
+ target = target[String(key)];
3255
2902
  if (!target || typeof target !== "object")
3256
2903
  return;
3257
2904
  }
3258
- delete target[String(place.keys[place.keys.length - 1])];
2905
+ const lastKey = place.keys[place.keys.length - 1];
2906
+ const access = this.canWriteProperty(target, lastKey);
2907
+ if (!access)
2908
+ return;
2909
+ if (access.kind === "array") {
2910
+ delete target[access.index];
2911
+ return;
2912
+ }
2913
+ delete target[String(lastKey)];
3259
2914
  }
3260
2915
  skipValue() {
3261
2916
  this.pos = this.skipValueFrom(this.pos);
@@ -3271,12 +2926,12 @@ class CursorInterpreter {
3271
2926
  return startPos;
3272
2927
  }
3273
2928
  if (tag === ",") {
3274
- this.pos += 1 + prefix.value;
2929
+ this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
3275
2930
  const end2 = this.pos;
3276
2931
  this.pos = save;
3277
2932
  return end2;
3278
2933
  }
3279
- if (tag === "=") {
2934
+ if (tag === "=" || tag === "/") {
3280
2935
  this.pos += 1;
3281
2936
  this.skipValue();
3282
2937
  this.skipValue();
@@ -3311,7 +2966,8 @@ class CursorInterpreter {
3311
2966
  if (opener && "([{".includes(opener)) {
3312
2967
  const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
3313
2968
  if (prefix.value > 0) {
3314
- this.pos += 1 + prefix.value + 1;
2969
+ const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
2970
+ this.pos = bodyEnd + 1;
3315
2971
  const end3 = this.pos;
3316
2972
  this.pos = save;
3317
2973
  return end3;
@@ -3347,28 +3003,29 @@ var init_rexc_interpreter = __esm(() => {
3347
3003
  init_rex();
3348
3004
  digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
3349
3005
  OPCODES = {
3350
- do: 0,
3351
- add: 1,
3352
- sub: 2,
3353
- mul: 3,
3354
- div: 4,
3355
- eq: 5,
3356
- neq: 6,
3357
- lt: 7,
3358
- lte: 8,
3359
- gt: 9,
3360
- gte: 10,
3361
- and: 11,
3362
- or: 12,
3363
- xor: 13,
3364
- not: 14,
3365
- boolean: 15,
3366
- number: 16,
3367
- string: 17,
3368
- array: 18,
3369
- object: 19,
3370
- mod: 20,
3371
- neg: 21
3006
+ do: "",
3007
+ add: "ad",
3008
+ sub: "sb",
3009
+ mul: "ml",
3010
+ div: "dv",
3011
+ eq: "eq",
3012
+ neq: "nq",
3013
+ lt: "lt",
3014
+ lte: "le",
3015
+ gt: "gt",
3016
+ gte: "ge",
3017
+ and: "an",
3018
+ or: "or",
3019
+ xor: "xr",
3020
+ not: "nt",
3021
+ boolean: "bt",
3022
+ number: "nm",
3023
+ string: "st",
3024
+ array: "ar",
3025
+ object: "ob",
3026
+ mod: "md",
3027
+ neg: "ng",
3028
+ range: "rn"
3372
3029
  };
3373
3030
  });
3374
3031
 
@@ -3376,14 +3033,73 @@ var init_rexc_interpreter = __esm(() => {
3376
3033
  var exports_rex_repl = {};
3377
3034
  __export(exports_rex_repl, {
3378
3035
  startRepl: () => startRepl,
3036
+ setColorEnabled: () => setColorEnabled,
3379
3037
  isIncomplete: () => isIncomplete,
3380
3038
  highlightRexc: () => highlightRexc,
3381
3039
  highlightLine: () => highlightLine,
3382
3040
  highlightJSON: () => highlightJSON,
3041
+ highlightAuto: () => highlightAuto,
3383
3042
  formatVarState: () => formatVarState
3384
3043
  });
3385
3044
  import * as readline from "node:readline";
3386
3045
  import { createRequire as createRequire2 } from "node:module";
3046
+ import { readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
3047
+ import { resolve, dirname, basename } from "node:path";
3048
+ import { homedir } from "node:os";
3049
+ function createColors(enabled) {
3050
+ if (!enabled) {
3051
+ return {
3052
+ reset: "",
3053
+ bold: "",
3054
+ dim: "",
3055
+ red: "",
3056
+ green: "",
3057
+ yellow: "",
3058
+ blue: "",
3059
+ magenta: "",
3060
+ cyan: "",
3061
+ gray: "",
3062
+ keyword: ""
3063
+ };
3064
+ }
3065
+ return {
3066
+ reset: "\x1B[0m",
3067
+ bold: "\x1B[1m",
3068
+ dim: "\x1B[2m",
3069
+ red: "\x1B[38;5;203m",
3070
+ green: "\x1B[38;5;114m",
3071
+ yellow: "\x1B[38;5;179m",
3072
+ blue: "\x1B[38;5;75m",
3073
+ magenta: "\x1B[38;5;141m",
3074
+ cyan: "\x1B[38;5;81m",
3075
+ gray: "\x1B[38;5;245m",
3076
+ keyword: "\x1B[1;38;5;208m"
3077
+ };
3078
+ }
3079
+ function setColorEnabled(enabled) {
3080
+ colorEnabled = enabled;
3081
+ C = createColors(enabled);
3082
+ }
3083
+ function formatJson(value, indent = 2) {
3084
+ const normalized = normalizeJsonValue(value, false);
3085
+ const text = JSON.stringify(normalized, null, indent);
3086
+ return text ?? "null";
3087
+ }
3088
+ function normalizeJsonValue(value, inArray) {
3089
+ if (value === undefined)
3090
+ return inArray ? null : undefined;
3091
+ if (value === null || typeof value !== "object")
3092
+ return value;
3093
+ if (Array.isArray(value))
3094
+ return value.map((item) => normalizeJsonValue(item, true));
3095
+ const out = {};
3096
+ for (const [key, val] of Object.entries(value)) {
3097
+ const normalized = normalizeJsonValue(val, false);
3098
+ if (normalized !== undefined)
3099
+ out[key] = normalized;
3100
+ }
3101
+ return out;
3102
+ }
3387
3103
  function highlightLine(line) {
3388
3104
  let result = "";
3389
3105
  let lastIndex = 0;
@@ -3396,14 +3112,18 @@ function highlightLine(line) {
3396
3112
  result += C.gray + text + C.reset;
3397
3113
  } else if (g.dstring || g.sstring) {
3398
3114
  result += C.green + text + C.reset;
3115
+ } else if (g.objKey) {
3116
+ result += C.magenta + text + C.reset;
3399
3117
  } else if (g.keyword) {
3400
- result += C.boldBlue + text + C.reset;
3118
+ result += C.keyword + text + C.reset;
3401
3119
  } else if (g.literal) {
3402
3120
  result += C.yellow + text + C.reset;
3403
3121
  } else if (g.typePred) {
3404
3122
  result += C.cyan + text + C.reset;
3405
3123
  } else if (g.num) {
3406
3124
  result += C.cyan + text + C.reset;
3125
+ } else if (g.identifier) {
3126
+ result += C.blue + text + C.reset;
3407
3127
  } else {
3408
3128
  result += text;
3409
3129
  }
@@ -3465,7 +3185,7 @@ function highlightRexc(text) {
3465
3185
  i++;
3466
3186
  break;
3467
3187
  case "%":
3468
- out += C.boldBlue + prefix + tag + C.reset;
3188
+ out += C.keyword + prefix + tag + C.reset;
3469
3189
  i++;
3470
3190
  break;
3471
3191
  case "$":
@@ -3491,6 +3211,7 @@ function highlightRexc(text) {
3491
3211
  break;
3492
3212
  }
3493
3213
  case "=":
3214
+ case "/":
3494
3215
  case "~":
3495
3216
  out += C.red + prefix + tag + C.reset;
3496
3217
  i++;
@@ -3502,11 +3223,11 @@ function highlightRexc(text) {
3502
3223
  case ">":
3503
3224
  case "<":
3504
3225
  case "#":
3505
- out += C.boldBlue + prefix + tag + C.reset;
3226
+ out += C.keyword + prefix + tag + C.reset;
3506
3227
  i++;
3507
3228
  break;
3508
3229
  case ";":
3509
- out += C.boldBlue + prefix + tag + C.reset;
3230
+ out += C.keyword + prefix + tag + C.reset;
3510
3231
  i++;
3511
3232
  break;
3512
3233
  case "^":
@@ -3530,6 +3251,23 @@ function highlightRexc(text) {
3530
3251
  }
3531
3252
  return out;
3532
3253
  }
3254
+ function highlightAuto(text, hint) {
3255
+ if (hint === "rexc")
3256
+ return highlightRexc(text);
3257
+ if (hint === "rex")
3258
+ return text.split(`
3259
+ `).map((line) => highlightLine(line)).join(`
3260
+ `);
3261
+ try {
3262
+ const match = grammar.match(text);
3263
+ if (match.succeeded()) {
3264
+ return text.split(`
3265
+ `).map((line) => highlightLine(line)).join(`
3266
+ `);
3267
+ }
3268
+ } catch {}
3269
+ return highlightRexc(text);
3270
+ }
3533
3271
  function highlightJSON(json) {
3534
3272
  let result = "";
3535
3273
  let lastIndex = 0;
@@ -3593,23 +3331,18 @@ function isIncomplete(buffer) {
3593
3331
  const trimmed = buffer.trimEnd();
3594
3332
  if (/[+\-*/%&|^=<>]$/.test(trimmed))
3595
3333
  return true;
3596
- if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
3334
+ if (/\b(?:and|or|nor|do|in|of)\s*$/.test(trimmed))
3597
3335
  return true;
3598
3336
  return false;
3599
3337
  }
3600
- function formatResult(value) {
3601
- let text;
3602
- try {
3603
- text = stringify(value, { maxWidth: 60 });
3604
- } catch {
3605
- text = String(value);
3606
- }
3607
- return `${C.gray}→${C.reset} ${highlightLine(text)}`;
3608
- }
3609
- function formatVarState(vars) {
3338
+ function formatVarState(vars, format) {
3610
3339
  const entries = Object.entries(vars);
3611
3340
  if (entries.length === 0)
3612
3341
  return "";
3342
+ if (format === "json") {
3343
+ const rendered = highlightJSON(formatJson(vars, 2));
3344
+ return `${C.dim} vars:${C.reset} ${rendered}`;
3345
+ }
3613
3346
  const MAX_LINE = 70;
3614
3347
  const MAX_VALUE = 30;
3615
3348
  const parts = [];
@@ -3634,8 +3367,31 @@ function formatVarState(vars) {
3634
3367
  }
3635
3368
  return `${C.dim} ${parts.join(", ")}${C.reset}`;
3636
3369
  }
3370
+ function renderValue(value, format, kind) {
3371
+ if (format === "json") {
3372
+ return highlightJSON(formatJson(value, 2));
3373
+ }
3374
+ if (kind === "source") {
3375
+ return highlightAuto(String(value ?? ""));
3376
+ }
3377
+ if (kind === "rexc") {
3378
+ return highlightRexc(String(value ?? ""));
3379
+ }
3380
+ return highlightLine(stringify(value, { maxWidth: 120 }));
3381
+ }
3637
3382
  function completer(state) {
3638
3383
  return (line) => {
3384
+ if (line.startsWith(".file ")) {
3385
+ return completeFilePath(line, 6);
3386
+ }
3387
+ if (line.startsWith(".cat ")) {
3388
+ return completeFilePath(line, 5);
3389
+ }
3390
+ if (line.startsWith(".") && !line.includes(" ")) {
3391
+ const partial2 = line.trim();
3392
+ const matches = DOT_COMMANDS.filter((cmd) => cmd.startsWith(partial2));
3393
+ return [matches, line];
3394
+ }
3639
3395
  const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
3640
3396
  const partial = match ? match[0] : "";
3641
3397
  if (!partial)
@@ -3646,45 +3402,92 @@ function completer(state) {
3646
3402
  return [hits, partial];
3647
3403
  };
3648
3404
  }
3649
- function handleDotCommand(cmd, state, rl) {
3405
+ function completeFilePath(line, prefixLength) {
3406
+ const raw = line.slice(prefixLength);
3407
+ const trimmed = raw.trimStart();
3408
+ const quote = trimmed.startsWith('"') || trimmed.startsWith("'") ? trimmed[0] : "";
3409
+ const pathPart = quote ? trimmed.slice(1) : trimmed;
3410
+ const endsWithSlash = pathPart.endsWith("/");
3411
+ const rawDir = endsWithSlash ? pathPart.slice(0, -1) : pathPart;
3412
+ const baseName = endsWithSlash ? "" : rawDir.includes("/") ? basename(rawDir) : rawDir;
3413
+ const dirPart = endsWithSlash ? rawDir || "." : rawDir.includes("/") ? dirname(rawDir) : ".";
3414
+ const dirPath = resolve(dirPart);
3415
+ let entries = [];
3416
+ try {
3417
+ entries = readdirSync(dirPath);
3418
+ } catch {
3419
+ return [[], ""];
3420
+ }
3421
+ const prefix = dirPart === "." ? "" : `${dirPart}/`;
3422
+ const matches = entries.filter((entry) => entry.startsWith(baseName)).map((entry) => {
3423
+ const fullPath = resolve(dirPath, entry);
3424
+ let suffix = "";
3425
+ try {
3426
+ if (statSync(fullPath).isDirectory())
3427
+ suffix = "/";
3428
+ } catch {
3429
+ suffix = "";
3430
+ }
3431
+ return `${quote}${prefix}${entry}${suffix}`;
3432
+ });
3433
+ return [matches, trimmed];
3434
+ }
3435
+ function stripQuotes(value) {
3436
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3437
+ return value.slice(1, -1);
3438
+ }
3439
+ return value;
3440
+ }
3441
+ async function handleDotCommand(cmd, state, rl, runSource, updatePromptStyles) {
3650
3442
  function toggleLabel(on) {
3651
3443
  return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
3652
3444
  }
3653
3445
  switch (cmd) {
3654
3446
  case ".help":
3655
3447
  console.log([
3656
- `${C.boldBlue}Rex REPL Commands:${C.reset}`,
3657
- " .help Show this help message",
3658
- " .vars Show all current variables",
3659
- " .clear Clear all variables",
3660
- " .ir Toggle showing IR JSON after parsing",
3661
- " .rexc Toggle showing compiled rexc before execution",
3662
- " .opt Toggle IR optimizations",
3663
- " .exit Exit the REPL",
3448
+ `${C.keyword}Rex REPL Commands:${C.reset}`,
3449
+ " .help Show this help message",
3450
+ " .file <path> Load and execute a Rex file",
3451
+ " .cat <path> Print a Rex/rexc file with highlighting",
3452
+ " .expr Toggle showing expression results",
3453
+ " .source Toggle showing input source",
3454
+ " .vars Toggle showing variable summary",
3455
+ " .vars! Show all current variables",
3456
+ " .clear Clear all variables",
3457
+ " .ir Toggle showing IR JSON after parsing",
3458
+ " .rexc Toggle showing compiled rexc before execution",
3459
+ " .opt Toggle IR optimizations",
3460
+ " .json Toggle JSON output format",
3461
+ " .color Toggle ANSI color output",
3462
+ " .exit Exit the REPL",
3664
3463
  "",
3665
3464
  "Enter Rex expressions to evaluate them.",
3666
3465
  "Multi-line: open brackets or do/end blocks continue on the next line.",
3667
3466
  "Ctrl-C cancels multi-line input.",
3668
- "Ctrl-D exits."
3467
+ "Ctrl-D exits.",
3468
+ "",
3469
+ "Outputs are printed as labeled blocks when enabled."
3669
3470
  ].join(`
3670
3471
  `));
3671
- return true;
3672
- case ".ir":
3673
- state.showIR = !state.showIR;
3674
- console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3675
- return true;
3676
- case ".rexc":
3677
- state.showRexc = !state.showRexc;
3678
- console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3679
- return true;
3680
- case ".opt":
3681
- state.optimize = !state.optimize;
3682
- console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3683
- return true;
3684
- case ".vars": {
3472
+ return "handled";
3473
+ case ".expr":
3474
+ state.showExpr = !state.showExpr;
3475
+ console.log(`${C.dim} Expression output: ${toggleLabel(state.showExpr)}${C.reset}`);
3476
+ return "handled";
3477
+ case ".source":
3478
+ state.showSource = !state.showSource;
3479
+ console.log(`${C.dim} Source output: ${toggleLabel(state.showSource)}${C.reset}`);
3480
+ return "handled";
3481
+ case ".vars":
3482
+ state.showVars = !state.showVars;
3483
+ console.log(`${C.dim} Variable summary: ${toggleLabel(state.showVars)}${C.reset}`);
3484
+ return "handled";
3485
+ case ".vars!": {
3685
3486
  const entries = Object.entries(state.vars);
3686
3487
  if (entries.length === 0) {
3687
3488
  console.log(`${C.dim} (no variables)${C.reset}`);
3489
+ } else if (state.outputFormat === "json") {
3490
+ console.log(highlightJSON(formatJson(state.vars, 2)));
3688
3491
  } else {
3689
3492
  for (const [key, val] of entries) {
3690
3493
  let valStr;
@@ -3696,42 +3499,118 @@ function handleDotCommand(cmd, state, rl) {
3696
3499
  console.log(` ${key} = ${highlightLine(valStr)}`);
3697
3500
  }
3698
3501
  }
3699
- return true;
3502
+ return "handled";
3700
3503
  }
3504
+ case ".ir":
3505
+ state.showIR = !state.showIR;
3506
+ console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3507
+ return "handled";
3508
+ case ".rexc":
3509
+ state.showRexc = !state.showRexc;
3510
+ console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3511
+ return "handled";
3512
+ case ".opt":
3513
+ state.optimize = !state.optimize;
3514
+ console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3515
+ return "handled";
3516
+ case ".json":
3517
+ state.outputFormat = state.outputFormat === "json" ? "rex" : "json";
3518
+ console.log(`${C.dim} Output format: ${state.outputFormat}${C.reset}`);
3519
+ return "handled";
3520
+ case ".color":
3521
+ setColorEnabled(!colorEnabled);
3522
+ updatePromptStyles();
3523
+ console.log(`${C.dim} Color output: ${toggleLabel(colorEnabled)}${C.reset}`);
3524
+ return "handled";
3701
3525
  case ".clear":
3702
3526
  state.vars = {};
3703
3527
  state.refs = {};
3704
3528
  console.log(`${C.dim} Variables cleared.${C.reset}`);
3705
- return true;
3529
+ return "handled";
3706
3530
  case ".exit":
3707
3531
  rl.close();
3708
- return true;
3532
+ return "handled-noprompt";
3709
3533
  default:
3534
+ if (cmd.startsWith(".cat ")) {
3535
+ const rawPath = cmd.slice(5).trim();
3536
+ if (!rawPath) {
3537
+ console.log(`${C.red} Missing file path. Usage: .cat <path>${C.reset}`);
3538
+ return "handled";
3539
+ }
3540
+ const filePath = resolve(stripQuotes(rawPath));
3541
+ try {
3542
+ const source = readFileSync(filePath, "utf8");
3543
+ const hint = filePath.endsWith(".rexc") ? "rexc" : filePath.endsWith(".rex") ? "rex" : undefined;
3544
+ console.log(highlightAuto(source, hint));
3545
+ } catch (error) {
3546
+ const message = error instanceof Error ? error.message : String(error);
3547
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3548
+ }
3549
+ return "handled";
3550
+ }
3551
+ if (cmd.startsWith(".file ")) {
3552
+ const rawPath = cmd.slice(6).trim();
3553
+ if (!rawPath) {
3554
+ console.log(`${C.red} Missing file path. Usage: .file <path>${C.reset}`);
3555
+ return "handled";
3556
+ }
3557
+ const filePath = resolve(stripQuotes(rawPath));
3558
+ try {
3559
+ const source = readFileSync(filePath, "utf8");
3560
+ runSource(source);
3561
+ } catch (error) {
3562
+ const message = error instanceof Error ? error.message : String(error);
3563
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3564
+ }
3565
+ return "handled";
3566
+ }
3710
3567
  if (cmd.startsWith(".")) {
3711
3568
  console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
3712
- return true;
3569
+ return "handled";
3713
3570
  }
3714
- return false;
3571
+ return "unhandled";
3715
3572
  }
3716
3573
  }
3717
3574
  async function startRepl() {
3718
- const state = { vars: {}, refs: {}, showIR: false, showRexc: false, optimize: false };
3575
+ const state = {
3576
+ vars: {},
3577
+ refs: {},
3578
+ showIR: false,
3579
+ showRexc: false,
3580
+ optimize: false,
3581
+ showExpr: true,
3582
+ showVars: false,
3583
+ showSource: false,
3584
+ outputFormat: "rex"
3585
+ };
3719
3586
  let multiLineBuffer = "";
3720
3587
  const PRIMARY_PROMPT = "rex> ";
3721
3588
  const CONT_PROMPT = "... ";
3722
- const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
3723
- const STYLED_CONT = `${C.dim}...${C.reset} `;
3724
3589
  let currentPrompt = PRIMARY_PROMPT;
3725
- let styledPrompt = STYLED_PRIMARY;
3726
- console.log(`${C.boldBlue}Rex${C.reset} v${version} type ${C.dim}.help${C.reset} for commands`);
3590
+ let styledPrompt = "";
3591
+ let styledPrimary = "";
3592
+ let styledCont = "";
3593
+ function updatePromptStyles() {
3594
+ styledPrimary = `${C.keyword}rex${C.reset}> `;
3595
+ styledCont = `${C.dim}...${C.reset} `;
3596
+ styledPrompt = currentPrompt === PRIMARY_PROMPT ? styledPrimary : styledCont;
3597
+ }
3598
+ updatePromptStyles();
3599
+ console.log(`${C.keyword}Rex${C.reset} v${version} — type ${C.dim}.help${C.reset} for commands`);
3727
3600
  const rl = readline.createInterface({
3728
3601
  input: process.stdin,
3729
3602
  output: process.stdout,
3730
3603
  prompt: PRIMARY_PROMPT,
3731
- historySize: 500,
3604
+ historySize: HISTORY_LIMIT,
3732
3605
  completer: completer(state),
3733
3606
  terminal: true
3734
3607
  });
3608
+ try {
3609
+ const historyText = readFileSync(HISTORY_PATH, "utf8");
3610
+ const lines = historyText.split(/\r?\n/).filter((line) => line.trim().length > 0);
3611
+ const recent = lines.slice(-HISTORY_LIMIT);
3612
+ rl.history = recent.reverse();
3613
+ } catch {}
3735
3614
  process.stdin.on("keypress", () => {
3736
3615
  process.nextTick(() => {
3737
3616
  if (!rl.line && rl.line !== "")
@@ -3753,7 +3632,7 @@ async function startRepl() {
3753
3632
  if (multiLineBuffer) {
3754
3633
  multiLineBuffer = "";
3755
3634
  currentPrompt = PRIMARY_PROMPT;
3756
- styledPrompt = STYLED_PRIMARY;
3635
+ styledPrompt = styledPrimary;
3757
3636
  rl.setPrompt(PRIMARY_PROMPT);
3758
3637
  process.stdout.write(`
3759
3638
  `);
@@ -3765,50 +3644,33 @@ async function startRepl() {
3765
3644
  });
3766
3645
  function resetPrompt() {
3767
3646
  currentPrompt = PRIMARY_PROMPT;
3768
- styledPrompt = STYLED_PRIMARY;
3647
+ styledPrompt = styledPrimary;
3769
3648
  rl.setPrompt(PRIMARY_PROMPT);
3770
3649
  rl.prompt();
3771
3650
  }
3772
- rl.on("line", (line) => {
3773
- const trimmed = line.trim();
3774
- if (!multiLineBuffer && trimmed.startsWith(".")) {
3775
- if (handleDotCommand(trimmed, state, rl)) {
3776
- rl.prompt();
3777
- return;
3778
- }
3779
- }
3780
- multiLineBuffer += (multiLineBuffer ? `
3781
- ` : "") + line;
3782
- if (multiLineBuffer.trim() === "") {
3783
- multiLineBuffer = "";
3784
- rl.prompt();
3785
- return;
3786
- }
3787
- if (isIncomplete(multiLineBuffer)) {
3788
- currentPrompt = CONT_PROMPT;
3789
- styledPrompt = STYLED_CONT;
3790
- rl.setPrompt(CONT_PROMPT);
3791
- rl.prompt();
3792
- return;
3793
- }
3794
- const source = multiLineBuffer;
3795
- multiLineBuffer = "";
3651
+ function runSource(source) {
3796
3652
  const match = grammar.match(source);
3797
3653
  if (!match.succeeded()) {
3798
- console.log(`${C.red} ${match.message}${C.reset}`);
3799
- resetPrompt();
3654
+ const message = formatParseError(source, match);
3655
+ console.log(`${C.red} ${message}${C.reset}`);
3800
3656
  return;
3801
3657
  }
3802
3658
  try {
3803
- const ir = parseToIR(source);
3804
- const lowered = state.optimize ? optimizeIR(ir) : ir;
3805
- if (state.showIR) {
3806
- console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3659
+ const outputs = {};
3660
+ if (state.showSource)
3661
+ outputs.source = source;
3662
+ const isRex = grammar.match(source).succeeded();
3663
+ if (!isRex && state.showIR) {
3664
+ console.log(`${C.red} IR output is only available for Rex source.${C.reset}`);
3807
3665
  }
3808
- const rexc = compile(source, { optimize: state.optimize });
3809
- if (state.showRexc) {
3810
- console.log(`${C.dim} rexc:${C.reset} ${highlightRexc(rexc)}`);
3666
+ const rexc = isRex ? compile(source, { optimize: state.optimize }) : source;
3667
+ if (state.showIR && isRex) {
3668
+ const ir = parseToIR(source);
3669
+ const lowered = state.optimize ? optimizeIR(ir) : ir;
3670
+ outputs.ir = lowered;
3811
3671
  }
3672
+ if (state.showRexc)
3673
+ outputs.rexc = rexc;
3812
3674
  const result = evaluateRexc(rexc, {
3813
3675
  vars: { ...state.vars },
3814
3676
  refs: { ...state.refs },
@@ -3816,10 +3678,17 @@ async function startRepl() {
3816
3678
  });
3817
3679
  state.vars = result.state.vars;
3818
3680
  state.refs = result.state.refs;
3819
- console.log(formatResult(result.value));
3820
- const varLine = formatVarState(state.vars);
3821
- if (varLine)
3822
- console.log(varLine);
3681
+ if (state.showExpr)
3682
+ outputs.result = result.value;
3683
+ if (state.showVars)
3684
+ outputs.vars = state.vars;
3685
+ const order = ["source", "ir", "rexc", "vars", "result"];
3686
+ for (const key of order) {
3687
+ const value = outputs[key];
3688
+ if (value === undefined)
3689
+ continue;
3690
+ console.log(`${C.gray} ${key}:${C.reset} ${renderValue(value, state.outputFormat, key)}`);
3691
+ }
3823
3692
  } catch (error) {
3824
3693
  const message = error instanceof Error ? error.message : String(error);
3825
3694
  if (message.includes("Gas limit exceeded")) {
@@ -3828,32 +3697,58 @@ async function startRepl() {
3828
3697
  console.log(`${C.red} Error: ${message}${C.reset}`);
3829
3698
  }
3830
3699
  }
3700
+ }
3701
+ rl.on("line", async (line) => {
3702
+ const trimmed = line.trim();
3703
+ if (!multiLineBuffer && trimmed.startsWith(".")) {
3704
+ const result = await handleDotCommand(trimmed, state, rl, runSource, updatePromptStyles);
3705
+ if (result === "handled") {
3706
+ rl.prompt();
3707
+ return;
3708
+ }
3709
+ if (result === "handled-noprompt")
3710
+ return;
3711
+ }
3712
+ multiLineBuffer += (multiLineBuffer ? `
3713
+ ` : "") + line;
3714
+ if (multiLineBuffer.trim() === "") {
3715
+ multiLineBuffer = "";
3716
+ rl.prompt();
3717
+ return;
3718
+ }
3719
+ if (isIncomplete(multiLineBuffer)) {
3720
+ currentPrompt = CONT_PROMPT;
3721
+ styledPrompt = styledCont;
3722
+ rl.setPrompt(CONT_PROMPT);
3723
+ rl.prompt();
3724
+ return;
3725
+ }
3726
+ const source = multiLineBuffer;
3727
+ multiLineBuffer = "";
3728
+ runSource(source);
3831
3729
  resetPrompt();
3832
3730
  });
3833
3731
  rl.on("close", () => {
3732
+ try {
3733
+ const history = rl.history ?? [];
3734
+ const trimmed = history.slice().reverse().filter((line) => line.trim().length > 0).slice(-HISTORY_LIMIT);
3735
+ writeFileSync(HISTORY_PATH, `${trimmed.join(`
3736
+ `)}
3737
+ `, "utf8");
3738
+ } catch {}
3834
3739
  process.exit(0);
3835
3740
  });
3836
3741
  rl.prompt();
3837
3742
  }
3838
- var req, version, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, GAS_LIMIT = 1e7;
3743
+ var req, version, colorEnabled, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, DOT_COMMANDS, GAS_LIMIT = 1e7, HISTORY_LIMIT = 1000, HISTORY_PATH;
3839
3744
  var init_rex_repl = __esm(() => {
3840
3745
  init_rex();
3841
3746
  init_rexc_interpreter();
3842
3747
  req = createRequire2(import.meta.url);
3843
3748
  ({ version } = req("./package.json"));
3844
- C = {
3845
- reset: "\x1B[0m",
3846
- bold: "\x1B[1m",
3847
- dim: "\x1B[2m",
3848
- red: "\x1B[31m",
3849
- green: "\x1B[32m",
3850
- yellow: "\x1B[33m",
3851
- blue: "\x1B[34m",
3852
- cyan: "\x1B[36m",
3853
- gray: "\x1B[90m",
3854
- boldBlue: "\x1B[1;34m"
3855
- };
3856
- 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;
3749
+ colorEnabled = process.stdout.isTTY;
3750
+ C = createColors(colorEnabled);
3751
+ 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;
3857
3752
  REXC_DIGITS = new Set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_");
3858
3753
  JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3859
3754
  KEYWORDS = [
@@ -3867,6 +3762,7 @@ var init_rex_repl = __esm(() => {
3867
3762
  "of",
3868
3763
  "and",
3869
3764
  "or",
3765
+ "nor",
3870
3766
  "else",
3871
3767
  "break",
3872
3768
  "continue",
@@ -3884,20 +3780,49 @@ var init_rex_repl = __esm(() => {
3884
3780
  "array",
3885
3781
  "boolean"
3886
3782
  ];
3783
+ DOT_COMMANDS = [
3784
+ ".help",
3785
+ ".file",
3786
+ ".cat",
3787
+ ".expr",
3788
+ ".source",
3789
+ ".vars",
3790
+ ".vars!",
3791
+ ".clear",
3792
+ ".ir",
3793
+ ".rexc",
3794
+ ".opt",
3795
+ ".json",
3796
+ ".color",
3797
+ ".exit"
3798
+ ];
3799
+ HISTORY_PATH = resolve(homedir(), ".rex_history");
3887
3800
  });
3888
3801
 
3889
3802
  // rex-cli.ts
3890
3803
  init_rex();
3891
3804
  init_rexc_interpreter();
3892
- import { dirname, resolve } from "node:path";
3805
+ init_rex_repl();
3806
+ import { dirname as dirname2, resolve as resolve2 } from "node:path";
3893
3807
  import { readFile, writeFile } from "node:fs/promises";
3894
3808
  function parseArgs(argv) {
3895
3809
  const options = {
3810
+ sources: [],
3896
3811
  compile: false,
3897
3812
  ir: false,
3898
3813
  minifyNames: false,
3899
3814
  dedupeValues: false,
3900
- help: false
3815
+ help: false,
3816
+ cat: false,
3817
+ showSource: false,
3818
+ showExpr: true,
3819
+ showIR: false,
3820
+ showRexc: false,
3821
+ showVars: false,
3822
+ color: process.stdout.isTTY,
3823
+ colorExplicit: false,
3824
+ showExprExplicit: false,
3825
+ format: "rex"
3901
3826
  };
3902
3827
  for (let index = 0;index < argv.length; index += 1) {
3903
3828
  const arg = argv[index];
@@ -3907,6 +3832,60 @@ function parseArgs(argv) {
3907
3832
  options.help = true;
3908
3833
  continue;
3909
3834
  }
3835
+ if (arg === "--cat") {
3836
+ options.cat = true;
3837
+ continue;
3838
+ }
3839
+ if (arg === "--show-source") {
3840
+ options.showSource = true;
3841
+ continue;
3842
+ }
3843
+ if (arg === "--show-expr") {
3844
+ options.showExpr = true;
3845
+ options.showExprExplicit = true;
3846
+ continue;
3847
+ }
3848
+ if (arg === "--no-expr") {
3849
+ options.showExpr = false;
3850
+ options.showExprExplicit = true;
3851
+ continue;
3852
+ }
3853
+ if (arg === "--show-ir") {
3854
+ options.showIR = true;
3855
+ continue;
3856
+ }
3857
+ if (arg === "--show-rexc") {
3858
+ options.showRexc = true;
3859
+ continue;
3860
+ }
3861
+ if (arg === "--show-vars") {
3862
+ options.showVars = true;
3863
+ continue;
3864
+ }
3865
+ if (arg === "--json") {
3866
+ options.format = "json";
3867
+ continue;
3868
+ }
3869
+ if (arg === "--format") {
3870
+ const value = argv[index + 1];
3871
+ if (!value)
3872
+ throw new Error("Missing value for --format");
3873
+ if (value !== "rex" && value !== "json")
3874
+ throw new Error("--format must be 'rex' or 'json'");
3875
+ options.format = value;
3876
+ index += 1;
3877
+ continue;
3878
+ }
3879
+ if (arg === "--color") {
3880
+ options.color = true;
3881
+ options.colorExplicit = true;
3882
+ continue;
3883
+ }
3884
+ if (arg === "--no-color") {
3885
+ options.color = false;
3886
+ options.colorExplicit = true;
3887
+ continue;
3888
+ }
3910
3889
  if (arg === "--compile" || arg === "-c") {
3911
3890
  options.compile = true;
3912
3891
  continue;
@@ -3938,7 +3917,7 @@ function parseArgs(argv) {
3938
3917
  const value = argv[index + 1];
3939
3918
  if (!value)
3940
3919
  throw new Error("Missing value for --expr");
3941
- options.expr = value;
3920
+ options.sources.push({ type: "expr", value });
3942
3921
  index += 1;
3943
3922
  continue;
3944
3923
  }
@@ -3946,7 +3925,7 @@ function parseArgs(argv) {
3946
3925
  const value = argv[index + 1];
3947
3926
  if (!value)
3948
3927
  throw new Error("Missing value for --file");
3949
- options.file = value;
3928
+ options.sources.push({ type: "file", path: value });
3950
3929
  index += 1;
3951
3930
  continue;
3952
3931
  }
@@ -3959,9 +3938,7 @@ function parseArgs(argv) {
3959
3938
  continue;
3960
3939
  }
3961
3940
  if (!arg.startsWith("-")) {
3962
- if (options.file)
3963
- throw new Error("Multiple file arguments provided");
3964
- options.file = arg;
3941
+ options.sources.push({ type: "file", path: arg });
3965
3942
  continue;
3966
3943
  }
3967
3944
  throw new Error(`Unknown option: ${arg}`);
@@ -3979,15 +3956,39 @@ function usage() {
3979
3956
  " cat input.rex | rex Evaluate from stdin",
3980
3957
  " rex -c input.rex Compile to rexc bytecode",
3981
3958
  "",
3959
+ " Sources are concatenated in order, so flags and files can be mixed:",
3960
+ " rex -e 'max = 200' primes.rex Set max before running script",
3961
+ " rex primes.rex -e '42' Run script, then evaluate 42",
3962
+ "",
3982
3963
  "Input:",
3983
3964
  " <file> Evaluate/compile a Rex source file",
3984
3965
  " -e, --expr <source> Evaluate/compile an inline expression",
3985
3966
  " -f, --file <path> Evaluate/compile source from a file",
3986
3967
  "",
3968
+ " Multiple -e and -f flags (and positional files) can be combined.",
3969
+ " They are concatenated in the order they appear on the command line.",
3970
+ "",
3987
3971
  "Output mode:",
3988
- " (default) Evaluate and output result as JSON",
3989
- " -c, --compile Compile to rexc bytecode",
3990
- " --ir Output lowered IR as JSON",
3972
+ " (default) Evaluate and output result",
3973
+ " -c, --compile Show rexc only (same as --show-rexc --no-expr)",
3974
+ " --ir Show IR only (same as --show-ir --no-expr)",
3975
+ " --cat Print input with Rex highlighting",
3976
+ " --show-source Show input source text",
3977
+ " --show-expr Show expression result",
3978
+ " --no-expr Hide expression result",
3979
+ " --show-ir Show IR JSON",
3980
+ " --show-rexc Show rexc bytecode",
3981
+ " --show-vars Show variable state",
3982
+ "",
3983
+ "Output formatting:",
3984
+ " --format <type> Output format: rex|json (default: rex)",
3985
+ " --json Shortcut for --format json",
3986
+ " --color Force color output",
3987
+ " --no-color Disable color output",
3988
+ "",
3989
+ "TTY vs non-TTY:",
3990
+ " TTY output prints labeled blocks when multiple outputs are selected.",
3991
+ " Non-TTY output emits a single value (object if multiple outputs).",
3991
3992
  "",
3992
3993
  "Compile options:",
3993
3994
  " -m, --minify-names Minify local variable names",
@@ -4008,12 +4009,17 @@ async function readStdin() {
4008
4009
  return Buffer.concat(chunks).toString("utf8");
4009
4010
  }
4010
4011
  async function resolveSource(options) {
4011
- if (options.expr && options.file)
4012
- throw new Error("Use only one of --expr, --file, or a positional file path");
4013
- if (options.expr)
4014
- return options.expr;
4015
- if (options.file)
4016
- return readFile(options.file, "utf8");
4012
+ if (options.sources.length > 0) {
4013
+ const parts = [];
4014
+ for (const seg of options.sources) {
4015
+ if (seg.type === "expr")
4016
+ parts.push(seg.value);
4017
+ else
4018
+ parts.push(await readFile(seg.path, "utf8"));
4019
+ }
4020
+ return parts.join(`
4021
+ `);
4022
+ }
4017
4023
  if (!process.stdin.isTTY) {
4018
4024
  const piped = await readStdin();
4019
4025
  if (piped.trim().length > 0)
@@ -4021,8 +4027,15 @@ async function resolveSource(options) {
4021
4027
  }
4022
4028
  throw new Error("No input provided. Use a file path, --expr, or pipe source via stdin.");
4023
4029
  }
4030
+ function findFirstFilePath(sources) {
4031
+ for (const seg of sources) {
4032
+ if (seg.type === "file")
4033
+ return seg.path;
4034
+ }
4035
+ return;
4036
+ }
4024
4037
  async function loadDomainConfigFromFolder(folderPath) {
4025
- const configPath = resolve(folderPath, ".config.rex");
4038
+ const configPath = resolve2(folderPath, ".config.rex");
4026
4039
  try {
4027
4040
  return parse(await readFile(configPath, "utf8"));
4028
4041
  } catch (error) {
@@ -4032,36 +4045,167 @@ async function loadDomainConfigFromFolder(folderPath) {
4032
4045
  }
4033
4046
  }
4034
4047
  async function resolveDomainConfig(options) {
4035
- const baseFolder = options.file ? dirname(resolve(options.file)) : process.cwd();
4048
+ const filePath = findFirstFilePath(options.sources);
4049
+ const baseFolder = filePath ? dirname2(resolve2(filePath)) : process.cwd();
4036
4050
  return loadDomainConfigFromFolder(baseFolder);
4037
4051
  }
4052
+ function formatJson2(value, indent = 2) {
4053
+ const normalized = normalizeJsonValue2(value, false);
4054
+ const text = JSON.stringify(normalized, null, indent);
4055
+ return text ?? "null";
4056
+ }
4057
+ function normalizeJsonValue2(value, inArray) {
4058
+ if (value === undefined)
4059
+ return inArray ? null : undefined;
4060
+ if (value === null || typeof value !== "object")
4061
+ return value;
4062
+ if (Array.isArray(value)) {
4063
+ return value.map((item) => normalizeJsonValue2(item, true));
4064
+ }
4065
+ const out = {};
4066
+ for (const [key, val] of Object.entries(value)) {
4067
+ const normalized = normalizeJsonValue2(val, false);
4068
+ if (normalized !== undefined)
4069
+ out[key] = normalized;
4070
+ }
4071
+ return out;
4072
+ }
4073
+ function isRexSource(source) {
4074
+ try {
4075
+ return grammar.match(source).succeeded();
4076
+ } catch {
4077
+ return false;
4078
+ }
4079
+ }
4038
4080
  async function main() {
4039
4081
  const options = parseArgs(process.argv.slice(2));
4040
4082
  if (options.help) {
4041
4083
  console.log(usage());
4042
4084
  return;
4043
4085
  }
4044
- const hasSource = options.expr || options.file || !process.stdin.isTTY;
4086
+ setColorEnabled(options.color);
4087
+ if (options.cat) {
4088
+ const source2 = await resolveSource(options);
4089
+ const hasRexc = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rexc"));
4090
+ const hasRex = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rex"));
4091
+ const hint = hasRexc && !hasRex ? "rexc" : hasRex && !hasRexc ? "rex" : undefined;
4092
+ const output2 = process.stdout.isTTY && options.color ? highlightAuto(source2, hint) : source2;
4093
+ if (options.out) {
4094
+ await writeFile(options.out, `${output2}
4095
+ `, "utf8");
4096
+ return;
4097
+ }
4098
+ console.log(output2);
4099
+ return;
4100
+ }
4101
+ const hasSource = options.sources.length > 0 || !process.stdin.isTTY;
4045
4102
  if (!hasSource && !options.compile && !options.ir) {
4046
4103
  const { startRepl: startRepl2 } = await Promise.resolve().then(() => (init_rex_repl(), exports_rex_repl));
4047
4104
  await startRepl2();
4048
4105
  return;
4049
4106
  }
4050
4107
  const source = await resolveSource(options);
4051
- let output;
4108
+ const sourceIsRex = isRexSource(source);
4109
+ if (options.compile) {
4110
+ if (!sourceIsRex)
4111
+ throw new Error("--compile requires Rex source");
4112
+ options.showRexc = true;
4113
+ if (!options.showExprExplicit)
4114
+ options.showExpr = false;
4115
+ }
4052
4116
  if (options.ir) {
4053
- output = JSON.stringify(parseToIR(source), null, 2);
4054
- } else if (options.compile) {
4117
+ if (!sourceIsRex)
4118
+ throw new Error("--ir requires Rex source");
4119
+ options.showIR = true;
4120
+ if (!options.showExprExplicit)
4121
+ options.showExpr = false;
4122
+ }
4123
+ const outputFlags = [options.showSource, options.showExpr, options.showIR, options.showRexc, options.showVars].filter(Boolean).length;
4124
+ if (outputFlags === 0) {
4125
+ throw new Error("No output selected. Use --show-source, --show-expr, --show-ir, --show-rexc, or --show-vars.");
4126
+ }
4127
+ const humanMode = process.stdout.isTTY && options.color;
4128
+ const outputs = {};
4129
+ if (options.showSource)
4130
+ outputs.source = source;
4131
+ if (!sourceIsRex && options.showIR) {
4132
+ throw new Error("--show-ir is only available for Rex source");
4133
+ }
4134
+ if (options.showIR) {
4135
+ outputs.ir = parseToIR(source);
4136
+ }
4137
+ if (options.showRexc) {
4055
4138
  const domainConfig = await resolveDomainConfig(options);
4056
- output = compile(source, {
4139
+ outputs.rexc = sourceIsRex ? compile(source, {
4057
4140
  minifyNames: options.minifyNames,
4058
4141
  dedupeValues: options.dedupeValues,
4059
4142
  dedupeMinBytes: options.dedupeMinBytes,
4060
4143
  domainConfig
4061
- });
4144
+ }) : source;
4145
+ }
4146
+ if (options.showExpr || options.showVars) {
4147
+ const result = sourceIsRex ? evaluateSource(source) : evaluateRexc(source);
4148
+ if (options.showExpr)
4149
+ outputs.result = result.value;
4150
+ if (options.showVars)
4151
+ outputs.vars = result.state.vars;
4152
+ }
4153
+ const order = ["source", "ir", "rexc", "vars", "result"];
4154
+ const selected = order.filter((key) => outputs[key] !== undefined);
4155
+ function formatValue(value, kind) {
4156
+ if (kind === "source") {
4157
+ const raw2 = String(value ?? "");
4158
+ if (options.format === "json") {
4159
+ const json = formatJson2(raw2, 2);
4160
+ return humanMode && options.color ? highlightJSON(json) : json;
4161
+ }
4162
+ return humanMode && options.color ? highlightAuto(raw2) : raw2;
4163
+ }
4164
+ if (kind === "rexc" && humanMode) {
4165
+ const raw2 = String(value ?? "");
4166
+ return options.color ? highlightAuto(raw2, "rexc") : raw2;
4167
+ }
4168
+ if (options.format === "json") {
4169
+ const raw2 = formatJson2(value, 2);
4170
+ return humanMode && options.color ? highlightJSON(raw2) : raw2;
4171
+ }
4172
+ if (kind === "rexc") {
4173
+ const raw2 = stringify(String(value ?? ""));
4174
+ return humanMode && options.color ? highlightLine(raw2) : raw2;
4175
+ }
4176
+ const raw = stringify(value);
4177
+ return humanMode && options.color ? highlightLine(raw) : raw;
4178
+ }
4179
+ let output;
4180
+ if (humanMode) {
4181
+ const header = options.color ? "\x1B[90m" : "";
4182
+ const reset = options.color ? "\x1B[0m" : "";
4183
+ const lines = [];
4184
+ for (const key of order) {
4185
+ const value = outputs[key];
4186
+ if (value === undefined)
4187
+ continue;
4188
+ lines.push(`${header}${key}:${reset} ${formatValue(value, key)}`);
4189
+ }
4190
+ output = lines.join(`
4191
+ `);
4062
4192
  } else {
4063
- const { value } = evaluateSource(source);
4064
- output = stringify(value);
4193
+ let value;
4194
+ if (selected.length === 1) {
4195
+ const only = selected[0];
4196
+ if (!only)
4197
+ throw new Error("No output selected.");
4198
+ value = outputs[only];
4199
+ } else {
4200
+ const out = {};
4201
+ for (const key of order) {
4202
+ const v = outputs[key];
4203
+ if (v !== undefined)
4204
+ out[key] = v;
4205
+ }
4206
+ value = out;
4207
+ }
4208
+ output = options.format === "json" ? formatJson2(value, 2) : stringify(value);
4065
4209
  }
4066
4210
  if (options.out) {
4067
4211
  await writeFile(options.out, `${output}