@creationix/rex 0.1.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,7 @@
1
+ // rex-repl.ts
2
+ import * as readline from "node:readline";
3
+ import { createRequire as createRequire2 } from "node:module";
4
+
1
5
  // rex.ts
2
6
  import { createRequire } from "node:module";
3
7
  var require2 = createRequire(import.meta.url);
@@ -33,19 +37,8 @@ var OPCODE_IDS = {
33
37
  mod: 20,
34
38
  neg: 21
35
39
  };
36
- var registeredDomainRefs = {};
37
- function resolveDomainRefMap(overrides) {
38
- if (!overrides || Object.keys(overrides).length === 0) {
39
- return Object.keys(registeredDomainRefs).length > 0 ? { ...registeredDomainRefs } : undefined;
40
- }
41
- const merged = { ...registeredDomainRefs };
42
- for (const [name, refId] of Object.entries(overrides)) {
43
- if (!Number.isInteger(refId) || refId < 0)
44
- throw new Error(`Invalid domain extension ref id for '${name}': ${refId}`);
45
- merged[name] = refId;
46
- }
47
- return merged;
48
- }
40
+ var FIRST_NON_RESERVED_REF = 8;
41
+ var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
49
42
  var BINARY_TO_OPCODE = {
50
43
  add: "add",
51
44
  sub: "sub",
@@ -158,12 +151,33 @@ function encodeBareOrLengthString(value) {
158
151
  return `${value}:`;
159
152
  return `${encodeUint(byteLength(value))},${value}`;
160
153
  }
154
+ var DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
155
+ function splitDecimal(num) {
156
+ const match = num.toExponential().match(DEC_PARTS);
157
+ if (!match)
158
+ throw new Error(`Failed to split decimal for ${num}`);
159
+ const [, b1, b2 = "", e1] = match;
160
+ const base = Number.parseInt(b1 + b2, 10);
161
+ const exp = Number.parseInt(e1, 10) - b2.length;
162
+ return { base, exp };
163
+ }
164
+ function encodeDecimal(significand, power) {
165
+ return `${encodeZigzag(power)}*${encodeInt(significand)}`;
166
+ }
161
167
  function encodeNumberNode(node) {
162
168
  const numberValue = node.value;
163
- if (!Number.isFinite(numberValue))
164
- throw new Error(`Cannot encode non-finite number: ${node.raw}`);
165
- if (Number.isInteger(numberValue))
166
- return encodeInt(numberValue);
169
+ if (Number.isNaN(numberValue))
170
+ return "5'";
171
+ if (numberValue === Infinity)
172
+ return "6'";
173
+ if (numberValue === -Infinity)
174
+ return "7'";
175
+ if (Number.isInteger(numberValue)) {
176
+ const { base, exp } = splitDecimal(numberValue);
177
+ if (exp >= 0 && exp <= 4)
178
+ return encodeInt(numberValue);
179
+ return encodeDecimal(base, exp);
180
+ }
167
181
  const raw = node.raw.toLowerCase();
168
182
  const sign = raw.startsWith("-") ? -1 : 1;
169
183
  const unsigned = sign < 0 ? raw.slice(1) : raw;
@@ -186,7 +200,7 @@ function encodeNumberNode(node) {
186
200
  significand /= 10;
187
201
  power += 1;
188
202
  }
189
- return `${encodeZigzag(power)}*${encodeInt(significand)}`;
203
+ return encodeDecimal(significand, power);
190
204
  }
191
205
  function encodeOpcode(opcode) {
192
206
  return `${encodeUint(OPCODE_IDS[opcode])}%`;
@@ -198,13 +212,13 @@ function needsOptionalPrefix(encoded) {
198
212
  const first = encoded[0];
199
213
  if (!first)
200
214
  return false;
201
- return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<";
215
+ return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<" || first === "#";
202
216
  }
203
217
  function addOptionalPrefix(encoded) {
204
218
  if (!needsOptionalPrefix(encoded))
205
219
  return encoded;
206
220
  let payload = encoded;
207
- if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(")) {
221
+ if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
208
222
  payload = encoded.slice(2, -1);
209
223
  } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
210
224
  payload = encoded.slice(2, -1);
@@ -235,6 +249,33 @@ function encodeConditionalElse(elseBranch) {
235
249
  return encodeNode(nested);
236
250
  }
237
251
  function encodeNavigation(node) {
252
+ const domainRefs = activeEncodeOptions?.domainRefs;
253
+ if (domainRefs && node.target.type === "identifier") {
254
+ const staticPath = [node.target.name];
255
+ for (const segment of node.segments) {
256
+ if (segment.type !== "static")
257
+ break;
258
+ staticPath.push(segment.key);
259
+ }
260
+ for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
261
+ const dottedName = staticPath.slice(0, pathLength).join(".");
262
+ const domainRef = domainRefs[dottedName];
263
+ if (domainRef === undefined)
264
+ continue;
265
+ const consumedStaticSegments = pathLength - 1;
266
+ if (consumedStaticSegments === node.segments.length) {
267
+ return `${encodeUint(domainRef)}'`;
268
+ }
269
+ const parts2 = [`${encodeUint(domainRef)}'`];
270
+ for (const segment of node.segments.slice(consumedStaticSegments)) {
271
+ if (segment.type === "static")
272
+ parts2.push(encodeBareOrLengthString(segment.key));
273
+ else
274
+ parts2.push(encodeNode(segment.key));
275
+ }
276
+ return encodeCallParts(parts2);
277
+ }
278
+ }
238
279
  const parts = [encodeNode(node.target)];
239
280
  for (const segment of node.segments) {
240
281
  if (segment.type === "static")
@@ -244,6 +285,11 @@ function encodeNavigation(node) {
244
285
  }
245
286
  return encodeCallParts(parts);
246
287
  }
288
+ function encodeWhile(node) {
289
+ const cond = encodeNode(node.condition);
290
+ const body = addOptionalPrefix(encodeBlockExpression(node.body));
291
+ return `#(${cond}${body})`;
292
+ }
247
293
  function encodeFor(node) {
248
294
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
249
295
  if (node.binding.type === "binding:expr") {
@@ -380,6 +426,8 @@ function encodeNode(node) {
380
426
  }
381
427
  case "for":
382
428
  return encodeFor(node);
429
+ case "while":
430
+ return encodeWhile(node);
383
431
  case "break":
384
432
  return ";";
385
433
  case "continue":
@@ -403,6 +451,194 @@ function parseToIR(source) {
403
451
  }
404
452
  return semantics(match).toIR();
405
453
  }
454
+ function isPlainObject(value) {
455
+ if (!value || typeof value !== "object" || Array.isArray(value))
456
+ return false;
457
+ const proto = Object.getPrototypeOf(value);
458
+ return proto === Object.prototype || proto === null;
459
+ }
460
+ function isBareKeyName(key) {
461
+ return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
462
+ }
463
+ function isNumericKey(key) {
464
+ if (key === "")
465
+ return false;
466
+ return String(Number(key)) === key && Number.isFinite(Number(key));
467
+ }
468
+ function stringifyKey(key) {
469
+ if (isBareKeyName(key))
470
+ return key;
471
+ if (isNumericKey(key))
472
+ return key;
473
+ return stringifyString(key);
474
+ }
475
+ function stringifyString(value) {
476
+ return JSON.stringify(value);
477
+ }
478
+ function stringifyInline(value) {
479
+ if (value === undefined)
480
+ return "undefined";
481
+ if (value === null)
482
+ return "null";
483
+ if (typeof value === "boolean")
484
+ return value ? "true" : "false";
485
+ if (typeof value === "number") {
486
+ if (Number.isNaN(value))
487
+ return "nan";
488
+ if (value === Infinity)
489
+ return "inf";
490
+ if (value === -Infinity)
491
+ return "-inf";
492
+ return String(value);
493
+ }
494
+ if (typeof value === "string")
495
+ return stringifyString(value);
496
+ if (Array.isArray(value)) {
497
+ if (value.length === 0)
498
+ return "[]";
499
+ return `[${value.map((item) => stringifyInline(item)).join(" ")}]`;
500
+ }
501
+ if (isPlainObject(value)) {
502
+ const entries = Object.entries(value);
503
+ if (entries.length === 0)
504
+ return "{}";
505
+ const body = entries.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`).join(" ");
506
+ return `{${body}}`;
507
+ }
508
+ throw new Error(`Rex stringify() cannot encode value of type ${typeof value}`);
509
+ }
510
+ function fitsInline(rendered, depth, indentSize, maxWidth) {
511
+ if (rendered.includes(`
512
+ `))
513
+ return false;
514
+ return depth * indentSize + rendered.length <= maxWidth;
515
+ }
516
+ function stringifyPretty(value, depth, indentSize, maxWidth) {
517
+ const inline = stringifyInline(value);
518
+ if (fitsInline(inline, depth, indentSize, maxWidth))
519
+ return inline;
520
+ const indent = " ".repeat(depth * indentSize);
521
+ const childIndent = " ".repeat((depth + 1) * indentSize);
522
+ if (Array.isArray(value)) {
523
+ if (value.length === 0)
524
+ return "[]";
525
+ const lines = value.map((item) => {
526
+ const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
527
+ if (!rendered.includes(`
528
+ `))
529
+ return `${childIndent}${rendered}`;
530
+ return `${childIndent}${rendered}`;
531
+ });
532
+ return `[
533
+ ${lines.join(`
534
+ `)}
535
+ ${indent}]`;
536
+ }
537
+ if (isPlainObject(value)) {
538
+ const entries = Object.entries(value);
539
+ if (entries.length === 0)
540
+ return "{}";
541
+ const lines = entries.map(([key, item]) => {
542
+ const keyText = stringifyKey(key);
543
+ const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
544
+ return `${childIndent}${keyText}: ${rendered}`;
545
+ });
546
+ return `{
547
+ ${lines.join(`
548
+ `)}
549
+ ${indent}}`;
550
+ }
551
+ return inline;
552
+ }
553
+ function domainRefsFromConfig(config) {
554
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
555
+ throw new Error("Domain config must be an object");
556
+ }
557
+ const refs = {};
558
+ for (const section of Object.values(config)) {
559
+ if (!section || typeof section !== "object" || Array.isArray(section))
560
+ continue;
561
+ mapConfigEntries(section, refs);
562
+ }
563
+ return refs;
564
+ }
565
+ function decodeDomainRefKey(refText) {
566
+ if (!refText)
567
+ throw new Error("Domain ref key cannot be empty");
568
+ if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
569
+ throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
570
+ }
571
+ if (refText.length > 1 && refText[0] === "0") {
572
+ throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
573
+ }
574
+ if (/^[1-9]$/.test(refText)) {
575
+ throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
576
+ }
577
+ let value = 0;
578
+ for (const char of refText) {
579
+ const digit = DOMAIN_DIGIT_INDEX.get(char);
580
+ if (digit === undefined)
581
+ throw new Error(`Invalid domain ref key '${refText}'`);
582
+ value = value * 64 + digit;
583
+ if (value > Number.MAX_SAFE_INTEGER) {
584
+ throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
585
+ }
586
+ }
587
+ if (value < FIRST_NON_RESERVED_REF) {
588
+ throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
589
+ }
590
+ return value;
591
+ }
592
+ function mapConfigEntries(entries, refs) {
593
+ const sourceKindByRoot = new Map;
594
+ for (const root of Object.keys(refs)) {
595
+ sourceKindByRoot.set(root, "explicit");
596
+ }
597
+ for (const [refText, rawEntry] of Object.entries(entries)) {
598
+ const entry = rawEntry;
599
+ if (!entry || typeof entry !== "object")
600
+ continue;
601
+ if (!Array.isArray(entry.names))
602
+ continue;
603
+ const refId = decodeDomainRefKey(refText);
604
+ for (const rawName of entry.names) {
605
+ if (typeof rawName !== "string")
606
+ continue;
607
+ const existingNameRef = refs[rawName];
608
+ if (existingNameRef !== undefined && existingNameRef !== refId) {
609
+ throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
610
+ }
611
+ refs[rawName] = refId;
612
+ const root = rawName.split(".")[0];
613
+ if (!root)
614
+ continue;
615
+ const currentKind = rawName.includes(".") ? "implicit" : "explicit";
616
+ const existing = refs[root];
617
+ if (existing !== undefined) {
618
+ if (existing === refId)
619
+ continue;
620
+ const existingKind = sourceKindByRoot.get(root) ?? "explicit";
621
+ if (currentKind === "explicit") {
622
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
623
+ }
624
+ if (existingKind === "explicit")
625
+ continue;
626
+ continue;
627
+ }
628
+ refs[root] = refId;
629
+ sourceKindByRoot.set(root, currentKind);
630
+ }
631
+ }
632
+ }
633
+ function stringify(value, options) {
634
+ const indent = options?.indent ?? 2;
635
+ const maxWidth = options?.maxWidth ?? 80;
636
+ if (!Number.isInteger(indent) || indent < 0)
637
+ throw new Error("Rex stringify() indent must be a non-negative integer");
638
+ if (!Number.isInteger(maxWidth) || maxWidth < 20)
639
+ throw new Error("Rex stringify() maxWidth must be an integer >= 20");
640
+ return stringifyPretty(value, 0, indent, maxWidth);
641
+ }
406
642
  var DIGIT_SET = new Set(DIGITS.split(""));
407
643
  var DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
408
644
  function readPrefixAt(text, start) {
@@ -960,7 +1196,16 @@ function inlineAdjacentPureAssignments(block) {
960
1196
  return out;
961
1197
  }
962
1198
  function toNumberNode(value) {
963
- return { type: "number", raw: String(value), value };
1199
+ let raw;
1200
+ if (Number.isNaN(value))
1201
+ raw = "nan";
1202
+ else if (value === Infinity)
1203
+ raw = "inf";
1204
+ else if (value === -Infinity)
1205
+ raw = "-inf";
1206
+ else
1207
+ raw = String(value);
1208
+ return { type: "number", raw, value };
964
1209
  }
965
1210
  function toStringNode(value) {
966
1211
  return { type: "string", raw: JSON.stringify(value) };
@@ -972,7 +1217,7 @@ function toLiteralNode(value) {
972
1217
  return { type: "null" };
973
1218
  if (typeof value === "boolean")
974
1219
  return { type: "boolean", value };
975
- if (typeof value === "number" && Number.isFinite(value))
1220
+ if (typeof value === "number")
976
1221
  return toNumberNode(value);
977
1222
  if (typeof value === "string")
978
1223
  return toStringNode(value);
@@ -1284,12 +1529,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1284
1529
  if (conditionValue !== undefined || condition.type === "undefined") {
1285
1530
  const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1286
1531
  if (passes) {
1287
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1288
- if (thenBlock.length === 0)
1532
+ const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1533
+ if (thenBlock2.length === 0)
1289
1534
  return { type: "undefined" };
1290
- if (thenBlock.length === 1)
1291
- return thenBlock[0];
1292
- return { type: "program", body: thenBlock };
1535
+ if (thenBlock2.length === 1)
1536
+ return thenBlock2[0];
1537
+ return { type: "program", body: thenBlock2 };
1293
1538
  }
1294
1539
  if (!node.elseBranch)
1295
1540
  return { type: "undefined" };
@@ -1311,12 +1556,26 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1311
1556
  elseBranch: loweredElse.elseBranch
1312
1557
  };
1313
1558
  }
1559
+ const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1560
+ const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1561
+ let finalCondition = condition;
1562
+ if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1563
+ const name = condition.place.name;
1564
+ const reads = new Set;
1565
+ for (const part of thenBlock)
1566
+ collectReads(part, reads);
1567
+ if (elseBranch)
1568
+ collectReadsElse(elseBranch, reads);
1569
+ if (!reads.has(name)) {
1570
+ finalCondition = condition.value;
1571
+ }
1572
+ }
1314
1573
  return {
1315
1574
  type: "conditional",
1316
1575
  head: node.head,
1317
- condition,
1318
- thenBlock: optimizeBlock(node.thenBlock, thenEnv, currentDepth),
1319
- elseBranch: optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth)
1576
+ condition: finalCondition,
1577
+ thenBlock,
1578
+ elseBranch
1320
1579
  };
1321
1580
  }
1322
1581
  case "for": {
@@ -1771,13 +2030,20 @@ function compile(source, options) {
1771
2030
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
1772
2031
  if (options?.minifyNames)
1773
2032
  lowered = minifyLocalNamesIR(lowered);
2033
+ const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1774
2034
  return encodeIR(lowered, {
1775
- domainRefs: resolveDomainRefMap(options?.domainRefs),
2035
+ domainRefs,
1776
2036
  dedupeValues: options?.dedupeValues,
1777
2037
  dedupeMinBytes: options?.dedupeMinBytes
1778
2038
  });
1779
2039
  }
1780
2040
  function parseNumber(raw) {
2041
+ if (raw === "nan")
2042
+ return NaN;
2043
+ if (raw === "inf")
2044
+ return Infinity;
2045
+ if (raw === "-inf")
2046
+ return -Infinity;
1781
2047
  if (/^-?0x/i.test(raw))
1782
2048
  return parseInt(raw, 16);
1783
2049
  if (/^-?0b/i.test(raw)) {
@@ -2002,6 +2268,13 @@ semantics.addOperation("toIR", {
2002
2268
  return body[0];
2003
2269
  return { type: "program", body };
2004
2270
  },
2271
+ WhileExpr(_while, condition, _do, block, _end) {
2272
+ return {
2273
+ type: "while",
2274
+ condition: condition.toIR(),
2275
+ body: block.toIR()
2276
+ };
2277
+ },
2005
2278
  ForExpr(_for, binding, _do, block, _end) {
2006
2279
  return {
2007
2280
  type: "for",
@@ -2145,211 +2418,1403 @@ semantics.addOperation("toIR", {
2145
2418
  }
2146
2419
  });
2147
2420
 
2148
- // rex-compile.ts
2149
- import { dirname, resolve } from "node:path";
2150
- import { readFile, writeFile } from "node:fs/promises";
2151
- var FIRST_NON_RESERVED_REF = 5;
2152
- function parseArgs(argv) {
2153
- const options = {
2154
- ir: false,
2155
- minifyNames: false,
2156
- dedupeValues: false,
2157
- domainRefs: {},
2158
- help: false
2159
- };
2160
- for (let index = 0;index < argv.length; index += 1) {
2161
- const arg = argv[index];
2162
- if (!arg)
2163
- continue;
2164
- if (arg === "--help" || arg === "-h") {
2165
- options.help = true;
2166
- continue;
2421
+ // rexc-interpreter.ts
2422
+ var DIGITS2 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
2423
+ var digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
2424
+ var OPCODES = {
2425
+ do: 0,
2426
+ add: 1,
2427
+ sub: 2,
2428
+ mul: 3,
2429
+ div: 4,
2430
+ eq: 5,
2431
+ neq: 6,
2432
+ lt: 7,
2433
+ lte: 8,
2434
+ gt: 9,
2435
+ gte: 10,
2436
+ and: 11,
2437
+ or: 12,
2438
+ xor: 13,
2439
+ not: 14,
2440
+ boolean: 15,
2441
+ number: 16,
2442
+ string: 17,
2443
+ array: 18,
2444
+ object: 19,
2445
+ mod: 20,
2446
+ neg: 21
2447
+ };
2448
+ function decodePrefix(text, start, end) {
2449
+ let value = 0;
2450
+ for (let index = start;index < end; index += 1) {
2451
+ const digit = digitMap.get(text[index] ?? "") ?? -1;
2452
+ if (digit < 0)
2453
+ throw new Error(`Invalid digit '${text[index]}'`);
2454
+ value = value * 64 + digit;
2455
+ }
2456
+ return value;
2457
+ }
2458
+ function decodeZigzag(value) {
2459
+ return value % 2 === 0 ? value / 2 : -(value + 1) / 2;
2460
+ }
2461
+ function isDefined(value) {
2462
+ return value !== undefined;
2463
+ }
2464
+
2465
+ class CursorInterpreter {
2466
+ text;
2467
+ pos = 0;
2468
+ state;
2469
+ selfStack;
2470
+ opcodeMarkers;
2471
+ pointerCache = new Map;
2472
+ gasLimit;
2473
+ gas = 0;
2474
+ tick() {
2475
+ if (this.gasLimit && ++this.gas > this.gasLimit) {
2476
+ throw new Error("Gas limit exceeded (too many loop iterations)");
2167
2477
  }
2168
- if (arg === "--ir") {
2169
- options.ir = true;
2170
- continue;
2478
+ }
2479
+ constructor(text, ctx = {}) {
2480
+ const initialSelf = ctx.selfStack && ctx.selfStack.length > 0 ? ctx.selfStack[ctx.selfStack.length - 1] : ctx.self;
2481
+ this.text = text;
2482
+ this.state = {
2483
+ vars: ctx.vars ?? {},
2484
+ refs: {
2485
+ 0: ctx.refs?.[0],
2486
+ 1: ctx.refs?.[1] ?? true,
2487
+ 2: ctx.refs?.[2] ?? false,
2488
+ 3: ctx.refs?.[3] ?? null,
2489
+ 4: ctx.refs?.[4] ?? undefined,
2490
+ 5: ctx.refs?.[5] ?? NaN,
2491
+ 6: ctx.refs?.[6] ?? Infinity,
2492
+ 7: ctx.refs?.[7] ?? -Infinity
2493
+ }
2494
+ };
2495
+ this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
2496
+ this.gasLimit = ctx.gasLimit ?? 0;
2497
+ this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
2498
+ for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
2499
+ const id = Number(idText);
2500
+ if (Number.isInteger(id))
2501
+ this.state.refs[id] = value;
2171
2502
  }
2172
- if (arg === "--minify-names" || arg === "-m") {
2173
- options.minifyNames = true;
2174
- continue;
2503
+ if (ctx.opcodes) {
2504
+ for (const [idText, op] of Object.entries(ctx.opcodes)) {
2505
+ if (op)
2506
+ this.customOpcodes.set(Number(idText), op);
2507
+ }
2175
2508
  }
2176
- if (arg === "--dedupe-values") {
2177
- options.dedupeValues = true;
2178
- continue;
2509
+ }
2510
+ customOpcodes = new Map;
2511
+ readSelf(depthPrefix) {
2512
+ const depth = depthPrefix + 1;
2513
+ const index = this.selfStack.length - depth;
2514
+ if (index < 0)
2515
+ return;
2516
+ return this.selfStack[index];
2517
+ }
2518
+ get runtimeState() {
2519
+ return this.state;
2520
+ }
2521
+ evaluateTopLevel() {
2522
+ this.skipNonCode();
2523
+ if (this.pos >= this.text.length)
2524
+ return;
2525
+ const value = this.evalValue();
2526
+ this.skipNonCode();
2527
+ if (this.pos < this.text.length)
2528
+ throw new Error(`Unexpected trailing input at ${this.pos}`);
2529
+ return value;
2530
+ }
2531
+ skipNonCode() {
2532
+ while (this.pos < this.text.length) {
2533
+ const ch = this.text[this.pos];
2534
+ if (ch === " " || ch === "\t" || ch === `
2535
+ ` || ch === "\r") {
2536
+ this.pos += 1;
2537
+ continue;
2538
+ }
2539
+ if (ch === "/" && this.text[this.pos + 1] === "/") {
2540
+ this.pos += 2;
2541
+ while (this.pos < this.text.length && this.text[this.pos] !== `
2542
+ `)
2543
+ this.pos += 1;
2544
+ continue;
2545
+ }
2546
+ if (ch === "/" && this.text[this.pos + 1] === "*") {
2547
+ this.pos += 2;
2548
+ while (this.pos < this.text.length) {
2549
+ if (this.text[this.pos] === "*" && this.text[this.pos + 1] === "/") {
2550
+ this.pos += 2;
2551
+ break;
2552
+ }
2553
+ this.pos += 1;
2554
+ }
2555
+ continue;
2556
+ }
2557
+ break;
2179
2558
  }
2180
- if (arg === "--dedupe-min-bytes") {
2181
- const value = argv[index + 1];
2182
- if (!value)
2183
- throw new Error("Missing value for --dedupe-min-bytes");
2184
- const parsed = Number(value);
2185
- if (!Number.isInteger(parsed) || parsed < 1)
2186
- throw new Error("--dedupe-min-bytes must be a positive integer");
2187
- options.dedupeMinBytes = parsed;
2188
- index += 1;
2189
- continue;
2559
+ }
2560
+ readPrefix() {
2561
+ const start = this.pos;
2562
+ while (this.pos < this.text.length && digitMap.has(this.text[this.pos] ?? ""))
2563
+ this.pos += 1;
2564
+ const end = this.pos;
2565
+ return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
2566
+ }
2567
+ ensure(char) {
2568
+ if (this.text[this.pos] !== char)
2569
+ throw new Error(`Expected '${char}' at ${this.pos}`);
2570
+ this.pos += 1;
2571
+ }
2572
+ hasMoreBefore(close) {
2573
+ const save = this.pos;
2574
+ this.skipNonCode();
2575
+ const more = this.pos < this.text.length && this.text[this.pos] !== close;
2576
+ this.pos = save;
2577
+ return more;
2578
+ }
2579
+ readBindingVarIfPresent() {
2580
+ const save = this.pos;
2581
+ this.skipNonCode();
2582
+ const prefix = this.readPrefix();
2583
+ const tag = this.text[this.pos];
2584
+ if (tag === "$" && prefix.raw.length > 0) {
2585
+ this.pos += 1;
2586
+ return prefix.raw;
2190
2587
  }
2191
- if (arg === "--domain-extension") {
2192
- const value = argv[index + 1];
2193
- if (!value)
2194
- throw new Error("Missing value for --domain-extension");
2195
- options.domainRefs[value] = 0;
2196
- index += 1;
2197
- continue;
2588
+ this.pos = save;
2589
+ return;
2590
+ }
2591
+ evalValue() {
2592
+ this.skipNonCode();
2593
+ const prefix = this.readPrefix();
2594
+ const tag = this.text[this.pos];
2595
+ if (!tag)
2596
+ throw new Error("Unexpected end of input");
2597
+ switch (tag) {
2598
+ case "+":
2599
+ this.pos += 1;
2600
+ return decodeZigzag(prefix.value);
2601
+ case "*": {
2602
+ this.pos += 1;
2603
+ const power = decodeZigzag(prefix.value);
2604
+ const significand = this.evalValue();
2605
+ if (typeof significand !== "number")
2606
+ throw new Error("Decimal significand must be numeric");
2607
+ return significand * 10 ** power;
2608
+ }
2609
+ case ":":
2610
+ this.pos += 1;
2611
+ return prefix.raw;
2612
+ case "%":
2613
+ this.pos += 1;
2614
+ return this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
2615
+ case "@":
2616
+ this.pos += 1;
2617
+ return this.readSelf(prefix.value);
2618
+ case "'":
2619
+ this.pos += 1;
2620
+ return this.state.refs[prefix.value];
2621
+ case "$":
2622
+ this.pos += 1;
2623
+ return this.state.vars[prefix.raw];
2624
+ case ",": {
2625
+ this.pos += 1;
2626
+ const start = this.pos;
2627
+ const end = start + prefix.value;
2628
+ if (end > this.text.length)
2629
+ throw new Error("String container overflows input");
2630
+ const value = this.text.slice(start, end);
2631
+ this.pos = end;
2632
+ return value;
2633
+ }
2634
+ case "^": {
2635
+ this.pos += 1;
2636
+ const target = this.pos + prefix.value;
2637
+ if (this.pointerCache.has(target))
2638
+ return this.pointerCache.get(target);
2639
+ const save = this.pos;
2640
+ this.pos = target;
2641
+ const value = this.evalValue();
2642
+ this.pos = save;
2643
+ this.pointerCache.set(target, value);
2644
+ return value;
2645
+ }
2646
+ case "=": {
2647
+ this.pos += 1;
2648
+ const place = this.readPlace();
2649
+ const value = this.evalValue();
2650
+ this.writePlace(place, value);
2651
+ return value;
2652
+ }
2653
+ case "~": {
2654
+ this.pos += 1;
2655
+ const place = this.readPlace();
2656
+ this.deletePlace(place);
2657
+ return;
2658
+ }
2659
+ case ";": {
2660
+ this.pos += 1;
2661
+ const kind = prefix.value % 2 === 0 ? "break" : "continue";
2662
+ const depth = Math.floor(prefix.value / 2) + 1;
2663
+ return { kind, depth };
2664
+ }
2665
+ case "(":
2666
+ return this.evalCall(prefix.value);
2667
+ case "[":
2668
+ return this.evalArray(prefix.value);
2669
+ case "{":
2670
+ return this.evalObject(prefix.value);
2671
+ case "?":
2672
+ case "!":
2673
+ case "|":
2674
+ case "&":
2675
+ return this.evalFlowParen(tag);
2676
+ case ">":
2677
+ case "<":
2678
+ return this.evalLoopLike(tag);
2679
+ case "#":
2680
+ return this.evalWhileLoop();
2681
+ default:
2682
+ throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
2683
+ }
2684
+ }
2685
+ evalCall(_prefix) {
2686
+ this.ensure("(");
2687
+ this.skipNonCode();
2688
+ if (this.text[this.pos] === ")") {
2689
+ this.pos += 1;
2690
+ return;
2691
+ }
2692
+ const callee = this.evalValue();
2693
+ const args = [];
2694
+ while (true) {
2695
+ this.skipNonCode();
2696
+ if (this.text[this.pos] === ")")
2697
+ break;
2698
+ args.push(this.evalValue());
2699
+ }
2700
+ this.ensure(")");
2701
+ if (typeof callee === "object" && callee && "__opcode" in callee) {
2702
+ return this.applyOpcode(callee.__opcode, args);
2703
+ }
2704
+ return this.navigate(callee, args);
2705
+ }
2706
+ evalArray(_prefix) {
2707
+ this.ensure("[");
2708
+ this.skipNonCode();
2709
+ this.skipIndexHeaderIfPresent();
2710
+ const out = [];
2711
+ while (true) {
2712
+ this.skipNonCode();
2713
+ if (this.text[this.pos] === "]")
2714
+ break;
2715
+ out.push(this.evalValue());
2198
2716
  }
2199
- if (arg === "--domain-ref") {
2200
- const value = argv[index + 1];
2201
- if (!value)
2202
- throw new Error("Missing value for --domain-ref");
2203
- const separator = value.indexOf("=");
2204
- if (separator < 1 || separator === value.length - 1) {
2205
- throw new Error("--domain-ref expects NAME=ID (for example: headers=0)");
2717
+ this.ensure("]");
2718
+ return out;
2719
+ }
2720
+ evalObject(_prefix) {
2721
+ this.ensure("{");
2722
+ this.skipNonCode();
2723
+ this.skipIndexHeaderIfPresent();
2724
+ const out = {};
2725
+ while (true) {
2726
+ this.skipNonCode();
2727
+ if (this.text[this.pos] === "}")
2728
+ break;
2729
+ const key = this.evalValue();
2730
+ const value = this.evalValue();
2731
+ out[String(key)] = value;
2732
+ }
2733
+ this.ensure("}");
2734
+ return out;
2735
+ }
2736
+ skipIndexHeaderIfPresent() {
2737
+ const save = this.pos;
2738
+ const countPrefix = this.readPrefix();
2739
+ if (this.text[this.pos] !== "#") {
2740
+ this.pos = save;
2741
+ return;
2742
+ }
2743
+ this.pos += 1;
2744
+ const widthChar = this.text[this.pos];
2745
+ const width = widthChar ? digitMap.get(widthChar) ?? 0 : 0;
2746
+ if (widthChar)
2747
+ this.pos += 1;
2748
+ const skipLen = countPrefix.value * width;
2749
+ this.pos += skipLen;
2750
+ }
2751
+ evalFlowParen(tag) {
2752
+ this.pos += 1;
2753
+ this.ensure("(");
2754
+ if (tag === "?") {
2755
+ const cond = this.evalValue();
2756
+ if (isDefined(cond)) {
2757
+ this.selfStack.push(cond);
2758
+ const thenValue = this.evalValue();
2759
+ this.selfStack.pop();
2760
+ if (this.hasMoreBefore(")"))
2761
+ this.skipValue();
2762
+ this.ensure(")");
2763
+ return thenValue;
2206
2764
  }
2207
- const name = value.slice(0, separator);
2208
- const idText = value.slice(separator + 1);
2209
- const id = Number(idText);
2210
- if (!Number.isInteger(id) || id < 0)
2211
- throw new Error(`Invalid domain ref id in --domain-ref '${value}'`);
2212
- options.domainRefs[name] = id;
2213
- index += 1;
2765
+ this.skipValue();
2766
+ let elseValue = undefined;
2767
+ if (this.hasMoreBefore(")"))
2768
+ elseValue = this.evalValue();
2769
+ this.ensure(")");
2770
+ return elseValue;
2771
+ }
2772
+ if (tag === "!") {
2773
+ const cond = this.evalValue();
2774
+ if (!isDefined(cond)) {
2775
+ const thenValue = this.evalValue();
2776
+ if (this.hasMoreBefore(")"))
2777
+ this.skipValue();
2778
+ this.ensure(")");
2779
+ return thenValue;
2780
+ }
2781
+ this.skipValue();
2782
+ let elseValue = undefined;
2783
+ if (this.hasMoreBefore(")")) {
2784
+ this.selfStack.push(cond);
2785
+ elseValue = this.evalValue();
2786
+ this.selfStack.pop();
2787
+ }
2788
+ this.ensure(")");
2789
+ return elseValue;
2790
+ }
2791
+ if (tag === "|") {
2792
+ let result2 = undefined;
2793
+ while (this.hasMoreBefore(")")) {
2794
+ if (isDefined(result2))
2795
+ this.skipValue();
2796
+ else
2797
+ result2 = this.evalValue();
2798
+ }
2799
+ this.ensure(")");
2800
+ return result2;
2801
+ }
2802
+ let result = undefined;
2803
+ while (this.hasMoreBefore(")")) {
2804
+ if (!isDefined(result) && result !== undefined) {
2805
+ this.skipValue();
2806
+ continue;
2807
+ }
2808
+ result = this.evalValue();
2809
+ if (!isDefined(result)) {
2810
+ while (this.hasMoreBefore(")"))
2811
+ this.skipValue();
2812
+ break;
2813
+ }
2814
+ }
2815
+ this.ensure(")");
2816
+ return result;
2817
+ }
2818
+ evalLoopLike(tag) {
2819
+ this.pos += 1;
2820
+ const open = this.text[this.pos];
2821
+ if (!open || !"([{".includes(open))
2822
+ throw new Error(`Expected loop opener after '${tag}'`);
2823
+ const close = open === "(" ? ")" : open === "[" ? "]" : "}";
2824
+ this.pos += 1;
2825
+ const iterable = this.evalValue();
2826
+ const afterIterable = this.pos;
2827
+ const bodyValueCount = open === "{" ? 2 : 1;
2828
+ let varA;
2829
+ let varB;
2830
+ let bodyStart = afterIterable;
2831
+ let bodyEnd = afterIterable;
2832
+ let matched = false;
2833
+ for (const bindingCount of [2, 1, 0]) {
2834
+ this.pos = afterIterable;
2835
+ const vars = [];
2836
+ let bindingsOk = true;
2837
+ for (let index = 0;index < bindingCount; index += 1) {
2838
+ const binding = this.readBindingVarIfPresent();
2839
+ if (!binding) {
2840
+ bindingsOk = false;
2841
+ break;
2842
+ }
2843
+ vars.push(binding);
2844
+ }
2845
+ if (!bindingsOk)
2846
+ continue;
2847
+ const candidateBodyStart = this.pos;
2848
+ let cursor = candidateBodyStart;
2849
+ let bodyOk = true;
2850
+ for (let index = 0;index < bodyValueCount; index += 1) {
2851
+ const next = this.skipValueFrom(cursor);
2852
+ if (next <= cursor) {
2853
+ bodyOk = false;
2854
+ break;
2855
+ }
2856
+ cursor = next;
2857
+ }
2858
+ if (!bodyOk)
2859
+ continue;
2860
+ this.pos = cursor;
2861
+ this.skipNonCode();
2862
+ if (this.text[this.pos] !== close)
2863
+ continue;
2864
+ varA = vars[0];
2865
+ varB = vars[1];
2866
+ bodyStart = candidateBodyStart;
2867
+ bodyEnd = cursor;
2868
+ matched = true;
2869
+ break;
2870
+ }
2871
+ if (!matched)
2872
+ throw new Error(`Invalid loop/comprehension body before '${close}' at ${this.pos}`);
2873
+ this.pos = bodyEnd;
2874
+ this.ensure(close);
2875
+ if (open === "[")
2876
+ return this.evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, tag === "<");
2877
+ if (open === "{")
2878
+ return this.evalObjectComprehension(iterable, varA, varB, bodyStart, bodyEnd, tag === "<");
2879
+ return this.evalForLoop(iterable, varA, varB, bodyStart, bodyEnd, tag === "<");
2880
+ }
2881
+ iterate(iterable, keysOnly) {
2882
+ if (Array.isArray(iterable)) {
2883
+ if (keysOnly)
2884
+ return iterable.map((_value, index) => ({ key: index, value: index }));
2885
+ return iterable.map((value, index) => ({ key: index, value }));
2886
+ }
2887
+ if (iterable && typeof iterable === "object") {
2888
+ const entries = Object.entries(iterable);
2889
+ if (keysOnly)
2890
+ return entries.map(([key]) => ({ key, value: key }));
2891
+ return entries.map(([key, value]) => ({ key, value }));
2892
+ }
2893
+ if (typeof iterable === "number" && Number.isFinite(iterable) && iterable > 0) {
2894
+ const out = [];
2895
+ for (let index = 0;index < Math.floor(iterable); index += 1) {
2896
+ if (keysOnly)
2897
+ out.push({ key: index, value: index });
2898
+ else
2899
+ out.push({ key: index, value: index + 1 });
2900
+ }
2901
+ return out;
2902
+ }
2903
+ return [];
2904
+ }
2905
+ evalBodySlice(start, end) {
2906
+ const save = this.pos;
2907
+ this.pos = start;
2908
+ const value = this.evalValue();
2909
+ this.pos = end;
2910
+ this.pos = save;
2911
+ return value;
2912
+ }
2913
+ handleLoopControl(value) {
2914
+ if (value && typeof value === "object" && "kind" in value && "depth" in value) {
2915
+ return value;
2916
+ }
2917
+ return;
2918
+ }
2919
+ evalForLoop(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
2920
+ const items = this.iterate(iterable, keysOnly);
2921
+ let last = undefined;
2922
+ for (const item of items) {
2923
+ this.tick();
2924
+ const currentSelf = keysOnly ? item.key : item.value;
2925
+ this.selfStack.push(currentSelf);
2926
+ if (varA && varB) {
2927
+ this.state.vars[varA] = item.key;
2928
+ this.state.vars[varB] = keysOnly ? item.key : item.value;
2929
+ } else if (varA) {
2930
+ this.state.vars[varA] = keysOnly ? item.key : item.value;
2931
+ }
2932
+ last = this.evalBodySlice(bodyStart, bodyEnd);
2933
+ this.selfStack.pop();
2934
+ const control = this.handleLoopControl(last);
2935
+ if (!control)
2936
+ continue;
2937
+ if (control.depth > 1)
2938
+ return { kind: control.kind, depth: control.depth - 1 };
2939
+ if (control.kind === "break")
2940
+ return;
2941
+ last = undefined;
2214
2942
  continue;
2215
2943
  }
2216
- if (arg === "--expr" || arg === "-e") {
2217
- const value = argv[index + 1];
2218
- if (!value)
2219
- throw new Error("Missing value for --expr");
2220
- options.expr = value;
2221
- index += 1;
2944
+ return last;
2945
+ }
2946
+ evalWhileLoop() {
2947
+ this.pos += 1;
2948
+ this.ensure("(");
2949
+ const condStart = this.pos;
2950
+ const condValue = this.evalValue();
2951
+ const bodyStart = this.pos;
2952
+ const bodyValueCount = 1;
2953
+ let cursor = bodyStart;
2954
+ for (let index = 0;index < bodyValueCount; index += 1) {
2955
+ cursor = this.skipValueFrom(cursor);
2956
+ }
2957
+ const bodyEnd = cursor;
2958
+ this.pos = bodyEnd;
2959
+ this.ensure(")");
2960
+ const afterClose = this.pos;
2961
+ let last = undefined;
2962
+ let currentCond = condValue;
2963
+ while (isDefined(currentCond)) {
2964
+ this.tick();
2965
+ this.selfStack.push(currentCond);
2966
+ last = this.evalBodySlice(bodyStart, bodyEnd);
2967
+ this.selfStack.pop();
2968
+ const control = this.handleLoopControl(last);
2969
+ if (control) {
2970
+ if (control.depth > 1)
2971
+ return { kind: control.kind, depth: control.depth - 1 };
2972
+ if (control.kind === "break")
2973
+ return;
2974
+ last = undefined;
2975
+ }
2976
+ currentCond = this.evalBodySlice(condStart, bodyStart);
2977
+ }
2978
+ this.pos = afterClose;
2979
+ return last;
2980
+ }
2981
+ evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
2982
+ const items = this.iterate(iterable, keysOnly);
2983
+ const out = [];
2984
+ for (const item of items) {
2985
+ this.tick();
2986
+ const currentSelf = keysOnly ? item.key : item.value;
2987
+ this.selfStack.push(currentSelf);
2988
+ if (varA && varB) {
2989
+ this.state.vars[varA] = item.key;
2990
+ this.state.vars[varB] = keysOnly ? item.key : item.value;
2991
+ } else if (varA) {
2992
+ this.state.vars[varA] = keysOnly ? item.key : item.value;
2993
+ }
2994
+ const value = this.evalBodySlice(bodyStart, bodyEnd);
2995
+ this.selfStack.pop();
2996
+ const control = this.handleLoopControl(value);
2997
+ if (control) {
2998
+ if (control.depth > 1)
2999
+ return { kind: control.kind, depth: control.depth - 1 };
3000
+ if (control.kind === "break")
3001
+ break;
3002
+ continue;
3003
+ }
3004
+ if (isDefined(value))
3005
+ out.push(value);
3006
+ }
3007
+ return out;
3008
+ }
3009
+ evalObjectComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
3010
+ const items = this.iterate(iterable, keysOnly);
3011
+ const out = {};
3012
+ for (const item of items) {
3013
+ this.tick();
3014
+ const currentSelf = keysOnly ? item.key : item.value;
3015
+ this.selfStack.push(currentSelf);
3016
+ if (varA && varB) {
3017
+ this.state.vars[varA] = item.key;
3018
+ this.state.vars[varB] = keysOnly ? item.key : item.value;
3019
+ } else if (varA) {
3020
+ this.state.vars[varA] = keysOnly ? item.key : item.value;
3021
+ }
3022
+ const save = this.pos;
3023
+ this.pos = bodyStart;
3024
+ const key = this.evalValue();
3025
+ const value = this.evalValue();
3026
+ this.pos = save;
3027
+ this.selfStack.pop();
3028
+ const control = this.handleLoopControl(value);
3029
+ if (control) {
3030
+ if (control.depth > 1)
3031
+ return { kind: control.kind, depth: control.depth - 1 };
3032
+ if (control.kind === "break")
3033
+ break;
3034
+ continue;
3035
+ }
3036
+ if (isDefined(value))
3037
+ out[String(key)] = value;
3038
+ }
3039
+ return out;
3040
+ }
3041
+ applyOpcode(id, args) {
3042
+ const custom = this.customOpcodes.get(id);
3043
+ if (custom)
3044
+ return custom(args, this.state);
3045
+ switch (id) {
3046
+ case OPCODES.do:
3047
+ return args.length ? args[args.length - 1] : undefined;
3048
+ case OPCODES.add:
3049
+ if (args[0] === undefined || args[1] === undefined)
3050
+ return;
3051
+ if (typeof args[0] === "string" || typeof args[1] === "string") {
3052
+ return String(args[0]) + String(args[1]);
3053
+ }
3054
+ return Number(args[0]) + Number(args[1]);
3055
+ case OPCODES.sub:
3056
+ if (args[0] === undefined || args[1] === undefined)
3057
+ return;
3058
+ return Number(args[0]) - Number(args[1]);
3059
+ case OPCODES.mul:
3060
+ if (args[0] === undefined || args[1] === undefined)
3061
+ return;
3062
+ return Number(args[0]) * Number(args[1]);
3063
+ case OPCODES.div:
3064
+ if (args[0] === undefined || args[1] === undefined)
3065
+ return;
3066
+ return Number(args[0]) / Number(args[1]);
3067
+ case OPCODES.mod:
3068
+ if (args[0] === undefined || args[1] === undefined)
3069
+ return;
3070
+ return Number(args[0]) % Number(args[1]);
3071
+ case OPCODES.neg:
3072
+ if (args[0] === undefined)
3073
+ return;
3074
+ return -Number(args[0]);
3075
+ case OPCODES.not: {
3076
+ const value = args[0];
3077
+ if (value === undefined)
3078
+ return;
3079
+ if (typeof value === "boolean")
3080
+ return !value;
3081
+ return ~Number(value);
3082
+ }
3083
+ case OPCODES.and: {
3084
+ const [a, b] = args;
3085
+ if (typeof a === "boolean" || typeof b === "boolean")
3086
+ return Boolean(a) && Boolean(b);
3087
+ return Number(a ?? 0) & Number(b ?? 0);
3088
+ }
3089
+ case OPCODES.or: {
3090
+ const [a, b] = args;
3091
+ if (typeof a === "boolean" || typeof b === "boolean")
3092
+ return Boolean(a) || Boolean(b);
3093
+ return Number(a ?? 0) | Number(b ?? 0);
3094
+ }
3095
+ case OPCODES.xor: {
3096
+ const [a, b] = args;
3097
+ if (typeof a === "boolean" || typeof b === "boolean")
3098
+ return Boolean(a) !== Boolean(b);
3099
+ return Number(a ?? 0) ^ Number(b ?? 0);
3100
+ }
3101
+ case OPCODES.eq:
3102
+ return args[0] === args[1] ? args[0] : undefined;
3103
+ case OPCODES.neq:
3104
+ return args[0] !== args[1] ? args[0] : undefined;
3105
+ case OPCODES.gt:
3106
+ return Number(args[0]) > Number(args[1]) ? args[0] : undefined;
3107
+ case OPCODES.gte:
3108
+ return Number(args[0]) >= Number(args[1]) ? args[0] : undefined;
3109
+ case OPCODES.lt:
3110
+ return Number(args[0]) < Number(args[1]) ? args[0] : undefined;
3111
+ case OPCODES.lte:
3112
+ return Number(args[0]) <= Number(args[1]) ? args[0] : undefined;
3113
+ case OPCODES.boolean:
3114
+ return typeof args[0] === "boolean" ? args[0] : undefined;
3115
+ case OPCODES.number:
3116
+ return typeof args[0] === "number" ? args[0] : undefined;
3117
+ case OPCODES.string:
3118
+ return typeof args[0] === "string" ? args[0] : undefined;
3119
+ case OPCODES.array:
3120
+ return Array.isArray(args[0]) ? args[0] : undefined;
3121
+ case OPCODES.object:
3122
+ return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
3123
+ default:
3124
+ throw new Error(`Unknown opcode ${id}`);
3125
+ }
3126
+ }
3127
+ navigate(base, keys) {
3128
+ let current = base;
3129
+ for (const key of keys) {
3130
+ if (current === undefined || current === null)
3131
+ return;
3132
+ current = current[String(key)];
3133
+ }
3134
+ return current;
3135
+ }
3136
+ readPlace() {
3137
+ this.skipNonCode();
3138
+ const direct = this.readRootVarOrRefIfPresent();
3139
+ if (direct) {
3140
+ const keys = [];
3141
+ this.skipNonCode();
3142
+ if (this.text[this.pos] === "(") {
3143
+ this.pos += 1;
3144
+ while (true) {
3145
+ this.skipNonCode();
3146
+ if (this.text[this.pos] === ")")
3147
+ break;
3148
+ keys.push(this.evalValue());
3149
+ }
3150
+ this.pos += 1;
3151
+ }
3152
+ return {
3153
+ root: direct.root,
3154
+ keys,
3155
+ isRef: direct.isRef
3156
+ };
3157
+ }
3158
+ if (this.text[this.pos] === "(") {
3159
+ this.pos += 1;
3160
+ this.skipNonCode();
3161
+ const rootFromNav = this.readRootVarOrRefIfPresent();
3162
+ if (!rootFromNav)
3163
+ throw new Error(`Invalid place root at ${this.pos}`);
3164
+ const keys = [];
3165
+ while (true) {
3166
+ this.skipNonCode();
3167
+ if (this.text[this.pos] === ")")
3168
+ break;
3169
+ keys.push(this.evalValue());
3170
+ }
3171
+ this.pos += 1;
3172
+ return {
3173
+ root: rootFromNav.root,
3174
+ keys,
3175
+ isRef: rootFromNav.isRef
3176
+ };
3177
+ }
3178
+ throw new Error(`Invalid place at ${this.pos}`);
3179
+ }
3180
+ readRootVarOrRefIfPresent() {
3181
+ const save = this.pos;
3182
+ const prefix = this.readPrefix();
3183
+ const tag = this.text[this.pos];
3184
+ if (tag !== "$" && tag !== "'") {
3185
+ this.pos = save;
3186
+ return;
3187
+ }
3188
+ this.pos += 1;
3189
+ return {
3190
+ root: tag === "$" ? prefix.raw : prefix.value,
3191
+ isRef: tag === "'"
3192
+ };
3193
+ }
3194
+ writePlace(place, value) {
3195
+ const rootTable = place.isRef ? this.state.refs : this.state.vars;
3196
+ const rootKey = String(place.root);
3197
+ if (place.keys.length === 0) {
3198
+ rootTable[rootKey] = value;
3199
+ return;
3200
+ }
3201
+ let target = rootTable[rootKey];
3202
+ if (!target || typeof target !== "object") {
3203
+ target = {};
3204
+ rootTable[rootKey] = target;
3205
+ }
3206
+ for (let index = 0;index < place.keys.length - 1; index += 1) {
3207
+ const key = String(place.keys[index]);
3208
+ const next = target[key];
3209
+ if (!next || typeof next !== "object")
3210
+ target[key] = {};
3211
+ target = target[key];
3212
+ }
3213
+ target[String(place.keys[place.keys.length - 1])] = value;
3214
+ }
3215
+ deletePlace(place) {
3216
+ const rootTable = place.isRef ? this.state.refs : this.state.vars;
3217
+ const rootKey = String(place.root);
3218
+ if (place.keys.length === 0) {
3219
+ delete rootTable[rootKey];
3220
+ return;
3221
+ }
3222
+ let target = rootTable[rootKey];
3223
+ if (!target || typeof target !== "object")
3224
+ return;
3225
+ for (let index = 0;index < place.keys.length - 1; index += 1) {
3226
+ target = target[String(place.keys[index])];
3227
+ if (!target || typeof target !== "object")
3228
+ return;
3229
+ }
3230
+ delete target[String(place.keys[place.keys.length - 1])];
3231
+ }
3232
+ skipValue() {
3233
+ this.pos = this.skipValueFrom(this.pos);
3234
+ }
3235
+ skipValueFrom(startPos) {
3236
+ const save = this.pos;
3237
+ this.pos = startPos;
3238
+ this.skipNonCode();
3239
+ const prefix = this.readPrefix();
3240
+ const tag = this.text[this.pos];
3241
+ if (!tag) {
3242
+ this.pos = save;
3243
+ return startPos;
3244
+ }
3245
+ if (tag === ",") {
3246
+ this.pos += 1 + prefix.value;
3247
+ const end2 = this.pos;
3248
+ this.pos = save;
3249
+ return end2;
3250
+ }
3251
+ if (tag === "=") {
3252
+ this.pos += 1;
3253
+ this.skipValue();
3254
+ this.skipValue();
3255
+ const end2 = this.pos;
3256
+ this.pos = save;
3257
+ return end2;
3258
+ }
3259
+ if (tag === "~") {
3260
+ this.pos += 1;
3261
+ this.skipValue();
3262
+ const end2 = this.pos;
3263
+ this.pos = save;
3264
+ return end2;
3265
+ }
3266
+ if (tag === "*") {
3267
+ this.pos += 1;
3268
+ this.skipValue();
3269
+ const end2 = this.pos;
3270
+ this.pos = save;
3271
+ return end2;
3272
+ }
3273
+ if ("+:%$@'^;".includes(tag)) {
3274
+ this.pos += 1;
3275
+ const end2 = this.pos;
3276
+ this.pos = save;
3277
+ return end2;
3278
+ }
3279
+ if (tag === "?" || tag === "!" || tag === "|" || tag === "&" || tag === ">" || tag === "<" || tag === "#") {
3280
+ this.pos += 1;
3281
+ }
3282
+ const opener = this.text[this.pos];
3283
+ if (opener && "([{".includes(opener)) {
3284
+ const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
3285
+ if (prefix.value > 0) {
3286
+ this.pos += 1 + prefix.value + 1;
3287
+ const end3 = this.pos;
3288
+ this.pos = save;
3289
+ return end3;
3290
+ }
3291
+ this.pos += 1;
3292
+ while (true) {
3293
+ this.skipNonCode();
3294
+ if (this.text[this.pos] === close)
3295
+ break;
3296
+ this.skipValue();
3297
+ }
3298
+ this.pos += 1;
3299
+ const end2 = this.pos;
3300
+ this.pos = save;
3301
+ return end2;
3302
+ }
3303
+ this.pos += 1;
3304
+ const end = this.pos;
3305
+ this.pos = save;
3306
+ return end;
3307
+ }
3308
+ }
3309
+ function evaluateRexc(text, ctx = {}) {
3310
+ const interpreter = new CursorInterpreter(text, ctx);
3311
+ const value = interpreter.evaluateTopLevel();
3312
+ return { value, state: interpreter.runtimeState };
3313
+ }
3314
+
3315
+ // rex-repl.ts
3316
+ var req = createRequire2(import.meta.url);
3317
+ var { version } = req("./package.json");
3318
+ var C = {
3319
+ reset: "\x1B[0m",
3320
+ bold: "\x1B[1m",
3321
+ dim: "\x1B[2m",
3322
+ red: "\x1B[31m",
3323
+ green: "\x1B[32m",
3324
+ yellow: "\x1B[33m",
3325
+ blue: "\x1B[34m",
3326
+ cyan: "\x1B[36m",
3327
+ gray: "\x1B[90m",
3328
+ boldBlue: "\x1B[1;34m"
3329
+ };
3330
+ var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
3331
+ function highlightLine(line) {
3332
+ let result = "";
3333
+ let lastIndex = 0;
3334
+ TOKEN_RE.lastIndex = 0;
3335
+ for (const m of line.matchAll(TOKEN_RE)) {
3336
+ result += line.slice(lastIndex, m.index);
3337
+ const text = m[0];
3338
+ const g = m.groups;
3339
+ if (g.blockComment || g.lineComment) {
3340
+ result += C.gray + text + C.reset;
3341
+ } else if (g.dstring || g.sstring) {
3342
+ result += C.green + text + C.reset;
3343
+ } else if (g.keyword) {
3344
+ result += C.boldBlue + text + C.reset;
3345
+ } else if (g.literal) {
3346
+ result += C.yellow + text + C.reset;
3347
+ } else if (g.typePred) {
3348
+ result += C.cyan + text + C.reset;
3349
+ } else if (g.num) {
3350
+ result += C.cyan + text + C.reset;
3351
+ } else {
3352
+ result += text;
3353
+ }
3354
+ lastIndex = m.index + text.length;
3355
+ }
3356
+ result += line.slice(lastIndex);
3357
+ return result;
3358
+ }
3359
+ var REXC_DIGITS = new Set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_");
3360
+ function highlightRexc(text) {
3361
+ let out = "";
3362
+ let i = 0;
3363
+ function readPrefix() {
3364
+ const start = i;
3365
+ while (i < text.length && REXC_DIGITS.has(text[i]))
3366
+ i++;
3367
+ return text.slice(start, i);
3368
+ }
3369
+ while (i < text.length) {
3370
+ const ch = text[i];
3371
+ if (ch === " " || ch === "\t" || ch === `
3372
+ ` || ch === "\r") {
3373
+ out += ch;
3374
+ i++;
2222
3375
  continue;
2223
3376
  }
2224
- if (arg === "--file" || arg === "-f") {
2225
- const value = argv[index + 1];
2226
- if (!value)
2227
- throw new Error("Missing value for --file");
2228
- options.file = value;
2229
- index += 1;
3377
+ if (ch === "/" && text[i + 1] === "/") {
3378
+ const start = i;
3379
+ i += 2;
3380
+ while (i < text.length && text[i] !== `
3381
+ `)
3382
+ i++;
3383
+ out += C.gray + text.slice(start, i) + C.reset;
2230
3384
  continue;
2231
3385
  }
2232
- if (arg === "--out" || arg === "-o") {
2233
- const value = argv[index + 1];
2234
- if (!value)
2235
- throw new Error("Missing value for --out");
2236
- options.out = value;
2237
- index += 1;
3386
+ if (ch === "/" && text[i + 1] === "*") {
3387
+ const start = i;
3388
+ i += 2;
3389
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
3390
+ i++;
3391
+ if (i < text.length)
3392
+ i += 2;
3393
+ out += C.gray + text.slice(start, i) + C.reset;
2238
3394
  continue;
2239
3395
  }
2240
- throw new Error(`Unknown option: ${arg}`);
2241
- }
2242
- return options;
2243
- }
2244
- function usage() {
2245
- return [
2246
- "Compile high-level Rex to compact encoding (rexc).",
2247
- "",
2248
- "Usage:",
2249
- ' rex --expr "when x do y end"',
2250
- " rex --file input.rex",
2251
- " cat input.rex | rex",
2252
- "",
2253
- '(Repo script alternative: bun run rex:compile --expr "when x do y end")',
2254
- "",
2255
- "Options:",
2256
- " -e, --expr <source> Compile an inline expression/program",
2257
- " -f, --file <path> Compile source from a file",
2258
- " -o, --out <path> Write output to file instead of stdout",
2259
- " --ir Output lowered IR JSON instead of compact encoding",
2260
- " -m, --minify-names Minify local variable names in compiled output",
2261
- " --dedupe-values Deduplicate large repeated values using forward pointers",
2262
- " --dedupe-min-bytes <n> Minimum encoded value bytes for pointer dedupe (default: 4)",
2263
- " --domain-extension <name> Map domain symbol name to ref 0 (apostrophe)",
2264
- " --domain-ref <name=id> Map domain symbol name to a specific ref id",
2265
- " -h, --help Show this message"
2266
- ].join(`
2267
- `);
3396
+ const prefix = readPrefix();
3397
+ if (i >= text.length) {
3398
+ out += prefix;
3399
+ break;
3400
+ }
3401
+ const tag = text[i];
3402
+ switch (tag) {
3403
+ case "+":
3404
+ case "*":
3405
+ out += C.cyan + prefix + tag + C.reset;
3406
+ i++;
3407
+ break;
3408
+ case ":":
3409
+ out += C.dim + prefix + tag + C.reset;
3410
+ i++;
3411
+ break;
3412
+ case "%":
3413
+ out += C.boldBlue + prefix + tag + C.reset;
3414
+ i++;
3415
+ break;
3416
+ case "$":
3417
+ out += C.yellow + prefix + tag + C.reset;
3418
+ i++;
3419
+ break;
3420
+ case "@":
3421
+ out += C.yellow + prefix + tag + C.reset;
3422
+ i++;
3423
+ break;
3424
+ case "'":
3425
+ out += C.dim + prefix + tag + C.reset;
3426
+ i++;
3427
+ break;
3428
+ case ",": {
3429
+ i++;
3430
+ let len = 0;
3431
+ for (const ch2 of prefix)
3432
+ len = len * 64 + (REXC_DIGITS.has(ch2) ? "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_".indexOf(ch2) : 0);
3433
+ const content = text.slice(i, i + len);
3434
+ i += len;
3435
+ out += C.green + prefix + "," + content + C.reset;
3436
+ break;
3437
+ }
3438
+ case "=":
3439
+ case "~":
3440
+ out += C.red + prefix + tag + C.reset;
3441
+ i++;
3442
+ break;
3443
+ case "?":
3444
+ case "!":
3445
+ case "|":
3446
+ case "&":
3447
+ case ">":
3448
+ case "<":
3449
+ case "#":
3450
+ out += C.boldBlue + prefix + tag + C.reset;
3451
+ i++;
3452
+ break;
3453
+ case ";":
3454
+ out += C.boldBlue + prefix + tag + C.reset;
3455
+ i++;
3456
+ break;
3457
+ case "^":
3458
+ out += C.dim + prefix + tag + C.reset;
3459
+ i++;
3460
+ break;
3461
+ case "(":
3462
+ case ")":
3463
+ case "[":
3464
+ case "]":
3465
+ case "{":
3466
+ case "}":
3467
+ out += C.dim + prefix + C.reset + tag;
3468
+ i++;
3469
+ break;
3470
+ default:
3471
+ out += prefix + tag;
3472
+ i++;
3473
+ break;
3474
+ }
3475
+ }
3476
+ return out;
2268
3477
  }
2269
- async function readStdin() {
2270
- const chunks = [];
2271
- for await (const chunk of process.stdin) {
2272
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
2273
- }
2274
- return Buffer.concat(chunks).toString("utf8");
2275
- }
2276
- async function resolveSource(options) {
2277
- if (options.expr && options.file)
2278
- throw new Error("Use only one of --expr or --file");
2279
- if (options.expr)
2280
- return options.expr;
2281
- if (options.file)
2282
- return readFile(options.file, "utf8");
2283
- if (!process.stdin.isTTY) {
2284
- const piped = await readStdin();
2285
- if (piped.trim().length > 0)
2286
- return piped;
2287
- }
2288
- throw new Error("No input provided. Use --expr, --file, or pipe source via stdin.");
2289
- }
2290
- async function loadDomainRefsFromFolder(folderPath) {
2291
- const schemaPath = resolve(folderPath, "rex-domain.json");
2292
- let parsed;
2293
- try {
2294
- parsed = JSON.parse(await readFile(schemaPath, "utf8"));
2295
- } catch (error) {
2296
- if (error.code === "ENOENT")
2297
- return {};
2298
- throw error;
3478
+ var JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3479
+ function highlightJSON(json) {
3480
+ let result = "";
3481
+ let lastIndex = 0;
3482
+ JSON_TOKEN_RE.lastIndex = 0;
3483
+ for (const m of json.matchAll(JSON_TOKEN_RE)) {
3484
+ result += json.slice(lastIndex, m.index);
3485
+ const text = m[0];
3486
+ const g = m.groups;
3487
+ if (g.key) {
3488
+ result += C.cyan + g.key + C.reset + ":";
3489
+ } else if (g.string) {
3490
+ result += C.green + text + C.reset;
3491
+ } else if (g.number) {
3492
+ result += C.yellow + text + C.reset;
3493
+ } else if (g.bool) {
3494
+ result += C.yellow + text + C.reset;
3495
+ } else if (g.null) {
3496
+ result += C.dim + text + C.reset;
3497
+ } else {
3498
+ result += text;
3499
+ }
3500
+ lastIndex = m.index + text.length;
2299
3501
  }
2300
- if (!parsed || typeof parsed !== "object" || !parsed.globals || typeof parsed.globals !== "object") {
2301
- throw new Error(`Invalid rex-domain.json at ${schemaPath}: expected { globals: { ... } }`);
3502
+ result += json.slice(lastIndex);
3503
+ return result;
3504
+ }
3505
+ function stripStringsAndComments(source) {
3506
+ return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, (m) => " ".repeat(m.length));
3507
+ }
3508
+ function countWord(text, word) {
3509
+ const re = new RegExp(`\\b${word}(?![a-zA-Z0-9_-])`, "g");
3510
+ return (text.match(re) || []).length;
3511
+ }
3512
+ function isIncomplete(buffer) {
3513
+ try {
3514
+ if (grammar.match(buffer).succeeded())
3515
+ return false;
3516
+ } catch {}
3517
+ const stripped = stripStringsAndComments(buffer);
3518
+ let parens = 0, brackets = 0, braces = 0;
3519
+ for (const ch of stripped) {
3520
+ if (ch === "(")
3521
+ parens++;
3522
+ else if (ch === ")")
3523
+ parens--;
3524
+ else if (ch === "[")
3525
+ brackets++;
3526
+ else if (ch === "]")
3527
+ brackets--;
3528
+ else if (ch === "{")
3529
+ braces++;
3530
+ else if (ch === "}")
3531
+ braces--;
3532
+ }
3533
+ if (parens > 0 || brackets > 0 || braces > 0)
3534
+ return true;
3535
+ const doCount = countWord(stripped, "do");
3536
+ const endCount = countWord(stripped, "end");
3537
+ if (doCount > endCount)
3538
+ return true;
3539
+ const trimmed = buffer.trimEnd();
3540
+ if (/[+\-*/%&|^=<>]$/.test(trimmed))
3541
+ return true;
3542
+ if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
3543
+ return true;
3544
+ return false;
3545
+ }
3546
+ function formatResult(value) {
3547
+ let text;
3548
+ try {
3549
+ text = stringify(value, { maxWidth: 60 });
3550
+ } catch {
3551
+ text = String(value);
2302
3552
  }
2303
- const refs = {};
2304
- const seenRefIds = new Map;
2305
- for (const [name, entry] of Object.entries(parsed.globals)) {
2306
- if (!entry || typeof entry !== "object") {
2307
- throw new Error(`Invalid rex-domain.json at ${schemaPath}: globals.${name} must be an object with a numeric ref`);
3553
+ return `${C.gray}→${C.reset} ${highlightLine(text)}`;
3554
+ }
3555
+ function formatVarState(vars) {
3556
+ const entries = Object.entries(vars);
3557
+ if (entries.length === 0)
3558
+ return "";
3559
+ const MAX_LINE = 70;
3560
+ const MAX_VALUE = 30;
3561
+ const parts = [];
3562
+ let totalLen = 0;
3563
+ for (const [key, val] of entries) {
3564
+ let valStr;
3565
+ try {
3566
+ valStr = stringify(val, { maxWidth: MAX_VALUE });
3567
+ } catch {
3568
+ valStr = String(val);
2308
3569
  }
2309
- const ref = entry.ref;
2310
- if (!Number.isInteger(ref)) {
2311
- throw new Error(`Invalid rex-domain.json at ${schemaPath}: globals.${name}.ref must be an integer`);
3570
+ if (valStr.length > MAX_VALUE) {
3571
+ valStr = valStr.slice(0, MAX_VALUE - 1) + "…";
2312
3572
  }
2313
- if (ref < FIRST_NON_RESERVED_REF) {
2314
- throw new Error(`Invalid rex-domain.json at ${schemaPath}: globals.${name}.ref must be >= ${FIRST_NON_RESERVED_REF} (0-4 are reserved built-ins)`);
3573
+ const part = `${key} = ${valStr}`;
3574
+ if (totalLen + part.length + 2 > MAX_LINE && parts.length > 0) {
3575
+ parts.push("…");
3576
+ break;
2315
3577
  }
2316
- const existing = seenRefIds.get(ref);
2317
- if (existing) {
2318
- throw new Error(`Invalid rex-domain.json at ${schemaPath}: duplicate ref ${ref} for globals.${existing} and globals.${name}`);
3578
+ parts.push(part);
3579
+ totalLen += part.length + 2;
3580
+ }
3581
+ return `${C.dim} ${parts.join(", ")}${C.reset}`;
3582
+ }
3583
+ var KEYWORDS = [
3584
+ "when",
3585
+ "unless",
3586
+ "while",
3587
+ "for",
3588
+ "do",
3589
+ "end",
3590
+ "in",
3591
+ "of",
3592
+ "and",
3593
+ "or",
3594
+ "else",
3595
+ "break",
3596
+ "continue",
3597
+ "delete",
3598
+ "self",
3599
+ "true",
3600
+ "false",
3601
+ "null",
3602
+ "undefined",
3603
+ "nan",
3604
+ "inf",
3605
+ "string",
3606
+ "number",
3607
+ "object",
3608
+ "array",
3609
+ "boolean"
3610
+ ];
3611
+ function completer(state) {
3612
+ return (line) => {
3613
+ const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
3614
+ const partial = match ? match[0] : "";
3615
+ if (!partial)
3616
+ return [[], ""];
3617
+ const varNames = Object.keys(state.vars);
3618
+ const all = [...new Set([...KEYWORDS, ...varNames])];
3619
+ const hits = all.filter((w) => w.startsWith(partial));
3620
+ return [hits, partial];
3621
+ };
3622
+ }
3623
+ function handleDotCommand(cmd, state, rl) {
3624
+ function toggleLabel(on) {
3625
+ return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
3626
+ }
3627
+ switch (cmd) {
3628
+ case ".help":
3629
+ console.log([
3630
+ `${C.boldBlue}Rex REPL Commands:${C.reset}`,
3631
+ " .help Show this help message",
3632
+ " .vars Show all current variables",
3633
+ " .clear Clear all variables",
3634
+ " .ir Toggle showing IR JSON after parsing",
3635
+ " .rexc Toggle showing compiled rexc before execution",
3636
+ " .opt Toggle IR optimizations",
3637
+ " .exit Exit the REPL",
3638
+ "",
3639
+ "Enter Rex expressions to evaluate them.",
3640
+ "Multi-line: open brackets or do/end blocks continue on the next line.",
3641
+ "Ctrl-C cancels multi-line input.",
3642
+ "Ctrl-D exits."
3643
+ ].join(`
3644
+ `));
3645
+ return true;
3646
+ case ".ir":
3647
+ state.showIR = !state.showIR;
3648
+ console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3649
+ return true;
3650
+ case ".rexc":
3651
+ state.showRexc = !state.showRexc;
3652
+ console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3653
+ return true;
3654
+ case ".opt":
3655
+ state.optimize = !state.optimize;
3656
+ console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3657
+ return true;
3658
+ case ".vars": {
3659
+ const entries = Object.entries(state.vars);
3660
+ if (entries.length === 0) {
3661
+ console.log(`${C.dim} (no variables)${C.reset}`);
3662
+ } else {
3663
+ for (const [key, val] of entries) {
3664
+ let valStr;
3665
+ try {
3666
+ valStr = stringify(val, { maxWidth: 60 });
3667
+ } catch {
3668
+ valStr = String(val);
3669
+ }
3670
+ console.log(` ${key} = ${highlightLine(valStr)}`);
3671
+ }
3672
+ }
3673
+ return true;
2319
3674
  }
2320
- seenRefIds.set(ref, name);
2321
- refs[name] = ref;
3675
+ case ".clear":
3676
+ state.vars = {};
3677
+ state.refs = {};
3678
+ console.log(`${C.dim} Variables cleared.${C.reset}`);
3679
+ return true;
3680
+ case ".exit":
3681
+ rl.close();
3682
+ return true;
3683
+ default:
3684
+ if (cmd.startsWith(".")) {
3685
+ console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
3686
+ return true;
3687
+ }
3688
+ return false;
2322
3689
  }
2323
- return refs;
2324
3690
  }
2325
- async function resolveDomainRefs(options) {
2326
- const baseFolder = options.file ? dirname(resolve(options.file)) : process.cwd();
2327
- const autoRefs = await loadDomainRefsFromFolder(baseFolder);
2328
- return { ...autoRefs, ...options.domainRefs };
2329
- }
2330
- async function main() {
2331
- const options = parseArgs(process.argv.slice(2));
2332
- if (options.help) {
2333
- console.log(usage());
2334
- return;
2335
- }
2336
- const source = await resolveSource(options);
2337
- const domainRefs = await resolveDomainRefs(options);
2338
- const output = options.ir ? JSON.stringify(parseToIR(source), null, 2) : compile(source, {
2339
- minifyNames: options.minifyNames,
2340
- dedupeValues: options.dedupeValues,
2341
- dedupeMinBytes: options.dedupeMinBytes,
2342
- domainRefs
3691
+ var GAS_LIMIT = 1e7;
3692
+ async function startRepl() {
3693
+ const state = { vars: {}, refs: {}, showIR: false, showRexc: false, optimize: false };
3694
+ let multiLineBuffer = "";
3695
+ const PRIMARY_PROMPT = "rex> ";
3696
+ const CONT_PROMPT = "... ";
3697
+ const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
3698
+ const STYLED_CONT = `${C.dim}...${C.reset} `;
3699
+ let currentPrompt = PRIMARY_PROMPT;
3700
+ let styledPrompt = STYLED_PRIMARY;
3701
+ console.log(`${C.boldBlue}Rex${C.reset} v${version} — type ${C.dim}.help${C.reset} for commands`);
3702
+ const rl = readline.createInterface({
3703
+ input: process.stdin,
3704
+ output: process.stdout,
3705
+ prompt: PRIMARY_PROMPT,
3706
+ historySize: 500,
3707
+ completer: completer(state),
3708
+ terminal: true
2343
3709
  });
2344
- if (options.out) {
2345
- await writeFile(options.out, `${output}
2346
- `, "utf8");
2347
- return;
2348
- }
2349
- console.log(output);
2350
- }
2351
- await main().catch((error) => {
2352
- const message = error instanceof Error ? error.message : String(error);
2353
- console.error(`rex:compile error: ${message}`);
2354
- process.exit(1);
2355
- });
3710
+ process.stdin.on("keypress", () => {
3711
+ process.nextTick(() => {
3712
+ if (!rl.line && rl.line !== "")
3713
+ return;
3714
+ readline.clearLine(process.stdout, 0);
3715
+ readline.cursorTo(process.stdout, 0);
3716
+ process.stdout.write(styledPrompt + highlightLine(rl.line));
3717
+ readline.cursorTo(process.stdout, currentPrompt.length + rl.cursor);
3718
+ });
3719
+ });
3720
+ process.stdin.on("keypress", (_ch, key) => {
3721
+ if (key?.ctrl && key.name === "l") {
3722
+ readline.cursorTo(process.stdout, 0, 0);
3723
+ readline.clearScreenDown(process.stdout);
3724
+ rl.prompt();
3725
+ }
3726
+ });
3727
+ rl.on("SIGINT", () => {
3728
+ if (multiLineBuffer) {
3729
+ multiLineBuffer = "";
3730
+ currentPrompt = PRIMARY_PROMPT;
3731
+ styledPrompt = STYLED_PRIMARY;
3732
+ rl.setPrompt(PRIMARY_PROMPT);
3733
+ process.stdout.write(`
3734
+ `);
3735
+ rl.prompt();
3736
+ } else {
3737
+ console.log();
3738
+ rl.close();
3739
+ }
3740
+ });
3741
+ function resetPrompt() {
3742
+ currentPrompt = PRIMARY_PROMPT;
3743
+ styledPrompt = STYLED_PRIMARY;
3744
+ rl.setPrompt(PRIMARY_PROMPT);
3745
+ rl.prompt();
3746
+ }
3747
+ rl.on("line", (line) => {
3748
+ const trimmed = line.trim();
3749
+ if (!multiLineBuffer && trimmed.startsWith(".")) {
3750
+ if (handleDotCommand(trimmed, state, rl)) {
3751
+ rl.prompt();
3752
+ return;
3753
+ }
3754
+ }
3755
+ multiLineBuffer += (multiLineBuffer ? `
3756
+ ` : "") + line;
3757
+ if (multiLineBuffer.trim() === "") {
3758
+ multiLineBuffer = "";
3759
+ rl.prompt();
3760
+ return;
3761
+ }
3762
+ if (isIncomplete(multiLineBuffer)) {
3763
+ currentPrompt = CONT_PROMPT;
3764
+ styledPrompt = STYLED_CONT;
3765
+ rl.setPrompt(CONT_PROMPT);
3766
+ rl.prompt();
3767
+ return;
3768
+ }
3769
+ const source = multiLineBuffer;
3770
+ multiLineBuffer = "";
3771
+ const match = grammar.match(source);
3772
+ if (!match.succeeded()) {
3773
+ console.log(`${C.red} ${match.message}${C.reset}`);
3774
+ resetPrompt();
3775
+ return;
3776
+ }
3777
+ try {
3778
+ const ir = parseToIR(source);
3779
+ const lowered = state.optimize ? optimizeIR(ir) : ir;
3780
+ if (state.showIR) {
3781
+ console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3782
+ }
3783
+ const rexc = compile(source, { optimize: state.optimize });
3784
+ if (state.showRexc) {
3785
+ console.log(`${C.dim} rexc:${C.reset} ${highlightRexc(rexc)}`);
3786
+ }
3787
+ const result = evaluateRexc(rexc, {
3788
+ vars: { ...state.vars },
3789
+ refs: { ...state.refs },
3790
+ gasLimit: GAS_LIMIT
3791
+ });
3792
+ state.vars = result.state.vars;
3793
+ state.refs = result.state.refs;
3794
+ console.log(formatResult(result.value));
3795
+ const varLine = formatVarState(state.vars);
3796
+ if (varLine)
3797
+ console.log(varLine);
3798
+ } catch (error) {
3799
+ const message = error instanceof Error ? error.message : String(error);
3800
+ if (message.includes("Gas limit exceeded")) {
3801
+ console.log(`${C.yellow} ${message}${C.reset}`);
3802
+ } else {
3803
+ console.log(`${C.red} Error: ${message}${C.reset}`);
3804
+ }
3805
+ }
3806
+ resetPrompt();
3807
+ });
3808
+ rl.on("close", () => {
3809
+ process.exit(0);
3810
+ });
3811
+ rl.prompt();
3812
+ }
3813
+ export {
3814
+ startRepl,
3815
+ isIncomplete,
3816
+ highlightRexc,
3817
+ highlightLine,
3818
+ highlightJSON,
3819
+ formatVarState
3820
+ };