@creationix/rex 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/rex-repl.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // rex-repl.ts
2
2
  import * as readline from "node:readline";
3
3
  import { createRequire as createRequire2 } from "node:module";
4
+ import { readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { resolve, dirname, basename } from "node:path";
6
+ import { homedir } from "node:os";
4
7
 
5
8
  // rex.ts
6
9
  import { createRequire } from "node:module";
@@ -36,10 +39,9 @@ var OPCODE_IDS = {
36
39
  object: "ob",
37
40
  mod: "md",
38
41
  neg: "ng",
39
- range: "rn",
40
- size: "sz"
42
+ range: "rn"
41
43
  };
42
- var KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object", "size"]);
44
+ var KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object"]);
43
45
  var BINARY_TO_OPCODE = {
44
46
  add: "add",
45
47
  sub: "sub",
@@ -495,11 +497,30 @@ function collectLogicalChain(node, op) {
495
497
  return [node];
496
498
  return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
497
499
  }
500
+ function formatParseError(source, match) {
501
+ const message = match.message ?? "Parse failed";
502
+ const pos = match.getRightmostFailurePosition?.();
503
+ if (typeof pos !== "number" || !Number.isFinite(pos))
504
+ return message;
505
+ const safePos = Math.max(0, Math.min(source.length, pos));
506
+ const lineStart = source.lastIndexOf(`
507
+ `, safePos - 1) + 1;
508
+ const lineEndIndex = source.indexOf(`
509
+ `, safePos);
510
+ const lineEnd = lineEndIndex === -1 ? source.length : lineEndIndex;
511
+ const lineText = source.slice(lineStart, lineEnd);
512
+ const lineNumber = source.slice(0, lineStart).split(`
513
+ `).length;
514
+ const columnNumber = safePos - lineStart + 1;
515
+ const caret = `${" ".repeat(Math.max(0, columnNumber - 1))}^`;
516
+ return `${message}
517
+ ${lineText}
518
+ ${caret}`;
519
+ }
498
520
  function parseToIR(source) {
499
521
  const match = grammar.match(source);
500
522
  if (!match.succeeded()) {
501
- const failure = match;
502
- throw new Error(failure.message ?? "Parse failed");
523
+ throw new Error(formatParseError(source, match));
503
524
  }
504
525
  return semantics(match).toIR();
505
526
  }
@@ -800,20 +821,11 @@ function gatherEncodedValueSpans(text) {
800
821
  }
801
822
  return spans;
802
823
  }
803
- function buildPointerToken(pointerStart, targetStart) {
804
- let offset = targetStart - (pointerStart + 1);
824
+ function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
825
+ const offset = targetStart - pointerStart - occurrenceSize;
805
826
  if (offset < 0)
806
827
  return;
807
- for (let guard = 0;guard < 8; guard += 1) {
808
- const prefix = encodeUint(offset);
809
- const recalculated = targetStart - (pointerStart + prefix.length + 1);
810
- if (recalculated === offset)
811
- return `${prefix}^`;
812
- offset = recalculated;
813
- if (offset < 0)
814
- return;
815
- }
816
- return;
828
+ return `${encodeUint(offset)}^`;
817
829
  }
818
830
  function buildDedupeCandidateTable(encoded, minBytes) {
819
831
  const spans = gatherEncodedValueSpans(encoded);
@@ -840,875 +852,52 @@ function buildDedupeCandidateTable(encoded, minBytes) {
840
852
  }
841
853
  function dedupeLargeEncodedValues(encoded, minBytes = 4) {
842
854
  const effectiveMinBytes = Math.max(1, minBytes);
843
- let current = encoded;
844
- while (true) {
845
- const groups = buildDedupeCandidateTable(current, effectiveMinBytes);
846
- let replaced = false;
847
- for (const [value, occurrences] of groups.entries()) {
848
- if (occurrences.length < 2)
849
- continue;
850
- const canonical = occurrences[occurrences.length - 1];
851
- for (let index = occurrences.length - 2;index >= 0; index -= 1) {
852
- const occurrence = occurrences[index];
853
- if (occurrence.span.end > canonical.span.start)
854
- continue;
855
- if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
856
- continue;
857
- const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
858
- const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
859
- if (!pointerToken)
860
- continue;
861
- if (pointerToken.length >= occurrence.sizeBytes)
862
- continue;
863
- current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
864
- replaced = true;
865
- break;
866
- }
867
- if (replaced)
868
- break;
869
- }
870
- if (!replaced)
871
- return current;
872
- }
873
- }
874
- function encodeIR(node, options) {
875
- const previous = activeEncodeOptions;
876
- activeEncodeOptions = options;
877
- try {
878
- const encoded = encodeNode(node);
879
- if (options?.dedupeValues) {
880
- return dedupeLargeEncodedValues(encoded, options.dedupeMinBytes ?? 4);
881
- }
882
- return encoded;
883
- } finally {
884
- activeEncodeOptions = previous;
885
- }
886
- }
887
- function cloneNode(node) {
888
- return structuredClone(node);
889
- }
890
- function emptyOptimizeEnv() {
891
- return { constants: {}, selfCaptures: {} };
892
- }
893
- function cloneOptimizeEnv(env) {
894
- return {
895
- constants: { ...env.constants },
896
- selfCaptures: { ...env.selfCaptures }
897
- };
898
- }
899
- function clearOptimizeEnv(env) {
900
- for (const key of Object.keys(env.constants))
901
- delete env.constants[key];
902
- for (const key of Object.keys(env.selfCaptures))
903
- delete env.selfCaptures[key];
904
- }
905
- function clearBinding(env, name) {
906
- delete env.constants[name];
907
- delete env.selfCaptures[name];
908
- }
909
- function selfTargetFromNode(node, currentDepth) {
910
- if (node.type === "self")
911
- return currentDepth;
912
- if (node.type === "selfDepth") {
913
- const target = currentDepth - (node.depth - 1);
914
- if (target >= 1)
915
- return target;
916
- }
917
- return;
918
- }
919
- function selfNodeFromTarget(targetDepth, currentDepth) {
920
- const relDepth = currentDepth - targetDepth + 1;
921
- if (!Number.isInteger(relDepth) || relDepth < 1)
922
- return;
923
- if (relDepth === 1)
924
- return { type: "self" };
925
- return { type: "selfDepth", depth: relDepth };
926
- }
927
- function dropBindingNames(env, binding) {
928
- if (binding.type === "binding:valueIn") {
929
- clearBinding(env, binding.value);
930
- return;
931
- }
932
- if (binding.type === "binding:keyValueIn") {
933
- clearBinding(env, binding.key);
934
- clearBinding(env, binding.value);
935
- return;
936
- }
937
- if (binding.type === "binding:keyOf") {
938
- clearBinding(env, binding.key);
939
- }
940
- }
941
- function optimizeBinding(binding, sourceEnv, currentDepth) {
942
- const source = optimizeNode(binding.source, sourceEnv, currentDepth);
943
- switch (binding.type) {
944
- case "binding:bareIn":
945
- return { type: "binding:bareIn", source };
946
- case "binding:bareOf":
947
- return { type: "binding:bareOf", source };
948
- case "binding:valueIn":
949
- return { type: "binding:valueIn", value: binding.value, source };
950
- case "binding:keyValueIn":
951
- return { type: "binding:keyValueIn", key: binding.key, value: binding.value, source };
952
- case "binding:keyOf":
953
- return { type: "binding:keyOf", key: binding.key, source };
954
- }
955
- }
956
- function collectReads(node, out) {
957
- switch (node.type) {
958
- case "identifier":
959
- out.add(node.name);
960
- return;
961
- case "group":
962
- collectReads(node.expression, out);
963
- return;
964
- case "array":
965
- for (const item of node.items)
966
- collectReads(item, out);
967
- return;
968
- case "object":
969
- for (const entry of node.entries) {
970
- collectReads(entry.key, out);
971
- collectReads(entry.value, out);
972
- }
973
- return;
974
- case "arrayComprehension":
975
- collectReads(node.binding.source, out);
976
- collectReads(node.body, out);
977
- return;
978
- case "whileArrayComprehension":
979
- collectReads(node.condition, out);
980
- collectReads(node.body, out);
981
- return;
982
- case "objectComprehension":
983
- collectReads(node.binding.source, out);
984
- collectReads(node.key, out);
985
- collectReads(node.value, out);
986
- return;
987
- case "whileObjectComprehension":
988
- collectReads(node.condition, out);
989
- collectReads(node.key, out);
990
- collectReads(node.value, out);
991
- return;
992
- case "unary":
993
- collectReads(node.value, out);
994
- return;
995
- case "binary":
996
- collectReads(node.left, out);
997
- collectReads(node.right, out);
998
- return;
999
- case "assign":
1000
- if (!(node.op === "=" && node.place.type === "identifier"))
1001
- collectReads(node.place, out);
1002
- collectReads(node.value, out);
1003
- return;
1004
- case "navigation":
1005
- collectReads(node.target, out);
1006
- for (const segment of node.segments) {
1007
- if (segment.type === "dynamic")
1008
- collectReads(segment.key, out);
1009
- }
1010
- return;
1011
- case "call":
1012
- collectReads(node.callee, out);
1013
- for (const arg of node.args)
1014
- collectReads(arg, out);
1015
- return;
1016
- case "conditional":
1017
- collectReads(node.condition, out);
1018
- for (const part of node.thenBlock)
1019
- collectReads(part, out);
1020
- if (node.elseBranch)
1021
- collectReadsElse(node.elseBranch, out);
1022
- return;
1023
- case "for":
1024
- collectReads(node.binding.source, out);
1025
- for (const part of node.body)
1026
- collectReads(part, out);
1027
- return;
1028
- case "range":
1029
- collectReads(node.from, out);
1030
- collectReads(node.to, out);
1031
- return;
1032
- case "program":
1033
- for (const part of node.body)
1034
- collectReads(part, out);
1035
- return;
1036
- default:
1037
- return;
1038
- }
1039
- }
1040
- function collectReadsElse(elseBranch, out) {
1041
- if (elseBranch.type === "else") {
1042
- for (const part of elseBranch.block)
1043
- collectReads(part, out);
1044
- return;
1045
- }
1046
- collectReads(elseBranch.condition, out);
1047
- for (const part of elseBranch.thenBlock)
1048
- collectReads(part, out);
1049
- if (elseBranch.elseBranch)
1050
- collectReadsElse(elseBranch.elseBranch, out);
1051
- }
1052
- function isPureNode(node) {
1053
- switch (node.type) {
1054
- case "identifier":
1055
- case "self":
1056
- case "selfDepth":
1057
- case "boolean":
1058
- case "null":
1059
- case "undefined":
1060
- case "number":
1061
- case "string":
1062
- case "key":
1063
- return true;
1064
- case "group":
1065
- return isPureNode(node.expression);
1066
- case "array":
1067
- return node.items.every((item) => isPureNode(item));
1068
- case "object":
1069
- return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
1070
- case "navigation":
1071
- return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
1072
- case "unary":
1073
- return node.op !== "delete" && isPureNode(node.value);
1074
- case "binary":
1075
- return isPureNode(node.left) && isPureNode(node.right);
1076
- case "range":
1077
- return isPureNode(node.from) && isPureNode(node.to);
1078
- default:
1079
- return false;
1080
- }
1081
- }
1082
- function eliminateDeadAssignments(block) {
1083
- const needed = new Set;
1084
- const out = [];
1085
- for (let index = block.length - 1;index >= 0; index -= 1) {
1086
- const node = block[index];
1087
- if (node.type === "conditional") {
1088
- let rewritten = node;
1089
- if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
1090
- const name = node.condition.place.name;
1091
- const branchReads = new Set;
1092
- for (const part of node.thenBlock)
1093
- collectReads(part, branchReads);
1094
- if (node.elseBranch)
1095
- collectReadsElse(node.elseBranch, branchReads);
1096
- if (!needed.has(name) && !branchReads.has(name)) {
1097
- rewritten = {
1098
- type: "conditional",
1099
- head: node.head,
1100
- condition: node.condition.value,
1101
- thenBlock: node.thenBlock,
1102
- elseBranch: node.elseBranch
1103
- };
1104
- }
1105
- }
1106
- collectReads(rewritten, needed);
1107
- out.push(rewritten);
1108
- continue;
1109
- }
1110
- if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
1111
- collectReads(node.value, needed);
1112
- const name = node.place.name;
1113
- const canDrop = !needed.has(name) && isPureNode(node.value);
1114
- needed.delete(name);
1115
- if (canDrop)
1116
- continue;
1117
- out.push(node);
1118
- continue;
1119
- }
1120
- collectReads(node, needed);
1121
- out.push(node);
1122
- }
1123
- out.reverse();
1124
- return out;
1125
- }
1126
- function hasIdentifierRead(node, name, asPlace = false) {
1127
- if (node.type === "identifier")
1128
- return !asPlace && node.name === name;
1129
- switch (node.type) {
1130
- case "group":
1131
- return hasIdentifierRead(node.expression, name);
1132
- case "array":
1133
- return node.items.some((item) => hasIdentifierRead(item, name));
1134
- case "object":
1135
- return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
1136
- case "navigation":
1137
- return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
1138
- case "unary":
1139
- return hasIdentifierRead(node.value, name, node.op === "delete");
1140
- case "binary":
1141
- return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1142
- case "range":
1143
- return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
1144
- case "assign":
1145
- return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1146
- default:
1147
- return false;
1148
- }
1149
- }
1150
- function countIdentifierReads(node, name, asPlace = false) {
1151
- if (node.type === "identifier")
1152
- return !asPlace && node.name === name ? 1 : 0;
1153
- switch (node.type) {
1154
- case "group":
1155
- return countIdentifierReads(node.expression, name);
1156
- case "array":
1157
- return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
1158
- case "object":
1159
- return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
1160
- case "navigation":
1161
- return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
1162
- case "unary":
1163
- return countIdentifierReads(node.value, name, node.op === "delete");
1164
- case "binary":
1165
- return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1166
- case "range":
1167
- return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
1168
- case "assign":
1169
- return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1170
- default:
1171
- return 0;
1172
- }
1173
- }
1174
- function replaceIdentifier(node, name, replacement, asPlace = false) {
1175
- if (node.type === "identifier") {
1176
- if (!asPlace && node.name === name)
1177
- return cloneNode(replacement);
1178
- return node;
1179
- }
1180
- switch (node.type) {
1181
- case "group":
1182
- return {
1183
- type: "group",
1184
- expression: replaceIdentifier(node.expression, name, replacement)
1185
- };
1186
- case "array":
1187
- return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
1188
- case "object":
1189
- return {
1190
- type: "object",
1191
- entries: node.entries.map((entry) => ({
1192
- key: replaceIdentifier(entry.key, name, replacement),
1193
- value: replaceIdentifier(entry.value, name, replacement)
1194
- }))
1195
- };
1196
- case "navigation":
1197
- return {
1198
- type: "navigation",
1199
- target: replaceIdentifier(node.target, name, replacement),
1200
- segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
1201
- };
1202
- case "unary":
1203
- return {
1204
- type: "unary",
1205
- op: node.op,
1206
- value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
1207
- };
1208
- case "binary":
1209
- return {
1210
- type: "binary",
1211
- op: node.op,
1212
- left: replaceIdentifier(node.left, name, replacement),
1213
- right: replaceIdentifier(node.right, name, replacement)
1214
- };
1215
- case "assign":
1216
- return {
1217
- type: "assign",
1218
- op: node.op,
1219
- place: replaceIdentifier(node.place, name, replacement, true),
1220
- value: replaceIdentifier(node.value, name, replacement)
1221
- };
1222
- case "range":
1223
- return {
1224
- type: "range",
1225
- from: replaceIdentifier(node.from, name, replacement),
1226
- to: replaceIdentifier(node.to, name, replacement)
1227
- };
1228
- default:
1229
- return node;
1230
- }
1231
- }
1232
- function isSafeInlineTargetNode(node) {
1233
- if (isPureNode(node))
1234
- return true;
1235
- if (node.type === "assign" && node.op === "=") {
1236
- return isPureNode(node.place) && isPureNode(node.value);
1237
- }
1238
- return false;
1239
- }
1240
- function inlineAdjacentPureAssignments(block) {
1241
- const out = [...block];
1242
- let changed = true;
1243
- while (changed) {
1244
- changed = false;
1245
- for (let index = 0;index < out.length - 1; index += 1) {
1246
- const current = out[index];
1247
- if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
1248
- continue;
1249
- if (!isPureNode(current.value))
1250
- continue;
1251
- const name = current.place.name;
1252
- if (hasIdentifierRead(current.value, name))
1253
- continue;
1254
- const next = out[index + 1];
1255
- if (!isSafeInlineTargetNode(next))
1256
- continue;
1257
- if (countIdentifierReads(next, name) !== 1)
1258
- continue;
1259
- out[index + 1] = replaceIdentifier(next, name, current.value);
1260
- out.splice(index, 1);
1261
- changed = true;
1262
- break;
1263
- }
1264
- }
1265
- return out;
1266
- }
1267
- function toNumberNode(value) {
1268
- let raw;
1269
- if (Number.isNaN(value))
1270
- raw = "nan";
1271
- else if (value === Infinity)
1272
- raw = "inf";
1273
- else if (value === -Infinity)
1274
- raw = "-inf";
1275
- else
1276
- raw = String(value);
1277
- return { type: "number", raw, value };
1278
- }
1279
- function toStringNode(value) {
1280
- return { type: "string", raw: JSON.stringify(value) };
1281
- }
1282
- function toLiteralNode(value) {
1283
- if (value === undefined)
1284
- return { type: "undefined" };
1285
- if (value === null)
1286
- return { type: "null" };
1287
- if (typeof value === "boolean")
1288
- return { type: "boolean", value };
1289
- if (typeof value === "number")
1290
- return toNumberNode(value);
1291
- if (typeof value === "string")
1292
- return toStringNode(value);
1293
- if (Array.isArray(value)) {
1294
- const items = [];
1295
- for (const item of value) {
1296
- const lowered = toLiteralNode(item);
1297
- if (!lowered)
1298
- return;
1299
- items.push(lowered);
1300
- }
1301
- return { type: "array", items };
1302
- }
1303
- if (value && typeof value === "object") {
1304
- const entries = [];
1305
- for (const [key, entryValue] of Object.entries(value)) {
1306
- const loweredValue = toLiteralNode(entryValue);
1307
- if (!loweredValue)
1308
- return;
1309
- entries.push({ key: { type: "key", name: key }, value: loweredValue });
1310
- }
1311
- return { type: "object", entries };
1312
- }
1313
- return;
1314
- }
1315
- function constValue(node) {
1316
- switch (node.type) {
1317
- case "undefined":
1318
- return;
1319
- case "null":
1320
- return null;
1321
- case "boolean":
1322
- return node.value;
1323
- case "number":
1324
- return node.value;
1325
- case "string":
1326
- return decodeStringLiteral(node.raw);
1327
- case "key":
1328
- return node.name;
1329
- case "array": {
1330
- const out = [];
1331
- for (const item of node.items) {
1332
- const value = constValue(item);
1333
- if (value === undefined && item.type !== "undefined")
1334
- return;
1335
- out.push(value);
1336
- }
1337
- return out;
1338
- }
1339
- case "object": {
1340
- const out = {};
1341
- for (const entry of node.entries) {
1342
- const key = constValue(entry.key);
1343
- if (key === undefined && entry.key.type !== "undefined")
1344
- return;
1345
- const value = constValue(entry.value);
1346
- if (value === undefined && entry.value.type !== "undefined")
1347
- return;
1348
- out[String(key)] = value;
1349
- }
1350
- return out;
1351
- }
1352
- default:
1353
- return;
1354
- }
1355
- }
1356
- function isDefinedValue(value) {
1357
- return value !== undefined;
1358
- }
1359
- function foldUnary(op, value) {
1360
- if (op === "neg") {
1361
- if (typeof value !== "number")
1362
- return;
1363
- return -value;
1364
- }
1365
- if (op === "not") {
1366
- if (typeof value === "boolean")
1367
- return !value;
1368
- if (typeof value === "number")
1369
- return ~value;
1370
- return;
1371
- }
1372
- if (op === "logicalNot") {
1373
- return value === undefined ? true : undefined;
1374
- }
1375
- return;
1376
- }
1377
- function foldBinary(op, left, right) {
1378
- if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
1379
- if (typeof left !== "number" || typeof right !== "number")
1380
- return;
1381
- if (op === "add")
1382
- return left + right;
1383
- if (op === "sub")
1384
- return left - right;
1385
- if (op === "mul")
1386
- return left * right;
1387
- if (op === "div")
1388
- return left / right;
1389
- return left % right;
1390
- }
1391
- if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
1392
- if (typeof left !== "number" || typeof right !== "number")
1393
- return;
1394
- if (op === "bitAnd")
1395
- return left & right;
1396
- if (op === "bitOr")
1397
- return left | right;
1398
- return left ^ right;
1399
- }
1400
- if (op === "eq")
1401
- return left === right ? left : undefined;
1402
- if (op === "neq")
1403
- return left !== right ? left : undefined;
1404
- if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
1405
- if (typeof left !== "number" || typeof right !== "number")
1406
- return;
1407
- if (op === "gt")
1408
- return left > right ? left : undefined;
1409
- if (op === "gte")
1410
- return left >= right ? left : undefined;
1411
- if (op === "lt")
1412
- return left < right ? left : undefined;
1413
- return left <= right ? left : undefined;
1414
- }
1415
- if (op === "and")
1416
- return isDefinedValue(left) ? right : undefined;
1417
- if (op === "or")
1418
- return isDefinedValue(left) ? left : right;
1419
- return;
1420
- }
1421
- function optimizeElse(elseBranch, env, currentDepth) {
1422
- if (!elseBranch)
1423
- return;
1424
- if (elseBranch.type === "else") {
1425
- return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
1426
- }
1427
- const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
1428
- const foldedCondition = constValue(optimizedCondition);
1429
- if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
1430
- const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
1431
- if (passes) {
1432
- return {
1433
- type: "else",
1434
- block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
1435
- };
1436
- }
1437
- return optimizeElse(elseBranch.elseBranch, env, currentDepth);
1438
- }
1439
- return {
1440
- type: "elseChain",
1441
- head: elseBranch.head,
1442
- condition: optimizedCondition,
1443
- thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
1444
- elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
1445
- };
1446
- }
1447
- function optimizeBlock(block, env, currentDepth) {
1448
- const out = [];
1449
- for (const node of block) {
1450
- const optimized = optimizeNode(node, env, currentDepth);
1451
- out.push(optimized);
1452
- if (optimized.type === "break" || optimized.type === "continue")
1453
- break;
1454
- if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
1455
- const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
1456
- if (selfTarget !== undefined) {
1457
- env.selfCaptures[optimized.place.name] = selfTarget;
1458
- delete env.constants[optimized.place.name];
1459
- continue;
1460
- }
1461
- const folded = constValue(optimized.value);
1462
- if (folded !== undefined || optimized.value.type === "undefined") {
1463
- env.constants[optimized.place.name] = cloneNode(optimized.value);
1464
- delete env.selfCaptures[optimized.place.name];
1465
- } else {
1466
- clearBinding(env, optimized.place.name);
1467
- }
1468
- continue;
1469
- }
1470
- if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
1471
- clearBinding(env, optimized.value.name);
1472
- continue;
1473
- }
1474
- if (optimized.type === "assign" && optimized.place.type === "identifier") {
1475
- clearBinding(env, optimized.place.name);
1476
- continue;
1477
- }
1478
- if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
1479
- clearOptimizeEnv(env);
1480
- }
1481
- }
1482
- return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
1483
- }
1484
- function optimizeNode(node, env, currentDepth, asPlace = false) {
1485
- switch (node.type) {
1486
- case "program": {
1487
- const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
1488
- if (body.length === 0)
1489
- return { type: "undefined" };
1490
- if (body.length === 1)
1491
- return body[0];
1492
- return { type: "program", body };
1493
- }
1494
- case "identifier": {
1495
- if (asPlace)
1496
- return node;
1497
- const selfTarget = env.selfCaptures[node.name];
1498
- if (selfTarget !== undefined) {
1499
- const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
1500
- if (rewritten)
1501
- return rewritten;
1502
- }
1503
- const replacement = env.constants[node.name];
1504
- return replacement ? cloneNode(replacement) : node;
1505
- }
1506
- case "group": {
1507
- return optimizeNode(node.expression, env, currentDepth);
1508
- }
1509
- case "array": {
1510
- return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
1511
- }
1512
- case "object": {
1513
- return {
1514
- type: "object",
1515
- entries: node.entries.map((entry) => ({
1516
- key: optimizeNode(entry.key, env, currentDepth),
1517
- value: optimizeNode(entry.value, env, currentDepth)
1518
- }))
1519
- };
1520
- }
1521
- case "unary": {
1522
- const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
1523
- const foldedValue = constValue(value);
1524
- if (foldedValue !== undefined || value.type === "undefined") {
1525
- const folded = foldUnary(node.op, foldedValue);
1526
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1527
- if (literal)
1528
- return literal;
1529
- }
1530
- return { type: "unary", op: node.op, value };
1531
- }
1532
- case "binary": {
1533
- const left = optimizeNode(node.left, env, currentDepth);
1534
- const right = optimizeNode(node.right, env, currentDepth);
1535
- const leftValue = constValue(left);
1536
- const rightValue = constValue(right);
1537
- if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
1538
- const folded = foldBinary(node.op, leftValue, rightValue);
1539
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1540
- if (literal)
1541
- return literal;
1542
- }
1543
- return { type: "binary", op: node.op, left, right };
1544
- }
1545
- case "range":
1546
- return {
1547
- type: "range",
1548
- from: optimizeNode(node.from, env, currentDepth),
1549
- to: optimizeNode(node.to, env, currentDepth)
1550
- };
1551
- case "navigation": {
1552
- const target = optimizeNode(node.target, env, currentDepth);
1553
- const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
1554
- const targetValue = constValue(target);
1555
- if (targetValue !== undefined || target.type === "undefined") {
1556
- let current = targetValue;
1557
- let foldable = true;
1558
- for (const segment of segments) {
1559
- if (!foldable)
1560
- break;
1561
- const key = segment.type === "static" ? segment.key : constValue(segment.key);
1562
- if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
1563
- foldable = false;
1564
- break;
1565
- }
1566
- if (current === null || current === undefined) {
1567
- current = undefined;
1568
- continue;
1569
- }
1570
- current = current[String(key)];
1571
- }
1572
- if (foldable) {
1573
- const literal = toLiteralNode(current);
1574
- if (literal)
1575
- return literal;
1576
- }
1577
- }
1578
- return {
1579
- type: "navigation",
1580
- target,
1581
- segments
1582
- };
1583
- }
1584
- case "call": {
1585
- return {
1586
- type: "call",
1587
- callee: optimizeNode(node.callee, env, currentDepth),
1588
- args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
1589
- };
1590
- }
1591
- case "assign": {
1592
- return {
1593
- type: "assign",
1594
- op: node.op,
1595
- place: optimizeNode(node.place, env, currentDepth, true),
1596
- value: optimizeNode(node.value, env, currentDepth)
1597
- };
1598
- }
1599
- case "conditional": {
1600
- const condition = optimizeNode(node.condition, env, currentDepth);
1601
- const thenEnv = cloneOptimizeEnv(env);
1602
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1603
- thenEnv.selfCaptures[condition.place.name] = currentDepth;
1604
- delete thenEnv.constants[condition.place.name];
1605
- }
1606
- const conditionValue = constValue(condition);
1607
- if (conditionValue !== undefined || condition.type === "undefined") {
1608
- const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1609
- if (passes) {
1610
- const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1611
- if (thenBlock2.length === 0)
1612
- return { type: "undefined" };
1613
- if (thenBlock2.length === 1)
1614
- return thenBlock2[0];
1615
- return { type: "program", body: thenBlock2 };
1616
- }
1617
- if (!node.elseBranch)
1618
- return { type: "undefined" };
1619
- const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1620
- if (!loweredElse)
1621
- return { type: "undefined" };
1622
- if (loweredElse.type === "else") {
1623
- if (loweredElse.block.length === 0)
1624
- return { type: "undefined" };
1625
- if (loweredElse.block.length === 1)
1626
- return loweredElse.block[0];
1627
- return { type: "program", body: loweredElse.block };
1628
- }
1629
- return {
1630
- type: "conditional",
1631
- head: loweredElse.head,
1632
- condition: loweredElse.condition,
1633
- thenBlock: loweredElse.thenBlock,
1634
- elseBranch: loweredElse.elseBranch
1635
- };
1636
- }
1637
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1638
- const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1639
- let finalCondition = condition;
1640
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1641
- const name = condition.place.name;
1642
- const reads = new Set;
1643
- for (const part of thenBlock)
1644
- collectReads(part, reads);
1645
- if (elseBranch)
1646
- collectReadsElse(elseBranch, reads);
1647
- if (!reads.has(name)) {
1648
- finalCondition = condition.value;
1649
- }
855
+ let current = encoded;
856
+ while (true) {
857
+ const groups = buildDedupeCandidateTable(current, effectiveMinBytes);
858
+ let replaced = false;
859
+ for (const [value, occurrences] of groups.entries()) {
860
+ if (occurrences.length < 2)
861
+ continue;
862
+ const canonical = occurrences[occurrences.length - 1];
863
+ for (let index = occurrences.length - 2;index >= 0; index -= 1) {
864
+ const occurrence = occurrences[index];
865
+ if (occurrence.span.end > canonical.span.start)
866
+ continue;
867
+ if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
868
+ continue;
869
+ const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
870
+ const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
871
+ if (!pointerToken)
872
+ continue;
873
+ if (pointerToken.length >= occurrence.sizeBytes)
874
+ continue;
875
+ current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
876
+ replaced = true;
877
+ break;
1650
878
  }
1651
- return {
1652
- type: "conditional",
1653
- head: node.head,
1654
- condition: finalCondition,
1655
- thenBlock,
1656
- elseBranch
1657
- };
1658
- }
1659
- case "for": {
1660
- const sourceEnv = cloneOptimizeEnv(env);
1661
- const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1662
- const bodyEnv = cloneOptimizeEnv(env);
1663
- dropBindingNames(bodyEnv, binding);
1664
- return {
1665
- type: "for",
1666
- binding,
1667
- body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
1668
- };
1669
- }
1670
- case "arrayComprehension": {
1671
- const sourceEnv = cloneOptimizeEnv(env);
1672
- const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1673
- const bodyEnv = cloneOptimizeEnv(env);
1674
- dropBindingNames(bodyEnv, binding);
1675
- return {
1676
- type: "arrayComprehension",
1677
- binding,
1678
- body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1679
- };
879
+ if (replaced)
880
+ break;
1680
881
  }
1681
- case "whileArrayComprehension":
1682
- return {
1683
- type: "whileArrayComprehension",
1684
- condition: optimizeNode(node.condition, env, currentDepth),
1685
- body: optimizeNode(node.body, env, currentDepth + 1)
1686
- };
1687
- case "objectComprehension": {
1688
- const sourceEnv = cloneOptimizeEnv(env);
1689
- const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1690
- const bodyEnv = cloneOptimizeEnv(env);
1691
- dropBindingNames(bodyEnv, binding);
1692
- return {
1693
- type: "objectComprehension",
1694
- binding,
1695
- key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
1696
- value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1697
- };
882
+ if (!replaced)
883
+ return current;
884
+ }
885
+ }
886
+ function encodeIR(node, options) {
887
+ const previous = activeEncodeOptions;
888
+ activeEncodeOptions = options;
889
+ try {
890
+ const encoded = encodeNode(node);
891
+ if (options?.dedupeValues) {
892
+ return dedupeLargeEncodedValues(encoded, options.dedupeMinBytes ?? 4);
1698
893
  }
1699
- case "whileObjectComprehension":
1700
- return {
1701
- type: "whileObjectComprehension",
1702
- condition: optimizeNode(node.condition, env, currentDepth),
1703
- key: optimizeNode(node.key, env, currentDepth + 1),
1704
- value: optimizeNode(node.value, env, currentDepth + 1)
1705
- };
1706
- default:
1707
- return node;
894
+ return encoded;
895
+ } finally {
896
+ activeEncodeOptions = previous;
1708
897
  }
1709
898
  }
1710
899
  function optimizeIR(node) {
1711
- return optimizeNode(node, emptyOptimizeEnv(), 1);
900
+ return node;
1712
901
  }
1713
902
  function collectLocalBindings(node, locals) {
1714
903
  switch (node.type) {
@@ -2468,6 +1657,28 @@ semantics.addOperation("toIR", {
2468
1657
  source: source.toIR()
2469
1658
  };
2470
1659
  },
1660
+ IterBindingComprehension_keyValueIn(key, _comma, value, _in, source) {
1661
+ return {
1662
+ type: "binding:keyValueIn",
1663
+ key: key.sourceString,
1664
+ value: value.sourceString,
1665
+ source: source.toIR()
1666
+ };
1667
+ },
1668
+ IterBindingComprehension_valueIn(value, _in, source) {
1669
+ return {
1670
+ type: "binding:valueIn",
1671
+ value: value.sourceString,
1672
+ source: source.toIR()
1673
+ };
1674
+ },
1675
+ IterBindingComprehension_keyOf(key, _of, source) {
1676
+ return {
1677
+ type: "binding:keyOf",
1678
+ key: key.sourceString,
1679
+ source: source.toIR()
1680
+ };
1681
+ },
2471
1682
  Pair(key, _colon, value) {
2472
1683
  return { key: key.toIR(), value: value.toIR() };
2473
1684
  },
@@ -2531,9 +1742,6 @@ semantics.addOperation("toIR", {
2531
1742
  BooleanKw(_kw) {
2532
1743
  return { type: "identifier", name: "boolean" };
2533
1744
  },
2534
- SizeKw(_kw) {
2535
- return { type: "identifier", name: "size" };
2536
- },
2537
1745
  identifier(_a, _b) {
2538
1746
  return { type: "identifier", name: this.sourceString };
2539
1747
  },
@@ -2574,8 +1782,7 @@ var OPCODES = {
2574
1782
  object: "ob",
2575
1783
  mod: "md",
2576
1784
  neg: "ng",
2577
- range: "rn",
2578
- size: "sz"
1785
+ range: "rn"
2579
1786
  };
2580
1787
  function decodePrefix(text, start, end) {
2581
1788
  let value = 0;
@@ -2846,7 +2053,9 @@ class CursorInterpreter {
2846
2053
  }
2847
2054
  this.ensure(")");
2848
2055
  if (typeof callee === "object" && callee && "__opcode" in callee) {
2849
- return this.applyOpcode(callee.__opcode, args);
2056
+ const marker = callee;
2057
+ const opArgs = marker.__receiver !== undefined ? [marker.__receiver, ...args] : args;
2058
+ return this.applyOpcode(marker.__opcode, opArgs);
2850
2059
  }
2851
2060
  return this.navigate(callee, args);
2852
2061
  }
@@ -3254,10 +2463,19 @@ class CursorInterpreter {
3254
2463
  case OPCODES.add:
3255
2464
  if (args[0] === undefined || args[1] === undefined)
3256
2465
  return;
3257
- if (typeof args[0] === "string" || typeof args[1] === "string") {
3258
- return String(args[0]) + String(args[1]);
2466
+ if (Array.isArray(args[0]) && Array.isArray(args[1])) {
2467
+ return [...args[0], ...args[1]];
2468
+ }
2469
+ if (args[0] && args[1] && typeof args[0] === "object" && typeof args[1] === "object" && !Array.isArray(args[0]) && !Array.isArray(args[1])) {
2470
+ return { ...args[0], ...args[1] };
3259
2471
  }
3260
- return Number(args[0]) + Number(args[1]);
2472
+ if (typeof args[0] === "string" && typeof args[1] === "string") {
2473
+ return args[0] + args[1];
2474
+ }
2475
+ if (typeof args[0] === "number" && typeof args[1] === "number") {
2476
+ return args[0] + args[1];
2477
+ }
2478
+ return;
3261
2479
  case OPCODES.sub:
3262
2480
  if (args[0] === undefined || args[1] === undefined)
3263
2481
  return;
@@ -3335,15 +2553,88 @@ class CursorInterpreter {
3335
2553
  out.push(v);
3336
2554
  return out;
3337
2555
  }
3338
- case OPCODES.size: {
2556
+ case "array:push": {
3339
2557
  const target = args[0];
3340
- if (Array.isArray(target))
3341
- return target.length;
3342
- if (typeof target === "string")
3343
- return Array.from(target).length;
3344
- if (target && typeof target === "object")
3345
- return Object.keys(target).length;
3346
- return;
2558
+ if (!Array.isArray(target))
2559
+ return;
2560
+ const next = target.slice();
2561
+ for (let i = 1;i < args.length; i += 1)
2562
+ next.push(args[i]);
2563
+ return next;
2564
+ }
2565
+ case "array:pop": {
2566
+ const target = args[0];
2567
+ if (!Array.isArray(target) || target.length === 0)
2568
+ return;
2569
+ return target[target.length - 1];
2570
+ }
2571
+ case "array:unshift": {
2572
+ const target = args[0];
2573
+ if (!Array.isArray(target))
2574
+ return;
2575
+ const next = target.slice();
2576
+ for (let i = args.length - 1;i >= 1; i -= 1)
2577
+ next.unshift(args[i]);
2578
+ return next;
2579
+ }
2580
+ case "array:shift": {
2581
+ const target = args[0];
2582
+ if (!Array.isArray(target) || target.length === 0)
2583
+ return;
2584
+ return target[0];
2585
+ }
2586
+ case "array:slice": {
2587
+ const target = args[0];
2588
+ if (!Array.isArray(target))
2589
+ return;
2590
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2591
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2592
+ return target.slice(start, end);
2593
+ }
2594
+ case "array:join": {
2595
+ const target = args[0];
2596
+ if (!Array.isArray(target))
2597
+ return;
2598
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : ",";
2599
+ return target.map((item) => String(item)).join(sep);
2600
+ }
2601
+ case "string:split": {
2602
+ const target = args[0];
2603
+ if (typeof target !== "string")
2604
+ return;
2605
+ if (args.length < 2 || args[1] === undefined)
2606
+ return [target];
2607
+ return target.split(String(args[1]));
2608
+ }
2609
+ case "string:join": {
2610
+ const target = args[0];
2611
+ if (typeof target !== "string")
2612
+ return;
2613
+ const parts = Array.from(target);
2614
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2615
+ return parts.join(sep);
2616
+ }
2617
+ case "string:slice": {
2618
+ const target = args[0];
2619
+ if (typeof target !== "string")
2620
+ return;
2621
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2622
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2623
+ return Array.from(target).slice(start, end).join("");
2624
+ }
2625
+ case "string:starts-with": {
2626
+ const target = args[0];
2627
+ if (typeof target !== "string")
2628
+ return;
2629
+ const prefix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2630
+ return target.startsWith(prefix);
2631
+ }
2632
+ case "string:ends-with": {
2633
+ const target = args[0];
2634
+ if (typeof target !== "string")
2635
+ return;
2636
+ const suffix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2637
+ return target.endsWith(suffix);
3347
2638
  }
3348
2639
  default:
3349
2640
  throw new Error(`Unknown opcode ${id}`);
@@ -3361,16 +2652,26 @@ class CursorInterpreter {
3361
2652
  return current;
3362
2653
  }
3363
2654
  readProperty(target, key) {
2655
+ if (typeof key === "string" && key === "size") {
2656
+ if (Array.isArray(target))
2657
+ return target.length;
2658
+ if (typeof target === "string")
2659
+ return Array.from(target).length;
2660
+ }
3364
2661
  const index = this.parseIndexKey(key);
3365
2662
  if (Array.isArray(target)) {
3366
- if (index === undefined)
3367
- return;
3368
- return target[index];
2663
+ if (index !== undefined)
2664
+ return target[index];
2665
+ if (typeof key === "string")
2666
+ return this.resolveArrayMethod(target, key);
2667
+ return;
3369
2668
  }
3370
2669
  if (typeof target === "string") {
3371
- if (index === undefined)
3372
- return;
3373
- return Array.from(target)[index];
2670
+ if (index !== undefined)
2671
+ return Array.from(target)[index];
2672
+ if (typeof key === "string")
2673
+ return this.resolveStringMethod(target, key);
2674
+ return;
3374
2675
  }
3375
2676
  if (this.isPlainObject(target)) {
3376
2677
  const prop = String(key);
@@ -3380,6 +2681,40 @@ class CursorInterpreter {
3380
2681
  }
3381
2682
  return;
3382
2683
  }
2684
+ resolveArrayMethod(target, key) {
2685
+ switch (key) {
2686
+ case "push":
2687
+ return { __opcode: "array:push", __receiver: target };
2688
+ case "pop":
2689
+ return { __opcode: "array:pop", __receiver: target };
2690
+ case "unshift":
2691
+ return { __opcode: "array:unshift", __receiver: target };
2692
+ case "shift":
2693
+ return { __opcode: "array:shift", __receiver: target };
2694
+ case "slice":
2695
+ return { __opcode: "array:slice", __receiver: target };
2696
+ case "join":
2697
+ return { __opcode: "array:join", __receiver: target };
2698
+ default:
2699
+ return;
2700
+ }
2701
+ }
2702
+ resolveStringMethod(target, key) {
2703
+ switch (key) {
2704
+ case "split":
2705
+ return { __opcode: "string:split", __receiver: target };
2706
+ case "join":
2707
+ return { __opcode: "string:join", __receiver: target };
2708
+ case "slice":
2709
+ return { __opcode: "string:slice", __receiver: target };
2710
+ case "starts-with":
2711
+ return { __opcode: "string:starts-with", __receiver: target };
2712
+ case "ends-with":
2713
+ return { __opcode: "string:ends-with", __receiver: target };
2714
+ default:
2715
+ return;
2716
+ }
2717
+ }
3383
2718
  canWriteProperty(target, key) {
3384
2719
  const index = this.parseIndexKey(key);
3385
2720
  if (Array.isArray(target)) {
@@ -3639,19 +2974,63 @@ function evaluateRexc(text, ctx = {}) {
3639
2974
  // rex-repl.ts
3640
2975
  var req = createRequire2(import.meta.url);
3641
2976
  var { version } = req("./package.json");
3642
- var C = {
3643
- reset: "\x1B[0m",
3644
- bold: "\x1B[1m",
3645
- dim: "\x1B[2m",
3646
- red: "\x1B[31m",
3647
- green: "\x1B[32m",
3648
- yellow: "\x1B[33m",
3649
- blue: "\x1B[34m",
3650
- cyan: "\x1B[36m",
3651
- gray: "\x1B[90m",
3652
- boldBlue: "\x1B[1;34m"
3653
- };
3654
- var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|nor|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
2977
+ function createColors(enabled) {
2978
+ if (!enabled) {
2979
+ return {
2980
+ reset: "",
2981
+ bold: "",
2982
+ dim: "",
2983
+ red: "",
2984
+ green: "",
2985
+ yellow: "",
2986
+ blue: "",
2987
+ magenta: "",
2988
+ cyan: "",
2989
+ gray: "",
2990
+ keyword: ""
2991
+ };
2992
+ }
2993
+ return {
2994
+ reset: "\x1B[0m",
2995
+ bold: "\x1B[1m",
2996
+ dim: "\x1B[2m",
2997
+ red: "\x1B[38;5;203m",
2998
+ green: "\x1B[38;5;114m",
2999
+ yellow: "\x1B[38;5;179m",
3000
+ blue: "\x1B[38;5;75m",
3001
+ magenta: "\x1B[38;5;141m",
3002
+ cyan: "\x1B[38;5;81m",
3003
+ gray: "\x1B[38;5;245m",
3004
+ keyword: "\x1B[1;38;5;208m"
3005
+ };
3006
+ }
3007
+ var colorEnabled = process.stdout.isTTY;
3008
+ var C = createColors(colorEnabled);
3009
+ function setColorEnabled(enabled) {
3010
+ colorEnabled = enabled;
3011
+ C = createColors(enabled);
3012
+ }
3013
+ function formatJson(value, indent = 2) {
3014
+ const normalized = normalizeJsonValue(value, false);
3015
+ const text = JSON.stringify(normalized, null, indent);
3016
+ return text ?? "null";
3017
+ }
3018
+ function normalizeJsonValue(value, inArray) {
3019
+ if (value === undefined)
3020
+ return inArray ? null : undefined;
3021
+ if (value === null || typeof value !== "object")
3022
+ return value;
3023
+ if (Array.isArray(value))
3024
+ return value.map((item) => normalizeJsonValue(item, true));
3025
+ const out = {};
3026
+ for (const [key, val] of Object.entries(value)) {
3027
+ const normalized = normalizeJsonValue(val, false);
3028
+ if (normalized !== undefined)
3029
+ out[key] = normalized;
3030
+ }
3031
+ return out;
3032
+ }
3033
+ var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<objKey>\b[A-Za-z_][A-Za-z0-9_-]*\b)(?=\s*:)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|nor|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)|(?<identifier>\b[A-Za-z_][A-Za-z0-9_.-]*\b)/g;
3655
3034
  function highlightLine(line) {
3656
3035
  let result = "";
3657
3036
  let lastIndex = 0;
@@ -3664,14 +3043,18 @@ function highlightLine(line) {
3664
3043
  result += C.gray + text + C.reset;
3665
3044
  } else if (g.dstring || g.sstring) {
3666
3045
  result += C.green + text + C.reset;
3046
+ } else if (g.objKey) {
3047
+ result += C.magenta + text + C.reset;
3667
3048
  } else if (g.keyword) {
3668
- result += C.boldBlue + text + C.reset;
3049
+ result += C.keyword + text + C.reset;
3669
3050
  } else if (g.literal) {
3670
3051
  result += C.yellow + text + C.reset;
3671
3052
  } else if (g.typePred) {
3672
3053
  result += C.cyan + text + C.reset;
3673
3054
  } else if (g.num) {
3674
3055
  result += C.cyan + text + C.reset;
3056
+ } else if (g.identifier) {
3057
+ result += C.blue + text + C.reset;
3675
3058
  } else {
3676
3059
  result += text;
3677
3060
  }
@@ -3734,7 +3117,7 @@ function highlightRexc(text) {
3734
3117
  i++;
3735
3118
  break;
3736
3119
  case "%":
3737
- out += C.boldBlue + prefix + tag + C.reset;
3120
+ out += C.keyword + prefix + tag + C.reset;
3738
3121
  i++;
3739
3122
  break;
3740
3123
  case "$":
@@ -3772,11 +3155,11 @@ function highlightRexc(text) {
3772
3155
  case ">":
3773
3156
  case "<":
3774
3157
  case "#":
3775
- out += C.boldBlue + prefix + tag + C.reset;
3158
+ out += C.keyword + prefix + tag + C.reset;
3776
3159
  i++;
3777
3160
  break;
3778
3161
  case ";":
3779
- out += C.boldBlue + prefix + tag + C.reset;
3162
+ out += C.keyword + prefix + tag + C.reset;
3780
3163
  i++;
3781
3164
  break;
3782
3165
  case "^":
@@ -3800,6 +3183,23 @@ function highlightRexc(text) {
3800
3183
  }
3801
3184
  return out;
3802
3185
  }
3186
+ function highlightAuto(text, hint) {
3187
+ if (hint === "rexc")
3188
+ return highlightRexc(text);
3189
+ if (hint === "rex")
3190
+ return text.split(`
3191
+ `).map((line) => highlightLine(line)).join(`
3192
+ `);
3193
+ try {
3194
+ const match = grammar.match(text);
3195
+ if (match.succeeded()) {
3196
+ return text.split(`
3197
+ `).map((line) => highlightLine(line)).join(`
3198
+ `);
3199
+ }
3200
+ } catch {}
3201
+ return highlightRexc(text);
3202
+ }
3803
3203
  var JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3804
3204
  function highlightJSON(json) {
3805
3205
  let result = "";
@@ -3868,19 +3268,14 @@ function isIncomplete(buffer) {
3868
3268
  return true;
3869
3269
  return false;
3870
3270
  }
3871
- function formatResult(value) {
3872
- let text;
3873
- try {
3874
- text = stringify(value, { maxWidth: 60 });
3875
- } catch {
3876
- text = String(value);
3877
- }
3878
- return `${C.gray}→${C.reset} ${highlightLine(text)}`;
3879
- }
3880
- function formatVarState(vars) {
3271
+ function formatVarState(vars, format) {
3881
3272
  const entries = Object.entries(vars);
3882
3273
  if (entries.length === 0)
3883
3274
  return "";
3275
+ if (format === "json") {
3276
+ const rendered = highlightJSON(formatJson(vars, 2));
3277
+ return `${C.dim} vars:${C.reset} ${rendered}`;
3278
+ }
3884
3279
  const MAX_LINE = 70;
3885
3280
  const MAX_VALUE = 30;
3886
3281
  const parts = [];
@@ -3905,6 +3300,18 @@ function formatVarState(vars) {
3905
3300
  }
3906
3301
  return `${C.dim} ${parts.join(", ")}${C.reset}`;
3907
3302
  }
3303
+ function renderValue(value, format, kind) {
3304
+ if (format === "json") {
3305
+ return highlightJSON(formatJson(value, 2));
3306
+ }
3307
+ if (kind === "source") {
3308
+ return highlightAuto(String(value ?? ""));
3309
+ }
3310
+ if (kind === "rexc") {
3311
+ return highlightRexc(String(value ?? ""));
3312
+ }
3313
+ return highlightLine(stringify(value, { maxWidth: 120 }));
3314
+ }
3908
3315
  var KEYWORDS = [
3909
3316
  "when",
3910
3317
  "unless",
@@ -3934,8 +3341,35 @@ var KEYWORDS = [
3934
3341
  "array",
3935
3342
  "boolean"
3936
3343
  ];
3344
+ var DOT_COMMANDS = [
3345
+ ".help",
3346
+ ".file",
3347
+ ".cat",
3348
+ ".expr",
3349
+ ".source",
3350
+ ".vars",
3351
+ ".vars!",
3352
+ ".clear",
3353
+ ".ir",
3354
+ ".rexc",
3355
+ ".opt",
3356
+ ".json",
3357
+ ".color",
3358
+ ".exit"
3359
+ ];
3937
3360
  function completer(state) {
3938
3361
  return (line) => {
3362
+ if (line.startsWith(".file ")) {
3363
+ return completeFilePath(line, 6);
3364
+ }
3365
+ if (line.startsWith(".cat ")) {
3366
+ return completeFilePath(line, 5);
3367
+ }
3368
+ if (line.startsWith(".") && !line.includes(" ")) {
3369
+ const partial2 = line.trim();
3370
+ const matches = DOT_COMMANDS.filter((cmd) => cmd.startsWith(partial2));
3371
+ return [matches, line];
3372
+ }
3939
3373
  const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
3940
3374
  const partial = match ? match[0] : "";
3941
3375
  if (!partial)
@@ -3946,45 +3380,92 @@ function completer(state) {
3946
3380
  return [hits, partial];
3947
3381
  };
3948
3382
  }
3949
- function handleDotCommand(cmd, state, rl) {
3383
+ function completeFilePath(line, prefixLength) {
3384
+ const raw = line.slice(prefixLength);
3385
+ const trimmed = raw.trimStart();
3386
+ const quote = trimmed.startsWith('"') || trimmed.startsWith("'") ? trimmed[0] : "";
3387
+ const pathPart = quote ? trimmed.slice(1) : trimmed;
3388
+ const endsWithSlash = pathPart.endsWith("/");
3389
+ const rawDir = endsWithSlash ? pathPart.slice(0, -1) : pathPart;
3390
+ const baseName = endsWithSlash ? "" : rawDir.includes("/") ? basename(rawDir) : rawDir;
3391
+ const dirPart = endsWithSlash ? rawDir || "." : rawDir.includes("/") ? dirname(rawDir) : ".";
3392
+ const dirPath = resolve(dirPart);
3393
+ let entries = [];
3394
+ try {
3395
+ entries = readdirSync(dirPath);
3396
+ } catch {
3397
+ return [[], ""];
3398
+ }
3399
+ const prefix = dirPart === "." ? "" : `${dirPart}/`;
3400
+ const matches = entries.filter((entry) => entry.startsWith(baseName)).map((entry) => {
3401
+ const fullPath = resolve(dirPath, entry);
3402
+ let suffix = "";
3403
+ try {
3404
+ if (statSync(fullPath).isDirectory())
3405
+ suffix = "/";
3406
+ } catch {
3407
+ suffix = "";
3408
+ }
3409
+ return `${quote}${prefix}${entry}${suffix}`;
3410
+ });
3411
+ return [matches, trimmed];
3412
+ }
3413
+ function stripQuotes(value) {
3414
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3415
+ return value.slice(1, -1);
3416
+ }
3417
+ return value;
3418
+ }
3419
+ async function handleDotCommand(cmd, state, rl, runSource, updatePromptStyles) {
3950
3420
  function toggleLabel(on) {
3951
3421
  return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
3952
3422
  }
3953
3423
  switch (cmd) {
3954
3424
  case ".help":
3955
3425
  console.log([
3956
- `${C.boldBlue}Rex REPL Commands:${C.reset}`,
3957
- " .help Show this help message",
3958
- " .vars Show all current variables",
3959
- " .clear Clear all variables",
3960
- " .ir Toggle showing IR JSON after parsing",
3961
- " .rexc Toggle showing compiled rexc before execution",
3962
- " .opt Toggle IR optimizations",
3963
- " .exit Exit the REPL",
3426
+ `${C.keyword}Rex REPL Commands:${C.reset}`,
3427
+ " .help Show this help message",
3428
+ " .file <path> Load and execute a Rex file",
3429
+ " .cat <path> Print a Rex/rexc file with highlighting",
3430
+ " .expr Toggle showing expression results",
3431
+ " .source Toggle showing input source",
3432
+ " .vars Toggle showing variable summary",
3433
+ " .vars! Show all current variables",
3434
+ " .clear Clear all variables",
3435
+ " .ir Toggle showing IR JSON after parsing",
3436
+ " .rexc Toggle showing compiled rexc before execution",
3437
+ " .opt Toggle IR optimizations",
3438
+ " .json Toggle JSON output format",
3439
+ " .color Toggle ANSI color output",
3440
+ " .exit Exit the REPL",
3964
3441
  "",
3965
3442
  "Enter Rex expressions to evaluate them.",
3966
3443
  "Multi-line: open brackets or do/end blocks continue on the next line.",
3967
3444
  "Ctrl-C cancels multi-line input.",
3968
- "Ctrl-D exits."
3445
+ "Ctrl-D exits.",
3446
+ "",
3447
+ "Outputs are printed as labeled blocks when enabled."
3969
3448
  ].join(`
3970
3449
  `));
3971
- return true;
3972
- case ".ir":
3973
- state.showIR = !state.showIR;
3974
- console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3975
- return true;
3976
- case ".rexc":
3977
- state.showRexc = !state.showRexc;
3978
- console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3979
- return true;
3980
- case ".opt":
3981
- state.optimize = !state.optimize;
3982
- console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3983
- return true;
3984
- case ".vars": {
3450
+ return "handled";
3451
+ case ".expr":
3452
+ state.showExpr = !state.showExpr;
3453
+ console.log(`${C.dim} Expression output: ${toggleLabel(state.showExpr)}${C.reset}`);
3454
+ return "handled";
3455
+ case ".source":
3456
+ state.showSource = !state.showSource;
3457
+ console.log(`${C.dim} Source output: ${toggleLabel(state.showSource)}${C.reset}`);
3458
+ return "handled";
3459
+ case ".vars":
3460
+ state.showVars = !state.showVars;
3461
+ console.log(`${C.dim} Variable summary: ${toggleLabel(state.showVars)}${C.reset}`);
3462
+ return "handled";
3463
+ case ".vars!": {
3985
3464
  const entries = Object.entries(state.vars);
3986
3465
  if (entries.length === 0) {
3987
3466
  console.log(`${C.dim} (no variables)${C.reset}`);
3467
+ } else if (state.outputFormat === "json") {
3468
+ console.log(highlightJSON(formatJson(state.vars, 2)));
3988
3469
  } else {
3989
3470
  for (const [key, val] of entries) {
3990
3471
  let valStr;
@@ -3996,43 +3477,121 @@ function handleDotCommand(cmd, state, rl) {
3996
3477
  console.log(` ${key} = ${highlightLine(valStr)}`);
3997
3478
  }
3998
3479
  }
3999
- return true;
3480
+ return "handled";
4000
3481
  }
3482
+ case ".ir":
3483
+ state.showIR = !state.showIR;
3484
+ console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3485
+ return "handled";
3486
+ case ".rexc":
3487
+ state.showRexc = !state.showRexc;
3488
+ console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3489
+ return "handled";
3490
+ case ".opt":
3491
+ state.optimize = !state.optimize;
3492
+ console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3493
+ return "handled";
3494
+ case ".json":
3495
+ state.outputFormat = state.outputFormat === "json" ? "rex" : "json";
3496
+ console.log(`${C.dim} Output format: ${state.outputFormat}${C.reset}`);
3497
+ return "handled";
3498
+ case ".color":
3499
+ setColorEnabled(!colorEnabled);
3500
+ updatePromptStyles();
3501
+ console.log(`${C.dim} Color output: ${toggleLabel(colorEnabled)}${C.reset}`);
3502
+ return "handled";
4001
3503
  case ".clear":
4002
3504
  state.vars = {};
4003
3505
  state.refs = {};
4004
3506
  console.log(`${C.dim} Variables cleared.${C.reset}`);
4005
- return true;
3507
+ return "handled";
4006
3508
  case ".exit":
4007
3509
  rl.close();
4008
- return true;
3510
+ return "handled-noprompt";
4009
3511
  default:
3512
+ if (cmd.startsWith(".cat ")) {
3513
+ const rawPath = cmd.slice(5).trim();
3514
+ if (!rawPath) {
3515
+ console.log(`${C.red} Missing file path. Usage: .cat <path>${C.reset}`);
3516
+ return "handled";
3517
+ }
3518
+ const filePath = resolve(stripQuotes(rawPath));
3519
+ try {
3520
+ const source = readFileSync(filePath, "utf8");
3521
+ const hint = filePath.endsWith(".rexc") ? "rexc" : filePath.endsWith(".rex") ? "rex" : undefined;
3522
+ console.log(highlightAuto(source, hint));
3523
+ } catch (error) {
3524
+ const message = error instanceof Error ? error.message : String(error);
3525
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3526
+ }
3527
+ return "handled";
3528
+ }
3529
+ if (cmd.startsWith(".file ")) {
3530
+ const rawPath = cmd.slice(6).trim();
3531
+ if (!rawPath) {
3532
+ console.log(`${C.red} Missing file path. Usage: .file <path>${C.reset}`);
3533
+ return "handled";
3534
+ }
3535
+ const filePath = resolve(stripQuotes(rawPath));
3536
+ try {
3537
+ const source = readFileSync(filePath, "utf8");
3538
+ runSource(source);
3539
+ } catch (error) {
3540
+ const message = error instanceof Error ? error.message : String(error);
3541
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3542
+ }
3543
+ return "handled";
3544
+ }
4010
3545
  if (cmd.startsWith(".")) {
4011
3546
  console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
4012
- return true;
3547
+ return "handled";
4013
3548
  }
4014
- return false;
3549
+ return "unhandled";
4015
3550
  }
4016
3551
  }
4017
3552
  var GAS_LIMIT = 1e7;
3553
+ var HISTORY_LIMIT = 1000;
3554
+ var HISTORY_PATH = resolve(homedir(), ".rex_history");
4018
3555
  async function startRepl() {
4019
- const state = { vars: {}, refs: {}, showIR: false, showRexc: false, optimize: false };
3556
+ const state = {
3557
+ vars: {},
3558
+ refs: {},
3559
+ showIR: false,
3560
+ showRexc: false,
3561
+ optimize: false,
3562
+ showExpr: true,
3563
+ showVars: false,
3564
+ showSource: false,
3565
+ outputFormat: "rex"
3566
+ };
4020
3567
  let multiLineBuffer = "";
4021
3568
  const PRIMARY_PROMPT = "rex> ";
4022
3569
  const CONT_PROMPT = "... ";
4023
- const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
4024
- const STYLED_CONT = `${C.dim}...${C.reset} `;
4025
3570
  let currentPrompt = PRIMARY_PROMPT;
4026
- let styledPrompt = STYLED_PRIMARY;
4027
- console.log(`${C.boldBlue}Rex${C.reset} v${version} type ${C.dim}.help${C.reset} for commands`);
3571
+ let styledPrompt = "";
3572
+ let styledPrimary = "";
3573
+ let styledCont = "";
3574
+ function updatePromptStyles() {
3575
+ styledPrimary = `${C.keyword}rex${C.reset}> `;
3576
+ styledCont = `${C.dim}...${C.reset} `;
3577
+ styledPrompt = currentPrompt === PRIMARY_PROMPT ? styledPrimary : styledCont;
3578
+ }
3579
+ updatePromptStyles();
3580
+ console.log(`${C.keyword}Rex${C.reset} v${version} — type ${C.dim}.help${C.reset} for commands`);
4028
3581
  const rl = readline.createInterface({
4029
3582
  input: process.stdin,
4030
3583
  output: process.stdout,
4031
3584
  prompt: PRIMARY_PROMPT,
4032
- historySize: 500,
3585
+ historySize: HISTORY_LIMIT,
4033
3586
  completer: completer(state),
4034
3587
  terminal: true
4035
3588
  });
3589
+ try {
3590
+ const historyText = readFileSync(HISTORY_PATH, "utf8");
3591
+ const lines = historyText.split(/\r?\n/).filter((line) => line.trim().length > 0);
3592
+ const recent = lines.slice(-HISTORY_LIMIT);
3593
+ rl.history = recent.reverse();
3594
+ } catch {}
4036
3595
  process.stdin.on("keypress", () => {
4037
3596
  process.nextTick(() => {
4038
3597
  if (!rl.line && rl.line !== "")
@@ -4054,7 +3613,7 @@ async function startRepl() {
4054
3613
  if (multiLineBuffer) {
4055
3614
  multiLineBuffer = "";
4056
3615
  currentPrompt = PRIMARY_PROMPT;
4057
- styledPrompt = STYLED_PRIMARY;
3616
+ styledPrompt = styledPrimary;
4058
3617
  rl.setPrompt(PRIMARY_PROMPT);
4059
3618
  process.stdout.write(`
4060
3619
  `);
@@ -4066,17 +3625,70 @@ async function startRepl() {
4066
3625
  });
4067
3626
  function resetPrompt() {
4068
3627
  currentPrompt = PRIMARY_PROMPT;
4069
- styledPrompt = STYLED_PRIMARY;
3628
+ styledPrompt = styledPrimary;
4070
3629
  rl.setPrompt(PRIMARY_PROMPT);
4071
3630
  rl.prompt();
4072
3631
  }
4073
- rl.on("line", (line) => {
3632
+ function runSource(source) {
3633
+ const match = grammar.match(source);
3634
+ if (!match.succeeded()) {
3635
+ const message = formatParseError(source, match);
3636
+ console.log(`${C.red} ${message}${C.reset}`);
3637
+ return;
3638
+ }
3639
+ try {
3640
+ const outputs = {};
3641
+ if (state.showSource)
3642
+ outputs.source = source;
3643
+ const isRex = grammar.match(source).succeeded();
3644
+ if (!isRex && state.showIR) {
3645
+ console.log(`${C.red} IR output is only available for Rex source.${C.reset}`);
3646
+ }
3647
+ const rexc = isRex ? compile(source, { optimize: state.optimize }) : source;
3648
+ if (state.showIR && isRex) {
3649
+ const ir = parseToIR(source);
3650
+ const lowered = state.optimize ? optimizeIR(ir) : ir;
3651
+ outputs.ir = lowered;
3652
+ }
3653
+ if (state.showRexc)
3654
+ outputs.rexc = rexc;
3655
+ const result = evaluateRexc(rexc, {
3656
+ vars: { ...state.vars },
3657
+ refs: { ...state.refs },
3658
+ gasLimit: GAS_LIMIT
3659
+ });
3660
+ state.vars = result.state.vars;
3661
+ state.refs = result.state.refs;
3662
+ if (state.showExpr)
3663
+ outputs.result = result.value;
3664
+ if (state.showVars)
3665
+ outputs.vars = state.vars;
3666
+ const order = ["source", "ir", "rexc", "vars", "result"];
3667
+ for (const key of order) {
3668
+ const value = outputs[key];
3669
+ if (value === undefined)
3670
+ continue;
3671
+ console.log(`${C.gray} ${key}:${C.reset} ${renderValue(value, state.outputFormat, key)}`);
3672
+ }
3673
+ } catch (error) {
3674
+ const message = error instanceof Error ? error.message : String(error);
3675
+ if (message.includes("Gas limit exceeded")) {
3676
+ console.log(`${C.yellow} ${message}${C.reset}`);
3677
+ } else {
3678
+ console.log(`${C.red} Error: ${message}${C.reset}`);
3679
+ }
3680
+ }
3681
+ }
3682
+ rl.on("line", async (line) => {
4074
3683
  const trimmed = line.trim();
4075
3684
  if (!multiLineBuffer && trimmed.startsWith(".")) {
4076
- if (handleDotCommand(trimmed, state, rl)) {
3685
+ const result = await handleDotCommand(trimmed, state, rl, runSource, updatePromptStyles);
3686
+ if (result === "handled") {
4077
3687
  rl.prompt();
4078
3688
  return;
4079
3689
  }
3690
+ if (result === "handled-noprompt")
3691
+ return;
4080
3692
  }
4081
3693
  multiLineBuffer += (multiLineBuffer ? `
4082
3694
  ` : "") + line;
@@ -4087,60 +3699,35 @@ async function startRepl() {
4087
3699
  }
4088
3700
  if (isIncomplete(multiLineBuffer)) {
4089
3701
  currentPrompt = CONT_PROMPT;
4090
- styledPrompt = STYLED_CONT;
3702
+ styledPrompt = styledCont;
4091
3703
  rl.setPrompt(CONT_PROMPT);
4092
3704
  rl.prompt();
4093
3705
  return;
4094
3706
  }
4095
3707
  const source = multiLineBuffer;
4096
3708
  multiLineBuffer = "";
4097
- const match = grammar.match(source);
4098
- if (!match.succeeded()) {
4099
- console.log(`${C.red} ${match.message}${C.reset}`);
4100
- resetPrompt();
4101
- return;
4102
- }
4103
- try {
4104
- const ir = parseToIR(source);
4105
- const lowered = state.optimize ? optimizeIR(ir) : ir;
4106
- if (state.showIR) {
4107
- console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
4108
- }
4109
- const rexc = compile(source, { optimize: state.optimize });
4110
- if (state.showRexc) {
4111
- console.log(`${C.dim} rexc:${C.reset} ${highlightRexc(rexc)}`);
4112
- }
4113
- const result = evaluateRexc(rexc, {
4114
- vars: { ...state.vars },
4115
- refs: { ...state.refs },
4116
- gasLimit: GAS_LIMIT
4117
- });
4118
- state.vars = result.state.vars;
4119
- state.refs = result.state.refs;
4120
- console.log(formatResult(result.value));
4121
- const varLine = formatVarState(state.vars);
4122
- if (varLine)
4123
- console.log(varLine);
4124
- } catch (error) {
4125
- const message = error instanceof Error ? error.message : String(error);
4126
- if (message.includes("Gas limit exceeded")) {
4127
- console.log(`${C.yellow} ${message}${C.reset}`);
4128
- } else {
4129
- console.log(`${C.red} Error: ${message}${C.reset}`);
4130
- }
4131
- }
3709
+ runSource(source);
4132
3710
  resetPrompt();
4133
3711
  });
4134
3712
  rl.on("close", () => {
3713
+ try {
3714
+ const history = rl.history ?? [];
3715
+ const trimmed = history.slice().reverse().filter((line) => line.trim().length > 0).slice(-HISTORY_LIMIT);
3716
+ writeFileSync(HISTORY_PATH, `${trimmed.join(`
3717
+ `)}
3718
+ `, "utf8");
3719
+ } catch {}
4135
3720
  process.exit(0);
4136
3721
  });
4137
3722
  rl.prompt();
4138
3723
  }
4139
3724
  export {
4140
3725
  startRepl,
3726
+ setColorEnabled,
4141
3727
  isIncomplete,
4142
3728
  highlightRexc,
4143
3729
  highlightLine,
4144
3730
  highlightJSON,
3731
+ highlightAuto,
4145
3732
  formatVarState
4146
3733
  };