@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/README.md +24 -0
- package/package.json +9 -6
- package/rex-cli.js +785 -987
- package/rex-cli.ts +236 -16
- package/rex-repl.js +592 -1005
- package/rex-repl.ts +389 -101
- package/rex.js +51 -845
- package/rex.ohm +7 -8
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +4 -3
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +52 -23
- package/rexc-interpreter.ts +136 -15
- package/rx-cli.js +2836 -0
- package/rx-cli.ts +298 -0
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
|
-
|
|
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
|
-
|
|
811
|
+
function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
|
|
812
|
+
const offset = targetStart - pointerStart - occurrenceSize;
|
|
794
813
|
if (offset < 0)
|
|
795
814
|
return;
|
|
796
|
-
|
|
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
|
|
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"
|
|
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:
|
|
1713
|
+
type: "binding:keyValueIn",
|
|
2509
1714
|
key: key.sourceString,
|
|
1715
|
+
value: value.sourceString,
|
|
2510
1716
|
source: source.toIR()
|
|
2511
1717
|
};
|
|
2512
1718
|
},
|
|
2513
|
-
|
|
1719
|
+
IterBindingComprehension_valueIn(value, _in, source) {
|
|
2514
1720
|
return {
|
|
2515
|
-
type: "binding:
|
|
1721
|
+
type: "binding:valueIn",
|
|
1722
|
+
value: value.sourceString,
|
|
2516
1723
|
source: source.toIR()
|
|
2517
1724
|
};
|
|
2518
1725
|
},
|
|
2519
|
-
|
|
1726
|
+
IterBindingComprehension_keyOf(key, _of, source) {
|
|
2520
1727
|
return {
|
|
2521
|
-
type: "binding:
|
|
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
|
-
|
|
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 (
|
|
3285
|
-
return
|
|
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
|
-
|
|
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
|
|
2581
|
+
case "array:push": {
|
|
3366
2582
|
const target = args[0];
|
|
3367
|
-
if (Array.isArray(target))
|
|
3368
|
-
return
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
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
|
|
3394
|
-
return;
|
|
3395
|
-
|
|
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
|
|
3399
|
-
return;
|
|
3400
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
3226
|
+
out += C.keyword + prefix + tag + C.reset;
|
|
3832
3227
|
i++;
|
|
3833
3228
|
break;
|
|
3834
3229
|
case ";":
|
|
3835
|
-
out += C.
|
|
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
|
|
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
|
|
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.
|
|
3983
|
-
" .help
|
|
3984
|
-
" .
|
|
3985
|
-
" .
|
|
3986
|
-
" .
|
|
3987
|
-
" .
|
|
3988
|
-
" .
|
|
3989
|
-
" .
|
|
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
|
|
3998
|
-
case ".
|
|
3999
|
-
state.
|
|
4000
|
-
console.log(`${C.dim}
|
|
4001
|
-
return
|
|
4002
|
-
case ".
|
|
4003
|
-
state.
|
|
4004
|
-
console.log(`${C.dim}
|
|
4005
|
-
return
|
|
4006
|
-
case ".
|
|
4007
|
-
state.
|
|
4008
|
-
console.log(`${C.dim}
|
|
4009
|
-
return
|
|
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
|
|
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
|
|
3529
|
+
return "handled";
|
|
4032
3530
|
case ".exit":
|
|
4033
3531
|
rl.close();
|
|
4034
|
-
return
|
|
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
|
|
3569
|
+
return "handled";
|
|
4039
3570
|
}
|
|
4040
|
-
return
|
|
3571
|
+
return "unhandled";
|
|
4041
3572
|
}
|
|
4042
3573
|
}
|
|
4043
3574
|
async function startRepl() {
|
|
4044
|
-
const state = {
|
|
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 =
|
|
4052
|
-
|
|
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:
|
|
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 =
|
|
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 =
|
|
3647
|
+
styledPrompt = styledPrimary;
|
|
4095
3648
|
rl.setPrompt(PRIMARY_PROMPT);
|
|
4096
3649
|
rl.prompt();
|
|
4097
3650
|
}
|
|
4098
|
-
|
|
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
|
-
|
|
4125
|
-
|
|
3654
|
+
const message = formatParseError(source, match);
|
|
3655
|
+
console.log(`${C.red} ${message}${C.reset}`);
|
|
4126
3656
|
return;
|
|
4127
3657
|
}
|
|
4128
3658
|
try {
|
|
4129
|
-
const
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
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.
|
|
4136
|
-
|
|
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
|
-
|
|
4146
|
-
|
|
4147
|
-
if (
|
|
4148
|
-
|
|
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
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
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
|
-
|
|
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
|
|
4322
|
-
" -c, --compile
|
|
4323
|
-
" --ir
|
|
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 =
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
4400
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4410
|
-
|
|
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}
|