@creationix/rex 0.1.4 → 0.3.1
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 +62 -62
- package/package.json +9 -7
- package/rex-cli.js +4077 -30
- package/rex-cli.ts +200 -0
- package/{rex-compile.js → rex-repl.js} +1679 -214
- package/rex-repl.ts +578 -0
- package/rex.js +214 -59
- package/rex.ohm +34 -3
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +8 -0
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +2579 -2422
- package/rexc-interpreter.ts +926 -848
- package/rex-compile.ts +0 -224
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// rex-repl.ts
|
|
2
|
+
import * as readline from "node:readline";
|
|
3
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
4
|
+
|
|
1
5
|
// rex.ts
|
|
2
6
|
import { createRequire } from "node:module";
|
|
3
7
|
var require2 = createRequire(import.meta.url);
|
|
@@ -33,19 +37,8 @@ var OPCODE_IDS = {
|
|
|
33
37
|
mod: 20,
|
|
34
38
|
neg: 21
|
|
35
39
|
};
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
if (!overrides || Object.keys(overrides).length === 0) {
|
|
39
|
-
return Object.keys(registeredDomainRefs).length > 0 ? { ...registeredDomainRefs } : undefined;
|
|
40
|
-
}
|
|
41
|
-
const merged = { ...registeredDomainRefs };
|
|
42
|
-
for (const [name, refId] of Object.entries(overrides)) {
|
|
43
|
-
if (!Number.isInteger(refId) || refId < 0)
|
|
44
|
-
throw new Error(`Invalid domain extension ref id for '${name}': ${refId}`);
|
|
45
|
-
merged[name] = refId;
|
|
46
|
-
}
|
|
47
|
-
return merged;
|
|
48
|
-
}
|
|
40
|
+
var FIRST_NON_RESERVED_REF = 8;
|
|
41
|
+
var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
|
|
49
42
|
var BINARY_TO_OPCODE = {
|
|
50
43
|
add: "add",
|
|
51
44
|
sub: "sub",
|
|
@@ -158,12 +151,33 @@ function encodeBareOrLengthString(value) {
|
|
|
158
151
|
return `${value}:`;
|
|
159
152
|
return `${encodeUint(byteLength(value))},${value}`;
|
|
160
153
|
}
|
|
154
|
+
var DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
|
|
155
|
+
function splitDecimal(num) {
|
|
156
|
+
const match = num.toExponential().match(DEC_PARTS);
|
|
157
|
+
if (!match)
|
|
158
|
+
throw new Error(`Failed to split decimal for ${num}`);
|
|
159
|
+
const [, b1, b2 = "", e1] = match;
|
|
160
|
+
const base = Number.parseInt(b1 + b2, 10);
|
|
161
|
+
const exp = Number.parseInt(e1, 10) - b2.length;
|
|
162
|
+
return { base, exp };
|
|
163
|
+
}
|
|
164
|
+
function encodeDecimal(significand, power) {
|
|
165
|
+
return `${encodeZigzag(power)}*${encodeInt(significand)}`;
|
|
166
|
+
}
|
|
161
167
|
function encodeNumberNode(node) {
|
|
162
168
|
const numberValue = node.value;
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
return
|
|
169
|
+
if (Number.isNaN(numberValue))
|
|
170
|
+
return "5'";
|
|
171
|
+
if (numberValue === Infinity)
|
|
172
|
+
return "6'";
|
|
173
|
+
if (numberValue === -Infinity)
|
|
174
|
+
return "7'";
|
|
175
|
+
if (Number.isInteger(numberValue)) {
|
|
176
|
+
const { base, exp } = splitDecimal(numberValue);
|
|
177
|
+
if (exp >= 0 && exp <= 4)
|
|
178
|
+
return encodeInt(numberValue);
|
|
179
|
+
return encodeDecimal(base, exp);
|
|
180
|
+
}
|
|
167
181
|
const raw = node.raw.toLowerCase();
|
|
168
182
|
const sign = raw.startsWith("-") ? -1 : 1;
|
|
169
183
|
const unsigned = sign < 0 ? raw.slice(1) : raw;
|
|
@@ -186,7 +200,7 @@ function encodeNumberNode(node) {
|
|
|
186
200
|
significand /= 10;
|
|
187
201
|
power += 1;
|
|
188
202
|
}
|
|
189
|
-
return
|
|
203
|
+
return encodeDecimal(significand, power);
|
|
190
204
|
}
|
|
191
205
|
function encodeOpcode(opcode) {
|
|
192
206
|
return `${encodeUint(OPCODE_IDS[opcode])}%`;
|
|
@@ -198,13 +212,13 @@ function needsOptionalPrefix(encoded) {
|
|
|
198
212
|
const first = encoded[0];
|
|
199
213
|
if (!first)
|
|
200
214
|
return false;
|
|
201
|
-
return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<";
|
|
215
|
+
return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<" || first === "#";
|
|
202
216
|
}
|
|
203
217
|
function addOptionalPrefix(encoded) {
|
|
204
218
|
if (!needsOptionalPrefix(encoded))
|
|
205
219
|
return encoded;
|
|
206
220
|
let payload = encoded;
|
|
207
|
-
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(")) {
|
|
221
|
+
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
|
|
208
222
|
payload = encoded.slice(2, -1);
|
|
209
223
|
} else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
210
224
|
payload = encoded.slice(2, -1);
|
|
@@ -235,6 +249,33 @@ function encodeConditionalElse(elseBranch) {
|
|
|
235
249
|
return encodeNode(nested);
|
|
236
250
|
}
|
|
237
251
|
function encodeNavigation(node) {
|
|
252
|
+
const domainRefs = activeEncodeOptions?.domainRefs;
|
|
253
|
+
if (domainRefs && node.target.type === "identifier") {
|
|
254
|
+
const staticPath = [node.target.name];
|
|
255
|
+
for (const segment of node.segments) {
|
|
256
|
+
if (segment.type !== "static")
|
|
257
|
+
break;
|
|
258
|
+
staticPath.push(segment.key);
|
|
259
|
+
}
|
|
260
|
+
for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
|
|
261
|
+
const dottedName = staticPath.slice(0, pathLength).join(".");
|
|
262
|
+
const domainRef = domainRefs[dottedName];
|
|
263
|
+
if (domainRef === undefined)
|
|
264
|
+
continue;
|
|
265
|
+
const consumedStaticSegments = pathLength - 1;
|
|
266
|
+
if (consumedStaticSegments === node.segments.length) {
|
|
267
|
+
return `${encodeUint(domainRef)}'`;
|
|
268
|
+
}
|
|
269
|
+
const parts2 = [`${encodeUint(domainRef)}'`];
|
|
270
|
+
for (const segment of node.segments.slice(consumedStaticSegments)) {
|
|
271
|
+
if (segment.type === "static")
|
|
272
|
+
parts2.push(encodeBareOrLengthString(segment.key));
|
|
273
|
+
else
|
|
274
|
+
parts2.push(encodeNode(segment.key));
|
|
275
|
+
}
|
|
276
|
+
return encodeCallParts(parts2);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
238
279
|
const parts = [encodeNode(node.target)];
|
|
239
280
|
for (const segment of node.segments) {
|
|
240
281
|
if (segment.type === "static")
|
|
@@ -244,6 +285,11 @@ function encodeNavigation(node) {
|
|
|
244
285
|
}
|
|
245
286
|
return encodeCallParts(parts);
|
|
246
287
|
}
|
|
288
|
+
function encodeWhile(node) {
|
|
289
|
+
const cond = encodeNode(node.condition);
|
|
290
|
+
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
291
|
+
return `#(${cond}${body})`;
|
|
292
|
+
}
|
|
247
293
|
function encodeFor(node) {
|
|
248
294
|
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
249
295
|
if (node.binding.type === "binding:expr") {
|
|
@@ -380,6 +426,8 @@ function encodeNode(node) {
|
|
|
380
426
|
}
|
|
381
427
|
case "for":
|
|
382
428
|
return encodeFor(node);
|
|
429
|
+
case "while":
|
|
430
|
+
return encodeWhile(node);
|
|
383
431
|
case "break":
|
|
384
432
|
return ";";
|
|
385
433
|
case "continue":
|
|
@@ -403,6 +451,194 @@ function parseToIR(source) {
|
|
|
403
451
|
}
|
|
404
452
|
return semantics(match).toIR();
|
|
405
453
|
}
|
|
454
|
+
function isPlainObject(value) {
|
|
455
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
456
|
+
return false;
|
|
457
|
+
const proto = Object.getPrototypeOf(value);
|
|
458
|
+
return proto === Object.prototype || proto === null;
|
|
459
|
+
}
|
|
460
|
+
function isBareKeyName(key) {
|
|
461
|
+
return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
|
|
462
|
+
}
|
|
463
|
+
function isNumericKey(key) {
|
|
464
|
+
if (key === "")
|
|
465
|
+
return false;
|
|
466
|
+
return String(Number(key)) === key && Number.isFinite(Number(key));
|
|
467
|
+
}
|
|
468
|
+
function stringifyKey(key) {
|
|
469
|
+
if (isBareKeyName(key))
|
|
470
|
+
return key;
|
|
471
|
+
if (isNumericKey(key))
|
|
472
|
+
return key;
|
|
473
|
+
return stringifyString(key);
|
|
474
|
+
}
|
|
475
|
+
function stringifyString(value) {
|
|
476
|
+
return JSON.stringify(value);
|
|
477
|
+
}
|
|
478
|
+
function stringifyInline(value) {
|
|
479
|
+
if (value === undefined)
|
|
480
|
+
return "undefined";
|
|
481
|
+
if (value === null)
|
|
482
|
+
return "null";
|
|
483
|
+
if (typeof value === "boolean")
|
|
484
|
+
return value ? "true" : "false";
|
|
485
|
+
if (typeof value === "number") {
|
|
486
|
+
if (Number.isNaN(value))
|
|
487
|
+
return "nan";
|
|
488
|
+
if (value === Infinity)
|
|
489
|
+
return "inf";
|
|
490
|
+
if (value === -Infinity)
|
|
491
|
+
return "-inf";
|
|
492
|
+
return String(value);
|
|
493
|
+
}
|
|
494
|
+
if (typeof value === "string")
|
|
495
|
+
return stringifyString(value);
|
|
496
|
+
if (Array.isArray(value)) {
|
|
497
|
+
if (value.length === 0)
|
|
498
|
+
return "[]";
|
|
499
|
+
return `[${value.map((item) => stringifyInline(item)).join(" ")}]`;
|
|
500
|
+
}
|
|
501
|
+
if (isPlainObject(value)) {
|
|
502
|
+
const entries = Object.entries(value);
|
|
503
|
+
if (entries.length === 0)
|
|
504
|
+
return "{}";
|
|
505
|
+
const body = entries.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`).join(" ");
|
|
506
|
+
return `{${body}}`;
|
|
507
|
+
}
|
|
508
|
+
throw new Error(`Rex stringify() cannot encode value of type ${typeof value}`);
|
|
509
|
+
}
|
|
510
|
+
function fitsInline(rendered, depth, indentSize, maxWidth) {
|
|
511
|
+
if (rendered.includes(`
|
|
512
|
+
`))
|
|
513
|
+
return false;
|
|
514
|
+
return depth * indentSize + rendered.length <= maxWidth;
|
|
515
|
+
}
|
|
516
|
+
function stringifyPretty(value, depth, indentSize, maxWidth) {
|
|
517
|
+
const inline = stringifyInline(value);
|
|
518
|
+
if (fitsInline(inline, depth, indentSize, maxWidth))
|
|
519
|
+
return inline;
|
|
520
|
+
const indent = " ".repeat(depth * indentSize);
|
|
521
|
+
const childIndent = " ".repeat((depth + 1) * indentSize);
|
|
522
|
+
if (Array.isArray(value)) {
|
|
523
|
+
if (value.length === 0)
|
|
524
|
+
return "[]";
|
|
525
|
+
const lines = value.map((item) => {
|
|
526
|
+
const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
|
|
527
|
+
if (!rendered.includes(`
|
|
528
|
+
`))
|
|
529
|
+
return `${childIndent}${rendered}`;
|
|
530
|
+
return `${childIndent}${rendered}`;
|
|
531
|
+
});
|
|
532
|
+
return `[
|
|
533
|
+
${lines.join(`
|
|
534
|
+
`)}
|
|
535
|
+
${indent}]`;
|
|
536
|
+
}
|
|
537
|
+
if (isPlainObject(value)) {
|
|
538
|
+
const entries = Object.entries(value);
|
|
539
|
+
if (entries.length === 0)
|
|
540
|
+
return "{}";
|
|
541
|
+
const lines = entries.map(([key, item]) => {
|
|
542
|
+
const keyText = stringifyKey(key);
|
|
543
|
+
const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
|
|
544
|
+
return `${childIndent}${keyText}: ${rendered}`;
|
|
545
|
+
});
|
|
546
|
+
return `{
|
|
547
|
+
${lines.join(`
|
|
548
|
+
`)}
|
|
549
|
+
${indent}}`;
|
|
550
|
+
}
|
|
551
|
+
return inline;
|
|
552
|
+
}
|
|
553
|
+
function domainRefsFromConfig(config) {
|
|
554
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
555
|
+
throw new Error("Domain config must be an object");
|
|
556
|
+
}
|
|
557
|
+
const refs = {};
|
|
558
|
+
for (const section of Object.values(config)) {
|
|
559
|
+
if (!section || typeof section !== "object" || Array.isArray(section))
|
|
560
|
+
continue;
|
|
561
|
+
mapConfigEntries(section, refs);
|
|
562
|
+
}
|
|
563
|
+
return refs;
|
|
564
|
+
}
|
|
565
|
+
function decodeDomainRefKey(refText) {
|
|
566
|
+
if (!refText)
|
|
567
|
+
throw new Error("Domain ref key cannot be empty");
|
|
568
|
+
if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
|
|
569
|
+
throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
|
|
570
|
+
}
|
|
571
|
+
if (refText.length > 1 && refText[0] === "0") {
|
|
572
|
+
throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
|
|
573
|
+
}
|
|
574
|
+
if (/^[1-9]$/.test(refText)) {
|
|
575
|
+
throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
|
|
576
|
+
}
|
|
577
|
+
let value = 0;
|
|
578
|
+
for (const char of refText) {
|
|
579
|
+
const digit = DOMAIN_DIGIT_INDEX.get(char);
|
|
580
|
+
if (digit === undefined)
|
|
581
|
+
throw new Error(`Invalid domain ref key '${refText}'`);
|
|
582
|
+
value = value * 64 + digit;
|
|
583
|
+
if (value > Number.MAX_SAFE_INTEGER) {
|
|
584
|
+
throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (value < FIRST_NON_RESERVED_REF) {
|
|
588
|
+
throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
|
|
589
|
+
}
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
function mapConfigEntries(entries, refs) {
|
|
593
|
+
const sourceKindByRoot = new Map;
|
|
594
|
+
for (const root of Object.keys(refs)) {
|
|
595
|
+
sourceKindByRoot.set(root, "explicit");
|
|
596
|
+
}
|
|
597
|
+
for (const [refText, rawEntry] of Object.entries(entries)) {
|
|
598
|
+
const entry = rawEntry;
|
|
599
|
+
if (!entry || typeof entry !== "object")
|
|
600
|
+
continue;
|
|
601
|
+
if (!Array.isArray(entry.names))
|
|
602
|
+
continue;
|
|
603
|
+
const refId = decodeDomainRefKey(refText);
|
|
604
|
+
for (const rawName of entry.names) {
|
|
605
|
+
if (typeof rawName !== "string")
|
|
606
|
+
continue;
|
|
607
|
+
const existingNameRef = refs[rawName];
|
|
608
|
+
if (existingNameRef !== undefined && existingNameRef !== refId) {
|
|
609
|
+
throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
|
|
610
|
+
}
|
|
611
|
+
refs[rawName] = refId;
|
|
612
|
+
const root = rawName.split(".")[0];
|
|
613
|
+
if (!root)
|
|
614
|
+
continue;
|
|
615
|
+
const currentKind = rawName.includes(".") ? "implicit" : "explicit";
|
|
616
|
+
const existing = refs[root];
|
|
617
|
+
if (existing !== undefined) {
|
|
618
|
+
if (existing === refId)
|
|
619
|
+
continue;
|
|
620
|
+
const existingKind = sourceKindByRoot.get(root) ?? "explicit";
|
|
621
|
+
if (currentKind === "explicit") {
|
|
622
|
+
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
|
|
623
|
+
}
|
|
624
|
+
if (existingKind === "explicit")
|
|
625
|
+
continue;
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
refs[root] = refId;
|
|
629
|
+
sourceKindByRoot.set(root, currentKind);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function stringify(value, options) {
|
|
634
|
+
const indent = options?.indent ?? 2;
|
|
635
|
+
const maxWidth = options?.maxWidth ?? 80;
|
|
636
|
+
if (!Number.isInteger(indent) || indent < 0)
|
|
637
|
+
throw new Error("Rex stringify() indent must be a non-negative integer");
|
|
638
|
+
if (!Number.isInteger(maxWidth) || maxWidth < 20)
|
|
639
|
+
throw new Error("Rex stringify() maxWidth must be an integer >= 20");
|
|
640
|
+
return stringifyPretty(value, 0, indent, maxWidth);
|
|
641
|
+
}
|
|
406
642
|
var DIGIT_SET = new Set(DIGITS.split(""));
|
|
407
643
|
var DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
|
|
408
644
|
function readPrefixAt(text, start) {
|
|
@@ -960,7 +1196,16 @@ function inlineAdjacentPureAssignments(block) {
|
|
|
960
1196
|
return out;
|
|
961
1197
|
}
|
|
962
1198
|
function toNumberNode(value) {
|
|
963
|
-
|
|
1199
|
+
let raw;
|
|
1200
|
+
if (Number.isNaN(value))
|
|
1201
|
+
raw = "nan";
|
|
1202
|
+
else if (value === Infinity)
|
|
1203
|
+
raw = "inf";
|
|
1204
|
+
else if (value === -Infinity)
|
|
1205
|
+
raw = "-inf";
|
|
1206
|
+
else
|
|
1207
|
+
raw = String(value);
|
|
1208
|
+
return { type: "number", raw, value };
|
|
964
1209
|
}
|
|
965
1210
|
function toStringNode(value) {
|
|
966
1211
|
return { type: "string", raw: JSON.stringify(value) };
|
|
@@ -972,7 +1217,7 @@ function toLiteralNode(value) {
|
|
|
972
1217
|
return { type: "null" };
|
|
973
1218
|
if (typeof value === "boolean")
|
|
974
1219
|
return { type: "boolean", value };
|
|
975
|
-
if (typeof value === "number"
|
|
1220
|
+
if (typeof value === "number")
|
|
976
1221
|
return toNumberNode(value);
|
|
977
1222
|
if (typeof value === "string")
|
|
978
1223
|
return toStringNode(value);
|
|
@@ -1284,12 +1529,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
|
1284
1529
|
if (conditionValue !== undefined || condition.type === "undefined") {
|
|
1285
1530
|
const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
|
|
1286
1531
|
if (passes) {
|
|
1287
|
-
const
|
|
1288
|
-
if (
|
|
1532
|
+
const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
|
|
1533
|
+
if (thenBlock2.length === 0)
|
|
1289
1534
|
return { type: "undefined" };
|
|
1290
|
-
if (
|
|
1291
|
-
return
|
|
1292
|
-
return { type: "program", body:
|
|
1535
|
+
if (thenBlock2.length === 1)
|
|
1536
|
+
return thenBlock2[0];
|
|
1537
|
+
return { type: "program", body: thenBlock2 };
|
|
1293
1538
|
}
|
|
1294
1539
|
if (!node.elseBranch)
|
|
1295
1540
|
return { type: "undefined" };
|
|
@@ -1311,12 +1556,26 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
|
1311
1556
|
elseBranch: loweredElse.elseBranch
|
|
1312
1557
|
};
|
|
1313
1558
|
}
|
|
1559
|
+
const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
|
|
1560
|
+
const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
|
|
1561
|
+
let finalCondition = condition;
|
|
1562
|
+
if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
|
|
1563
|
+
const name = condition.place.name;
|
|
1564
|
+
const reads = new Set;
|
|
1565
|
+
for (const part of thenBlock)
|
|
1566
|
+
collectReads(part, reads);
|
|
1567
|
+
if (elseBranch)
|
|
1568
|
+
collectReadsElse(elseBranch, reads);
|
|
1569
|
+
if (!reads.has(name)) {
|
|
1570
|
+
finalCondition = condition.value;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1314
1573
|
return {
|
|
1315
1574
|
type: "conditional",
|
|
1316
1575
|
head: node.head,
|
|
1317
|
-
condition,
|
|
1318
|
-
thenBlock
|
|
1319
|
-
elseBranch
|
|
1576
|
+
condition: finalCondition,
|
|
1577
|
+
thenBlock,
|
|
1578
|
+
elseBranch
|
|
1320
1579
|
};
|
|
1321
1580
|
}
|
|
1322
1581
|
case "for": {
|
|
@@ -1771,13 +2030,20 @@ function compile(source, options) {
|
|
|
1771
2030
|
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
1772
2031
|
if (options?.minifyNames)
|
|
1773
2032
|
lowered = minifyLocalNamesIR(lowered);
|
|
2033
|
+
const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
|
|
1774
2034
|
return encodeIR(lowered, {
|
|
1775
|
-
domainRefs
|
|
2035
|
+
domainRefs,
|
|
1776
2036
|
dedupeValues: options?.dedupeValues,
|
|
1777
2037
|
dedupeMinBytes: options?.dedupeMinBytes
|
|
1778
2038
|
});
|
|
1779
2039
|
}
|
|
1780
2040
|
function parseNumber(raw) {
|
|
2041
|
+
if (raw === "nan")
|
|
2042
|
+
return NaN;
|
|
2043
|
+
if (raw === "inf")
|
|
2044
|
+
return Infinity;
|
|
2045
|
+
if (raw === "-inf")
|
|
2046
|
+
return -Infinity;
|
|
1781
2047
|
if (/^-?0x/i.test(raw))
|
|
1782
2048
|
return parseInt(raw, 16);
|
|
1783
2049
|
if (/^-?0b/i.test(raw)) {
|
|
@@ -2002,6 +2268,13 @@ semantics.addOperation("toIR", {
|
|
|
2002
2268
|
return body[0];
|
|
2003
2269
|
return { type: "program", body };
|
|
2004
2270
|
},
|
|
2271
|
+
WhileExpr(_while, condition, _do, block, _end) {
|
|
2272
|
+
return {
|
|
2273
|
+
type: "while",
|
|
2274
|
+
condition: condition.toIR(),
|
|
2275
|
+
body: block.toIR()
|
|
2276
|
+
};
|
|
2277
|
+
},
|
|
2005
2278
|
ForExpr(_for, binding, _do, block, _end) {
|
|
2006
2279
|
return {
|
|
2007
2280
|
type: "for",
|
|
@@ -2145,211 +2418,1403 @@ semantics.addOperation("toIR", {
|
|
|
2145
2418
|
}
|
|
2146
2419
|
});
|
|
2147
2420
|
|
|
2148
|
-
//
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
var
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2421
|
+
// rexc-interpreter.ts
|
|
2422
|
+
var DIGITS2 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
|
|
2423
|
+
var digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
|
|
2424
|
+
var OPCODES = {
|
|
2425
|
+
do: 0,
|
|
2426
|
+
add: 1,
|
|
2427
|
+
sub: 2,
|
|
2428
|
+
mul: 3,
|
|
2429
|
+
div: 4,
|
|
2430
|
+
eq: 5,
|
|
2431
|
+
neq: 6,
|
|
2432
|
+
lt: 7,
|
|
2433
|
+
lte: 8,
|
|
2434
|
+
gt: 9,
|
|
2435
|
+
gte: 10,
|
|
2436
|
+
and: 11,
|
|
2437
|
+
or: 12,
|
|
2438
|
+
xor: 13,
|
|
2439
|
+
not: 14,
|
|
2440
|
+
boolean: 15,
|
|
2441
|
+
number: 16,
|
|
2442
|
+
string: 17,
|
|
2443
|
+
array: 18,
|
|
2444
|
+
object: 19,
|
|
2445
|
+
mod: 20,
|
|
2446
|
+
neg: 21
|
|
2447
|
+
};
|
|
2448
|
+
function decodePrefix(text, start, end) {
|
|
2449
|
+
let value = 0;
|
|
2450
|
+
for (let index = start;index < end; index += 1) {
|
|
2451
|
+
const digit = digitMap.get(text[index] ?? "") ?? -1;
|
|
2452
|
+
if (digit < 0)
|
|
2453
|
+
throw new Error(`Invalid digit '${text[index]}'`);
|
|
2454
|
+
value = value * 64 + digit;
|
|
2455
|
+
}
|
|
2456
|
+
return value;
|
|
2457
|
+
}
|
|
2458
|
+
function decodeZigzag(value) {
|
|
2459
|
+
return value % 2 === 0 ? value / 2 : -(value + 1) / 2;
|
|
2460
|
+
}
|
|
2461
|
+
function isDefined(value) {
|
|
2462
|
+
return value !== undefined;
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
class CursorInterpreter {
|
|
2466
|
+
text;
|
|
2467
|
+
pos = 0;
|
|
2468
|
+
state;
|
|
2469
|
+
selfStack;
|
|
2470
|
+
opcodeMarkers;
|
|
2471
|
+
pointerCache = new Map;
|
|
2472
|
+
gasLimit;
|
|
2473
|
+
gas = 0;
|
|
2474
|
+
tick() {
|
|
2475
|
+
if (this.gasLimit && ++this.gas > this.gasLimit) {
|
|
2476
|
+
throw new Error("Gas limit exceeded (too many loop iterations)");
|
|
2167
2477
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2478
|
+
}
|
|
2479
|
+
constructor(text, ctx = {}) {
|
|
2480
|
+
const initialSelf = ctx.selfStack && ctx.selfStack.length > 0 ? ctx.selfStack[ctx.selfStack.length - 1] : ctx.self;
|
|
2481
|
+
this.text = text;
|
|
2482
|
+
this.state = {
|
|
2483
|
+
vars: ctx.vars ?? {},
|
|
2484
|
+
refs: {
|
|
2485
|
+
0: ctx.refs?.[0],
|
|
2486
|
+
1: ctx.refs?.[1] ?? true,
|
|
2487
|
+
2: ctx.refs?.[2] ?? false,
|
|
2488
|
+
3: ctx.refs?.[3] ?? null,
|
|
2489
|
+
4: ctx.refs?.[4] ?? undefined,
|
|
2490
|
+
5: ctx.refs?.[5] ?? NaN,
|
|
2491
|
+
6: ctx.refs?.[6] ?? Infinity,
|
|
2492
|
+
7: ctx.refs?.[7] ?? -Infinity
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
|
|
2496
|
+
this.gasLimit = ctx.gasLimit ?? 0;
|
|
2497
|
+
this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
|
|
2498
|
+
for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
|
|
2499
|
+
const id = Number(idText);
|
|
2500
|
+
if (Number.isInteger(id))
|
|
2501
|
+
this.state.refs[id] = value;
|
|
2171
2502
|
}
|
|
2172
|
-
if (
|
|
2173
|
-
|
|
2174
|
-
|
|
2503
|
+
if (ctx.opcodes) {
|
|
2504
|
+
for (const [idText, op] of Object.entries(ctx.opcodes)) {
|
|
2505
|
+
if (op)
|
|
2506
|
+
this.customOpcodes.set(Number(idText), op);
|
|
2507
|
+
}
|
|
2175
2508
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2509
|
+
}
|
|
2510
|
+
customOpcodes = new Map;
|
|
2511
|
+
readSelf(depthPrefix) {
|
|
2512
|
+
const depth = depthPrefix + 1;
|
|
2513
|
+
const index = this.selfStack.length - depth;
|
|
2514
|
+
if (index < 0)
|
|
2515
|
+
return;
|
|
2516
|
+
return this.selfStack[index];
|
|
2517
|
+
}
|
|
2518
|
+
get runtimeState() {
|
|
2519
|
+
return this.state;
|
|
2520
|
+
}
|
|
2521
|
+
evaluateTopLevel() {
|
|
2522
|
+
this.skipNonCode();
|
|
2523
|
+
if (this.pos >= this.text.length)
|
|
2524
|
+
return;
|
|
2525
|
+
const value = this.evalValue();
|
|
2526
|
+
this.skipNonCode();
|
|
2527
|
+
if (this.pos < this.text.length)
|
|
2528
|
+
throw new Error(`Unexpected trailing input at ${this.pos}`);
|
|
2529
|
+
return value;
|
|
2530
|
+
}
|
|
2531
|
+
skipNonCode() {
|
|
2532
|
+
while (this.pos < this.text.length) {
|
|
2533
|
+
const ch = this.text[this.pos];
|
|
2534
|
+
if (ch === " " || ch === "\t" || ch === `
|
|
2535
|
+
` || ch === "\r") {
|
|
2536
|
+
this.pos += 1;
|
|
2537
|
+
continue;
|
|
2538
|
+
}
|
|
2539
|
+
if (ch === "/" && this.text[this.pos + 1] === "/") {
|
|
2540
|
+
this.pos += 2;
|
|
2541
|
+
while (this.pos < this.text.length && this.text[this.pos] !== `
|
|
2542
|
+
`)
|
|
2543
|
+
this.pos += 1;
|
|
2544
|
+
continue;
|
|
2545
|
+
}
|
|
2546
|
+
if (ch === "/" && this.text[this.pos + 1] === "*") {
|
|
2547
|
+
this.pos += 2;
|
|
2548
|
+
while (this.pos < this.text.length) {
|
|
2549
|
+
if (this.text[this.pos] === "*" && this.text[this.pos + 1] === "/") {
|
|
2550
|
+
this.pos += 2;
|
|
2551
|
+
break;
|
|
2552
|
+
}
|
|
2553
|
+
this.pos += 1;
|
|
2554
|
+
}
|
|
2555
|
+
continue;
|
|
2556
|
+
}
|
|
2557
|
+
break;
|
|
2179
2558
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2559
|
+
}
|
|
2560
|
+
readPrefix() {
|
|
2561
|
+
const start = this.pos;
|
|
2562
|
+
while (this.pos < this.text.length && digitMap.has(this.text[this.pos] ?? ""))
|
|
2563
|
+
this.pos += 1;
|
|
2564
|
+
const end = this.pos;
|
|
2565
|
+
return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
|
|
2566
|
+
}
|
|
2567
|
+
ensure(char) {
|
|
2568
|
+
if (this.text[this.pos] !== char)
|
|
2569
|
+
throw new Error(`Expected '${char}' at ${this.pos}`);
|
|
2570
|
+
this.pos += 1;
|
|
2571
|
+
}
|
|
2572
|
+
hasMoreBefore(close) {
|
|
2573
|
+
const save = this.pos;
|
|
2574
|
+
this.skipNonCode();
|
|
2575
|
+
const more = this.pos < this.text.length && this.text[this.pos] !== close;
|
|
2576
|
+
this.pos = save;
|
|
2577
|
+
return more;
|
|
2578
|
+
}
|
|
2579
|
+
readBindingVarIfPresent() {
|
|
2580
|
+
const save = this.pos;
|
|
2581
|
+
this.skipNonCode();
|
|
2582
|
+
const prefix = this.readPrefix();
|
|
2583
|
+
const tag = this.text[this.pos];
|
|
2584
|
+
if (tag === "$" && prefix.raw.length > 0) {
|
|
2585
|
+
this.pos += 1;
|
|
2586
|
+
return prefix.raw;
|
|
2190
2587
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2588
|
+
this.pos = save;
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
evalValue() {
|
|
2592
|
+
this.skipNonCode();
|
|
2593
|
+
const prefix = this.readPrefix();
|
|
2594
|
+
const tag = this.text[this.pos];
|
|
2595
|
+
if (!tag)
|
|
2596
|
+
throw new Error("Unexpected end of input");
|
|
2597
|
+
switch (tag) {
|
|
2598
|
+
case "+":
|
|
2599
|
+
this.pos += 1;
|
|
2600
|
+
return decodeZigzag(prefix.value);
|
|
2601
|
+
case "*": {
|
|
2602
|
+
this.pos += 1;
|
|
2603
|
+
const power = decodeZigzag(prefix.value);
|
|
2604
|
+
const significand = this.evalValue();
|
|
2605
|
+
if (typeof significand !== "number")
|
|
2606
|
+
throw new Error("Decimal significand must be numeric");
|
|
2607
|
+
return significand * 10 ** power;
|
|
2608
|
+
}
|
|
2609
|
+
case ":":
|
|
2610
|
+
this.pos += 1;
|
|
2611
|
+
return prefix.raw;
|
|
2612
|
+
case "%":
|
|
2613
|
+
this.pos += 1;
|
|
2614
|
+
return this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
|
|
2615
|
+
case "@":
|
|
2616
|
+
this.pos += 1;
|
|
2617
|
+
return this.readSelf(prefix.value);
|
|
2618
|
+
case "'":
|
|
2619
|
+
this.pos += 1;
|
|
2620
|
+
return this.state.refs[prefix.value];
|
|
2621
|
+
case "$":
|
|
2622
|
+
this.pos += 1;
|
|
2623
|
+
return this.state.vars[prefix.raw];
|
|
2624
|
+
case ",": {
|
|
2625
|
+
this.pos += 1;
|
|
2626
|
+
const start = this.pos;
|
|
2627
|
+
const end = start + prefix.value;
|
|
2628
|
+
if (end > this.text.length)
|
|
2629
|
+
throw new Error("String container overflows input");
|
|
2630
|
+
const value = this.text.slice(start, end);
|
|
2631
|
+
this.pos = end;
|
|
2632
|
+
return value;
|
|
2633
|
+
}
|
|
2634
|
+
case "^": {
|
|
2635
|
+
this.pos += 1;
|
|
2636
|
+
const target = this.pos + prefix.value;
|
|
2637
|
+
if (this.pointerCache.has(target))
|
|
2638
|
+
return this.pointerCache.get(target);
|
|
2639
|
+
const save = this.pos;
|
|
2640
|
+
this.pos = target;
|
|
2641
|
+
const value = this.evalValue();
|
|
2642
|
+
this.pos = save;
|
|
2643
|
+
this.pointerCache.set(target, value);
|
|
2644
|
+
return value;
|
|
2645
|
+
}
|
|
2646
|
+
case "=": {
|
|
2647
|
+
this.pos += 1;
|
|
2648
|
+
const place = this.readPlace();
|
|
2649
|
+
const value = this.evalValue();
|
|
2650
|
+
this.writePlace(place, value);
|
|
2651
|
+
return value;
|
|
2652
|
+
}
|
|
2653
|
+
case "~": {
|
|
2654
|
+
this.pos += 1;
|
|
2655
|
+
const place = this.readPlace();
|
|
2656
|
+
this.deletePlace(place);
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
case ";": {
|
|
2660
|
+
this.pos += 1;
|
|
2661
|
+
const kind = prefix.value % 2 === 0 ? "break" : "continue";
|
|
2662
|
+
const depth = Math.floor(prefix.value / 2) + 1;
|
|
2663
|
+
return { kind, depth };
|
|
2664
|
+
}
|
|
2665
|
+
case "(":
|
|
2666
|
+
return this.evalCall(prefix.value);
|
|
2667
|
+
case "[":
|
|
2668
|
+
return this.evalArray(prefix.value);
|
|
2669
|
+
case "{":
|
|
2670
|
+
return this.evalObject(prefix.value);
|
|
2671
|
+
case "?":
|
|
2672
|
+
case "!":
|
|
2673
|
+
case "|":
|
|
2674
|
+
case "&":
|
|
2675
|
+
return this.evalFlowParen(tag);
|
|
2676
|
+
case ">":
|
|
2677
|
+
case "<":
|
|
2678
|
+
return this.evalLoopLike(tag);
|
|
2679
|
+
case "#":
|
|
2680
|
+
return this.evalWhileLoop();
|
|
2681
|
+
default:
|
|
2682
|
+
throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
evalCall(_prefix) {
|
|
2686
|
+
this.ensure("(");
|
|
2687
|
+
this.skipNonCode();
|
|
2688
|
+
if (this.text[this.pos] === ")") {
|
|
2689
|
+
this.pos += 1;
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
const callee = this.evalValue();
|
|
2693
|
+
const args = [];
|
|
2694
|
+
while (true) {
|
|
2695
|
+
this.skipNonCode();
|
|
2696
|
+
if (this.text[this.pos] === ")")
|
|
2697
|
+
break;
|
|
2698
|
+
args.push(this.evalValue());
|
|
2699
|
+
}
|
|
2700
|
+
this.ensure(")");
|
|
2701
|
+
if (typeof callee === "object" && callee && "__opcode" in callee) {
|
|
2702
|
+
return this.applyOpcode(callee.__opcode, args);
|
|
2703
|
+
}
|
|
2704
|
+
return this.navigate(callee, args);
|
|
2705
|
+
}
|
|
2706
|
+
evalArray(_prefix) {
|
|
2707
|
+
this.ensure("[");
|
|
2708
|
+
this.skipNonCode();
|
|
2709
|
+
this.skipIndexHeaderIfPresent();
|
|
2710
|
+
const out = [];
|
|
2711
|
+
while (true) {
|
|
2712
|
+
this.skipNonCode();
|
|
2713
|
+
if (this.text[this.pos] === "]")
|
|
2714
|
+
break;
|
|
2715
|
+
out.push(this.evalValue());
|
|
2198
2716
|
}
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2717
|
+
this.ensure("]");
|
|
2718
|
+
return out;
|
|
2719
|
+
}
|
|
2720
|
+
evalObject(_prefix) {
|
|
2721
|
+
this.ensure("{");
|
|
2722
|
+
this.skipNonCode();
|
|
2723
|
+
this.skipIndexHeaderIfPresent();
|
|
2724
|
+
const out = {};
|
|
2725
|
+
while (true) {
|
|
2726
|
+
this.skipNonCode();
|
|
2727
|
+
if (this.text[this.pos] === "}")
|
|
2728
|
+
break;
|
|
2729
|
+
const key = this.evalValue();
|
|
2730
|
+
const value = this.evalValue();
|
|
2731
|
+
out[String(key)] = value;
|
|
2732
|
+
}
|
|
2733
|
+
this.ensure("}");
|
|
2734
|
+
return out;
|
|
2735
|
+
}
|
|
2736
|
+
skipIndexHeaderIfPresent() {
|
|
2737
|
+
const save = this.pos;
|
|
2738
|
+
const countPrefix = this.readPrefix();
|
|
2739
|
+
if (this.text[this.pos] !== "#") {
|
|
2740
|
+
this.pos = save;
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
this.pos += 1;
|
|
2744
|
+
const widthChar = this.text[this.pos];
|
|
2745
|
+
const width = widthChar ? digitMap.get(widthChar) ?? 0 : 0;
|
|
2746
|
+
if (widthChar)
|
|
2747
|
+
this.pos += 1;
|
|
2748
|
+
const skipLen = countPrefix.value * width;
|
|
2749
|
+
this.pos += skipLen;
|
|
2750
|
+
}
|
|
2751
|
+
evalFlowParen(tag) {
|
|
2752
|
+
this.pos += 1;
|
|
2753
|
+
this.ensure("(");
|
|
2754
|
+
if (tag === "?") {
|
|
2755
|
+
const cond = this.evalValue();
|
|
2756
|
+
if (isDefined(cond)) {
|
|
2757
|
+
this.selfStack.push(cond);
|
|
2758
|
+
const thenValue = this.evalValue();
|
|
2759
|
+
this.selfStack.pop();
|
|
2760
|
+
if (this.hasMoreBefore(")"))
|
|
2761
|
+
this.skipValue();
|
|
2762
|
+
this.ensure(")");
|
|
2763
|
+
return thenValue;
|
|
2206
2764
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2765
|
+
this.skipValue();
|
|
2766
|
+
let elseValue = undefined;
|
|
2767
|
+
if (this.hasMoreBefore(")"))
|
|
2768
|
+
elseValue = this.evalValue();
|
|
2769
|
+
this.ensure(")");
|
|
2770
|
+
return elseValue;
|
|
2771
|
+
}
|
|
2772
|
+
if (tag === "!") {
|
|
2773
|
+
const cond = this.evalValue();
|
|
2774
|
+
if (!isDefined(cond)) {
|
|
2775
|
+
const thenValue = this.evalValue();
|
|
2776
|
+
if (this.hasMoreBefore(")"))
|
|
2777
|
+
this.skipValue();
|
|
2778
|
+
this.ensure(")");
|
|
2779
|
+
return thenValue;
|
|
2780
|
+
}
|
|
2781
|
+
this.skipValue();
|
|
2782
|
+
let elseValue = undefined;
|
|
2783
|
+
if (this.hasMoreBefore(")")) {
|
|
2784
|
+
this.selfStack.push(cond);
|
|
2785
|
+
elseValue = this.evalValue();
|
|
2786
|
+
this.selfStack.pop();
|
|
2787
|
+
}
|
|
2788
|
+
this.ensure(")");
|
|
2789
|
+
return elseValue;
|
|
2790
|
+
}
|
|
2791
|
+
if (tag === "|") {
|
|
2792
|
+
let result2 = undefined;
|
|
2793
|
+
while (this.hasMoreBefore(")")) {
|
|
2794
|
+
if (isDefined(result2))
|
|
2795
|
+
this.skipValue();
|
|
2796
|
+
else
|
|
2797
|
+
result2 = this.evalValue();
|
|
2798
|
+
}
|
|
2799
|
+
this.ensure(")");
|
|
2800
|
+
return result2;
|
|
2801
|
+
}
|
|
2802
|
+
let result = undefined;
|
|
2803
|
+
while (this.hasMoreBefore(")")) {
|
|
2804
|
+
if (!isDefined(result) && result !== undefined) {
|
|
2805
|
+
this.skipValue();
|
|
2806
|
+
continue;
|
|
2807
|
+
}
|
|
2808
|
+
result = this.evalValue();
|
|
2809
|
+
if (!isDefined(result)) {
|
|
2810
|
+
while (this.hasMoreBefore(")"))
|
|
2811
|
+
this.skipValue();
|
|
2812
|
+
break;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
this.ensure(")");
|
|
2816
|
+
return result;
|
|
2817
|
+
}
|
|
2818
|
+
evalLoopLike(tag) {
|
|
2819
|
+
this.pos += 1;
|
|
2820
|
+
const open = this.text[this.pos];
|
|
2821
|
+
if (!open || !"([{".includes(open))
|
|
2822
|
+
throw new Error(`Expected loop opener after '${tag}'`);
|
|
2823
|
+
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
2824
|
+
this.pos += 1;
|
|
2825
|
+
const iterable = this.evalValue();
|
|
2826
|
+
const afterIterable = this.pos;
|
|
2827
|
+
const bodyValueCount = open === "{" ? 2 : 1;
|
|
2828
|
+
let varA;
|
|
2829
|
+
let varB;
|
|
2830
|
+
let bodyStart = afterIterable;
|
|
2831
|
+
let bodyEnd = afterIterable;
|
|
2832
|
+
let matched = false;
|
|
2833
|
+
for (const bindingCount of [2, 1, 0]) {
|
|
2834
|
+
this.pos = afterIterable;
|
|
2835
|
+
const vars = [];
|
|
2836
|
+
let bindingsOk = true;
|
|
2837
|
+
for (let index = 0;index < bindingCount; index += 1) {
|
|
2838
|
+
const binding = this.readBindingVarIfPresent();
|
|
2839
|
+
if (!binding) {
|
|
2840
|
+
bindingsOk = false;
|
|
2841
|
+
break;
|
|
2842
|
+
}
|
|
2843
|
+
vars.push(binding);
|
|
2844
|
+
}
|
|
2845
|
+
if (!bindingsOk)
|
|
2846
|
+
continue;
|
|
2847
|
+
const candidateBodyStart = this.pos;
|
|
2848
|
+
let cursor = candidateBodyStart;
|
|
2849
|
+
let bodyOk = true;
|
|
2850
|
+
for (let index = 0;index < bodyValueCount; index += 1) {
|
|
2851
|
+
const next = this.skipValueFrom(cursor);
|
|
2852
|
+
if (next <= cursor) {
|
|
2853
|
+
bodyOk = false;
|
|
2854
|
+
break;
|
|
2855
|
+
}
|
|
2856
|
+
cursor = next;
|
|
2857
|
+
}
|
|
2858
|
+
if (!bodyOk)
|
|
2859
|
+
continue;
|
|
2860
|
+
this.pos = cursor;
|
|
2861
|
+
this.skipNonCode();
|
|
2862
|
+
if (this.text[this.pos] !== close)
|
|
2863
|
+
continue;
|
|
2864
|
+
varA = vars[0];
|
|
2865
|
+
varB = vars[1];
|
|
2866
|
+
bodyStart = candidateBodyStart;
|
|
2867
|
+
bodyEnd = cursor;
|
|
2868
|
+
matched = true;
|
|
2869
|
+
break;
|
|
2870
|
+
}
|
|
2871
|
+
if (!matched)
|
|
2872
|
+
throw new Error(`Invalid loop/comprehension body before '${close}' at ${this.pos}`);
|
|
2873
|
+
this.pos = bodyEnd;
|
|
2874
|
+
this.ensure(close);
|
|
2875
|
+
if (open === "[")
|
|
2876
|
+
return this.evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, tag === "<");
|
|
2877
|
+
if (open === "{")
|
|
2878
|
+
return this.evalObjectComprehension(iterable, varA, varB, bodyStart, bodyEnd, tag === "<");
|
|
2879
|
+
return this.evalForLoop(iterable, varA, varB, bodyStart, bodyEnd, tag === "<");
|
|
2880
|
+
}
|
|
2881
|
+
iterate(iterable, keysOnly) {
|
|
2882
|
+
if (Array.isArray(iterable)) {
|
|
2883
|
+
if (keysOnly)
|
|
2884
|
+
return iterable.map((_value, index) => ({ key: index, value: index }));
|
|
2885
|
+
return iterable.map((value, index) => ({ key: index, value }));
|
|
2886
|
+
}
|
|
2887
|
+
if (iterable && typeof iterable === "object") {
|
|
2888
|
+
const entries = Object.entries(iterable);
|
|
2889
|
+
if (keysOnly)
|
|
2890
|
+
return entries.map(([key]) => ({ key, value: key }));
|
|
2891
|
+
return entries.map(([key, value]) => ({ key, value }));
|
|
2892
|
+
}
|
|
2893
|
+
if (typeof iterable === "number" && Number.isFinite(iterable) && iterable > 0) {
|
|
2894
|
+
const out = [];
|
|
2895
|
+
for (let index = 0;index < Math.floor(iterable); index += 1) {
|
|
2896
|
+
if (keysOnly)
|
|
2897
|
+
out.push({ key: index, value: index });
|
|
2898
|
+
else
|
|
2899
|
+
out.push({ key: index, value: index + 1 });
|
|
2900
|
+
}
|
|
2901
|
+
return out;
|
|
2902
|
+
}
|
|
2903
|
+
return [];
|
|
2904
|
+
}
|
|
2905
|
+
evalBodySlice(start, end) {
|
|
2906
|
+
const save = this.pos;
|
|
2907
|
+
this.pos = start;
|
|
2908
|
+
const value = this.evalValue();
|
|
2909
|
+
this.pos = end;
|
|
2910
|
+
this.pos = save;
|
|
2911
|
+
return value;
|
|
2912
|
+
}
|
|
2913
|
+
handleLoopControl(value) {
|
|
2914
|
+
if (value && typeof value === "object" && "kind" in value && "depth" in value) {
|
|
2915
|
+
return value;
|
|
2916
|
+
}
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
evalForLoop(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
|
|
2920
|
+
const items = this.iterate(iterable, keysOnly);
|
|
2921
|
+
let last = undefined;
|
|
2922
|
+
for (const item of items) {
|
|
2923
|
+
this.tick();
|
|
2924
|
+
const currentSelf = keysOnly ? item.key : item.value;
|
|
2925
|
+
this.selfStack.push(currentSelf);
|
|
2926
|
+
if (varA && varB) {
|
|
2927
|
+
this.state.vars[varA] = item.key;
|
|
2928
|
+
this.state.vars[varB] = keysOnly ? item.key : item.value;
|
|
2929
|
+
} else if (varA) {
|
|
2930
|
+
this.state.vars[varA] = keysOnly ? item.key : item.value;
|
|
2931
|
+
}
|
|
2932
|
+
last = this.evalBodySlice(bodyStart, bodyEnd);
|
|
2933
|
+
this.selfStack.pop();
|
|
2934
|
+
const control = this.handleLoopControl(last);
|
|
2935
|
+
if (!control)
|
|
2936
|
+
continue;
|
|
2937
|
+
if (control.depth > 1)
|
|
2938
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
2939
|
+
if (control.kind === "break")
|
|
2940
|
+
return;
|
|
2941
|
+
last = undefined;
|
|
2214
2942
|
continue;
|
|
2215
2943
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2944
|
+
return last;
|
|
2945
|
+
}
|
|
2946
|
+
evalWhileLoop() {
|
|
2947
|
+
this.pos += 1;
|
|
2948
|
+
this.ensure("(");
|
|
2949
|
+
const condStart = this.pos;
|
|
2950
|
+
const condValue = this.evalValue();
|
|
2951
|
+
const bodyStart = this.pos;
|
|
2952
|
+
const bodyValueCount = 1;
|
|
2953
|
+
let cursor = bodyStart;
|
|
2954
|
+
for (let index = 0;index < bodyValueCount; index += 1) {
|
|
2955
|
+
cursor = this.skipValueFrom(cursor);
|
|
2956
|
+
}
|
|
2957
|
+
const bodyEnd = cursor;
|
|
2958
|
+
this.pos = bodyEnd;
|
|
2959
|
+
this.ensure(")");
|
|
2960
|
+
const afterClose = this.pos;
|
|
2961
|
+
let last = undefined;
|
|
2962
|
+
let currentCond = condValue;
|
|
2963
|
+
while (isDefined(currentCond)) {
|
|
2964
|
+
this.tick();
|
|
2965
|
+
this.selfStack.push(currentCond);
|
|
2966
|
+
last = this.evalBodySlice(bodyStart, bodyEnd);
|
|
2967
|
+
this.selfStack.pop();
|
|
2968
|
+
const control = this.handleLoopControl(last);
|
|
2969
|
+
if (control) {
|
|
2970
|
+
if (control.depth > 1)
|
|
2971
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
2972
|
+
if (control.kind === "break")
|
|
2973
|
+
return;
|
|
2974
|
+
last = undefined;
|
|
2975
|
+
}
|
|
2976
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2977
|
+
}
|
|
2978
|
+
this.pos = afterClose;
|
|
2979
|
+
return last;
|
|
2980
|
+
}
|
|
2981
|
+
evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
|
|
2982
|
+
const items = this.iterate(iterable, keysOnly);
|
|
2983
|
+
const out = [];
|
|
2984
|
+
for (const item of items) {
|
|
2985
|
+
this.tick();
|
|
2986
|
+
const currentSelf = keysOnly ? item.key : item.value;
|
|
2987
|
+
this.selfStack.push(currentSelf);
|
|
2988
|
+
if (varA && varB) {
|
|
2989
|
+
this.state.vars[varA] = item.key;
|
|
2990
|
+
this.state.vars[varB] = keysOnly ? item.key : item.value;
|
|
2991
|
+
} else if (varA) {
|
|
2992
|
+
this.state.vars[varA] = keysOnly ? item.key : item.value;
|
|
2993
|
+
}
|
|
2994
|
+
const value = this.evalBodySlice(bodyStart, bodyEnd);
|
|
2995
|
+
this.selfStack.pop();
|
|
2996
|
+
const control = this.handleLoopControl(value);
|
|
2997
|
+
if (control) {
|
|
2998
|
+
if (control.depth > 1)
|
|
2999
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
3000
|
+
if (control.kind === "break")
|
|
3001
|
+
break;
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
if (isDefined(value))
|
|
3005
|
+
out.push(value);
|
|
3006
|
+
}
|
|
3007
|
+
return out;
|
|
3008
|
+
}
|
|
3009
|
+
evalObjectComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
|
|
3010
|
+
const items = this.iterate(iterable, keysOnly);
|
|
3011
|
+
const out = {};
|
|
3012
|
+
for (const item of items) {
|
|
3013
|
+
this.tick();
|
|
3014
|
+
const currentSelf = keysOnly ? item.key : item.value;
|
|
3015
|
+
this.selfStack.push(currentSelf);
|
|
3016
|
+
if (varA && varB) {
|
|
3017
|
+
this.state.vars[varA] = item.key;
|
|
3018
|
+
this.state.vars[varB] = keysOnly ? item.key : item.value;
|
|
3019
|
+
} else if (varA) {
|
|
3020
|
+
this.state.vars[varA] = keysOnly ? item.key : item.value;
|
|
3021
|
+
}
|
|
3022
|
+
const save = this.pos;
|
|
3023
|
+
this.pos = bodyStart;
|
|
3024
|
+
const key = this.evalValue();
|
|
3025
|
+
const value = this.evalValue();
|
|
3026
|
+
this.pos = save;
|
|
3027
|
+
this.selfStack.pop();
|
|
3028
|
+
const control = this.handleLoopControl(value);
|
|
3029
|
+
if (control) {
|
|
3030
|
+
if (control.depth > 1)
|
|
3031
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
3032
|
+
if (control.kind === "break")
|
|
3033
|
+
break;
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
if (isDefined(value))
|
|
3037
|
+
out[String(key)] = value;
|
|
3038
|
+
}
|
|
3039
|
+
return out;
|
|
3040
|
+
}
|
|
3041
|
+
applyOpcode(id, args) {
|
|
3042
|
+
const custom = this.customOpcodes.get(id);
|
|
3043
|
+
if (custom)
|
|
3044
|
+
return custom(args, this.state);
|
|
3045
|
+
switch (id) {
|
|
3046
|
+
case OPCODES.do:
|
|
3047
|
+
return args.length ? args[args.length - 1] : undefined;
|
|
3048
|
+
case OPCODES.add:
|
|
3049
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3050
|
+
return;
|
|
3051
|
+
if (typeof args[0] === "string" || typeof args[1] === "string") {
|
|
3052
|
+
return String(args[0]) + String(args[1]);
|
|
3053
|
+
}
|
|
3054
|
+
return Number(args[0]) + Number(args[1]);
|
|
3055
|
+
case OPCODES.sub:
|
|
3056
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3057
|
+
return;
|
|
3058
|
+
return Number(args[0]) - Number(args[1]);
|
|
3059
|
+
case OPCODES.mul:
|
|
3060
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3061
|
+
return;
|
|
3062
|
+
return Number(args[0]) * Number(args[1]);
|
|
3063
|
+
case OPCODES.div:
|
|
3064
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3065
|
+
return;
|
|
3066
|
+
return Number(args[0]) / Number(args[1]);
|
|
3067
|
+
case OPCODES.mod:
|
|
3068
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3069
|
+
return;
|
|
3070
|
+
return Number(args[0]) % Number(args[1]);
|
|
3071
|
+
case OPCODES.neg:
|
|
3072
|
+
if (args[0] === undefined)
|
|
3073
|
+
return;
|
|
3074
|
+
return -Number(args[0]);
|
|
3075
|
+
case OPCODES.not: {
|
|
3076
|
+
const value = args[0];
|
|
3077
|
+
if (value === undefined)
|
|
3078
|
+
return;
|
|
3079
|
+
if (typeof value === "boolean")
|
|
3080
|
+
return !value;
|
|
3081
|
+
return ~Number(value);
|
|
3082
|
+
}
|
|
3083
|
+
case OPCODES.and: {
|
|
3084
|
+
const [a, b] = args;
|
|
3085
|
+
if (typeof a === "boolean" || typeof b === "boolean")
|
|
3086
|
+
return Boolean(a) && Boolean(b);
|
|
3087
|
+
return Number(a ?? 0) & Number(b ?? 0);
|
|
3088
|
+
}
|
|
3089
|
+
case OPCODES.or: {
|
|
3090
|
+
const [a, b] = args;
|
|
3091
|
+
if (typeof a === "boolean" || typeof b === "boolean")
|
|
3092
|
+
return Boolean(a) || Boolean(b);
|
|
3093
|
+
return Number(a ?? 0) | Number(b ?? 0);
|
|
3094
|
+
}
|
|
3095
|
+
case OPCODES.xor: {
|
|
3096
|
+
const [a, b] = args;
|
|
3097
|
+
if (typeof a === "boolean" || typeof b === "boolean")
|
|
3098
|
+
return Boolean(a) !== Boolean(b);
|
|
3099
|
+
return Number(a ?? 0) ^ Number(b ?? 0);
|
|
3100
|
+
}
|
|
3101
|
+
case OPCODES.eq:
|
|
3102
|
+
return args[0] === args[1] ? args[0] : undefined;
|
|
3103
|
+
case OPCODES.neq:
|
|
3104
|
+
return args[0] !== args[1] ? args[0] : undefined;
|
|
3105
|
+
case OPCODES.gt:
|
|
3106
|
+
return Number(args[0]) > Number(args[1]) ? args[0] : undefined;
|
|
3107
|
+
case OPCODES.gte:
|
|
3108
|
+
return Number(args[0]) >= Number(args[1]) ? args[0] : undefined;
|
|
3109
|
+
case OPCODES.lt:
|
|
3110
|
+
return Number(args[0]) < Number(args[1]) ? args[0] : undefined;
|
|
3111
|
+
case OPCODES.lte:
|
|
3112
|
+
return Number(args[0]) <= Number(args[1]) ? args[0] : undefined;
|
|
3113
|
+
case OPCODES.boolean:
|
|
3114
|
+
return typeof args[0] === "boolean" ? args[0] : undefined;
|
|
3115
|
+
case OPCODES.number:
|
|
3116
|
+
return typeof args[0] === "number" ? args[0] : undefined;
|
|
3117
|
+
case OPCODES.string:
|
|
3118
|
+
return typeof args[0] === "string" ? args[0] : undefined;
|
|
3119
|
+
case OPCODES.array:
|
|
3120
|
+
return Array.isArray(args[0]) ? args[0] : undefined;
|
|
3121
|
+
case OPCODES.object:
|
|
3122
|
+
return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
|
|
3123
|
+
default:
|
|
3124
|
+
throw new Error(`Unknown opcode ${id}`);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
navigate(base, keys) {
|
|
3128
|
+
let current = base;
|
|
3129
|
+
for (const key of keys) {
|
|
3130
|
+
if (current === undefined || current === null)
|
|
3131
|
+
return;
|
|
3132
|
+
current = current[String(key)];
|
|
3133
|
+
}
|
|
3134
|
+
return current;
|
|
3135
|
+
}
|
|
3136
|
+
readPlace() {
|
|
3137
|
+
this.skipNonCode();
|
|
3138
|
+
const direct = this.readRootVarOrRefIfPresent();
|
|
3139
|
+
if (direct) {
|
|
3140
|
+
const keys = [];
|
|
3141
|
+
this.skipNonCode();
|
|
3142
|
+
if (this.text[this.pos] === "(") {
|
|
3143
|
+
this.pos += 1;
|
|
3144
|
+
while (true) {
|
|
3145
|
+
this.skipNonCode();
|
|
3146
|
+
if (this.text[this.pos] === ")")
|
|
3147
|
+
break;
|
|
3148
|
+
keys.push(this.evalValue());
|
|
3149
|
+
}
|
|
3150
|
+
this.pos += 1;
|
|
3151
|
+
}
|
|
3152
|
+
return {
|
|
3153
|
+
root: direct.root,
|
|
3154
|
+
keys,
|
|
3155
|
+
isRef: direct.isRef
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
if (this.text[this.pos] === "(") {
|
|
3159
|
+
this.pos += 1;
|
|
3160
|
+
this.skipNonCode();
|
|
3161
|
+
const rootFromNav = this.readRootVarOrRefIfPresent();
|
|
3162
|
+
if (!rootFromNav)
|
|
3163
|
+
throw new Error(`Invalid place root at ${this.pos}`);
|
|
3164
|
+
const keys = [];
|
|
3165
|
+
while (true) {
|
|
3166
|
+
this.skipNonCode();
|
|
3167
|
+
if (this.text[this.pos] === ")")
|
|
3168
|
+
break;
|
|
3169
|
+
keys.push(this.evalValue());
|
|
3170
|
+
}
|
|
3171
|
+
this.pos += 1;
|
|
3172
|
+
return {
|
|
3173
|
+
root: rootFromNav.root,
|
|
3174
|
+
keys,
|
|
3175
|
+
isRef: rootFromNav.isRef
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3178
|
+
throw new Error(`Invalid place at ${this.pos}`);
|
|
3179
|
+
}
|
|
3180
|
+
readRootVarOrRefIfPresent() {
|
|
3181
|
+
const save = this.pos;
|
|
3182
|
+
const prefix = this.readPrefix();
|
|
3183
|
+
const tag = this.text[this.pos];
|
|
3184
|
+
if (tag !== "$" && tag !== "'") {
|
|
3185
|
+
this.pos = save;
|
|
3186
|
+
return;
|
|
3187
|
+
}
|
|
3188
|
+
this.pos += 1;
|
|
3189
|
+
return {
|
|
3190
|
+
root: tag === "$" ? prefix.raw : prefix.value,
|
|
3191
|
+
isRef: tag === "'"
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
writePlace(place, value) {
|
|
3195
|
+
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3196
|
+
const rootKey = String(place.root);
|
|
3197
|
+
if (place.keys.length === 0) {
|
|
3198
|
+
rootTable[rootKey] = value;
|
|
3199
|
+
return;
|
|
3200
|
+
}
|
|
3201
|
+
let target = rootTable[rootKey];
|
|
3202
|
+
if (!target || typeof target !== "object") {
|
|
3203
|
+
target = {};
|
|
3204
|
+
rootTable[rootKey] = target;
|
|
3205
|
+
}
|
|
3206
|
+
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3207
|
+
const key = String(place.keys[index]);
|
|
3208
|
+
const next = target[key];
|
|
3209
|
+
if (!next || typeof next !== "object")
|
|
3210
|
+
target[key] = {};
|
|
3211
|
+
target = target[key];
|
|
3212
|
+
}
|
|
3213
|
+
target[String(place.keys[place.keys.length - 1])] = value;
|
|
3214
|
+
}
|
|
3215
|
+
deletePlace(place) {
|
|
3216
|
+
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3217
|
+
const rootKey = String(place.root);
|
|
3218
|
+
if (place.keys.length === 0) {
|
|
3219
|
+
delete rootTable[rootKey];
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
let target = rootTable[rootKey];
|
|
3223
|
+
if (!target || typeof target !== "object")
|
|
3224
|
+
return;
|
|
3225
|
+
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3226
|
+
target = target[String(place.keys[index])];
|
|
3227
|
+
if (!target || typeof target !== "object")
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3230
|
+
delete target[String(place.keys[place.keys.length - 1])];
|
|
3231
|
+
}
|
|
3232
|
+
skipValue() {
|
|
3233
|
+
this.pos = this.skipValueFrom(this.pos);
|
|
3234
|
+
}
|
|
3235
|
+
skipValueFrom(startPos) {
|
|
3236
|
+
const save = this.pos;
|
|
3237
|
+
this.pos = startPos;
|
|
3238
|
+
this.skipNonCode();
|
|
3239
|
+
const prefix = this.readPrefix();
|
|
3240
|
+
const tag = this.text[this.pos];
|
|
3241
|
+
if (!tag) {
|
|
3242
|
+
this.pos = save;
|
|
3243
|
+
return startPos;
|
|
3244
|
+
}
|
|
3245
|
+
if (tag === ",") {
|
|
3246
|
+
this.pos += 1 + prefix.value;
|
|
3247
|
+
const end2 = this.pos;
|
|
3248
|
+
this.pos = save;
|
|
3249
|
+
return end2;
|
|
3250
|
+
}
|
|
3251
|
+
if (tag === "=") {
|
|
3252
|
+
this.pos += 1;
|
|
3253
|
+
this.skipValue();
|
|
3254
|
+
this.skipValue();
|
|
3255
|
+
const end2 = this.pos;
|
|
3256
|
+
this.pos = save;
|
|
3257
|
+
return end2;
|
|
3258
|
+
}
|
|
3259
|
+
if (tag === "~") {
|
|
3260
|
+
this.pos += 1;
|
|
3261
|
+
this.skipValue();
|
|
3262
|
+
const end2 = this.pos;
|
|
3263
|
+
this.pos = save;
|
|
3264
|
+
return end2;
|
|
3265
|
+
}
|
|
3266
|
+
if (tag === "*") {
|
|
3267
|
+
this.pos += 1;
|
|
3268
|
+
this.skipValue();
|
|
3269
|
+
const end2 = this.pos;
|
|
3270
|
+
this.pos = save;
|
|
3271
|
+
return end2;
|
|
3272
|
+
}
|
|
3273
|
+
if ("+:%$@'^;".includes(tag)) {
|
|
3274
|
+
this.pos += 1;
|
|
3275
|
+
const end2 = this.pos;
|
|
3276
|
+
this.pos = save;
|
|
3277
|
+
return end2;
|
|
3278
|
+
}
|
|
3279
|
+
if (tag === "?" || tag === "!" || tag === "|" || tag === "&" || tag === ">" || tag === "<" || tag === "#") {
|
|
3280
|
+
this.pos += 1;
|
|
3281
|
+
}
|
|
3282
|
+
const opener = this.text[this.pos];
|
|
3283
|
+
if (opener && "([{".includes(opener)) {
|
|
3284
|
+
const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
|
|
3285
|
+
if (prefix.value > 0) {
|
|
3286
|
+
this.pos += 1 + prefix.value + 1;
|
|
3287
|
+
const end3 = this.pos;
|
|
3288
|
+
this.pos = save;
|
|
3289
|
+
return end3;
|
|
3290
|
+
}
|
|
3291
|
+
this.pos += 1;
|
|
3292
|
+
while (true) {
|
|
3293
|
+
this.skipNonCode();
|
|
3294
|
+
if (this.text[this.pos] === close)
|
|
3295
|
+
break;
|
|
3296
|
+
this.skipValue();
|
|
3297
|
+
}
|
|
3298
|
+
this.pos += 1;
|
|
3299
|
+
const end2 = this.pos;
|
|
3300
|
+
this.pos = save;
|
|
3301
|
+
return end2;
|
|
3302
|
+
}
|
|
3303
|
+
this.pos += 1;
|
|
3304
|
+
const end = this.pos;
|
|
3305
|
+
this.pos = save;
|
|
3306
|
+
return end;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
function evaluateRexc(text, ctx = {}) {
|
|
3310
|
+
const interpreter = new CursorInterpreter(text, ctx);
|
|
3311
|
+
const value = interpreter.evaluateTopLevel();
|
|
3312
|
+
return { value, state: interpreter.runtimeState };
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
// rex-repl.ts
|
|
3316
|
+
var req = createRequire2(import.meta.url);
|
|
3317
|
+
var { version } = req("./package.json");
|
|
3318
|
+
var C = {
|
|
3319
|
+
reset: "\x1B[0m",
|
|
3320
|
+
bold: "\x1B[1m",
|
|
3321
|
+
dim: "\x1B[2m",
|
|
3322
|
+
red: "\x1B[31m",
|
|
3323
|
+
green: "\x1B[32m",
|
|
3324
|
+
yellow: "\x1B[33m",
|
|
3325
|
+
blue: "\x1B[34m",
|
|
3326
|
+
cyan: "\x1B[36m",
|
|
3327
|
+
gray: "\x1B[90m",
|
|
3328
|
+
boldBlue: "\x1B[1;34m"
|
|
3329
|
+
};
|
|
3330
|
+
var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|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;
|
|
3331
|
+
function highlightLine(line) {
|
|
3332
|
+
let result = "";
|
|
3333
|
+
let lastIndex = 0;
|
|
3334
|
+
TOKEN_RE.lastIndex = 0;
|
|
3335
|
+
for (const m of line.matchAll(TOKEN_RE)) {
|
|
3336
|
+
result += line.slice(lastIndex, m.index);
|
|
3337
|
+
const text = m[0];
|
|
3338
|
+
const g = m.groups;
|
|
3339
|
+
if (g.blockComment || g.lineComment) {
|
|
3340
|
+
result += C.gray + text + C.reset;
|
|
3341
|
+
} else if (g.dstring || g.sstring) {
|
|
3342
|
+
result += C.green + text + C.reset;
|
|
3343
|
+
} else if (g.keyword) {
|
|
3344
|
+
result += C.boldBlue + text + C.reset;
|
|
3345
|
+
} else if (g.literal) {
|
|
3346
|
+
result += C.yellow + text + C.reset;
|
|
3347
|
+
} else if (g.typePred) {
|
|
3348
|
+
result += C.cyan + text + C.reset;
|
|
3349
|
+
} else if (g.num) {
|
|
3350
|
+
result += C.cyan + text + C.reset;
|
|
3351
|
+
} else {
|
|
3352
|
+
result += text;
|
|
3353
|
+
}
|
|
3354
|
+
lastIndex = m.index + text.length;
|
|
3355
|
+
}
|
|
3356
|
+
result += line.slice(lastIndex);
|
|
3357
|
+
return result;
|
|
3358
|
+
}
|
|
3359
|
+
var REXC_DIGITS = new Set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_");
|
|
3360
|
+
function highlightRexc(text) {
|
|
3361
|
+
let out = "";
|
|
3362
|
+
let i = 0;
|
|
3363
|
+
function readPrefix() {
|
|
3364
|
+
const start = i;
|
|
3365
|
+
while (i < text.length && REXC_DIGITS.has(text[i]))
|
|
3366
|
+
i++;
|
|
3367
|
+
return text.slice(start, i);
|
|
3368
|
+
}
|
|
3369
|
+
while (i < text.length) {
|
|
3370
|
+
const ch = text[i];
|
|
3371
|
+
if (ch === " " || ch === "\t" || ch === `
|
|
3372
|
+
` || ch === "\r") {
|
|
3373
|
+
out += ch;
|
|
3374
|
+
i++;
|
|
2222
3375
|
continue;
|
|
2223
3376
|
}
|
|
2224
|
-
if (
|
|
2225
|
-
const
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
3377
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
3378
|
+
const start = i;
|
|
3379
|
+
i += 2;
|
|
3380
|
+
while (i < text.length && text[i] !== `
|
|
3381
|
+
`)
|
|
3382
|
+
i++;
|
|
3383
|
+
out += C.gray + text.slice(start, i) + C.reset;
|
|
2230
3384
|
continue;
|
|
2231
3385
|
}
|
|
2232
|
-
if (
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
3386
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
3387
|
+
const start = i;
|
|
3388
|
+
i += 2;
|
|
3389
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
|
|
3390
|
+
i++;
|
|
3391
|
+
if (i < text.length)
|
|
3392
|
+
i += 2;
|
|
3393
|
+
out += C.gray + text.slice(start, i) + C.reset;
|
|
2238
3394
|
continue;
|
|
2239
3395
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
3396
|
+
const prefix = readPrefix();
|
|
3397
|
+
if (i >= text.length) {
|
|
3398
|
+
out += prefix;
|
|
3399
|
+
break;
|
|
3400
|
+
}
|
|
3401
|
+
const tag = text[i];
|
|
3402
|
+
switch (tag) {
|
|
3403
|
+
case "+":
|
|
3404
|
+
case "*":
|
|
3405
|
+
out += C.cyan + prefix + tag + C.reset;
|
|
3406
|
+
i++;
|
|
3407
|
+
break;
|
|
3408
|
+
case ":":
|
|
3409
|
+
out += C.dim + prefix + tag + C.reset;
|
|
3410
|
+
i++;
|
|
3411
|
+
break;
|
|
3412
|
+
case "%":
|
|
3413
|
+
out += C.boldBlue + prefix + tag + C.reset;
|
|
3414
|
+
i++;
|
|
3415
|
+
break;
|
|
3416
|
+
case "$":
|
|
3417
|
+
out += C.yellow + prefix + tag + C.reset;
|
|
3418
|
+
i++;
|
|
3419
|
+
break;
|
|
3420
|
+
case "@":
|
|
3421
|
+
out += C.yellow + prefix + tag + C.reset;
|
|
3422
|
+
i++;
|
|
3423
|
+
break;
|
|
3424
|
+
case "'":
|
|
3425
|
+
out += C.dim + prefix + tag + C.reset;
|
|
3426
|
+
i++;
|
|
3427
|
+
break;
|
|
3428
|
+
case ",": {
|
|
3429
|
+
i++;
|
|
3430
|
+
let len = 0;
|
|
3431
|
+
for (const ch2 of prefix)
|
|
3432
|
+
len = len * 64 + (REXC_DIGITS.has(ch2) ? "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_".indexOf(ch2) : 0);
|
|
3433
|
+
const content = text.slice(i, i + len);
|
|
3434
|
+
i += len;
|
|
3435
|
+
out += C.green + prefix + "," + content + C.reset;
|
|
3436
|
+
break;
|
|
3437
|
+
}
|
|
3438
|
+
case "=":
|
|
3439
|
+
case "~":
|
|
3440
|
+
out += C.red + prefix + tag + C.reset;
|
|
3441
|
+
i++;
|
|
3442
|
+
break;
|
|
3443
|
+
case "?":
|
|
3444
|
+
case "!":
|
|
3445
|
+
case "|":
|
|
3446
|
+
case "&":
|
|
3447
|
+
case ">":
|
|
3448
|
+
case "<":
|
|
3449
|
+
case "#":
|
|
3450
|
+
out += C.boldBlue + prefix + tag + C.reset;
|
|
3451
|
+
i++;
|
|
3452
|
+
break;
|
|
3453
|
+
case ";":
|
|
3454
|
+
out += C.boldBlue + prefix + tag + C.reset;
|
|
3455
|
+
i++;
|
|
3456
|
+
break;
|
|
3457
|
+
case "^":
|
|
3458
|
+
out += C.dim + prefix + tag + C.reset;
|
|
3459
|
+
i++;
|
|
3460
|
+
break;
|
|
3461
|
+
case "(":
|
|
3462
|
+
case ")":
|
|
3463
|
+
case "[":
|
|
3464
|
+
case "]":
|
|
3465
|
+
case "{":
|
|
3466
|
+
case "}":
|
|
3467
|
+
out += C.dim + prefix + C.reset + tag;
|
|
3468
|
+
i++;
|
|
3469
|
+
break;
|
|
3470
|
+
default:
|
|
3471
|
+
out += prefix + tag;
|
|
3472
|
+
i++;
|
|
3473
|
+
break;
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
return out;
|
|
2268
3477
|
}
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
let parsed;
|
|
2293
|
-
try {
|
|
2294
|
-
parsed = JSON.parse(await readFile(schemaPath, "utf8"));
|
|
2295
|
-
} catch (error) {
|
|
2296
|
-
if (error.code === "ENOENT")
|
|
2297
|
-
return {};
|
|
2298
|
-
throw error;
|
|
3478
|
+
var JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
|
|
3479
|
+
function highlightJSON(json) {
|
|
3480
|
+
let result = "";
|
|
3481
|
+
let lastIndex = 0;
|
|
3482
|
+
JSON_TOKEN_RE.lastIndex = 0;
|
|
3483
|
+
for (const m of json.matchAll(JSON_TOKEN_RE)) {
|
|
3484
|
+
result += json.slice(lastIndex, m.index);
|
|
3485
|
+
const text = m[0];
|
|
3486
|
+
const g = m.groups;
|
|
3487
|
+
if (g.key) {
|
|
3488
|
+
result += C.cyan + g.key + C.reset + ":";
|
|
3489
|
+
} else if (g.string) {
|
|
3490
|
+
result += C.green + text + C.reset;
|
|
3491
|
+
} else if (g.number) {
|
|
3492
|
+
result += C.yellow + text + C.reset;
|
|
3493
|
+
} else if (g.bool) {
|
|
3494
|
+
result += C.yellow + text + C.reset;
|
|
3495
|
+
} else if (g.null) {
|
|
3496
|
+
result += C.dim + text + C.reset;
|
|
3497
|
+
} else {
|
|
3498
|
+
result += text;
|
|
3499
|
+
}
|
|
3500
|
+
lastIndex = m.index + text.length;
|
|
2299
3501
|
}
|
|
2300
|
-
|
|
2301
|
-
|
|
3502
|
+
result += json.slice(lastIndex);
|
|
3503
|
+
return result;
|
|
3504
|
+
}
|
|
3505
|
+
function stripStringsAndComments(source) {
|
|
3506
|
+
return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, (m) => " ".repeat(m.length));
|
|
3507
|
+
}
|
|
3508
|
+
function countWord(text, word) {
|
|
3509
|
+
const re = new RegExp(`\\b${word}(?![a-zA-Z0-9_-])`, "g");
|
|
3510
|
+
return (text.match(re) || []).length;
|
|
3511
|
+
}
|
|
3512
|
+
function isIncomplete(buffer) {
|
|
3513
|
+
try {
|
|
3514
|
+
if (grammar.match(buffer).succeeded())
|
|
3515
|
+
return false;
|
|
3516
|
+
} catch {}
|
|
3517
|
+
const stripped = stripStringsAndComments(buffer);
|
|
3518
|
+
let parens = 0, brackets = 0, braces = 0;
|
|
3519
|
+
for (const ch of stripped) {
|
|
3520
|
+
if (ch === "(")
|
|
3521
|
+
parens++;
|
|
3522
|
+
else if (ch === ")")
|
|
3523
|
+
parens--;
|
|
3524
|
+
else if (ch === "[")
|
|
3525
|
+
brackets++;
|
|
3526
|
+
else if (ch === "]")
|
|
3527
|
+
brackets--;
|
|
3528
|
+
else if (ch === "{")
|
|
3529
|
+
braces++;
|
|
3530
|
+
else if (ch === "}")
|
|
3531
|
+
braces--;
|
|
3532
|
+
}
|
|
3533
|
+
if (parens > 0 || brackets > 0 || braces > 0)
|
|
3534
|
+
return true;
|
|
3535
|
+
const doCount = countWord(stripped, "do");
|
|
3536
|
+
const endCount = countWord(stripped, "end");
|
|
3537
|
+
if (doCount > endCount)
|
|
3538
|
+
return true;
|
|
3539
|
+
const trimmed = buffer.trimEnd();
|
|
3540
|
+
if (/[+\-*/%&|^=<>]$/.test(trimmed))
|
|
3541
|
+
return true;
|
|
3542
|
+
if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
|
|
3543
|
+
return true;
|
|
3544
|
+
return false;
|
|
3545
|
+
}
|
|
3546
|
+
function formatResult(value) {
|
|
3547
|
+
let text;
|
|
3548
|
+
try {
|
|
3549
|
+
text = stringify(value, { maxWidth: 60 });
|
|
3550
|
+
} catch {
|
|
3551
|
+
text = String(value);
|
|
2302
3552
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
3553
|
+
return `${C.gray}→${C.reset} ${highlightLine(text)}`;
|
|
3554
|
+
}
|
|
3555
|
+
function formatVarState(vars) {
|
|
3556
|
+
const entries = Object.entries(vars);
|
|
3557
|
+
if (entries.length === 0)
|
|
3558
|
+
return "";
|
|
3559
|
+
const MAX_LINE = 70;
|
|
3560
|
+
const MAX_VALUE = 30;
|
|
3561
|
+
const parts = [];
|
|
3562
|
+
let totalLen = 0;
|
|
3563
|
+
for (const [key, val] of entries) {
|
|
3564
|
+
let valStr;
|
|
3565
|
+
try {
|
|
3566
|
+
valStr = stringify(val, { maxWidth: MAX_VALUE });
|
|
3567
|
+
} catch {
|
|
3568
|
+
valStr = String(val);
|
|
2308
3569
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
throw new Error(`Invalid rex-domain.json at ${schemaPath}: globals.${name}.ref must be an integer`);
|
|
3570
|
+
if (valStr.length > MAX_VALUE) {
|
|
3571
|
+
valStr = valStr.slice(0, MAX_VALUE - 1) + "…";
|
|
2312
3572
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
3573
|
+
const part = `${key} = ${valStr}`;
|
|
3574
|
+
if (totalLen + part.length + 2 > MAX_LINE && parts.length > 0) {
|
|
3575
|
+
parts.push("…");
|
|
3576
|
+
break;
|
|
2315
3577
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
3578
|
+
parts.push(part);
|
|
3579
|
+
totalLen += part.length + 2;
|
|
3580
|
+
}
|
|
3581
|
+
return `${C.dim} ${parts.join(", ")}${C.reset}`;
|
|
3582
|
+
}
|
|
3583
|
+
var KEYWORDS = [
|
|
3584
|
+
"when",
|
|
3585
|
+
"unless",
|
|
3586
|
+
"while",
|
|
3587
|
+
"for",
|
|
3588
|
+
"do",
|
|
3589
|
+
"end",
|
|
3590
|
+
"in",
|
|
3591
|
+
"of",
|
|
3592
|
+
"and",
|
|
3593
|
+
"or",
|
|
3594
|
+
"else",
|
|
3595
|
+
"break",
|
|
3596
|
+
"continue",
|
|
3597
|
+
"delete",
|
|
3598
|
+
"self",
|
|
3599
|
+
"true",
|
|
3600
|
+
"false",
|
|
3601
|
+
"null",
|
|
3602
|
+
"undefined",
|
|
3603
|
+
"nan",
|
|
3604
|
+
"inf",
|
|
3605
|
+
"string",
|
|
3606
|
+
"number",
|
|
3607
|
+
"object",
|
|
3608
|
+
"array",
|
|
3609
|
+
"boolean"
|
|
3610
|
+
];
|
|
3611
|
+
function completer(state) {
|
|
3612
|
+
return (line) => {
|
|
3613
|
+
const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
|
|
3614
|
+
const partial = match ? match[0] : "";
|
|
3615
|
+
if (!partial)
|
|
3616
|
+
return [[], ""];
|
|
3617
|
+
const varNames = Object.keys(state.vars);
|
|
3618
|
+
const all = [...new Set([...KEYWORDS, ...varNames])];
|
|
3619
|
+
const hits = all.filter((w) => w.startsWith(partial));
|
|
3620
|
+
return [hits, partial];
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
function handleDotCommand(cmd, state, rl) {
|
|
3624
|
+
function toggleLabel(on) {
|
|
3625
|
+
return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
|
|
3626
|
+
}
|
|
3627
|
+
switch (cmd) {
|
|
3628
|
+
case ".help":
|
|
3629
|
+
console.log([
|
|
3630
|
+
`${C.boldBlue}Rex REPL Commands:${C.reset}`,
|
|
3631
|
+
" .help Show this help message",
|
|
3632
|
+
" .vars Show all current variables",
|
|
3633
|
+
" .clear Clear all variables",
|
|
3634
|
+
" .ir Toggle showing IR JSON after parsing",
|
|
3635
|
+
" .rexc Toggle showing compiled rexc before execution",
|
|
3636
|
+
" .opt Toggle IR optimizations",
|
|
3637
|
+
" .exit Exit the REPL",
|
|
3638
|
+
"",
|
|
3639
|
+
"Enter Rex expressions to evaluate them.",
|
|
3640
|
+
"Multi-line: open brackets or do/end blocks continue on the next line.",
|
|
3641
|
+
"Ctrl-C cancels multi-line input.",
|
|
3642
|
+
"Ctrl-D exits."
|
|
3643
|
+
].join(`
|
|
3644
|
+
`));
|
|
3645
|
+
return true;
|
|
3646
|
+
case ".ir":
|
|
3647
|
+
state.showIR = !state.showIR;
|
|
3648
|
+
console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
|
|
3649
|
+
return true;
|
|
3650
|
+
case ".rexc":
|
|
3651
|
+
state.showRexc = !state.showRexc;
|
|
3652
|
+
console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
|
|
3653
|
+
return true;
|
|
3654
|
+
case ".opt":
|
|
3655
|
+
state.optimize = !state.optimize;
|
|
3656
|
+
console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
|
|
3657
|
+
return true;
|
|
3658
|
+
case ".vars": {
|
|
3659
|
+
const entries = Object.entries(state.vars);
|
|
3660
|
+
if (entries.length === 0) {
|
|
3661
|
+
console.log(`${C.dim} (no variables)${C.reset}`);
|
|
3662
|
+
} else {
|
|
3663
|
+
for (const [key, val] of entries) {
|
|
3664
|
+
let valStr;
|
|
3665
|
+
try {
|
|
3666
|
+
valStr = stringify(val, { maxWidth: 60 });
|
|
3667
|
+
} catch {
|
|
3668
|
+
valStr = String(val);
|
|
3669
|
+
}
|
|
3670
|
+
console.log(` ${key} = ${highlightLine(valStr)}`);
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
return true;
|
|
2319
3674
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
3675
|
+
case ".clear":
|
|
3676
|
+
state.vars = {};
|
|
3677
|
+
state.refs = {};
|
|
3678
|
+
console.log(`${C.dim} Variables cleared.${C.reset}`);
|
|
3679
|
+
return true;
|
|
3680
|
+
case ".exit":
|
|
3681
|
+
rl.close();
|
|
3682
|
+
return true;
|
|
3683
|
+
default:
|
|
3684
|
+
if (cmd.startsWith(".")) {
|
|
3685
|
+
console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
|
|
3686
|
+
return true;
|
|
3687
|
+
}
|
|
3688
|
+
return false;
|
|
2322
3689
|
}
|
|
2323
|
-
return refs;
|
|
2324
3690
|
}
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
}
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
3691
|
+
var GAS_LIMIT = 1e7;
|
|
3692
|
+
async function startRepl() {
|
|
3693
|
+
const state = { vars: {}, refs: {}, showIR: false, showRexc: false, optimize: false };
|
|
3694
|
+
let multiLineBuffer = "";
|
|
3695
|
+
const PRIMARY_PROMPT = "rex> ";
|
|
3696
|
+
const CONT_PROMPT = "... ";
|
|
3697
|
+
const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
|
|
3698
|
+
const STYLED_CONT = `${C.dim}...${C.reset} `;
|
|
3699
|
+
let currentPrompt = PRIMARY_PROMPT;
|
|
3700
|
+
let styledPrompt = STYLED_PRIMARY;
|
|
3701
|
+
console.log(`${C.boldBlue}Rex${C.reset} v${version} — type ${C.dim}.help${C.reset} for commands`);
|
|
3702
|
+
const rl = readline.createInterface({
|
|
3703
|
+
input: process.stdin,
|
|
3704
|
+
output: process.stdout,
|
|
3705
|
+
prompt: PRIMARY_PROMPT,
|
|
3706
|
+
historySize: 500,
|
|
3707
|
+
completer: completer(state),
|
|
3708
|
+
terminal: true
|
|
2343
3709
|
});
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
process.
|
|
2355
|
-
|
|
3710
|
+
process.stdin.on("keypress", () => {
|
|
3711
|
+
process.nextTick(() => {
|
|
3712
|
+
if (!rl.line && rl.line !== "")
|
|
3713
|
+
return;
|
|
3714
|
+
readline.clearLine(process.stdout, 0);
|
|
3715
|
+
readline.cursorTo(process.stdout, 0);
|
|
3716
|
+
process.stdout.write(styledPrompt + highlightLine(rl.line));
|
|
3717
|
+
readline.cursorTo(process.stdout, currentPrompt.length + rl.cursor);
|
|
3718
|
+
});
|
|
3719
|
+
});
|
|
3720
|
+
process.stdin.on("keypress", (_ch, key) => {
|
|
3721
|
+
if (key?.ctrl && key.name === "l") {
|
|
3722
|
+
readline.cursorTo(process.stdout, 0, 0);
|
|
3723
|
+
readline.clearScreenDown(process.stdout);
|
|
3724
|
+
rl.prompt();
|
|
3725
|
+
}
|
|
3726
|
+
});
|
|
3727
|
+
rl.on("SIGINT", () => {
|
|
3728
|
+
if (multiLineBuffer) {
|
|
3729
|
+
multiLineBuffer = "";
|
|
3730
|
+
currentPrompt = PRIMARY_PROMPT;
|
|
3731
|
+
styledPrompt = STYLED_PRIMARY;
|
|
3732
|
+
rl.setPrompt(PRIMARY_PROMPT);
|
|
3733
|
+
process.stdout.write(`
|
|
3734
|
+
`);
|
|
3735
|
+
rl.prompt();
|
|
3736
|
+
} else {
|
|
3737
|
+
console.log();
|
|
3738
|
+
rl.close();
|
|
3739
|
+
}
|
|
3740
|
+
});
|
|
3741
|
+
function resetPrompt() {
|
|
3742
|
+
currentPrompt = PRIMARY_PROMPT;
|
|
3743
|
+
styledPrompt = STYLED_PRIMARY;
|
|
3744
|
+
rl.setPrompt(PRIMARY_PROMPT);
|
|
3745
|
+
rl.prompt();
|
|
3746
|
+
}
|
|
3747
|
+
rl.on("line", (line) => {
|
|
3748
|
+
const trimmed = line.trim();
|
|
3749
|
+
if (!multiLineBuffer && trimmed.startsWith(".")) {
|
|
3750
|
+
if (handleDotCommand(trimmed, state, rl)) {
|
|
3751
|
+
rl.prompt();
|
|
3752
|
+
return;
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
multiLineBuffer += (multiLineBuffer ? `
|
|
3756
|
+
` : "") + line;
|
|
3757
|
+
if (multiLineBuffer.trim() === "") {
|
|
3758
|
+
multiLineBuffer = "";
|
|
3759
|
+
rl.prompt();
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
if (isIncomplete(multiLineBuffer)) {
|
|
3763
|
+
currentPrompt = CONT_PROMPT;
|
|
3764
|
+
styledPrompt = STYLED_CONT;
|
|
3765
|
+
rl.setPrompt(CONT_PROMPT);
|
|
3766
|
+
rl.prompt();
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
const source = multiLineBuffer;
|
|
3770
|
+
multiLineBuffer = "";
|
|
3771
|
+
const match = grammar.match(source);
|
|
3772
|
+
if (!match.succeeded()) {
|
|
3773
|
+
console.log(`${C.red} ${match.message}${C.reset}`);
|
|
3774
|
+
resetPrompt();
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
try {
|
|
3778
|
+
const ir = parseToIR(source);
|
|
3779
|
+
const lowered = state.optimize ? optimizeIR(ir) : ir;
|
|
3780
|
+
if (state.showIR) {
|
|
3781
|
+
console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
|
|
3782
|
+
}
|
|
3783
|
+
const rexc = compile(source, { optimize: state.optimize });
|
|
3784
|
+
if (state.showRexc) {
|
|
3785
|
+
console.log(`${C.dim} rexc:${C.reset} ${highlightRexc(rexc)}`);
|
|
3786
|
+
}
|
|
3787
|
+
const result = evaluateRexc(rexc, {
|
|
3788
|
+
vars: { ...state.vars },
|
|
3789
|
+
refs: { ...state.refs },
|
|
3790
|
+
gasLimit: GAS_LIMIT
|
|
3791
|
+
});
|
|
3792
|
+
state.vars = result.state.vars;
|
|
3793
|
+
state.refs = result.state.refs;
|
|
3794
|
+
console.log(formatResult(result.value));
|
|
3795
|
+
const varLine = formatVarState(state.vars);
|
|
3796
|
+
if (varLine)
|
|
3797
|
+
console.log(varLine);
|
|
3798
|
+
} catch (error) {
|
|
3799
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3800
|
+
if (message.includes("Gas limit exceeded")) {
|
|
3801
|
+
console.log(`${C.yellow} ${message}${C.reset}`);
|
|
3802
|
+
} else {
|
|
3803
|
+
console.log(`${C.red} Error: ${message}${C.reset}`);
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
resetPrompt();
|
|
3807
|
+
});
|
|
3808
|
+
rl.on("close", () => {
|
|
3809
|
+
process.exit(0);
|
|
3810
|
+
});
|
|
3811
|
+
rl.prompt();
|
|
3812
|
+
}
|
|
3813
|
+
export {
|
|
3814
|
+
startRepl,
|
|
3815
|
+
isIncomplete,
|
|
3816
|
+
highlightRexc,
|
|
3817
|
+
highlightLine,
|
|
3818
|
+
highlightJSON,
|
|
3819
|
+
formatVarState
|
|
3820
|
+
};
|