@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-cli.js CHANGED
@@ -441,11 +441,30 @@ function collectLogicalChain(node, op) {
441
441
  return [node];
442
442
  return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
443
443
  }
444
+ function formatParseError(source, match) {
445
+ const message = match.message ?? "Parse failed";
446
+ const pos = match.getRightmostFailurePosition?.();
447
+ if (typeof pos !== "number" || !Number.isFinite(pos))
448
+ return message;
449
+ const safePos = Math.max(0, Math.min(source.length, pos));
450
+ const lineStart = source.lastIndexOf(`
451
+ `, safePos - 1) + 1;
452
+ const lineEndIndex = source.indexOf(`
453
+ `, safePos);
454
+ const lineEnd = lineEndIndex === -1 ? source.length : lineEndIndex;
455
+ const lineText = source.slice(lineStart, lineEnd);
456
+ const lineNumber = source.slice(0, lineStart).split(`
457
+ `).length;
458
+ const columnNumber = safePos - lineStart + 1;
459
+ const caret = `${" ".repeat(Math.max(0, columnNumber - 1))}^`;
460
+ return `${message}
461
+ ${lineText}
462
+ ${caret}`;
463
+ }
444
464
  function parseToIR(source) {
445
465
  const match = grammar.match(source);
446
466
  if (!match.succeeded()) {
447
- const failure = match;
448
- throw new Error(failure.message ?? "Parse failed");
467
+ throw new Error(formatParseError(source, match));
449
468
  }
450
469
  return semantics(match).toIR();
451
470
  }
@@ -789,20 +808,11 @@ function gatherEncodedValueSpans(text) {
789
808
  }
790
809
  return spans;
791
810
  }
792
- function buildPointerToken(pointerStart, targetStart) {
793
- let offset = targetStart - (pointerStart + 1);
811
+ function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
812
+ const offset = targetStart - pointerStart - occurrenceSize;
794
813
  if (offset < 0)
795
814
  return;
796
- for (let guard = 0;guard < 8; guard += 1) {
797
- const prefix = encodeUint(offset);
798
- const recalculated = targetStart - (pointerStart + prefix.length + 1);
799
- if (recalculated === offset)
800
- return `${prefix}^`;
801
- offset = recalculated;
802
- if (offset < 0)
803
- return;
804
- }
805
- return;
815
+ return `${encodeUint(offset)}^`;
806
816
  }
807
817
  function buildDedupeCandidateTable(encoded, minBytes) {
808
818
  const spans = gatherEncodedValueSpans(encoded);
@@ -844,7 +854,7 @@ function dedupeLargeEncodedValues(encoded, minBytes = 4) {
844
854
  if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
845
855
  continue;
846
856
  const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
847
- const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
857
+ const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
848
858
  if (!pointerToken)
849
859
  continue;
850
860
  if (pointerToken.length >= occurrence.sizeBytes)
@@ -873,831 +883,8 @@ function encodeIR(node, options) {
873
883
  activeEncodeOptions = previous;
874
884
  }
875
885
  }
876
- function cloneNode(node) {
877
- return structuredClone(node);
878
- }
879
- function emptyOptimizeEnv() {
880
- return { constants: {}, selfCaptures: {} };
881
- }
882
- function cloneOptimizeEnv(env) {
883
- return {
884
- constants: { ...env.constants },
885
- selfCaptures: { ...env.selfCaptures }
886
- };
887
- }
888
- function clearOptimizeEnv(env) {
889
- for (const key of Object.keys(env.constants))
890
- delete env.constants[key];
891
- for (const key of Object.keys(env.selfCaptures))
892
- delete env.selfCaptures[key];
893
- }
894
- function clearBinding(env, name) {
895
- delete env.constants[name];
896
- delete env.selfCaptures[name];
897
- }
898
- function selfTargetFromNode(node, currentDepth) {
899
- if (node.type === "self")
900
- return currentDepth;
901
- if (node.type === "selfDepth") {
902
- const target = currentDepth - (node.depth - 1);
903
- if (target >= 1)
904
- return target;
905
- }
906
- return;
907
- }
908
- function selfNodeFromTarget(targetDepth, currentDepth) {
909
- const relDepth = currentDepth - targetDepth + 1;
910
- if (!Number.isInteger(relDepth) || relDepth < 1)
911
- return;
912
- if (relDepth === 1)
913
- return { type: "self" };
914
- return { type: "selfDepth", depth: relDepth };
915
- }
916
- function dropBindingNames(env, binding) {
917
- if (binding.type === "binding:valueIn") {
918
- clearBinding(env, binding.value);
919
- return;
920
- }
921
- if (binding.type === "binding:keyValueIn") {
922
- clearBinding(env, binding.key);
923
- clearBinding(env, binding.value);
924
- return;
925
- }
926
- if (binding.type === "binding:keyOf") {
927
- clearBinding(env, binding.key);
928
- }
929
- }
930
- function optimizeBinding(binding, sourceEnv, currentDepth) {
931
- const source = optimizeNode(binding.source, sourceEnv, currentDepth);
932
- switch (binding.type) {
933
- case "binding:bareIn":
934
- return { type: "binding:bareIn", source };
935
- case "binding:bareOf":
936
- return { type: "binding:bareOf", source };
937
- case "binding:valueIn":
938
- return { type: "binding:valueIn", value: binding.value, source };
939
- case "binding:keyValueIn":
940
- return { type: "binding:keyValueIn", key: binding.key, value: binding.value, source };
941
- case "binding:keyOf":
942
- return { type: "binding:keyOf", key: binding.key, source };
943
- }
944
- }
945
- function collectReads(node, out) {
946
- switch (node.type) {
947
- case "identifier":
948
- out.add(node.name);
949
- return;
950
- case "group":
951
- collectReads(node.expression, out);
952
- return;
953
- case "array":
954
- for (const item of node.items)
955
- collectReads(item, out);
956
- return;
957
- case "object":
958
- for (const entry of node.entries) {
959
- collectReads(entry.key, out);
960
- collectReads(entry.value, out);
961
- }
962
- return;
963
- case "arrayComprehension":
964
- collectReads(node.binding.source, out);
965
- collectReads(node.body, out);
966
- return;
967
- case "whileArrayComprehension":
968
- collectReads(node.condition, out);
969
- collectReads(node.body, out);
970
- return;
971
- case "objectComprehension":
972
- collectReads(node.binding.source, out);
973
- collectReads(node.key, out);
974
- collectReads(node.value, out);
975
- return;
976
- case "whileObjectComprehension":
977
- collectReads(node.condition, out);
978
- collectReads(node.key, out);
979
- collectReads(node.value, out);
980
- return;
981
- case "unary":
982
- collectReads(node.value, out);
983
- return;
984
- case "binary":
985
- collectReads(node.left, out);
986
- collectReads(node.right, out);
987
- return;
988
- case "assign":
989
- if (!(node.op === "=" && node.place.type === "identifier"))
990
- collectReads(node.place, out);
991
- collectReads(node.value, out);
992
- return;
993
- case "navigation":
994
- collectReads(node.target, out);
995
- for (const segment of node.segments) {
996
- if (segment.type === "dynamic")
997
- collectReads(segment.key, out);
998
- }
999
- return;
1000
- case "call":
1001
- collectReads(node.callee, out);
1002
- for (const arg of node.args)
1003
- collectReads(arg, out);
1004
- return;
1005
- case "conditional":
1006
- collectReads(node.condition, out);
1007
- for (const part of node.thenBlock)
1008
- collectReads(part, out);
1009
- if (node.elseBranch)
1010
- collectReadsElse(node.elseBranch, out);
1011
- return;
1012
- case "for":
1013
- collectReads(node.binding.source, out);
1014
- for (const part of node.body)
1015
- collectReads(part, out);
1016
- return;
1017
- case "range":
1018
- collectReads(node.from, out);
1019
- collectReads(node.to, out);
1020
- return;
1021
- case "program":
1022
- for (const part of node.body)
1023
- collectReads(part, out);
1024
- return;
1025
- default:
1026
- return;
1027
- }
1028
- }
1029
- function collectReadsElse(elseBranch, out) {
1030
- if (elseBranch.type === "else") {
1031
- for (const part of elseBranch.block)
1032
- collectReads(part, out);
1033
- return;
1034
- }
1035
- collectReads(elseBranch.condition, out);
1036
- for (const part of elseBranch.thenBlock)
1037
- collectReads(part, out);
1038
- if (elseBranch.elseBranch)
1039
- collectReadsElse(elseBranch.elseBranch, out);
1040
- }
1041
- function isPureNode(node) {
1042
- switch (node.type) {
1043
- case "identifier":
1044
- case "self":
1045
- case "selfDepth":
1046
- case "boolean":
1047
- case "null":
1048
- case "undefined":
1049
- case "number":
1050
- case "string":
1051
- case "key":
1052
- return true;
1053
- case "group":
1054
- return isPureNode(node.expression);
1055
- case "array":
1056
- return node.items.every((item) => isPureNode(item));
1057
- case "object":
1058
- return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
1059
- case "navigation":
1060
- return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
1061
- case "unary":
1062
- return node.op !== "delete" && isPureNode(node.value);
1063
- case "binary":
1064
- return isPureNode(node.left) && isPureNode(node.right);
1065
- case "range":
1066
- return isPureNode(node.from) && isPureNode(node.to);
1067
- default:
1068
- return false;
1069
- }
1070
- }
1071
- function eliminateDeadAssignments(block) {
1072
- const needed = new Set;
1073
- const out = [];
1074
- for (let index = block.length - 1;index >= 0; index -= 1) {
1075
- const node = block[index];
1076
- if (node.type === "conditional") {
1077
- let rewritten = node;
1078
- if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
1079
- const name = node.condition.place.name;
1080
- const branchReads = new Set;
1081
- for (const part of node.thenBlock)
1082
- collectReads(part, branchReads);
1083
- if (node.elseBranch)
1084
- collectReadsElse(node.elseBranch, branchReads);
1085
- if (!needed.has(name) && !branchReads.has(name)) {
1086
- rewritten = {
1087
- type: "conditional",
1088
- head: node.head,
1089
- condition: node.condition.value,
1090
- thenBlock: node.thenBlock,
1091
- elseBranch: node.elseBranch
1092
- };
1093
- }
1094
- }
1095
- collectReads(rewritten, needed);
1096
- out.push(rewritten);
1097
- continue;
1098
- }
1099
- if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
1100
- collectReads(node.value, needed);
1101
- const name = node.place.name;
1102
- const canDrop = !needed.has(name) && isPureNode(node.value);
1103
- needed.delete(name);
1104
- if (canDrop)
1105
- continue;
1106
- out.push(node);
1107
- continue;
1108
- }
1109
- collectReads(node, needed);
1110
- out.push(node);
1111
- }
1112
- out.reverse();
1113
- return out;
1114
- }
1115
- function hasIdentifierRead(node, name, asPlace = false) {
1116
- if (node.type === "identifier")
1117
- return !asPlace && node.name === name;
1118
- switch (node.type) {
1119
- case "group":
1120
- return hasIdentifierRead(node.expression, name);
1121
- case "array":
1122
- return node.items.some((item) => hasIdentifierRead(item, name));
1123
- case "object":
1124
- return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
1125
- case "navigation":
1126
- return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
1127
- case "unary":
1128
- return hasIdentifierRead(node.value, name, node.op === "delete");
1129
- case "binary":
1130
- return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1131
- case "range":
1132
- return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
1133
- case "assign":
1134
- return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1135
- default:
1136
- return false;
1137
- }
1138
- }
1139
- function countIdentifierReads(node, name, asPlace = false) {
1140
- if (node.type === "identifier")
1141
- return !asPlace && node.name === name ? 1 : 0;
1142
- switch (node.type) {
1143
- case "group":
1144
- return countIdentifierReads(node.expression, name);
1145
- case "array":
1146
- return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
1147
- case "object":
1148
- return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
1149
- case "navigation":
1150
- return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
1151
- case "unary":
1152
- return countIdentifierReads(node.value, name, node.op === "delete");
1153
- case "binary":
1154
- return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1155
- case "range":
1156
- return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
1157
- case "assign":
1158
- return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1159
- default:
1160
- return 0;
1161
- }
1162
- }
1163
- function replaceIdentifier(node, name, replacement, asPlace = false) {
1164
- if (node.type === "identifier") {
1165
- if (!asPlace && node.name === name)
1166
- return cloneNode(replacement);
1167
- return node;
1168
- }
1169
- switch (node.type) {
1170
- case "group":
1171
- return {
1172
- type: "group",
1173
- expression: replaceIdentifier(node.expression, name, replacement)
1174
- };
1175
- case "array":
1176
- return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
1177
- case "object":
1178
- return {
1179
- type: "object",
1180
- entries: node.entries.map((entry) => ({
1181
- key: replaceIdentifier(entry.key, name, replacement),
1182
- value: replaceIdentifier(entry.value, name, replacement)
1183
- }))
1184
- };
1185
- case "navigation":
1186
- return {
1187
- type: "navigation",
1188
- target: replaceIdentifier(node.target, name, replacement),
1189
- segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
1190
- };
1191
- case "unary":
1192
- return {
1193
- type: "unary",
1194
- op: node.op,
1195
- value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
1196
- };
1197
- case "binary":
1198
- return {
1199
- type: "binary",
1200
- op: node.op,
1201
- left: replaceIdentifier(node.left, name, replacement),
1202
- right: replaceIdentifier(node.right, name, replacement)
1203
- };
1204
- case "assign":
1205
- return {
1206
- type: "assign",
1207
- op: node.op,
1208
- place: replaceIdentifier(node.place, name, replacement, true),
1209
- value: replaceIdentifier(node.value, name, replacement)
1210
- };
1211
- case "range":
1212
- return {
1213
- type: "range",
1214
- from: replaceIdentifier(node.from, name, replacement),
1215
- to: replaceIdentifier(node.to, name, replacement)
1216
- };
1217
- default:
1218
- return node;
1219
- }
1220
- }
1221
- function isSafeInlineTargetNode(node) {
1222
- if (isPureNode(node))
1223
- return true;
1224
- if (node.type === "assign" && node.op === "=") {
1225
- return isPureNode(node.place) && isPureNode(node.value);
1226
- }
1227
- return false;
1228
- }
1229
- function inlineAdjacentPureAssignments(block) {
1230
- const out = [...block];
1231
- let changed = true;
1232
- while (changed) {
1233
- changed = false;
1234
- for (let index = 0;index < out.length - 1; index += 1) {
1235
- const current = out[index];
1236
- if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
1237
- continue;
1238
- if (!isPureNode(current.value))
1239
- continue;
1240
- const name = current.place.name;
1241
- if (hasIdentifierRead(current.value, name))
1242
- continue;
1243
- const next = out[index + 1];
1244
- if (!isSafeInlineTargetNode(next))
1245
- continue;
1246
- if (countIdentifierReads(next, name) !== 1)
1247
- continue;
1248
- out[index + 1] = replaceIdentifier(next, name, current.value);
1249
- out.splice(index, 1);
1250
- changed = true;
1251
- break;
1252
- }
1253
- }
1254
- return out;
1255
- }
1256
- function toNumberNode(value) {
1257
- let raw;
1258
- if (Number.isNaN(value))
1259
- raw = "nan";
1260
- else if (value === Infinity)
1261
- raw = "inf";
1262
- else if (value === -Infinity)
1263
- raw = "-inf";
1264
- else
1265
- raw = String(value);
1266
- return { type: "number", raw, value };
1267
- }
1268
- function toStringNode(value) {
1269
- return { type: "string", raw: JSON.stringify(value) };
1270
- }
1271
- function toLiteralNode(value) {
1272
- if (value === undefined)
1273
- return { type: "undefined" };
1274
- if (value === null)
1275
- return { type: "null" };
1276
- if (typeof value === "boolean")
1277
- return { type: "boolean", value };
1278
- if (typeof value === "number")
1279
- return toNumberNode(value);
1280
- if (typeof value === "string")
1281
- return toStringNode(value);
1282
- if (Array.isArray(value)) {
1283
- const items = [];
1284
- for (const item of value) {
1285
- const lowered = toLiteralNode(item);
1286
- if (!lowered)
1287
- return;
1288
- items.push(lowered);
1289
- }
1290
- return { type: "array", items };
1291
- }
1292
- if (value && typeof value === "object") {
1293
- const entries = [];
1294
- for (const [key, entryValue] of Object.entries(value)) {
1295
- const loweredValue = toLiteralNode(entryValue);
1296
- if (!loweredValue)
1297
- return;
1298
- entries.push({ key: { type: "key", name: key }, value: loweredValue });
1299
- }
1300
- return { type: "object", entries };
1301
- }
1302
- return;
1303
- }
1304
- function constValue(node) {
1305
- switch (node.type) {
1306
- case "undefined":
1307
- return;
1308
- case "null":
1309
- return null;
1310
- case "boolean":
1311
- return node.value;
1312
- case "number":
1313
- return node.value;
1314
- case "string":
1315
- return decodeStringLiteral(node.raw);
1316
- case "key":
1317
- return node.name;
1318
- case "array": {
1319
- const out = [];
1320
- for (const item of node.items) {
1321
- const value = constValue(item);
1322
- if (value === undefined && item.type !== "undefined")
1323
- return;
1324
- out.push(value);
1325
- }
1326
- return out;
1327
- }
1328
- case "object": {
1329
- const out = {};
1330
- for (const entry of node.entries) {
1331
- const key = constValue(entry.key);
1332
- if (key === undefined && entry.key.type !== "undefined")
1333
- return;
1334
- const value = constValue(entry.value);
1335
- if (value === undefined && entry.value.type !== "undefined")
1336
- return;
1337
- out[String(key)] = value;
1338
- }
1339
- return out;
1340
- }
1341
- default:
1342
- return;
1343
- }
1344
- }
1345
- function isDefinedValue(value) {
1346
- return value !== undefined;
1347
- }
1348
- function foldUnary(op, value) {
1349
- if (op === "neg") {
1350
- if (typeof value !== "number")
1351
- return;
1352
- return -value;
1353
- }
1354
- if (op === "not") {
1355
- if (typeof value === "boolean")
1356
- return !value;
1357
- if (typeof value === "number")
1358
- return ~value;
1359
- return;
1360
- }
1361
- if (op === "logicalNot") {
1362
- return value === undefined ? true : undefined;
1363
- }
1364
- return;
1365
- }
1366
- function foldBinary(op, left, right) {
1367
- if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
1368
- if (typeof left !== "number" || typeof right !== "number")
1369
- return;
1370
- if (op === "add")
1371
- return left + right;
1372
- if (op === "sub")
1373
- return left - right;
1374
- if (op === "mul")
1375
- return left * right;
1376
- if (op === "div")
1377
- return left / right;
1378
- return left % right;
1379
- }
1380
- if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
1381
- if (typeof left !== "number" || typeof right !== "number")
1382
- return;
1383
- if (op === "bitAnd")
1384
- return left & right;
1385
- if (op === "bitOr")
1386
- return left | right;
1387
- return left ^ right;
1388
- }
1389
- if (op === "eq")
1390
- return left === right ? left : undefined;
1391
- if (op === "neq")
1392
- return left !== right ? left : undefined;
1393
- if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
1394
- if (typeof left !== "number" || typeof right !== "number")
1395
- return;
1396
- if (op === "gt")
1397
- return left > right ? left : undefined;
1398
- if (op === "gte")
1399
- return left >= right ? left : undefined;
1400
- if (op === "lt")
1401
- return left < right ? left : undefined;
1402
- return left <= right ? left : undefined;
1403
- }
1404
- if (op === "and")
1405
- return isDefinedValue(left) ? right : undefined;
1406
- if (op === "or")
1407
- return isDefinedValue(left) ? left : right;
1408
- return;
1409
- }
1410
- function optimizeElse(elseBranch, env, currentDepth) {
1411
- if (!elseBranch)
1412
- return;
1413
- if (elseBranch.type === "else") {
1414
- return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
1415
- }
1416
- const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
1417
- const foldedCondition = constValue(optimizedCondition);
1418
- if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
1419
- const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
1420
- if (passes) {
1421
- return {
1422
- type: "else",
1423
- block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
1424
- };
1425
- }
1426
- return optimizeElse(elseBranch.elseBranch, env, currentDepth);
1427
- }
1428
- return {
1429
- type: "elseChain",
1430
- head: elseBranch.head,
1431
- condition: optimizedCondition,
1432
- thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
1433
- elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
1434
- };
1435
- }
1436
- function optimizeBlock(block, env, currentDepth) {
1437
- const out = [];
1438
- for (const node of block) {
1439
- const optimized = optimizeNode(node, env, currentDepth);
1440
- out.push(optimized);
1441
- if (optimized.type === "break" || optimized.type === "continue")
1442
- break;
1443
- if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
1444
- const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
1445
- if (selfTarget !== undefined) {
1446
- env.selfCaptures[optimized.place.name] = selfTarget;
1447
- delete env.constants[optimized.place.name];
1448
- continue;
1449
- }
1450
- const folded = constValue(optimized.value);
1451
- if (folded !== undefined || optimized.value.type === "undefined") {
1452
- env.constants[optimized.place.name] = cloneNode(optimized.value);
1453
- delete env.selfCaptures[optimized.place.name];
1454
- } else {
1455
- clearBinding(env, optimized.place.name);
1456
- }
1457
- continue;
1458
- }
1459
- if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
1460
- clearBinding(env, optimized.value.name);
1461
- continue;
1462
- }
1463
- if (optimized.type === "assign" && optimized.place.type === "identifier") {
1464
- clearBinding(env, optimized.place.name);
1465
- continue;
1466
- }
1467
- if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
1468
- clearOptimizeEnv(env);
1469
- }
1470
- }
1471
- return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
1472
- }
1473
- function optimizeNode(node, env, currentDepth, asPlace = false) {
1474
- switch (node.type) {
1475
- case "program": {
1476
- const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
1477
- if (body.length === 0)
1478
- return { type: "undefined" };
1479
- if (body.length === 1)
1480
- return body[0];
1481
- return { type: "program", body };
1482
- }
1483
- case "identifier": {
1484
- if (asPlace)
1485
- return node;
1486
- const selfTarget = env.selfCaptures[node.name];
1487
- if (selfTarget !== undefined) {
1488
- const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
1489
- if (rewritten)
1490
- return rewritten;
1491
- }
1492
- const replacement = env.constants[node.name];
1493
- return replacement ? cloneNode(replacement) : node;
1494
- }
1495
- case "group": {
1496
- return optimizeNode(node.expression, env, currentDepth);
1497
- }
1498
- case "array": {
1499
- return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
1500
- }
1501
- case "object": {
1502
- return {
1503
- type: "object",
1504
- entries: node.entries.map((entry) => ({
1505
- key: optimizeNode(entry.key, env, currentDepth),
1506
- value: optimizeNode(entry.value, env, currentDepth)
1507
- }))
1508
- };
1509
- }
1510
- case "unary": {
1511
- const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
1512
- const foldedValue = constValue(value);
1513
- if (foldedValue !== undefined || value.type === "undefined") {
1514
- const folded = foldUnary(node.op, foldedValue);
1515
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1516
- if (literal)
1517
- return literal;
1518
- }
1519
- return { type: "unary", op: node.op, value };
1520
- }
1521
- case "binary": {
1522
- const left = optimizeNode(node.left, env, currentDepth);
1523
- const right = optimizeNode(node.right, env, currentDepth);
1524
- const leftValue = constValue(left);
1525
- const rightValue = constValue(right);
1526
- if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
1527
- const folded = foldBinary(node.op, leftValue, rightValue);
1528
- const literal = folded === undefined ? undefined : toLiteralNode(folded);
1529
- if (literal)
1530
- return literal;
1531
- }
1532
- return { type: "binary", op: node.op, left, right };
1533
- }
1534
- case "range":
1535
- return {
1536
- type: "range",
1537
- from: optimizeNode(node.from, env, currentDepth),
1538
- to: optimizeNode(node.to, env, currentDepth)
1539
- };
1540
- case "navigation": {
1541
- const target = optimizeNode(node.target, env, currentDepth);
1542
- const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
1543
- const targetValue = constValue(target);
1544
- if (targetValue !== undefined || target.type === "undefined") {
1545
- let current = targetValue;
1546
- let foldable = true;
1547
- for (const segment of segments) {
1548
- if (!foldable)
1549
- break;
1550
- const key = segment.type === "static" ? segment.key : constValue(segment.key);
1551
- if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
1552
- foldable = false;
1553
- break;
1554
- }
1555
- if (current === null || current === undefined) {
1556
- current = undefined;
1557
- continue;
1558
- }
1559
- current = current[String(key)];
1560
- }
1561
- if (foldable) {
1562
- const literal = toLiteralNode(current);
1563
- if (literal)
1564
- return literal;
1565
- }
1566
- }
1567
- return {
1568
- type: "navigation",
1569
- target,
1570
- segments
1571
- };
1572
- }
1573
- case "call": {
1574
- return {
1575
- type: "call",
1576
- callee: optimizeNode(node.callee, env, currentDepth),
1577
- args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
1578
- };
1579
- }
1580
- case "assign": {
1581
- return {
1582
- type: "assign",
1583
- op: node.op,
1584
- place: optimizeNode(node.place, env, currentDepth, true),
1585
- value: optimizeNode(node.value, env, currentDepth)
1586
- };
1587
- }
1588
- case "conditional": {
1589
- const condition = optimizeNode(node.condition, env, currentDepth);
1590
- const thenEnv = cloneOptimizeEnv(env);
1591
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1592
- thenEnv.selfCaptures[condition.place.name] = currentDepth;
1593
- delete thenEnv.constants[condition.place.name];
1594
- }
1595
- const conditionValue = constValue(condition);
1596
- if (conditionValue !== undefined || condition.type === "undefined") {
1597
- const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1598
- if (passes) {
1599
- const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1600
- if (thenBlock2.length === 0)
1601
- return { type: "undefined" };
1602
- if (thenBlock2.length === 1)
1603
- return thenBlock2[0];
1604
- return { type: "program", body: thenBlock2 };
1605
- }
1606
- if (!node.elseBranch)
1607
- return { type: "undefined" };
1608
- const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1609
- if (!loweredElse)
1610
- return { type: "undefined" };
1611
- if (loweredElse.type === "else") {
1612
- if (loweredElse.block.length === 0)
1613
- return { type: "undefined" };
1614
- if (loweredElse.block.length === 1)
1615
- return loweredElse.block[0];
1616
- return { type: "program", body: loweredElse.block };
1617
- }
1618
- return {
1619
- type: "conditional",
1620
- head: loweredElse.head,
1621
- condition: loweredElse.condition,
1622
- thenBlock: loweredElse.thenBlock,
1623
- elseBranch: loweredElse.elseBranch
1624
- };
1625
- }
1626
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1627
- const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1628
- let finalCondition = condition;
1629
- if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1630
- const name = condition.place.name;
1631
- const reads = new Set;
1632
- for (const part of thenBlock)
1633
- collectReads(part, reads);
1634
- if (elseBranch)
1635
- collectReadsElse(elseBranch, reads);
1636
- if (!reads.has(name)) {
1637
- finalCondition = condition.value;
1638
- }
1639
- }
1640
- return {
1641
- type: "conditional",
1642
- head: node.head,
1643
- condition: finalCondition,
1644
- thenBlock,
1645
- elseBranch
1646
- };
1647
- }
1648
- case "for": {
1649
- const sourceEnv = cloneOptimizeEnv(env);
1650
- const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1651
- const bodyEnv = cloneOptimizeEnv(env);
1652
- dropBindingNames(bodyEnv, binding);
1653
- return {
1654
- type: "for",
1655
- binding,
1656
- body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
1657
- };
1658
- }
1659
- case "arrayComprehension": {
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: "arrayComprehension",
1666
- binding,
1667
- body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1668
- };
1669
- }
1670
- case "whileArrayComprehension":
1671
- return {
1672
- type: "whileArrayComprehension",
1673
- condition: optimizeNode(node.condition, env, currentDepth),
1674
- body: optimizeNode(node.body, env, currentDepth + 1)
1675
- };
1676
- case "objectComprehension": {
1677
- const sourceEnv = cloneOptimizeEnv(env);
1678
- const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1679
- const bodyEnv = cloneOptimizeEnv(env);
1680
- dropBindingNames(bodyEnv, binding);
1681
- return {
1682
- type: "objectComprehension",
1683
- binding,
1684
- key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
1685
- value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1686
- };
1687
- }
1688
- case "whileObjectComprehension":
1689
- return {
1690
- type: "whileObjectComprehension",
1691
- condition: optimizeNode(node.condition, env, currentDepth),
1692
- key: optimizeNode(node.key, env, currentDepth + 1),
1693
- value: optimizeNode(node.value, env, currentDepth + 1)
1694
- };
1695
- default:
1696
- return node;
1697
- }
1698
- }
1699
886
  function optimizeIR(node) {
1700
- return optimizeNode(node, emptyOptimizeEnv(), 1);
887
+ return node;
1701
888
  }
1702
889
  function collectLocalBindings(node, locals) {
1703
890
  switch (node.type) {
@@ -2210,10 +1397,9 @@ var init_rex = __esm(() => {
2210
1397
  object: "ob",
2211
1398
  mod: "md",
2212
1399
  neg: "ng",
2213
- range: "rn",
2214
- size: "sz"
1400
+ range: "rn"
2215
1401
  };
2216
- KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object", "size"]);
1402
+ KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object"]);
2217
1403
  BINARY_TO_OPCODE = {
2218
1404
  add: "add",
2219
1405
  sub: "sub",
@@ -2503,22 +1689,44 @@ var init_rex = __esm(() => {
2503
1689
  source: source.toIR()
2504
1690
  };
2505
1691
  },
2506
- IterBinding_keyOf(key, _of, source) {
1692
+ IterBinding_keyOf(key, _of, source) {
1693
+ return {
1694
+ type: "binding:keyOf",
1695
+ key: key.sourceString,
1696
+ source: source.toIR()
1697
+ };
1698
+ },
1699
+ IterBinding_bareIn(_in, source) {
1700
+ return {
1701
+ type: "binding:bareIn",
1702
+ source: source.toIR()
1703
+ };
1704
+ },
1705
+ IterBinding_bareOf(_of, source) {
1706
+ return {
1707
+ type: "binding:bareOf",
1708
+ source: source.toIR()
1709
+ };
1710
+ },
1711
+ IterBindingComprehension_keyValueIn(key, _comma, value, _in, source) {
2507
1712
  return {
2508
- type: "binding:keyOf",
1713
+ type: "binding:keyValueIn",
2509
1714
  key: key.sourceString,
1715
+ value: value.sourceString,
2510
1716
  source: source.toIR()
2511
1717
  };
2512
1718
  },
2513
- IterBinding_bareIn(_in, source) {
1719
+ IterBindingComprehension_valueIn(value, _in, source) {
2514
1720
  return {
2515
- type: "binding:bareIn",
1721
+ type: "binding:valueIn",
1722
+ value: value.sourceString,
2516
1723
  source: source.toIR()
2517
1724
  };
2518
1725
  },
2519
- IterBinding_bareOf(_of, source) {
1726
+ IterBindingComprehension_keyOf(key, _of, source) {
2520
1727
  return {
2521
- type: "binding:bareOf",
1728
+ type: "binding:keyOf",
1729
+ key: key.sourceString,
2522
1730
  source: source.toIR()
2523
1731
  };
2524
1732
  },
@@ -2585,9 +1793,6 @@ var init_rex = __esm(() => {
2585
1793
  BooleanKw(_kw) {
2586
1794
  return { type: "identifier", name: "boolean" };
2587
1795
  },
2588
- SizeKw(_kw) {
2589
- return { type: "identifier", name: "size" };
2590
- },
2591
1796
  identifier(_a, _b) {
2592
1797
  return { type: "identifier", name: this.sourceString };
2593
1798
  },
@@ -2873,7 +2078,9 @@ class CursorInterpreter {
2873
2078
  }
2874
2079
  this.ensure(")");
2875
2080
  if (typeof callee === "object" && callee && "__opcode" in callee) {
2876
- return this.applyOpcode(callee.__opcode, args);
2081
+ const marker = callee;
2082
+ const opArgs = marker.__receiver !== undefined ? [marker.__receiver, ...args] : args;
2083
+ return this.applyOpcode(marker.__opcode, opArgs);
2877
2084
  }
2878
2085
  return this.navigate(callee, args);
2879
2086
  }
@@ -3281,10 +2488,19 @@ class CursorInterpreter {
3281
2488
  case OPCODES.add:
3282
2489
  if (args[0] === undefined || args[1] === undefined)
3283
2490
  return;
3284
- if (typeof args[0] === "string" || typeof args[1] === "string") {
3285
- return String(args[0]) + String(args[1]);
2491
+ if (Array.isArray(args[0]) && Array.isArray(args[1])) {
2492
+ return [...args[0], ...args[1]];
2493
+ }
2494
+ if (args[0] && args[1] && typeof args[0] === "object" && typeof args[1] === "object" && !Array.isArray(args[0]) && !Array.isArray(args[1])) {
2495
+ return { ...args[0], ...args[1] };
2496
+ }
2497
+ if (typeof args[0] === "string" && typeof args[1] === "string") {
2498
+ return args[0] + args[1];
3286
2499
  }
3287
- return Number(args[0]) + Number(args[1]);
2500
+ if (typeof args[0] === "number" && typeof args[1] === "number") {
2501
+ return args[0] + args[1];
2502
+ }
2503
+ return;
3288
2504
  case OPCODES.sub:
3289
2505
  if (args[0] === undefined || args[1] === undefined)
3290
2506
  return;
@@ -3362,15 +2578,88 @@ class CursorInterpreter {
3362
2578
  out.push(v);
3363
2579
  return out;
3364
2580
  }
3365
- case OPCODES.size: {
2581
+ case "array:push": {
3366
2582
  const target = args[0];
3367
- if (Array.isArray(target))
3368
- return target.length;
3369
- if (typeof target === "string")
3370
- return Array.from(target).length;
3371
- if (target && typeof target === "object")
3372
- return Object.keys(target).length;
3373
- return;
2583
+ if (!Array.isArray(target))
2584
+ return;
2585
+ const next = target.slice();
2586
+ for (let i = 1;i < args.length; i += 1)
2587
+ next.push(args[i]);
2588
+ return next;
2589
+ }
2590
+ case "array:pop": {
2591
+ const target = args[0];
2592
+ if (!Array.isArray(target) || target.length === 0)
2593
+ return;
2594
+ return target[target.length - 1];
2595
+ }
2596
+ case "array:unshift": {
2597
+ const target = args[0];
2598
+ if (!Array.isArray(target))
2599
+ return;
2600
+ const next = target.slice();
2601
+ for (let i = args.length - 1;i >= 1; i -= 1)
2602
+ next.unshift(args[i]);
2603
+ return next;
2604
+ }
2605
+ case "array:shift": {
2606
+ const target = args[0];
2607
+ if (!Array.isArray(target) || target.length === 0)
2608
+ return;
2609
+ return target[0];
2610
+ }
2611
+ case "array:slice": {
2612
+ const target = args[0];
2613
+ if (!Array.isArray(target))
2614
+ return;
2615
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2616
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2617
+ return target.slice(start, end);
2618
+ }
2619
+ case "array:join": {
2620
+ const target = args[0];
2621
+ if (!Array.isArray(target))
2622
+ return;
2623
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : ",";
2624
+ return target.map((item) => String(item)).join(sep);
2625
+ }
2626
+ case "string:split": {
2627
+ const target = args[0];
2628
+ if (typeof target !== "string")
2629
+ return;
2630
+ if (args.length < 2 || args[1] === undefined)
2631
+ return [target];
2632
+ return target.split(String(args[1]));
2633
+ }
2634
+ case "string:join": {
2635
+ const target = args[0];
2636
+ if (typeof target !== "string")
2637
+ return;
2638
+ const parts = Array.from(target);
2639
+ const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2640
+ return parts.join(sep);
2641
+ }
2642
+ case "string:slice": {
2643
+ const target = args[0];
2644
+ if (typeof target !== "string")
2645
+ return;
2646
+ const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
2647
+ const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
2648
+ return Array.from(target).slice(start, end).join("");
2649
+ }
2650
+ case "string:starts-with": {
2651
+ const target = args[0];
2652
+ if (typeof target !== "string")
2653
+ return;
2654
+ const prefix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2655
+ return target.startsWith(prefix);
2656
+ }
2657
+ case "string:ends-with": {
2658
+ const target = args[0];
2659
+ if (typeof target !== "string")
2660
+ return;
2661
+ const suffix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
2662
+ return target.endsWith(suffix);
3374
2663
  }
3375
2664
  default:
3376
2665
  throw new Error(`Unknown opcode ${id}`);
@@ -3388,16 +2677,26 @@ class CursorInterpreter {
3388
2677
  return current;
3389
2678
  }
3390
2679
  readProperty(target, key) {
2680
+ if (typeof key === "string" && key === "size") {
2681
+ if (Array.isArray(target))
2682
+ return target.length;
2683
+ if (typeof target === "string")
2684
+ return Array.from(target).length;
2685
+ }
3391
2686
  const index = this.parseIndexKey(key);
3392
2687
  if (Array.isArray(target)) {
3393
- if (index === undefined)
3394
- return;
3395
- return target[index];
2688
+ if (index !== undefined)
2689
+ return target[index];
2690
+ if (typeof key === "string")
2691
+ return this.resolveArrayMethod(target, key);
2692
+ return;
3396
2693
  }
3397
2694
  if (typeof target === "string") {
3398
- if (index === undefined)
3399
- return;
3400
- return Array.from(target)[index];
2695
+ if (index !== undefined)
2696
+ return Array.from(target)[index];
2697
+ if (typeof key === "string")
2698
+ return this.resolveStringMethod(target, key);
2699
+ return;
3401
2700
  }
3402
2701
  if (this.isPlainObject(target)) {
3403
2702
  const prop = String(key);
@@ -3407,6 +2706,40 @@ class CursorInterpreter {
3407
2706
  }
3408
2707
  return;
3409
2708
  }
2709
+ resolveArrayMethod(target, key) {
2710
+ switch (key) {
2711
+ case "push":
2712
+ return { __opcode: "array:push", __receiver: target };
2713
+ case "pop":
2714
+ return { __opcode: "array:pop", __receiver: target };
2715
+ case "unshift":
2716
+ return { __opcode: "array:unshift", __receiver: target };
2717
+ case "shift":
2718
+ return { __opcode: "array:shift", __receiver: target };
2719
+ case "slice":
2720
+ return { __opcode: "array:slice", __receiver: target };
2721
+ case "join":
2722
+ return { __opcode: "array:join", __receiver: target };
2723
+ default:
2724
+ return;
2725
+ }
2726
+ }
2727
+ resolveStringMethod(target, key) {
2728
+ switch (key) {
2729
+ case "split":
2730
+ return { __opcode: "string:split", __receiver: target };
2731
+ case "join":
2732
+ return { __opcode: "string:join", __receiver: target };
2733
+ case "slice":
2734
+ return { __opcode: "string:slice", __receiver: target };
2735
+ case "starts-with":
2736
+ return { __opcode: "string:starts-with", __receiver: target };
2737
+ case "ends-with":
2738
+ return { __opcode: "string:ends-with", __receiver: target };
2739
+ default:
2740
+ return;
2741
+ }
2742
+ }
3410
2743
  canWriteProperty(target, key) {
3411
2744
  const index = this.parseIndexKey(key);
3412
2745
  if (Array.isArray(target)) {
@@ -3692,8 +3025,7 @@ var init_rexc_interpreter = __esm(() => {
3692
3025
  object: "ob",
3693
3026
  mod: "md",
3694
3027
  neg: "ng",
3695
- range: "rn",
3696
- size: "sz"
3028
+ range: "rn"
3697
3029
  };
3698
3030
  });
3699
3031
 
@@ -3701,14 +3033,73 @@ var init_rexc_interpreter = __esm(() => {
3701
3033
  var exports_rex_repl = {};
3702
3034
  __export(exports_rex_repl, {
3703
3035
  startRepl: () => startRepl,
3036
+ setColorEnabled: () => setColorEnabled,
3704
3037
  isIncomplete: () => isIncomplete,
3705
3038
  highlightRexc: () => highlightRexc,
3706
3039
  highlightLine: () => highlightLine,
3707
3040
  highlightJSON: () => highlightJSON,
3041
+ highlightAuto: () => highlightAuto,
3708
3042
  formatVarState: () => formatVarState
3709
3043
  });
3710
3044
  import * as readline from "node:readline";
3711
3045
  import { createRequire as createRequire2 } from "node:module";
3046
+ import { readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
3047
+ import { resolve, dirname, basename } from "node:path";
3048
+ import { homedir } from "node:os";
3049
+ function createColors(enabled) {
3050
+ if (!enabled) {
3051
+ return {
3052
+ reset: "",
3053
+ bold: "",
3054
+ dim: "",
3055
+ red: "",
3056
+ green: "",
3057
+ yellow: "",
3058
+ blue: "",
3059
+ magenta: "",
3060
+ cyan: "",
3061
+ gray: "",
3062
+ keyword: ""
3063
+ };
3064
+ }
3065
+ return {
3066
+ reset: "\x1B[0m",
3067
+ bold: "\x1B[1m",
3068
+ dim: "\x1B[2m",
3069
+ red: "\x1B[38;5;203m",
3070
+ green: "\x1B[38;5;114m",
3071
+ yellow: "\x1B[38;5;179m",
3072
+ blue: "\x1B[38;5;75m",
3073
+ magenta: "\x1B[38;5;141m",
3074
+ cyan: "\x1B[38;5;81m",
3075
+ gray: "\x1B[38;5;245m",
3076
+ keyword: "\x1B[1;38;5;208m"
3077
+ };
3078
+ }
3079
+ function setColorEnabled(enabled) {
3080
+ colorEnabled = enabled;
3081
+ C = createColors(enabled);
3082
+ }
3083
+ function formatJson(value, indent = 2) {
3084
+ const normalized = normalizeJsonValue(value, false);
3085
+ const text = JSON.stringify(normalized, null, indent);
3086
+ return text ?? "null";
3087
+ }
3088
+ function normalizeJsonValue(value, inArray) {
3089
+ if (value === undefined)
3090
+ return inArray ? null : undefined;
3091
+ if (value === null || typeof value !== "object")
3092
+ return value;
3093
+ if (Array.isArray(value))
3094
+ return value.map((item) => normalizeJsonValue(item, true));
3095
+ const out = {};
3096
+ for (const [key, val] of Object.entries(value)) {
3097
+ const normalized = normalizeJsonValue(val, false);
3098
+ if (normalized !== undefined)
3099
+ out[key] = normalized;
3100
+ }
3101
+ return out;
3102
+ }
3712
3103
  function highlightLine(line) {
3713
3104
  let result = "";
3714
3105
  let lastIndex = 0;
@@ -3721,14 +3112,18 @@ function highlightLine(line) {
3721
3112
  result += C.gray + text + C.reset;
3722
3113
  } else if (g.dstring || g.sstring) {
3723
3114
  result += C.green + text + C.reset;
3115
+ } else if (g.objKey) {
3116
+ result += C.magenta + text + C.reset;
3724
3117
  } else if (g.keyword) {
3725
- result += C.boldBlue + text + C.reset;
3118
+ result += C.keyword + text + C.reset;
3726
3119
  } else if (g.literal) {
3727
3120
  result += C.yellow + text + C.reset;
3728
3121
  } else if (g.typePred) {
3729
3122
  result += C.cyan + text + C.reset;
3730
3123
  } else if (g.num) {
3731
3124
  result += C.cyan + text + C.reset;
3125
+ } else if (g.identifier) {
3126
+ result += C.blue + text + C.reset;
3732
3127
  } else {
3733
3128
  result += text;
3734
3129
  }
@@ -3790,7 +3185,7 @@ function highlightRexc(text) {
3790
3185
  i++;
3791
3186
  break;
3792
3187
  case "%":
3793
- out += C.boldBlue + prefix + tag + C.reset;
3188
+ out += C.keyword + prefix + tag + C.reset;
3794
3189
  i++;
3795
3190
  break;
3796
3191
  case "$":
@@ -3828,11 +3223,11 @@ function highlightRexc(text) {
3828
3223
  case ">":
3829
3224
  case "<":
3830
3225
  case "#":
3831
- out += C.boldBlue + prefix + tag + C.reset;
3226
+ out += C.keyword + prefix + tag + C.reset;
3832
3227
  i++;
3833
3228
  break;
3834
3229
  case ";":
3835
- out += C.boldBlue + prefix + tag + C.reset;
3230
+ out += C.keyword + prefix + tag + C.reset;
3836
3231
  i++;
3837
3232
  break;
3838
3233
  case "^":
@@ -3856,6 +3251,23 @@ function highlightRexc(text) {
3856
3251
  }
3857
3252
  return out;
3858
3253
  }
3254
+ function highlightAuto(text, hint) {
3255
+ if (hint === "rexc")
3256
+ return highlightRexc(text);
3257
+ if (hint === "rex")
3258
+ return text.split(`
3259
+ `).map((line) => highlightLine(line)).join(`
3260
+ `);
3261
+ try {
3262
+ const match = grammar.match(text);
3263
+ if (match.succeeded()) {
3264
+ return text.split(`
3265
+ `).map((line) => highlightLine(line)).join(`
3266
+ `);
3267
+ }
3268
+ } catch {}
3269
+ return highlightRexc(text);
3270
+ }
3859
3271
  function highlightJSON(json) {
3860
3272
  let result = "";
3861
3273
  let lastIndex = 0;
@@ -3923,19 +3335,14 @@ function isIncomplete(buffer) {
3923
3335
  return true;
3924
3336
  return false;
3925
3337
  }
3926
- function formatResult(value) {
3927
- let text;
3928
- try {
3929
- text = stringify(value, { maxWidth: 60 });
3930
- } catch {
3931
- text = String(value);
3932
- }
3933
- return `${C.gray}→${C.reset} ${highlightLine(text)}`;
3934
- }
3935
- function formatVarState(vars) {
3338
+ function formatVarState(vars, format) {
3936
3339
  const entries = Object.entries(vars);
3937
3340
  if (entries.length === 0)
3938
3341
  return "";
3342
+ if (format === "json") {
3343
+ const rendered = highlightJSON(formatJson(vars, 2));
3344
+ return `${C.dim} vars:${C.reset} ${rendered}`;
3345
+ }
3939
3346
  const MAX_LINE = 70;
3940
3347
  const MAX_VALUE = 30;
3941
3348
  const parts = [];
@@ -3960,8 +3367,31 @@ function formatVarState(vars) {
3960
3367
  }
3961
3368
  return `${C.dim} ${parts.join(", ")}${C.reset}`;
3962
3369
  }
3370
+ function renderValue(value, format, kind) {
3371
+ if (format === "json") {
3372
+ return highlightJSON(formatJson(value, 2));
3373
+ }
3374
+ if (kind === "source") {
3375
+ return highlightAuto(String(value ?? ""));
3376
+ }
3377
+ if (kind === "rexc") {
3378
+ return highlightRexc(String(value ?? ""));
3379
+ }
3380
+ return highlightLine(stringify(value, { maxWidth: 120 }));
3381
+ }
3963
3382
  function completer(state) {
3964
3383
  return (line) => {
3384
+ if (line.startsWith(".file ")) {
3385
+ return completeFilePath(line, 6);
3386
+ }
3387
+ if (line.startsWith(".cat ")) {
3388
+ return completeFilePath(line, 5);
3389
+ }
3390
+ if (line.startsWith(".") && !line.includes(" ")) {
3391
+ const partial2 = line.trim();
3392
+ const matches = DOT_COMMANDS.filter((cmd) => cmd.startsWith(partial2));
3393
+ return [matches, line];
3394
+ }
3965
3395
  const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
3966
3396
  const partial = match ? match[0] : "";
3967
3397
  if (!partial)
@@ -3972,45 +3402,92 @@ function completer(state) {
3972
3402
  return [hits, partial];
3973
3403
  };
3974
3404
  }
3975
- function handleDotCommand(cmd, state, rl) {
3405
+ function completeFilePath(line, prefixLength) {
3406
+ const raw = line.slice(prefixLength);
3407
+ const trimmed = raw.trimStart();
3408
+ const quote = trimmed.startsWith('"') || trimmed.startsWith("'") ? trimmed[0] : "";
3409
+ const pathPart = quote ? trimmed.slice(1) : trimmed;
3410
+ const endsWithSlash = pathPart.endsWith("/");
3411
+ const rawDir = endsWithSlash ? pathPart.slice(0, -1) : pathPart;
3412
+ const baseName = endsWithSlash ? "" : rawDir.includes("/") ? basename(rawDir) : rawDir;
3413
+ const dirPart = endsWithSlash ? rawDir || "." : rawDir.includes("/") ? dirname(rawDir) : ".";
3414
+ const dirPath = resolve(dirPart);
3415
+ let entries = [];
3416
+ try {
3417
+ entries = readdirSync(dirPath);
3418
+ } catch {
3419
+ return [[], ""];
3420
+ }
3421
+ const prefix = dirPart === "." ? "" : `${dirPart}/`;
3422
+ const matches = entries.filter((entry) => entry.startsWith(baseName)).map((entry) => {
3423
+ const fullPath = resolve(dirPath, entry);
3424
+ let suffix = "";
3425
+ try {
3426
+ if (statSync(fullPath).isDirectory())
3427
+ suffix = "/";
3428
+ } catch {
3429
+ suffix = "";
3430
+ }
3431
+ return `${quote}${prefix}${entry}${suffix}`;
3432
+ });
3433
+ return [matches, trimmed];
3434
+ }
3435
+ function stripQuotes(value) {
3436
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3437
+ return value.slice(1, -1);
3438
+ }
3439
+ return value;
3440
+ }
3441
+ async function handleDotCommand(cmd, state, rl, runSource, updatePromptStyles) {
3976
3442
  function toggleLabel(on) {
3977
3443
  return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
3978
3444
  }
3979
3445
  switch (cmd) {
3980
3446
  case ".help":
3981
3447
  console.log([
3982
- `${C.boldBlue}Rex REPL Commands:${C.reset}`,
3983
- " .help Show this help message",
3984
- " .vars Show all current variables",
3985
- " .clear Clear all variables",
3986
- " .ir Toggle showing IR JSON after parsing",
3987
- " .rexc Toggle showing compiled rexc before execution",
3988
- " .opt Toggle IR optimizations",
3989
- " .exit Exit the REPL",
3448
+ `${C.keyword}Rex REPL Commands:${C.reset}`,
3449
+ " .help Show this help message",
3450
+ " .file <path> Load and execute a Rex file",
3451
+ " .cat <path> Print a Rex/rexc file with highlighting",
3452
+ " .expr Toggle showing expression results",
3453
+ " .source Toggle showing input source",
3454
+ " .vars Toggle showing variable summary",
3455
+ " .vars! Show all current variables",
3456
+ " .clear Clear all variables",
3457
+ " .ir Toggle showing IR JSON after parsing",
3458
+ " .rexc Toggle showing compiled rexc before execution",
3459
+ " .opt Toggle IR optimizations",
3460
+ " .json Toggle JSON output format",
3461
+ " .color Toggle ANSI color output",
3462
+ " .exit Exit the REPL",
3990
3463
  "",
3991
3464
  "Enter Rex expressions to evaluate them.",
3992
3465
  "Multi-line: open brackets or do/end blocks continue on the next line.",
3993
3466
  "Ctrl-C cancels multi-line input.",
3994
- "Ctrl-D exits."
3467
+ "Ctrl-D exits.",
3468
+ "",
3469
+ "Outputs are printed as labeled blocks when enabled."
3995
3470
  ].join(`
3996
3471
  `));
3997
- return true;
3998
- case ".ir":
3999
- state.showIR = !state.showIR;
4000
- console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
4001
- return true;
4002
- case ".rexc":
4003
- state.showRexc = !state.showRexc;
4004
- console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
4005
- return true;
4006
- case ".opt":
4007
- state.optimize = !state.optimize;
4008
- console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
4009
- return true;
4010
- case ".vars": {
3472
+ return "handled";
3473
+ case ".expr":
3474
+ state.showExpr = !state.showExpr;
3475
+ console.log(`${C.dim} Expression output: ${toggleLabel(state.showExpr)}${C.reset}`);
3476
+ return "handled";
3477
+ case ".source":
3478
+ state.showSource = !state.showSource;
3479
+ console.log(`${C.dim} Source output: ${toggleLabel(state.showSource)}${C.reset}`);
3480
+ return "handled";
3481
+ case ".vars":
3482
+ state.showVars = !state.showVars;
3483
+ console.log(`${C.dim} Variable summary: ${toggleLabel(state.showVars)}${C.reset}`);
3484
+ return "handled";
3485
+ case ".vars!": {
4011
3486
  const entries = Object.entries(state.vars);
4012
3487
  if (entries.length === 0) {
4013
3488
  console.log(`${C.dim} (no variables)${C.reset}`);
3489
+ } else if (state.outputFormat === "json") {
3490
+ console.log(highlightJSON(formatJson(state.vars, 2)));
4014
3491
  } else {
4015
3492
  for (const [key, val] of entries) {
4016
3493
  let valStr;
@@ -4022,42 +3499,118 @@ function handleDotCommand(cmd, state, rl) {
4022
3499
  console.log(` ${key} = ${highlightLine(valStr)}`);
4023
3500
  }
4024
3501
  }
4025
- return true;
3502
+ return "handled";
4026
3503
  }
3504
+ case ".ir":
3505
+ state.showIR = !state.showIR;
3506
+ console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
3507
+ return "handled";
3508
+ case ".rexc":
3509
+ state.showRexc = !state.showRexc;
3510
+ console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
3511
+ return "handled";
3512
+ case ".opt":
3513
+ state.optimize = !state.optimize;
3514
+ console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
3515
+ return "handled";
3516
+ case ".json":
3517
+ state.outputFormat = state.outputFormat === "json" ? "rex" : "json";
3518
+ console.log(`${C.dim} Output format: ${state.outputFormat}${C.reset}`);
3519
+ return "handled";
3520
+ case ".color":
3521
+ setColorEnabled(!colorEnabled);
3522
+ updatePromptStyles();
3523
+ console.log(`${C.dim} Color output: ${toggleLabel(colorEnabled)}${C.reset}`);
3524
+ return "handled";
4027
3525
  case ".clear":
4028
3526
  state.vars = {};
4029
3527
  state.refs = {};
4030
3528
  console.log(`${C.dim} Variables cleared.${C.reset}`);
4031
- return true;
3529
+ return "handled";
4032
3530
  case ".exit":
4033
3531
  rl.close();
4034
- return true;
3532
+ return "handled-noprompt";
4035
3533
  default:
3534
+ if (cmd.startsWith(".cat ")) {
3535
+ const rawPath = cmd.slice(5).trim();
3536
+ if (!rawPath) {
3537
+ console.log(`${C.red} Missing file path. Usage: .cat <path>${C.reset}`);
3538
+ return "handled";
3539
+ }
3540
+ const filePath = resolve(stripQuotes(rawPath));
3541
+ try {
3542
+ const source = readFileSync(filePath, "utf8");
3543
+ const hint = filePath.endsWith(".rexc") ? "rexc" : filePath.endsWith(".rex") ? "rex" : undefined;
3544
+ console.log(highlightAuto(source, hint));
3545
+ } catch (error) {
3546
+ const message = error instanceof Error ? error.message : String(error);
3547
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3548
+ }
3549
+ return "handled";
3550
+ }
3551
+ if (cmd.startsWith(".file ")) {
3552
+ const rawPath = cmd.slice(6).trim();
3553
+ if (!rawPath) {
3554
+ console.log(`${C.red} Missing file path. Usage: .file <path>${C.reset}`);
3555
+ return "handled";
3556
+ }
3557
+ const filePath = resolve(stripQuotes(rawPath));
3558
+ try {
3559
+ const source = readFileSync(filePath, "utf8");
3560
+ runSource(source);
3561
+ } catch (error) {
3562
+ const message = error instanceof Error ? error.message : String(error);
3563
+ console.log(`${C.red} File error: ${message}${C.reset}`);
3564
+ }
3565
+ return "handled";
3566
+ }
4036
3567
  if (cmd.startsWith(".")) {
4037
3568
  console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
4038
- return true;
3569
+ return "handled";
4039
3570
  }
4040
- return false;
3571
+ return "unhandled";
4041
3572
  }
4042
3573
  }
4043
3574
  async function startRepl() {
4044
- const state = { vars: {}, refs: {}, showIR: false, showRexc: false, optimize: false };
3575
+ const state = {
3576
+ vars: {},
3577
+ refs: {},
3578
+ showIR: false,
3579
+ showRexc: false,
3580
+ optimize: false,
3581
+ showExpr: true,
3582
+ showVars: false,
3583
+ showSource: false,
3584
+ outputFormat: "rex"
3585
+ };
4045
3586
  let multiLineBuffer = "";
4046
3587
  const PRIMARY_PROMPT = "rex> ";
4047
3588
  const CONT_PROMPT = "... ";
4048
- const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
4049
- const STYLED_CONT = `${C.dim}...${C.reset} `;
4050
3589
  let currentPrompt = PRIMARY_PROMPT;
4051
- let styledPrompt = STYLED_PRIMARY;
4052
- console.log(`${C.boldBlue}Rex${C.reset} v${version} type ${C.dim}.help${C.reset} for commands`);
3590
+ let styledPrompt = "";
3591
+ let styledPrimary = "";
3592
+ let styledCont = "";
3593
+ function updatePromptStyles() {
3594
+ styledPrimary = `${C.keyword}rex${C.reset}> `;
3595
+ styledCont = `${C.dim}...${C.reset} `;
3596
+ styledPrompt = currentPrompt === PRIMARY_PROMPT ? styledPrimary : styledCont;
3597
+ }
3598
+ updatePromptStyles();
3599
+ console.log(`${C.keyword}Rex${C.reset} v${version} — type ${C.dim}.help${C.reset} for commands`);
4053
3600
  const rl = readline.createInterface({
4054
3601
  input: process.stdin,
4055
3602
  output: process.stdout,
4056
3603
  prompt: PRIMARY_PROMPT,
4057
- historySize: 500,
3604
+ historySize: HISTORY_LIMIT,
4058
3605
  completer: completer(state),
4059
3606
  terminal: true
4060
3607
  });
3608
+ try {
3609
+ const historyText = readFileSync(HISTORY_PATH, "utf8");
3610
+ const lines = historyText.split(/\r?\n/).filter((line) => line.trim().length > 0);
3611
+ const recent = lines.slice(-HISTORY_LIMIT);
3612
+ rl.history = recent.reverse();
3613
+ } catch {}
4061
3614
  process.stdin.on("keypress", () => {
4062
3615
  process.nextTick(() => {
4063
3616
  if (!rl.line && rl.line !== "")
@@ -4079,7 +3632,7 @@ async function startRepl() {
4079
3632
  if (multiLineBuffer) {
4080
3633
  multiLineBuffer = "";
4081
3634
  currentPrompt = PRIMARY_PROMPT;
4082
- styledPrompt = STYLED_PRIMARY;
3635
+ styledPrompt = styledPrimary;
4083
3636
  rl.setPrompt(PRIMARY_PROMPT);
4084
3637
  process.stdout.write(`
4085
3638
  `);
@@ -4091,50 +3644,33 @@ async function startRepl() {
4091
3644
  });
4092
3645
  function resetPrompt() {
4093
3646
  currentPrompt = PRIMARY_PROMPT;
4094
- styledPrompt = STYLED_PRIMARY;
3647
+ styledPrompt = styledPrimary;
4095
3648
  rl.setPrompt(PRIMARY_PROMPT);
4096
3649
  rl.prompt();
4097
3650
  }
4098
- rl.on("line", (line) => {
4099
- const trimmed = line.trim();
4100
- if (!multiLineBuffer && trimmed.startsWith(".")) {
4101
- if (handleDotCommand(trimmed, state, rl)) {
4102
- rl.prompt();
4103
- return;
4104
- }
4105
- }
4106
- multiLineBuffer += (multiLineBuffer ? `
4107
- ` : "") + line;
4108
- if (multiLineBuffer.trim() === "") {
4109
- multiLineBuffer = "";
4110
- rl.prompt();
4111
- return;
4112
- }
4113
- if (isIncomplete(multiLineBuffer)) {
4114
- currentPrompt = CONT_PROMPT;
4115
- styledPrompt = STYLED_CONT;
4116
- rl.setPrompt(CONT_PROMPT);
4117
- rl.prompt();
4118
- return;
4119
- }
4120
- const source = multiLineBuffer;
4121
- multiLineBuffer = "";
3651
+ function runSource(source) {
4122
3652
  const match = grammar.match(source);
4123
3653
  if (!match.succeeded()) {
4124
- console.log(`${C.red} ${match.message}${C.reset}`);
4125
- resetPrompt();
3654
+ const message = formatParseError(source, match);
3655
+ console.log(`${C.red} ${message}${C.reset}`);
4126
3656
  return;
4127
3657
  }
4128
3658
  try {
4129
- const ir = parseToIR(source);
4130
- const lowered = state.optimize ? optimizeIR(ir) : ir;
4131
- if (state.showIR) {
4132
- console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3659
+ const outputs = {};
3660
+ if (state.showSource)
3661
+ outputs.source = source;
3662
+ const isRex = grammar.match(source).succeeded();
3663
+ if (!isRex && state.showIR) {
3664
+ console.log(`${C.red} IR output is only available for Rex source.${C.reset}`);
4133
3665
  }
4134
- const rexc = compile(source, { optimize: state.optimize });
4135
- if (state.showRexc) {
4136
- console.log(`${C.dim} rexc:${C.reset} ${highlightRexc(rexc)}`);
3666
+ const rexc = isRex ? compile(source, { optimize: state.optimize }) : source;
3667
+ if (state.showIR && isRex) {
3668
+ const ir = parseToIR(source);
3669
+ const lowered = state.optimize ? optimizeIR(ir) : ir;
3670
+ outputs.ir = lowered;
4137
3671
  }
3672
+ if (state.showRexc)
3673
+ outputs.rexc = rexc;
4138
3674
  const result = evaluateRexc(rexc, {
4139
3675
  vars: { ...state.vars },
4140
3676
  refs: { ...state.refs },
@@ -4142,10 +3678,17 @@ async function startRepl() {
4142
3678
  });
4143
3679
  state.vars = result.state.vars;
4144
3680
  state.refs = result.state.refs;
4145
- console.log(formatResult(result.value));
4146
- const varLine = formatVarState(state.vars);
4147
- if (varLine)
4148
- console.log(varLine);
3681
+ if (state.showExpr)
3682
+ outputs.result = result.value;
3683
+ if (state.showVars)
3684
+ outputs.vars = state.vars;
3685
+ const order = ["source", "ir", "rexc", "vars", "result"];
3686
+ for (const key of order) {
3687
+ const value = outputs[key];
3688
+ if (value === undefined)
3689
+ continue;
3690
+ console.log(`${C.gray} ${key}:${C.reset} ${renderValue(value, state.outputFormat, key)}`);
3691
+ }
4149
3692
  } catch (error) {
4150
3693
  const message = error instanceof Error ? error.message : String(error);
4151
3694
  if (message.includes("Gas limit exceeded")) {
@@ -4154,32 +3697,58 @@ async function startRepl() {
4154
3697
  console.log(`${C.red} Error: ${message}${C.reset}`);
4155
3698
  }
4156
3699
  }
3700
+ }
3701
+ rl.on("line", async (line) => {
3702
+ const trimmed = line.trim();
3703
+ if (!multiLineBuffer && trimmed.startsWith(".")) {
3704
+ const result = await handleDotCommand(trimmed, state, rl, runSource, updatePromptStyles);
3705
+ if (result === "handled") {
3706
+ rl.prompt();
3707
+ return;
3708
+ }
3709
+ if (result === "handled-noprompt")
3710
+ return;
3711
+ }
3712
+ multiLineBuffer += (multiLineBuffer ? `
3713
+ ` : "") + line;
3714
+ if (multiLineBuffer.trim() === "") {
3715
+ multiLineBuffer = "";
3716
+ rl.prompt();
3717
+ return;
3718
+ }
3719
+ if (isIncomplete(multiLineBuffer)) {
3720
+ currentPrompt = CONT_PROMPT;
3721
+ styledPrompt = styledCont;
3722
+ rl.setPrompt(CONT_PROMPT);
3723
+ rl.prompt();
3724
+ return;
3725
+ }
3726
+ const source = multiLineBuffer;
3727
+ multiLineBuffer = "";
3728
+ runSource(source);
4157
3729
  resetPrompt();
4158
3730
  });
4159
3731
  rl.on("close", () => {
3732
+ try {
3733
+ const history = rl.history ?? [];
3734
+ const trimmed = history.slice().reverse().filter((line) => line.trim().length > 0).slice(-HISTORY_LIMIT);
3735
+ writeFileSync(HISTORY_PATH, `${trimmed.join(`
3736
+ `)}
3737
+ `, "utf8");
3738
+ } catch {}
4160
3739
  process.exit(0);
4161
3740
  });
4162
3741
  rl.prompt();
4163
3742
  }
4164
- var req, version, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, GAS_LIMIT = 1e7;
3743
+ var req, version, colorEnabled, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, DOT_COMMANDS, GAS_LIMIT = 1e7, HISTORY_LIMIT = 1000, HISTORY_PATH;
4165
3744
  var init_rex_repl = __esm(() => {
4166
3745
  init_rex();
4167
3746
  init_rexc_interpreter();
4168
3747
  req = createRequire2(import.meta.url);
4169
3748
  ({ version } = req("./package.json"));
4170
- C = {
4171
- reset: "\x1B[0m",
4172
- bold: "\x1B[1m",
4173
- dim: "\x1B[2m",
4174
- red: "\x1B[31m",
4175
- green: "\x1B[32m",
4176
- yellow: "\x1B[33m",
4177
- blue: "\x1B[34m",
4178
- cyan: "\x1B[36m",
4179
- gray: "\x1B[90m",
4180
- boldBlue: "\x1B[1;34m"
4181
- };
4182
- 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;
3749
+ colorEnabled = process.stdout.isTTY;
3750
+ C = createColors(colorEnabled);
3751
+ TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<objKey>\b[A-Za-z_][A-Za-z0-9_-]*\b)(?=\s*:)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|nor|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)|(?<identifier>\b[A-Za-z_][A-Za-z0-9_.-]*\b)/g;
4183
3752
  REXC_DIGITS = new Set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_");
4184
3753
  JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
4185
3754
  KEYWORDS = [
@@ -4211,12 +3780,30 @@ var init_rex_repl = __esm(() => {
4211
3780
  "array",
4212
3781
  "boolean"
4213
3782
  ];
3783
+ DOT_COMMANDS = [
3784
+ ".help",
3785
+ ".file",
3786
+ ".cat",
3787
+ ".expr",
3788
+ ".source",
3789
+ ".vars",
3790
+ ".vars!",
3791
+ ".clear",
3792
+ ".ir",
3793
+ ".rexc",
3794
+ ".opt",
3795
+ ".json",
3796
+ ".color",
3797
+ ".exit"
3798
+ ];
3799
+ HISTORY_PATH = resolve(homedir(), ".rex_history");
4214
3800
  });
4215
3801
 
4216
3802
  // rex-cli.ts
4217
3803
  init_rex();
4218
3804
  init_rexc_interpreter();
4219
- import { dirname, resolve } from "node:path";
3805
+ init_rex_repl();
3806
+ import { dirname as dirname2, resolve as resolve2 } from "node:path";
4220
3807
  import { readFile, writeFile } from "node:fs/promises";
4221
3808
  function parseArgs(argv) {
4222
3809
  const options = {
@@ -4225,7 +3812,17 @@ function parseArgs(argv) {
4225
3812
  ir: false,
4226
3813
  minifyNames: false,
4227
3814
  dedupeValues: false,
4228
- help: false
3815
+ help: false,
3816
+ cat: false,
3817
+ showSource: false,
3818
+ showExpr: true,
3819
+ showIR: false,
3820
+ showRexc: false,
3821
+ showVars: false,
3822
+ color: process.stdout.isTTY,
3823
+ colorExplicit: false,
3824
+ showExprExplicit: false,
3825
+ format: "rex"
4229
3826
  };
4230
3827
  for (let index = 0;index < argv.length; index += 1) {
4231
3828
  const arg = argv[index];
@@ -4235,6 +3832,60 @@ function parseArgs(argv) {
4235
3832
  options.help = true;
4236
3833
  continue;
4237
3834
  }
3835
+ if (arg === "--cat") {
3836
+ options.cat = true;
3837
+ continue;
3838
+ }
3839
+ if (arg === "--show-source") {
3840
+ options.showSource = true;
3841
+ continue;
3842
+ }
3843
+ if (arg === "--show-expr") {
3844
+ options.showExpr = true;
3845
+ options.showExprExplicit = true;
3846
+ continue;
3847
+ }
3848
+ if (arg === "--no-expr") {
3849
+ options.showExpr = false;
3850
+ options.showExprExplicit = true;
3851
+ continue;
3852
+ }
3853
+ if (arg === "--show-ir") {
3854
+ options.showIR = true;
3855
+ continue;
3856
+ }
3857
+ if (arg === "--show-rexc") {
3858
+ options.showRexc = true;
3859
+ continue;
3860
+ }
3861
+ if (arg === "--show-vars") {
3862
+ options.showVars = true;
3863
+ continue;
3864
+ }
3865
+ if (arg === "--json") {
3866
+ options.format = "json";
3867
+ continue;
3868
+ }
3869
+ if (arg === "--format") {
3870
+ const value = argv[index + 1];
3871
+ if (!value)
3872
+ throw new Error("Missing value for --format");
3873
+ if (value !== "rex" && value !== "json")
3874
+ throw new Error("--format must be 'rex' or 'json'");
3875
+ options.format = value;
3876
+ index += 1;
3877
+ continue;
3878
+ }
3879
+ if (arg === "--color") {
3880
+ options.color = true;
3881
+ options.colorExplicit = true;
3882
+ continue;
3883
+ }
3884
+ if (arg === "--no-color") {
3885
+ options.color = false;
3886
+ options.colorExplicit = true;
3887
+ continue;
3888
+ }
4238
3889
  if (arg === "--compile" || arg === "-c") {
4239
3890
  options.compile = true;
4240
3891
  continue;
@@ -4318,9 +3969,26 @@ function usage() {
4318
3969
  " They are concatenated in the order they appear on the command line.",
4319
3970
  "",
4320
3971
  "Output mode:",
4321
- " (default) Evaluate and output result as JSON",
4322
- " -c, --compile Compile to rexc bytecode",
4323
- " --ir Output lowered IR as JSON",
3972
+ " (default) Evaluate and output result",
3973
+ " -c, --compile Show rexc only (same as --show-rexc --no-expr)",
3974
+ " --ir Show IR only (same as --show-ir --no-expr)",
3975
+ " --cat Print input with Rex highlighting",
3976
+ " --show-source Show input source text",
3977
+ " --show-expr Show expression result",
3978
+ " --no-expr Hide expression result",
3979
+ " --show-ir Show IR JSON",
3980
+ " --show-rexc Show rexc bytecode",
3981
+ " --show-vars Show variable state",
3982
+ "",
3983
+ "Output formatting:",
3984
+ " --format <type> Output format: rex|json (default: rex)",
3985
+ " --json Shortcut for --format json",
3986
+ " --color Force color output",
3987
+ " --no-color Disable color output",
3988
+ "",
3989
+ "TTY vs non-TTY:",
3990
+ " TTY output prints labeled blocks when multiple outputs are selected.",
3991
+ " Non-TTY output emits a single value (object if multiple outputs).",
4324
3992
  "",
4325
3993
  "Compile options:",
4326
3994
  " -m, --minify-names Minify local variable names",
@@ -4367,7 +4035,7 @@ function findFirstFilePath(sources) {
4367
4035
  return;
4368
4036
  }
4369
4037
  async function loadDomainConfigFromFolder(folderPath) {
4370
- const configPath = resolve(folderPath, ".config.rex");
4038
+ const configPath = resolve2(folderPath, ".config.rex");
4371
4039
  try {
4372
4040
  return parse(await readFile(configPath, "utf8"));
4373
4041
  } catch (error) {
@@ -4378,15 +4046,58 @@ async function loadDomainConfigFromFolder(folderPath) {
4378
4046
  }
4379
4047
  async function resolveDomainConfig(options) {
4380
4048
  const filePath = findFirstFilePath(options.sources);
4381
- const baseFolder = filePath ? dirname(resolve(filePath)) : process.cwd();
4049
+ const baseFolder = filePath ? dirname2(resolve2(filePath)) : process.cwd();
4382
4050
  return loadDomainConfigFromFolder(baseFolder);
4383
4051
  }
4052
+ function formatJson2(value, indent = 2) {
4053
+ const normalized = normalizeJsonValue2(value, false);
4054
+ const text = JSON.stringify(normalized, null, indent);
4055
+ return text ?? "null";
4056
+ }
4057
+ function normalizeJsonValue2(value, inArray) {
4058
+ if (value === undefined)
4059
+ return inArray ? null : undefined;
4060
+ if (value === null || typeof value !== "object")
4061
+ return value;
4062
+ if (Array.isArray(value)) {
4063
+ return value.map((item) => normalizeJsonValue2(item, true));
4064
+ }
4065
+ const out = {};
4066
+ for (const [key, val] of Object.entries(value)) {
4067
+ const normalized = normalizeJsonValue2(val, false);
4068
+ if (normalized !== undefined)
4069
+ out[key] = normalized;
4070
+ }
4071
+ return out;
4072
+ }
4073
+ function isRexSource(source) {
4074
+ try {
4075
+ return grammar.match(source).succeeded();
4076
+ } catch {
4077
+ return false;
4078
+ }
4079
+ }
4384
4080
  async function main() {
4385
4081
  const options = parseArgs(process.argv.slice(2));
4386
4082
  if (options.help) {
4387
4083
  console.log(usage());
4388
4084
  return;
4389
4085
  }
4086
+ setColorEnabled(options.color);
4087
+ if (options.cat) {
4088
+ const source2 = await resolveSource(options);
4089
+ const hasRexc = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rexc"));
4090
+ const hasRex = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rex"));
4091
+ const hint = hasRexc && !hasRex ? "rexc" : hasRex && !hasRexc ? "rex" : undefined;
4092
+ const output2 = process.stdout.isTTY && options.color ? highlightAuto(source2, hint) : source2;
4093
+ if (options.out) {
4094
+ await writeFile(options.out, `${output2}
4095
+ `, "utf8");
4096
+ return;
4097
+ }
4098
+ console.log(output2);
4099
+ return;
4100
+ }
4390
4101
  const hasSource = options.sources.length > 0 || !process.stdin.isTTY;
4391
4102
  if (!hasSource && !options.compile && !options.ir) {
4392
4103
  const { startRepl: startRepl2 } = await Promise.resolve().then(() => (init_rex_repl(), exports_rex_repl));
@@ -4394,20 +4105,107 @@ async function main() {
4394
4105
  return;
4395
4106
  }
4396
4107
  const source = await resolveSource(options);
4397
- let output;
4108
+ const sourceIsRex = isRexSource(source);
4109
+ if (options.compile) {
4110
+ if (!sourceIsRex)
4111
+ throw new Error("--compile requires Rex source");
4112
+ options.showRexc = true;
4113
+ if (!options.showExprExplicit)
4114
+ options.showExpr = false;
4115
+ }
4398
4116
  if (options.ir) {
4399
- output = JSON.stringify(parseToIR(source), null, 2);
4400
- } else if (options.compile) {
4117
+ if (!sourceIsRex)
4118
+ throw new Error("--ir requires Rex source");
4119
+ options.showIR = true;
4120
+ if (!options.showExprExplicit)
4121
+ options.showExpr = false;
4122
+ }
4123
+ const outputFlags = [options.showSource, options.showExpr, options.showIR, options.showRexc, options.showVars].filter(Boolean).length;
4124
+ if (outputFlags === 0) {
4125
+ throw new Error("No output selected. Use --show-source, --show-expr, --show-ir, --show-rexc, or --show-vars.");
4126
+ }
4127
+ const humanMode = process.stdout.isTTY && options.color;
4128
+ const outputs = {};
4129
+ if (options.showSource)
4130
+ outputs.source = source;
4131
+ if (!sourceIsRex && options.showIR) {
4132
+ throw new Error("--show-ir is only available for Rex source");
4133
+ }
4134
+ if (options.showIR) {
4135
+ outputs.ir = parseToIR(source);
4136
+ }
4137
+ if (options.showRexc) {
4401
4138
  const domainConfig = await resolveDomainConfig(options);
4402
- output = compile(source, {
4139
+ outputs.rexc = sourceIsRex ? compile(source, {
4403
4140
  minifyNames: options.minifyNames,
4404
4141
  dedupeValues: options.dedupeValues,
4405
4142
  dedupeMinBytes: options.dedupeMinBytes,
4406
4143
  domainConfig
4407
- });
4144
+ }) : source;
4145
+ }
4146
+ if (options.showExpr || options.showVars) {
4147
+ const result = sourceIsRex ? evaluateSource(source) : evaluateRexc(source);
4148
+ if (options.showExpr)
4149
+ outputs.result = result.value;
4150
+ if (options.showVars)
4151
+ outputs.vars = result.state.vars;
4152
+ }
4153
+ const order = ["source", "ir", "rexc", "vars", "result"];
4154
+ const selected = order.filter((key) => outputs[key] !== undefined);
4155
+ function formatValue(value, kind) {
4156
+ if (kind === "source") {
4157
+ const raw2 = String(value ?? "");
4158
+ if (options.format === "json") {
4159
+ const json = formatJson2(raw2, 2);
4160
+ return humanMode && options.color ? highlightJSON(json) : json;
4161
+ }
4162
+ return humanMode && options.color ? highlightAuto(raw2) : raw2;
4163
+ }
4164
+ if (kind === "rexc" && humanMode) {
4165
+ const raw2 = String(value ?? "");
4166
+ return options.color ? highlightAuto(raw2, "rexc") : raw2;
4167
+ }
4168
+ if (options.format === "json") {
4169
+ const raw2 = formatJson2(value, 2);
4170
+ return humanMode && options.color ? highlightJSON(raw2) : raw2;
4171
+ }
4172
+ if (kind === "rexc") {
4173
+ const raw2 = stringify(String(value ?? ""));
4174
+ return humanMode && options.color ? highlightLine(raw2) : raw2;
4175
+ }
4176
+ const raw = stringify(value);
4177
+ return humanMode && options.color ? highlightLine(raw) : raw;
4178
+ }
4179
+ let output;
4180
+ if (humanMode) {
4181
+ const header = options.color ? "\x1B[90m" : "";
4182
+ const reset = options.color ? "\x1B[0m" : "";
4183
+ const lines = [];
4184
+ for (const key of order) {
4185
+ const value = outputs[key];
4186
+ if (value === undefined)
4187
+ continue;
4188
+ lines.push(`${header}${key}:${reset} ${formatValue(value, key)}`);
4189
+ }
4190
+ output = lines.join(`
4191
+ `);
4408
4192
  } else {
4409
- const { value } = evaluateSource(source);
4410
- output = stringify(value);
4193
+ let value;
4194
+ if (selected.length === 1) {
4195
+ const only = selected[0];
4196
+ if (!only)
4197
+ throw new Error("No output selected.");
4198
+ value = outputs[only];
4199
+ } else {
4200
+ const out = {};
4201
+ for (const key of order) {
4202
+ const v = outputs[key];
4203
+ if (v !== undefined)
4204
+ out[key] = v;
4205
+ }
4206
+ value = out;
4207
+ }
4208
+ output = options.format === "json" ? formatJson2(value, 2) : stringify(value);
4411
4209
  }
4412
4210
  if (options.out) {
4413
4211
  await writeFile(options.out, `${output}