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