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