@creationix/rex 0.3.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 +1334 -1190
- package/rex-cli.ts +268 -27
- package/rex-repl.js +1048 -1135
- package/rex-repl.ts +392 -103
- package/rex.js +290 -954
- package/rex.ohm +48 -21
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +27 -8
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +388 -218
- package/rexc-interpreter.ts +386 -88
- 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";
|
|
@@ -14,31 +17,31 @@ function byteLength(value) {
|
|
|
14
17
|
return Buffer.byteLength(value, "utf8");
|
|
15
18
|
}
|
|
16
19
|
var OPCODE_IDS = {
|
|
17
|
-
do:
|
|
18
|
-
add:
|
|
19
|
-
sub:
|
|
20
|
-
mul:
|
|
21
|
-
div:
|
|
22
|
-
eq:
|
|
23
|
-
neq:
|
|
24
|
-
lt:
|
|
25
|
-
lte:
|
|
26
|
-
gt:
|
|
27
|
-
gte:
|
|
28
|
-
and:
|
|
29
|
-
or:
|
|
30
|
-
xor:
|
|
31
|
-
not:
|
|
32
|
-
boolean:
|
|
33
|
-
number:
|
|
34
|
-
string:
|
|
35
|
-
array:
|
|
36
|
-
object:
|
|
37
|
-
mod:
|
|
38
|
-
neg:
|
|
20
|
+
do: "",
|
|
21
|
+
add: "ad",
|
|
22
|
+
sub: "sb",
|
|
23
|
+
mul: "ml",
|
|
24
|
+
div: "dv",
|
|
25
|
+
eq: "eq",
|
|
26
|
+
neq: "nq",
|
|
27
|
+
lt: "lt",
|
|
28
|
+
lte: "le",
|
|
29
|
+
gt: "gt",
|
|
30
|
+
gte: "ge",
|
|
31
|
+
and: "an",
|
|
32
|
+
or: "or",
|
|
33
|
+
xor: "xr",
|
|
34
|
+
not: "nt",
|
|
35
|
+
boolean: "bt",
|
|
36
|
+
number: "nm",
|
|
37
|
+
string: "st",
|
|
38
|
+
array: "ar",
|
|
39
|
+
object: "ob",
|
|
40
|
+
mod: "md",
|
|
41
|
+
neg: "ng",
|
|
42
|
+
range: "rn"
|
|
39
43
|
};
|
|
40
|
-
var
|
|
41
|
-
var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
|
|
44
|
+
var KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object"]);
|
|
42
45
|
var BINARY_TO_OPCODE = {
|
|
43
46
|
add: "add",
|
|
44
47
|
sub: "sub",
|
|
@@ -167,11 +170,11 @@ function encodeDecimal(significand, power) {
|
|
|
167
170
|
function encodeNumberNode(node) {
|
|
168
171
|
const numberValue = node.value;
|
|
169
172
|
if (Number.isNaN(numberValue))
|
|
170
|
-
return "
|
|
173
|
+
return "nan'";
|
|
171
174
|
if (numberValue === Infinity)
|
|
172
|
-
return "
|
|
175
|
+
return "inf'";
|
|
173
176
|
if (numberValue === -Infinity)
|
|
174
|
-
return "
|
|
177
|
+
return "nif'";
|
|
175
178
|
if (Number.isInteger(numberValue)) {
|
|
176
179
|
const { base, exp } = splitDecimal(numberValue);
|
|
177
180
|
if (exp >= 0 && exp <= 4)
|
|
@@ -203,7 +206,7 @@ function encodeNumberNode(node) {
|
|
|
203
206
|
return encodeDecimal(significand, power);
|
|
204
207
|
}
|
|
205
208
|
function encodeOpcode(opcode) {
|
|
206
|
-
return `${
|
|
209
|
+
return `${OPCODE_IDS[opcode]}%`;
|
|
207
210
|
}
|
|
208
211
|
function encodeCallParts(parts) {
|
|
209
212
|
return `(${parts.join("")})`;
|
|
@@ -220,7 +223,7 @@ function addOptionalPrefix(encoded) {
|
|
|
220
223
|
let payload = encoded;
|
|
221
224
|
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
|
|
222
225
|
payload = encoded.slice(2, -1);
|
|
223
|
-
} else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
226
|
+
} else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
|
|
224
227
|
payload = encoded.slice(2, -1);
|
|
225
228
|
} else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
|
|
226
229
|
payload = encoded.slice(1, -1);
|
|
@@ -231,7 +234,7 @@ function addOptionalPrefix(encoded) {
|
|
|
231
234
|
}
|
|
232
235
|
function encodeBlockExpression(block) {
|
|
233
236
|
if (block.length === 0)
|
|
234
|
-
return "
|
|
237
|
+
return "un'";
|
|
235
238
|
if (block.length === 1)
|
|
236
239
|
return encodeNode(block[0]);
|
|
237
240
|
return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
|
|
@@ -248,9 +251,13 @@ function encodeConditionalElse(elseBranch) {
|
|
|
248
251
|
};
|
|
249
252
|
return encodeNode(nested);
|
|
250
253
|
}
|
|
254
|
+
function encodeDomainLookup(shortCode, tag) {
|
|
255
|
+
return `${shortCode}${tag}`;
|
|
256
|
+
}
|
|
251
257
|
function encodeNavigation(node) {
|
|
252
258
|
const domainRefs = activeEncodeOptions?.domainRefs;
|
|
253
|
-
|
|
259
|
+
const domainOpcodes = activeEncodeOptions?.domainOpcodes;
|
|
260
|
+
if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
|
|
254
261
|
const staticPath = [node.target.name];
|
|
255
262
|
for (const segment of node.segments) {
|
|
256
263
|
if (segment.type !== "static")
|
|
@@ -259,14 +266,17 @@ function encodeNavigation(node) {
|
|
|
259
266
|
}
|
|
260
267
|
for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
|
|
261
268
|
const dottedName = staticPath.slice(0, pathLength).join(".");
|
|
262
|
-
const
|
|
263
|
-
|
|
269
|
+
const refCode = domainRefs?.[dottedName];
|
|
270
|
+
const opcodeCode = domainOpcodes?.[dottedName];
|
|
271
|
+
const shortCode = refCode ?? opcodeCode;
|
|
272
|
+
if (shortCode === undefined)
|
|
264
273
|
continue;
|
|
274
|
+
const tag = refCode !== undefined ? "'" : "%";
|
|
265
275
|
const consumedStaticSegments = pathLength - 1;
|
|
266
276
|
if (consumedStaticSegments === node.segments.length) {
|
|
267
|
-
return
|
|
277
|
+
return encodeDomainLookup(shortCode, tag);
|
|
268
278
|
}
|
|
269
|
-
const parts2 = [
|
|
279
|
+
const parts2 = [encodeDomainLookup(shortCode, tag)];
|
|
270
280
|
for (const segment of node.segments.slice(consumedStaticSegments)) {
|
|
271
281
|
if (segment.type === "static")
|
|
272
282
|
parts2.push(encodeBareOrLengthString(segment.key));
|
|
@@ -292,9 +302,12 @@ function encodeWhile(node) {
|
|
|
292
302
|
}
|
|
293
303
|
function encodeFor(node) {
|
|
294
304
|
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
295
|
-
if (node.binding.type === "binding:
|
|
305
|
+
if (node.binding.type === "binding:bareIn") {
|
|
296
306
|
return `>(${encodeNode(node.binding.source)}${body})`;
|
|
297
307
|
}
|
|
308
|
+
if (node.binding.type === "binding:bareOf") {
|
|
309
|
+
return `<(${encodeNode(node.binding.source)}${body})`;
|
|
310
|
+
}
|
|
298
311
|
if (node.binding.type === "binding:valueIn") {
|
|
299
312
|
return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
|
|
300
313
|
}
|
|
@@ -305,30 +318,47 @@ function encodeFor(node) {
|
|
|
305
318
|
}
|
|
306
319
|
function encodeArrayComprehension(node) {
|
|
307
320
|
const body = addOptionalPrefix(encodeNode(node.body));
|
|
308
|
-
if (node.binding.type === "binding:
|
|
321
|
+
if (node.binding.type === "binding:bareIn") {
|
|
309
322
|
return `>[${encodeNode(node.binding.source)}${body}]`;
|
|
310
323
|
}
|
|
324
|
+
if (node.binding.type === "binding:bareOf") {
|
|
325
|
+
return `<[${encodeNode(node.binding.source)}${body}]`;
|
|
326
|
+
}
|
|
311
327
|
if (node.binding.type === "binding:valueIn") {
|
|
312
328
|
return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
|
|
313
329
|
}
|
|
314
330
|
if (node.binding.type === "binding:keyValueIn") {
|
|
315
331
|
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
|
|
316
332
|
}
|
|
317
|
-
return
|
|
333
|
+
return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
|
|
318
334
|
}
|
|
319
335
|
function encodeObjectComprehension(node) {
|
|
320
336
|
const key = addOptionalPrefix(encodeNode(node.key));
|
|
321
337
|
const value = addOptionalPrefix(encodeNode(node.value));
|
|
322
|
-
if (node.binding.type === "binding:
|
|
338
|
+
if (node.binding.type === "binding:bareIn") {
|
|
323
339
|
return `>{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
324
340
|
}
|
|
341
|
+
if (node.binding.type === "binding:bareOf") {
|
|
342
|
+
return `<{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
343
|
+
}
|
|
325
344
|
if (node.binding.type === "binding:valueIn") {
|
|
326
345
|
return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
|
|
327
346
|
}
|
|
328
347
|
if (node.binding.type === "binding:keyValueIn") {
|
|
329
348
|
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
|
|
330
349
|
}
|
|
331
|
-
return
|
|
350
|
+
return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
|
|
351
|
+
}
|
|
352
|
+
function encodeWhileArrayComprehension(node) {
|
|
353
|
+
const cond = encodeNode(node.condition);
|
|
354
|
+
const body = addOptionalPrefix(encodeNode(node.body));
|
|
355
|
+
return `#[${cond}${body}]`;
|
|
356
|
+
}
|
|
357
|
+
function encodeWhileObjectComprehension(node) {
|
|
358
|
+
const cond = encodeNode(node.condition);
|
|
359
|
+
const key = addOptionalPrefix(encodeNode(node.key));
|
|
360
|
+
const value = addOptionalPrefix(encodeNode(node.value));
|
|
361
|
+
return `#{${cond}${key}${value}}`;
|
|
332
362
|
}
|
|
333
363
|
var activeEncodeOptions;
|
|
334
364
|
function encodeNode(node) {
|
|
@@ -338,7 +368,10 @@ function encodeNode(node) {
|
|
|
338
368
|
case "identifier": {
|
|
339
369
|
const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
|
|
340
370
|
if (domainRef !== undefined)
|
|
341
|
-
return `${
|
|
371
|
+
return `${domainRef}'`;
|
|
372
|
+
const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
|
|
373
|
+
if (domainOpcode !== undefined)
|
|
374
|
+
return `${domainOpcode}%`;
|
|
342
375
|
return `${node.name}$`;
|
|
343
376
|
}
|
|
344
377
|
case "self":
|
|
@@ -351,11 +384,11 @@ function encodeNode(node) {
|
|
|
351
384
|
return `${encodeUint(node.depth - 1)}@`;
|
|
352
385
|
}
|
|
353
386
|
case "boolean":
|
|
354
|
-
return node.value ? "
|
|
387
|
+
return node.value ? "tr'" : "fl'";
|
|
355
388
|
case "null":
|
|
356
|
-
return "
|
|
389
|
+
return "nl'";
|
|
357
390
|
case "undefined":
|
|
358
|
-
return "
|
|
391
|
+
return "un'";
|
|
359
392
|
case "number":
|
|
360
393
|
return encodeNumberNode(node);
|
|
361
394
|
case "string":
|
|
@@ -366,12 +399,16 @@ function encodeNode(node) {
|
|
|
366
399
|
}
|
|
367
400
|
case "arrayComprehension":
|
|
368
401
|
return encodeArrayComprehension(node);
|
|
402
|
+
case "whileArrayComprehension":
|
|
403
|
+
return encodeWhileArrayComprehension(node);
|
|
369
404
|
case "object": {
|
|
370
405
|
const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
|
|
371
406
|
return `{${body}}`;
|
|
372
407
|
}
|
|
373
408
|
case "objectComprehension":
|
|
374
409
|
return encodeObjectComprehension(node);
|
|
410
|
+
case "whileObjectComprehension":
|
|
411
|
+
return encodeWhileObjectComprehension(node);
|
|
375
412
|
case "key":
|
|
376
413
|
return encodeBareOrLengthString(node.name);
|
|
377
414
|
case "group":
|
|
@@ -381,6 +418,10 @@ function encodeNode(node) {
|
|
|
381
418
|
return `~${encodeNode(node.value)}`;
|
|
382
419
|
if (node.op === "neg")
|
|
383
420
|
return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
|
|
421
|
+
if (node.op === "logicalNot") {
|
|
422
|
+
const val = encodeNode(node.value);
|
|
423
|
+
return `!(${val}tr')`;
|
|
424
|
+
}
|
|
384
425
|
return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
|
|
385
426
|
case "binary":
|
|
386
427
|
if (node.op === "and") {
|
|
@@ -399,12 +440,19 @@ function encodeNode(node) {
|
|
|
399
440
|
}).join("");
|
|
400
441
|
return `|(${body})`;
|
|
401
442
|
}
|
|
443
|
+
if (node.op === "nor") {
|
|
444
|
+
const left = encodeNode(node.left);
|
|
445
|
+
const right = addOptionalPrefix(encodeNode(node.right));
|
|
446
|
+
return `!(${left}${right})`;
|
|
447
|
+
}
|
|
402
448
|
return encodeCallParts([
|
|
403
449
|
encodeOpcode(BINARY_TO_OPCODE[node.op]),
|
|
404
450
|
encodeNode(node.left),
|
|
405
451
|
encodeNode(node.right)
|
|
406
452
|
]);
|
|
407
453
|
case "assign": {
|
|
454
|
+
if (node.op === ":=")
|
|
455
|
+
return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
408
456
|
if (node.op === "=")
|
|
409
457
|
return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
410
458
|
const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
|
|
@@ -415,8 +463,12 @@ function encodeNode(node) {
|
|
|
415
463
|
}
|
|
416
464
|
case "navigation":
|
|
417
465
|
return encodeNavigation(node);
|
|
418
|
-
case "call":
|
|
466
|
+
case "call": {
|
|
467
|
+
if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
|
|
468
|
+
return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
|
|
469
|
+
}
|
|
419
470
|
return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
|
|
471
|
+
}
|
|
420
472
|
case "conditional": {
|
|
421
473
|
const opener = node.head === "when" ? "?(" : "!(";
|
|
422
474
|
const cond = encodeNode(node.condition);
|
|
@@ -424,6 +476,8 @@ function encodeNode(node) {
|
|
|
424
476
|
const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
|
|
425
477
|
return `${opener}${cond}${thenExpr}${elseExpr})`;
|
|
426
478
|
}
|
|
479
|
+
case "range":
|
|
480
|
+
return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
|
|
427
481
|
case "for":
|
|
428
482
|
return encodeFor(node);
|
|
429
483
|
case "while":
|
|
@@ -443,11 +497,30 @@ function collectLogicalChain(node, op) {
|
|
|
443
497
|
return [node];
|
|
444
498
|
return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
|
|
445
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
|
+
}
|
|
446
520
|
function parseToIR(source) {
|
|
447
521
|
const match = grammar.match(source);
|
|
448
522
|
if (!match.succeeded()) {
|
|
449
|
-
|
|
450
|
-
throw new Error(failure.message ?? "Parse failed");
|
|
523
|
+
throw new Error(formatParseError(source, match));
|
|
451
524
|
}
|
|
452
525
|
return semantics(match).toIR();
|
|
453
526
|
}
|
|
@@ -554,78 +627,55 @@ function domainRefsFromConfig(config) {
|
|
|
554
627
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
555
628
|
throw new Error("Domain config must be an object");
|
|
556
629
|
}
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
}
|
|
630
|
+
const configObj = config;
|
|
631
|
+
const domainRefs = {};
|
|
632
|
+
const domainOpcodes = {};
|
|
633
|
+
const dataSection = configObj.data;
|
|
634
|
+
if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
|
|
635
|
+
mapConfigEntries(dataSection, domainRefs);
|
|
586
636
|
}
|
|
587
|
-
|
|
588
|
-
|
|
637
|
+
const functionsSection = configObj.functions;
|
|
638
|
+
if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
|
|
639
|
+
mapConfigEntries(functionsSection, domainOpcodes);
|
|
589
640
|
}
|
|
590
|
-
return
|
|
641
|
+
return { domainRefs, domainOpcodes };
|
|
591
642
|
}
|
|
592
643
|
function mapConfigEntries(entries, refs) {
|
|
593
644
|
const sourceKindByRoot = new Map;
|
|
594
645
|
for (const root of Object.keys(refs)) {
|
|
595
646
|
sourceKindByRoot.set(root, "explicit");
|
|
596
647
|
}
|
|
597
|
-
for (const [
|
|
648
|
+
for (const [shortCode, rawEntry] of Object.entries(entries)) {
|
|
598
649
|
const entry = rawEntry;
|
|
599
650
|
if (!entry || typeof entry !== "object")
|
|
600
651
|
continue;
|
|
601
652
|
if (!Array.isArray(entry.names))
|
|
602
653
|
continue;
|
|
603
|
-
const refId = decodeDomainRefKey(refText);
|
|
604
654
|
for (const rawName of entry.names) {
|
|
605
655
|
if (typeof rawName !== "string")
|
|
606
656
|
continue;
|
|
607
|
-
const
|
|
608
|
-
if (
|
|
609
|
-
throw new Error(`Conflicting refs for '${rawName}': ${
|
|
657
|
+
const existingRef = refs[rawName];
|
|
658
|
+
if (existingRef !== undefined && existingRef !== shortCode) {
|
|
659
|
+
throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
|
|
610
660
|
}
|
|
611
|
-
refs[rawName] =
|
|
661
|
+
refs[rawName] = shortCode;
|
|
612
662
|
const root = rawName.split(".")[0];
|
|
613
663
|
if (!root)
|
|
614
664
|
continue;
|
|
615
665
|
const currentKind = rawName.includes(".") ? "implicit" : "explicit";
|
|
616
666
|
const existing = refs[root];
|
|
617
667
|
if (existing !== undefined) {
|
|
618
|
-
if (existing ===
|
|
668
|
+
if (existing === shortCode)
|
|
619
669
|
continue;
|
|
620
670
|
const existingKind = sourceKindByRoot.get(root) ?? "explicit";
|
|
621
671
|
if (currentKind === "explicit") {
|
|
622
|
-
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${
|
|
672
|
+
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
|
|
623
673
|
}
|
|
624
674
|
if (existingKind === "explicit")
|
|
625
675
|
continue;
|
|
626
676
|
continue;
|
|
627
677
|
}
|
|
628
|
-
refs[root] =
|
|
678
|
+
refs[root] = shortCode;
|
|
629
679
|
sourceKindByRoot.set(root, currentKind);
|
|
630
680
|
}
|
|
631
681
|
}
|
|
@@ -771,20 +821,11 @@ function gatherEncodedValueSpans(text) {
|
|
|
771
821
|
}
|
|
772
822
|
return spans;
|
|
773
823
|
}
|
|
774
|
-
function buildPointerToken(pointerStart, targetStart) {
|
|
775
|
-
|
|
824
|
+
function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
|
|
825
|
+
const offset = targetStart - pointerStart - occurrenceSize;
|
|
776
826
|
if (offset < 0)
|
|
777
827
|
return;
|
|
778
|
-
|
|
779
|
-
const prefix = encodeUint(offset);
|
|
780
|
-
const recalculated = targetStart - (pointerStart + prefix.length + 1);
|
|
781
|
-
if (recalculated === offset)
|
|
782
|
-
return `${prefix}^`;
|
|
783
|
-
offset = recalculated;
|
|
784
|
-
if (offset < 0)
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
return;
|
|
828
|
+
return `${encodeUint(offset)}^`;
|
|
788
829
|
}
|
|
789
830
|
function buildDedupeCandidateTable(encoded, minBytes) {
|
|
790
831
|
const spans = gatherEncodedValueSpans(encoded);
|
|
@@ -826,7 +867,7 @@ function dedupeLargeEncodedValues(encoded, minBytes = 4) {
|
|
|
826
867
|
if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
|
|
827
868
|
continue;
|
|
828
869
|
const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
|
|
829
|
-
const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
|
|
870
|
+
const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
|
|
830
871
|
if (!pointerToken)
|
|
831
872
|
continue;
|
|
832
873
|
if (pointerToken.length >= occurrence.sizeBytes)
|
|
@@ -855,819 +896,8 @@ function encodeIR(node, options) {
|
|
|
855
896
|
activeEncodeOptions = previous;
|
|
856
897
|
}
|
|
857
898
|
}
|
|
858
|
-
function cloneNode(node) {
|
|
859
|
-
return structuredClone(node);
|
|
860
|
-
}
|
|
861
|
-
function emptyOptimizeEnv() {
|
|
862
|
-
return { constants: {}, selfCaptures: {} };
|
|
863
|
-
}
|
|
864
|
-
function cloneOptimizeEnv(env) {
|
|
865
|
-
return {
|
|
866
|
-
constants: { ...env.constants },
|
|
867
|
-
selfCaptures: { ...env.selfCaptures }
|
|
868
|
-
};
|
|
869
|
-
}
|
|
870
|
-
function clearOptimizeEnv(env) {
|
|
871
|
-
for (const key of Object.keys(env.constants))
|
|
872
|
-
delete env.constants[key];
|
|
873
|
-
for (const key of Object.keys(env.selfCaptures))
|
|
874
|
-
delete env.selfCaptures[key];
|
|
875
|
-
}
|
|
876
|
-
function clearBinding(env, name) {
|
|
877
|
-
delete env.constants[name];
|
|
878
|
-
delete env.selfCaptures[name];
|
|
879
|
-
}
|
|
880
|
-
function selfTargetFromNode(node, currentDepth) {
|
|
881
|
-
if (node.type === "self")
|
|
882
|
-
return currentDepth;
|
|
883
|
-
if (node.type === "selfDepth") {
|
|
884
|
-
const target = currentDepth - (node.depth - 1);
|
|
885
|
-
if (target >= 1)
|
|
886
|
-
return target;
|
|
887
|
-
}
|
|
888
|
-
return;
|
|
889
|
-
}
|
|
890
|
-
function selfNodeFromTarget(targetDepth, currentDepth) {
|
|
891
|
-
const relDepth = currentDepth - targetDepth + 1;
|
|
892
|
-
if (!Number.isInteger(relDepth) || relDepth < 1)
|
|
893
|
-
return;
|
|
894
|
-
if (relDepth === 1)
|
|
895
|
-
return { type: "self" };
|
|
896
|
-
return { type: "selfDepth", depth: relDepth };
|
|
897
|
-
}
|
|
898
|
-
function dropBindingNames(env, binding) {
|
|
899
|
-
if (binding.type === "binding:valueIn") {
|
|
900
|
-
clearBinding(env, binding.value);
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
if (binding.type === "binding:keyValueIn") {
|
|
904
|
-
clearBinding(env, binding.key);
|
|
905
|
-
clearBinding(env, binding.value);
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
if (binding.type === "binding:keyOf") {
|
|
909
|
-
clearBinding(env, binding.key);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
function collectReads(node, out) {
|
|
913
|
-
switch (node.type) {
|
|
914
|
-
case "identifier":
|
|
915
|
-
out.add(node.name);
|
|
916
|
-
return;
|
|
917
|
-
case "group":
|
|
918
|
-
collectReads(node.expression, out);
|
|
919
|
-
return;
|
|
920
|
-
case "array":
|
|
921
|
-
for (const item of node.items)
|
|
922
|
-
collectReads(item, out);
|
|
923
|
-
return;
|
|
924
|
-
case "object":
|
|
925
|
-
for (const entry of node.entries) {
|
|
926
|
-
collectReads(entry.key, out);
|
|
927
|
-
collectReads(entry.value, out);
|
|
928
|
-
}
|
|
929
|
-
return;
|
|
930
|
-
case "arrayComprehension":
|
|
931
|
-
collectReads(node.binding.source, out);
|
|
932
|
-
collectReads(node.body, out);
|
|
933
|
-
return;
|
|
934
|
-
case "objectComprehension":
|
|
935
|
-
collectReads(node.binding.source, out);
|
|
936
|
-
collectReads(node.key, out);
|
|
937
|
-
collectReads(node.value, out);
|
|
938
|
-
return;
|
|
939
|
-
case "unary":
|
|
940
|
-
collectReads(node.value, out);
|
|
941
|
-
return;
|
|
942
|
-
case "binary":
|
|
943
|
-
collectReads(node.left, out);
|
|
944
|
-
collectReads(node.right, out);
|
|
945
|
-
return;
|
|
946
|
-
case "assign":
|
|
947
|
-
if (!(node.op === "=" && node.place.type === "identifier"))
|
|
948
|
-
collectReads(node.place, out);
|
|
949
|
-
collectReads(node.value, out);
|
|
950
|
-
return;
|
|
951
|
-
case "navigation":
|
|
952
|
-
collectReads(node.target, out);
|
|
953
|
-
for (const segment of node.segments) {
|
|
954
|
-
if (segment.type === "dynamic")
|
|
955
|
-
collectReads(segment.key, out);
|
|
956
|
-
}
|
|
957
|
-
return;
|
|
958
|
-
case "call":
|
|
959
|
-
collectReads(node.callee, out);
|
|
960
|
-
for (const arg of node.args)
|
|
961
|
-
collectReads(arg, out);
|
|
962
|
-
return;
|
|
963
|
-
case "conditional":
|
|
964
|
-
collectReads(node.condition, out);
|
|
965
|
-
for (const part of node.thenBlock)
|
|
966
|
-
collectReads(part, out);
|
|
967
|
-
if (node.elseBranch)
|
|
968
|
-
collectReadsElse(node.elseBranch, out);
|
|
969
|
-
return;
|
|
970
|
-
case "for":
|
|
971
|
-
collectReads(node.binding.source, out);
|
|
972
|
-
for (const part of node.body)
|
|
973
|
-
collectReads(part, out);
|
|
974
|
-
return;
|
|
975
|
-
case "program":
|
|
976
|
-
for (const part of node.body)
|
|
977
|
-
collectReads(part, out);
|
|
978
|
-
return;
|
|
979
|
-
default:
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
function collectReadsElse(elseBranch, out) {
|
|
984
|
-
if (elseBranch.type === "else") {
|
|
985
|
-
for (const part of elseBranch.block)
|
|
986
|
-
collectReads(part, out);
|
|
987
|
-
return;
|
|
988
|
-
}
|
|
989
|
-
collectReads(elseBranch.condition, out);
|
|
990
|
-
for (const part of elseBranch.thenBlock)
|
|
991
|
-
collectReads(part, out);
|
|
992
|
-
if (elseBranch.elseBranch)
|
|
993
|
-
collectReadsElse(elseBranch.elseBranch, out);
|
|
994
|
-
}
|
|
995
|
-
function isPureNode(node) {
|
|
996
|
-
switch (node.type) {
|
|
997
|
-
case "identifier":
|
|
998
|
-
case "self":
|
|
999
|
-
case "selfDepth":
|
|
1000
|
-
case "boolean":
|
|
1001
|
-
case "null":
|
|
1002
|
-
case "undefined":
|
|
1003
|
-
case "number":
|
|
1004
|
-
case "string":
|
|
1005
|
-
case "key":
|
|
1006
|
-
return true;
|
|
1007
|
-
case "group":
|
|
1008
|
-
return isPureNode(node.expression);
|
|
1009
|
-
case "array":
|
|
1010
|
-
return node.items.every((item) => isPureNode(item));
|
|
1011
|
-
case "object":
|
|
1012
|
-
return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
|
|
1013
|
-
case "navigation":
|
|
1014
|
-
return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
|
|
1015
|
-
case "unary":
|
|
1016
|
-
return node.op !== "delete" && isPureNode(node.value);
|
|
1017
|
-
case "binary":
|
|
1018
|
-
return isPureNode(node.left) && isPureNode(node.right);
|
|
1019
|
-
default:
|
|
1020
|
-
return false;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
function eliminateDeadAssignments(block) {
|
|
1024
|
-
const needed = new Set;
|
|
1025
|
-
const out = [];
|
|
1026
|
-
for (let index = block.length - 1;index >= 0; index -= 1) {
|
|
1027
|
-
const node = block[index];
|
|
1028
|
-
if (node.type === "conditional") {
|
|
1029
|
-
let rewritten = node;
|
|
1030
|
-
if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
|
|
1031
|
-
const name = node.condition.place.name;
|
|
1032
|
-
const branchReads = new Set;
|
|
1033
|
-
for (const part of node.thenBlock)
|
|
1034
|
-
collectReads(part, branchReads);
|
|
1035
|
-
if (node.elseBranch)
|
|
1036
|
-
collectReadsElse(node.elseBranch, branchReads);
|
|
1037
|
-
if (!needed.has(name) && !branchReads.has(name)) {
|
|
1038
|
-
rewritten = {
|
|
1039
|
-
type: "conditional",
|
|
1040
|
-
head: node.head,
|
|
1041
|
-
condition: node.condition.value,
|
|
1042
|
-
thenBlock: node.thenBlock,
|
|
1043
|
-
elseBranch: node.elseBranch
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
collectReads(rewritten, needed);
|
|
1048
|
-
out.push(rewritten);
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
|
|
1052
|
-
collectReads(node.value, needed);
|
|
1053
|
-
const name = node.place.name;
|
|
1054
|
-
const canDrop = !needed.has(name) && isPureNode(node.value);
|
|
1055
|
-
needed.delete(name);
|
|
1056
|
-
if (canDrop)
|
|
1057
|
-
continue;
|
|
1058
|
-
out.push(node);
|
|
1059
|
-
continue;
|
|
1060
|
-
}
|
|
1061
|
-
collectReads(node, needed);
|
|
1062
|
-
out.push(node);
|
|
1063
|
-
}
|
|
1064
|
-
out.reverse();
|
|
1065
|
-
return out;
|
|
1066
|
-
}
|
|
1067
|
-
function hasIdentifierRead(node, name, asPlace = false) {
|
|
1068
|
-
if (node.type === "identifier")
|
|
1069
|
-
return !asPlace && node.name === name;
|
|
1070
|
-
switch (node.type) {
|
|
1071
|
-
case "group":
|
|
1072
|
-
return hasIdentifierRead(node.expression, name);
|
|
1073
|
-
case "array":
|
|
1074
|
-
return node.items.some((item) => hasIdentifierRead(item, name));
|
|
1075
|
-
case "object":
|
|
1076
|
-
return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
|
|
1077
|
-
case "navigation":
|
|
1078
|
-
return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
|
|
1079
|
-
case "unary":
|
|
1080
|
-
return hasIdentifierRead(node.value, name, node.op === "delete");
|
|
1081
|
-
case "binary":
|
|
1082
|
-
return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
|
|
1083
|
-
case "assign":
|
|
1084
|
-
return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
|
|
1085
|
-
default:
|
|
1086
|
-
return false;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
function countIdentifierReads(node, name, asPlace = false) {
|
|
1090
|
-
if (node.type === "identifier")
|
|
1091
|
-
return !asPlace && node.name === name ? 1 : 0;
|
|
1092
|
-
switch (node.type) {
|
|
1093
|
-
case "group":
|
|
1094
|
-
return countIdentifierReads(node.expression, name);
|
|
1095
|
-
case "array":
|
|
1096
|
-
return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
|
|
1097
|
-
case "object":
|
|
1098
|
-
return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
|
|
1099
|
-
case "navigation":
|
|
1100
|
-
return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
|
|
1101
|
-
case "unary":
|
|
1102
|
-
return countIdentifierReads(node.value, name, node.op === "delete");
|
|
1103
|
-
case "binary":
|
|
1104
|
-
return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
|
|
1105
|
-
case "assign":
|
|
1106
|
-
return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
|
|
1107
|
-
default:
|
|
1108
|
-
return 0;
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
function replaceIdentifier(node, name, replacement, asPlace = false) {
|
|
1112
|
-
if (node.type === "identifier") {
|
|
1113
|
-
if (!asPlace && node.name === name)
|
|
1114
|
-
return cloneNode(replacement);
|
|
1115
|
-
return node;
|
|
1116
|
-
}
|
|
1117
|
-
switch (node.type) {
|
|
1118
|
-
case "group":
|
|
1119
|
-
return {
|
|
1120
|
-
type: "group",
|
|
1121
|
-
expression: replaceIdentifier(node.expression, name, replacement)
|
|
1122
|
-
};
|
|
1123
|
-
case "array":
|
|
1124
|
-
return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
|
|
1125
|
-
case "object":
|
|
1126
|
-
return {
|
|
1127
|
-
type: "object",
|
|
1128
|
-
entries: node.entries.map((entry) => ({
|
|
1129
|
-
key: replaceIdentifier(entry.key, name, replacement),
|
|
1130
|
-
value: replaceIdentifier(entry.value, name, replacement)
|
|
1131
|
-
}))
|
|
1132
|
-
};
|
|
1133
|
-
case "navigation":
|
|
1134
|
-
return {
|
|
1135
|
-
type: "navigation",
|
|
1136
|
-
target: replaceIdentifier(node.target, name, replacement),
|
|
1137
|
-
segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
|
|
1138
|
-
};
|
|
1139
|
-
case "unary":
|
|
1140
|
-
return {
|
|
1141
|
-
type: "unary",
|
|
1142
|
-
op: node.op,
|
|
1143
|
-
value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
|
|
1144
|
-
};
|
|
1145
|
-
case "binary":
|
|
1146
|
-
return {
|
|
1147
|
-
type: "binary",
|
|
1148
|
-
op: node.op,
|
|
1149
|
-
left: replaceIdentifier(node.left, name, replacement),
|
|
1150
|
-
right: replaceIdentifier(node.right, name, replacement)
|
|
1151
|
-
};
|
|
1152
|
-
case "assign":
|
|
1153
|
-
return {
|
|
1154
|
-
type: "assign",
|
|
1155
|
-
op: node.op,
|
|
1156
|
-
place: replaceIdentifier(node.place, name, replacement, true),
|
|
1157
|
-
value: replaceIdentifier(node.value, name, replacement)
|
|
1158
|
-
};
|
|
1159
|
-
default:
|
|
1160
|
-
return node;
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
function isSafeInlineTargetNode(node) {
|
|
1164
|
-
if (isPureNode(node))
|
|
1165
|
-
return true;
|
|
1166
|
-
if (node.type === "assign" && node.op === "=") {
|
|
1167
|
-
return isPureNode(node.place) && isPureNode(node.value);
|
|
1168
|
-
}
|
|
1169
|
-
return false;
|
|
1170
|
-
}
|
|
1171
|
-
function inlineAdjacentPureAssignments(block) {
|
|
1172
|
-
const out = [...block];
|
|
1173
|
-
let changed = true;
|
|
1174
|
-
while (changed) {
|
|
1175
|
-
changed = false;
|
|
1176
|
-
for (let index = 0;index < out.length - 1; index += 1) {
|
|
1177
|
-
const current = out[index];
|
|
1178
|
-
if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
|
|
1179
|
-
continue;
|
|
1180
|
-
if (!isPureNode(current.value))
|
|
1181
|
-
continue;
|
|
1182
|
-
const name = current.place.name;
|
|
1183
|
-
if (hasIdentifierRead(current.value, name))
|
|
1184
|
-
continue;
|
|
1185
|
-
const next = out[index + 1];
|
|
1186
|
-
if (!isSafeInlineTargetNode(next))
|
|
1187
|
-
continue;
|
|
1188
|
-
if (countIdentifierReads(next, name) !== 1)
|
|
1189
|
-
continue;
|
|
1190
|
-
out[index + 1] = replaceIdentifier(next, name, current.value);
|
|
1191
|
-
out.splice(index, 1);
|
|
1192
|
-
changed = true;
|
|
1193
|
-
break;
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
return out;
|
|
1197
|
-
}
|
|
1198
|
-
function toNumberNode(value) {
|
|
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 };
|
|
1209
|
-
}
|
|
1210
|
-
function toStringNode(value) {
|
|
1211
|
-
return { type: "string", raw: JSON.stringify(value) };
|
|
1212
|
-
}
|
|
1213
|
-
function toLiteralNode(value) {
|
|
1214
|
-
if (value === undefined)
|
|
1215
|
-
return { type: "undefined" };
|
|
1216
|
-
if (value === null)
|
|
1217
|
-
return { type: "null" };
|
|
1218
|
-
if (typeof value === "boolean")
|
|
1219
|
-
return { type: "boolean", value };
|
|
1220
|
-
if (typeof value === "number")
|
|
1221
|
-
return toNumberNode(value);
|
|
1222
|
-
if (typeof value === "string")
|
|
1223
|
-
return toStringNode(value);
|
|
1224
|
-
if (Array.isArray(value)) {
|
|
1225
|
-
const items = [];
|
|
1226
|
-
for (const item of value) {
|
|
1227
|
-
const lowered = toLiteralNode(item);
|
|
1228
|
-
if (!lowered)
|
|
1229
|
-
return;
|
|
1230
|
-
items.push(lowered);
|
|
1231
|
-
}
|
|
1232
|
-
return { type: "array", items };
|
|
1233
|
-
}
|
|
1234
|
-
if (value && typeof value === "object") {
|
|
1235
|
-
const entries = [];
|
|
1236
|
-
for (const [key, entryValue] of Object.entries(value)) {
|
|
1237
|
-
const loweredValue = toLiteralNode(entryValue);
|
|
1238
|
-
if (!loweredValue)
|
|
1239
|
-
return;
|
|
1240
|
-
entries.push({ key: { type: "key", name: key }, value: loweredValue });
|
|
1241
|
-
}
|
|
1242
|
-
return { type: "object", entries };
|
|
1243
|
-
}
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
function constValue(node) {
|
|
1247
|
-
switch (node.type) {
|
|
1248
|
-
case "undefined":
|
|
1249
|
-
return;
|
|
1250
|
-
case "null":
|
|
1251
|
-
return null;
|
|
1252
|
-
case "boolean":
|
|
1253
|
-
return node.value;
|
|
1254
|
-
case "number":
|
|
1255
|
-
return node.value;
|
|
1256
|
-
case "string":
|
|
1257
|
-
return decodeStringLiteral(node.raw);
|
|
1258
|
-
case "key":
|
|
1259
|
-
return node.name;
|
|
1260
|
-
case "array": {
|
|
1261
|
-
const out = [];
|
|
1262
|
-
for (const item of node.items) {
|
|
1263
|
-
const value = constValue(item);
|
|
1264
|
-
if (value === undefined && item.type !== "undefined")
|
|
1265
|
-
return;
|
|
1266
|
-
out.push(value);
|
|
1267
|
-
}
|
|
1268
|
-
return out;
|
|
1269
|
-
}
|
|
1270
|
-
case "object": {
|
|
1271
|
-
const out = {};
|
|
1272
|
-
for (const entry of node.entries) {
|
|
1273
|
-
const key = constValue(entry.key);
|
|
1274
|
-
if (key === undefined && entry.key.type !== "undefined")
|
|
1275
|
-
return;
|
|
1276
|
-
const value = constValue(entry.value);
|
|
1277
|
-
if (value === undefined && entry.value.type !== "undefined")
|
|
1278
|
-
return;
|
|
1279
|
-
out[String(key)] = value;
|
|
1280
|
-
}
|
|
1281
|
-
return out;
|
|
1282
|
-
}
|
|
1283
|
-
default:
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
function isDefinedValue(value) {
|
|
1288
|
-
return value !== undefined;
|
|
1289
|
-
}
|
|
1290
|
-
function foldUnary(op, value) {
|
|
1291
|
-
if (op === "neg") {
|
|
1292
|
-
if (typeof value !== "number")
|
|
1293
|
-
return;
|
|
1294
|
-
return -value;
|
|
1295
|
-
}
|
|
1296
|
-
if (op === "not") {
|
|
1297
|
-
if (typeof value === "boolean")
|
|
1298
|
-
return !value;
|
|
1299
|
-
if (typeof value === "number")
|
|
1300
|
-
return ~value;
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
function foldBinary(op, left, right) {
|
|
1306
|
-
if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
|
|
1307
|
-
if (typeof left !== "number" || typeof right !== "number")
|
|
1308
|
-
return;
|
|
1309
|
-
if (op === "add")
|
|
1310
|
-
return left + right;
|
|
1311
|
-
if (op === "sub")
|
|
1312
|
-
return left - right;
|
|
1313
|
-
if (op === "mul")
|
|
1314
|
-
return left * right;
|
|
1315
|
-
if (op === "div")
|
|
1316
|
-
return left / right;
|
|
1317
|
-
return left % right;
|
|
1318
|
-
}
|
|
1319
|
-
if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
|
|
1320
|
-
if (typeof left !== "number" || typeof right !== "number")
|
|
1321
|
-
return;
|
|
1322
|
-
if (op === "bitAnd")
|
|
1323
|
-
return left & right;
|
|
1324
|
-
if (op === "bitOr")
|
|
1325
|
-
return left | right;
|
|
1326
|
-
return left ^ right;
|
|
1327
|
-
}
|
|
1328
|
-
if (op === "eq")
|
|
1329
|
-
return left === right ? left : undefined;
|
|
1330
|
-
if (op === "neq")
|
|
1331
|
-
return left !== right ? left : undefined;
|
|
1332
|
-
if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
|
|
1333
|
-
if (typeof left !== "number" || typeof right !== "number")
|
|
1334
|
-
return;
|
|
1335
|
-
if (op === "gt")
|
|
1336
|
-
return left > right ? left : undefined;
|
|
1337
|
-
if (op === "gte")
|
|
1338
|
-
return left >= right ? left : undefined;
|
|
1339
|
-
if (op === "lt")
|
|
1340
|
-
return left < right ? left : undefined;
|
|
1341
|
-
return left <= right ? left : undefined;
|
|
1342
|
-
}
|
|
1343
|
-
if (op === "and")
|
|
1344
|
-
return isDefinedValue(left) ? right : undefined;
|
|
1345
|
-
if (op === "or")
|
|
1346
|
-
return isDefinedValue(left) ? left : right;
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
function optimizeElse(elseBranch, env, currentDepth) {
|
|
1350
|
-
if (!elseBranch)
|
|
1351
|
-
return;
|
|
1352
|
-
if (elseBranch.type === "else") {
|
|
1353
|
-
return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
|
|
1354
|
-
}
|
|
1355
|
-
const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
|
|
1356
|
-
const foldedCondition = constValue(optimizedCondition);
|
|
1357
|
-
if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
|
|
1358
|
-
const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
|
|
1359
|
-
if (passes) {
|
|
1360
|
-
return {
|
|
1361
|
-
type: "else",
|
|
1362
|
-
block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
return optimizeElse(elseBranch.elseBranch, env, currentDepth);
|
|
1366
|
-
}
|
|
1367
|
-
return {
|
|
1368
|
-
type: "elseChain",
|
|
1369
|
-
head: elseBranch.head,
|
|
1370
|
-
condition: optimizedCondition,
|
|
1371
|
-
thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
|
|
1372
|
-
elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
function optimizeBlock(block, env, currentDepth) {
|
|
1376
|
-
const out = [];
|
|
1377
|
-
for (const node of block) {
|
|
1378
|
-
const optimized = optimizeNode(node, env, currentDepth);
|
|
1379
|
-
out.push(optimized);
|
|
1380
|
-
if (optimized.type === "break" || optimized.type === "continue")
|
|
1381
|
-
break;
|
|
1382
|
-
if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
|
|
1383
|
-
const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
|
|
1384
|
-
if (selfTarget !== undefined) {
|
|
1385
|
-
env.selfCaptures[optimized.place.name] = selfTarget;
|
|
1386
|
-
delete env.constants[optimized.place.name];
|
|
1387
|
-
continue;
|
|
1388
|
-
}
|
|
1389
|
-
const folded = constValue(optimized.value);
|
|
1390
|
-
if (folded !== undefined || optimized.value.type === "undefined") {
|
|
1391
|
-
env.constants[optimized.place.name] = cloneNode(optimized.value);
|
|
1392
|
-
delete env.selfCaptures[optimized.place.name];
|
|
1393
|
-
} else {
|
|
1394
|
-
clearBinding(env, optimized.place.name);
|
|
1395
|
-
}
|
|
1396
|
-
continue;
|
|
1397
|
-
}
|
|
1398
|
-
if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
|
|
1399
|
-
clearBinding(env, optimized.value.name);
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
if (optimized.type === "assign" && optimized.place.type === "identifier") {
|
|
1403
|
-
clearBinding(env, optimized.place.name);
|
|
1404
|
-
continue;
|
|
1405
|
-
}
|
|
1406
|
-
if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
|
|
1407
|
-
clearOptimizeEnv(env);
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
|
|
1411
|
-
}
|
|
1412
|
-
function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
1413
|
-
switch (node.type) {
|
|
1414
|
-
case "program": {
|
|
1415
|
-
const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
|
|
1416
|
-
if (body.length === 0)
|
|
1417
|
-
return { type: "undefined" };
|
|
1418
|
-
if (body.length === 1)
|
|
1419
|
-
return body[0];
|
|
1420
|
-
return { type: "program", body };
|
|
1421
|
-
}
|
|
1422
|
-
case "identifier": {
|
|
1423
|
-
if (asPlace)
|
|
1424
|
-
return node;
|
|
1425
|
-
const selfTarget = env.selfCaptures[node.name];
|
|
1426
|
-
if (selfTarget !== undefined) {
|
|
1427
|
-
const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
|
|
1428
|
-
if (rewritten)
|
|
1429
|
-
return rewritten;
|
|
1430
|
-
}
|
|
1431
|
-
const replacement = env.constants[node.name];
|
|
1432
|
-
return replacement ? cloneNode(replacement) : node;
|
|
1433
|
-
}
|
|
1434
|
-
case "group": {
|
|
1435
|
-
return optimizeNode(node.expression, env, currentDepth);
|
|
1436
|
-
}
|
|
1437
|
-
case "array": {
|
|
1438
|
-
return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
|
|
1439
|
-
}
|
|
1440
|
-
case "object": {
|
|
1441
|
-
return {
|
|
1442
|
-
type: "object",
|
|
1443
|
-
entries: node.entries.map((entry) => ({
|
|
1444
|
-
key: optimizeNode(entry.key, env, currentDepth),
|
|
1445
|
-
value: optimizeNode(entry.value, env, currentDepth)
|
|
1446
|
-
}))
|
|
1447
|
-
};
|
|
1448
|
-
}
|
|
1449
|
-
case "unary": {
|
|
1450
|
-
const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
|
|
1451
|
-
const foldedValue = constValue(value);
|
|
1452
|
-
if (foldedValue !== undefined || value.type === "undefined") {
|
|
1453
|
-
const folded = foldUnary(node.op, foldedValue);
|
|
1454
|
-
const literal = folded === undefined ? undefined : toLiteralNode(folded);
|
|
1455
|
-
if (literal)
|
|
1456
|
-
return literal;
|
|
1457
|
-
}
|
|
1458
|
-
return { type: "unary", op: node.op, value };
|
|
1459
|
-
}
|
|
1460
|
-
case "binary": {
|
|
1461
|
-
const left = optimizeNode(node.left, env, currentDepth);
|
|
1462
|
-
const right = optimizeNode(node.right, env, currentDepth);
|
|
1463
|
-
const leftValue = constValue(left);
|
|
1464
|
-
const rightValue = constValue(right);
|
|
1465
|
-
if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
|
|
1466
|
-
const folded = foldBinary(node.op, leftValue, rightValue);
|
|
1467
|
-
const literal = folded === undefined ? undefined : toLiteralNode(folded);
|
|
1468
|
-
if (literal)
|
|
1469
|
-
return literal;
|
|
1470
|
-
}
|
|
1471
|
-
return { type: "binary", op: node.op, left, right };
|
|
1472
|
-
}
|
|
1473
|
-
case "navigation": {
|
|
1474
|
-
const target = optimizeNode(node.target, env, currentDepth);
|
|
1475
|
-
const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
|
|
1476
|
-
const targetValue = constValue(target);
|
|
1477
|
-
if (targetValue !== undefined || target.type === "undefined") {
|
|
1478
|
-
let current = targetValue;
|
|
1479
|
-
let foldable = true;
|
|
1480
|
-
for (const segment of segments) {
|
|
1481
|
-
if (!foldable)
|
|
1482
|
-
break;
|
|
1483
|
-
const key = segment.type === "static" ? segment.key : constValue(segment.key);
|
|
1484
|
-
if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
|
|
1485
|
-
foldable = false;
|
|
1486
|
-
break;
|
|
1487
|
-
}
|
|
1488
|
-
if (current === null || current === undefined) {
|
|
1489
|
-
current = undefined;
|
|
1490
|
-
continue;
|
|
1491
|
-
}
|
|
1492
|
-
current = current[String(key)];
|
|
1493
|
-
}
|
|
1494
|
-
if (foldable) {
|
|
1495
|
-
const literal = toLiteralNode(current);
|
|
1496
|
-
if (literal)
|
|
1497
|
-
return literal;
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
return {
|
|
1501
|
-
type: "navigation",
|
|
1502
|
-
target,
|
|
1503
|
-
segments
|
|
1504
|
-
};
|
|
1505
|
-
}
|
|
1506
|
-
case "call": {
|
|
1507
|
-
return {
|
|
1508
|
-
type: "call",
|
|
1509
|
-
callee: optimizeNode(node.callee, env, currentDepth),
|
|
1510
|
-
args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
|
|
1511
|
-
};
|
|
1512
|
-
}
|
|
1513
|
-
case "assign": {
|
|
1514
|
-
return {
|
|
1515
|
-
type: "assign",
|
|
1516
|
-
op: node.op,
|
|
1517
|
-
place: optimizeNode(node.place, env, currentDepth, true),
|
|
1518
|
-
value: optimizeNode(node.value, env, currentDepth)
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
case "conditional": {
|
|
1522
|
-
const condition = optimizeNode(node.condition, env, currentDepth);
|
|
1523
|
-
const thenEnv = cloneOptimizeEnv(env);
|
|
1524
|
-
if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
|
|
1525
|
-
thenEnv.selfCaptures[condition.place.name] = currentDepth;
|
|
1526
|
-
delete thenEnv.constants[condition.place.name];
|
|
1527
|
-
}
|
|
1528
|
-
const conditionValue = constValue(condition);
|
|
1529
|
-
if (conditionValue !== undefined || condition.type === "undefined") {
|
|
1530
|
-
const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
|
|
1531
|
-
if (passes) {
|
|
1532
|
-
const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
|
|
1533
|
-
if (thenBlock2.length === 0)
|
|
1534
|
-
return { type: "undefined" };
|
|
1535
|
-
if (thenBlock2.length === 1)
|
|
1536
|
-
return thenBlock2[0];
|
|
1537
|
-
return { type: "program", body: thenBlock2 };
|
|
1538
|
-
}
|
|
1539
|
-
if (!node.elseBranch)
|
|
1540
|
-
return { type: "undefined" };
|
|
1541
|
-
const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
|
|
1542
|
-
if (!loweredElse)
|
|
1543
|
-
return { type: "undefined" };
|
|
1544
|
-
if (loweredElse.type === "else") {
|
|
1545
|
-
if (loweredElse.block.length === 0)
|
|
1546
|
-
return { type: "undefined" };
|
|
1547
|
-
if (loweredElse.block.length === 1)
|
|
1548
|
-
return loweredElse.block[0];
|
|
1549
|
-
return { type: "program", body: loweredElse.block };
|
|
1550
|
-
}
|
|
1551
|
-
return {
|
|
1552
|
-
type: "conditional",
|
|
1553
|
-
head: loweredElse.head,
|
|
1554
|
-
condition: loweredElse.condition,
|
|
1555
|
-
thenBlock: loweredElse.thenBlock,
|
|
1556
|
-
elseBranch: loweredElse.elseBranch
|
|
1557
|
-
};
|
|
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
|
-
}
|
|
1573
|
-
return {
|
|
1574
|
-
type: "conditional",
|
|
1575
|
-
head: node.head,
|
|
1576
|
-
condition: finalCondition,
|
|
1577
|
-
thenBlock,
|
|
1578
|
-
elseBranch
|
|
1579
|
-
};
|
|
1580
|
-
}
|
|
1581
|
-
case "for": {
|
|
1582
|
-
const sourceEnv = cloneOptimizeEnv(env);
|
|
1583
|
-
const binding = (() => {
|
|
1584
|
-
if (node.binding.type === "binding:expr") {
|
|
1585
|
-
return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
|
|
1586
|
-
}
|
|
1587
|
-
if (node.binding.type === "binding:valueIn") {
|
|
1588
|
-
return {
|
|
1589
|
-
type: "binding:valueIn",
|
|
1590
|
-
value: node.binding.value,
|
|
1591
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
if (node.binding.type === "binding:keyValueIn") {
|
|
1595
|
-
return {
|
|
1596
|
-
type: "binding:keyValueIn",
|
|
1597
|
-
key: node.binding.key,
|
|
1598
|
-
value: node.binding.value,
|
|
1599
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
return {
|
|
1603
|
-
type: "binding:keyOf",
|
|
1604
|
-
key: node.binding.key,
|
|
1605
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1606
|
-
};
|
|
1607
|
-
})();
|
|
1608
|
-
const bodyEnv = cloneOptimizeEnv(env);
|
|
1609
|
-
dropBindingNames(bodyEnv, binding);
|
|
1610
|
-
return {
|
|
1611
|
-
type: "for",
|
|
1612
|
-
binding,
|
|
1613
|
-
body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
case "arrayComprehension": {
|
|
1617
|
-
const sourceEnv = cloneOptimizeEnv(env);
|
|
1618
|
-
const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
|
|
1619
|
-
type: "binding:valueIn",
|
|
1620
|
-
value: node.binding.value,
|
|
1621
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1622
|
-
} : node.binding.type === "binding:keyValueIn" ? {
|
|
1623
|
-
type: "binding:keyValueIn",
|
|
1624
|
-
key: node.binding.key,
|
|
1625
|
-
value: node.binding.value,
|
|
1626
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1627
|
-
} : {
|
|
1628
|
-
type: "binding:keyOf",
|
|
1629
|
-
key: node.binding.key,
|
|
1630
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1631
|
-
};
|
|
1632
|
-
const bodyEnv = cloneOptimizeEnv(env);
|
|
1633
|
-
dropBindingNames(bodyEnv, binding);
|
|
1634
|
-
return {
|
|
1635
|
-
type: "arrayComprehension",
|
|
1636
|
-
binding,
|
|
1637
|
-
body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
case "objectComprehension": {
|
|
1641
|
-
const sourceEnv = cloneOptimizeEnv(env);
|
|
1642
|
-
const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
|
|
1643
|
-
type: "binding:valueIn",
|
|
1644
|
-
value: node.binding.value,
|
|
1645
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1646
|
-
} : node.binding.type === "binding:keyValueIn" ? {
|
|
1647
|
-
type: "binding:keyValueIn",
|
|
1648
|
-
key: node.binding.key,
|
|
1649
|
-
value: node.binding.value,
|
|
1650
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1651
|
-
} : {
|
|
1652
|
-
type: "binding:keyOf",
|
|
1653
|
-
key: node.binding.key,
|
|
1654
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1655
|
-
};
|
|
1656
|
-
const bodyEnv = cloneOptimizeEnv(env);
|
|
1657
|
-
dropBindingNames(bodyEnv, binding);
|
|
1658
|
-
return {
|
|
1659
|
-
type: "objectComprehension",
|
|
1660
|
-
binding,
|
|
1661
|
-
key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
|
|
1662
|
-
value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
|
|
1663
|
-
};
|
|
1664
|
-
}
|
|
1665
|
-
default:
|
|
1666
|
-
return node;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
899
|
function optimizeIR(node) {
|
|
1670
|
-
return
|
|
900
|
+
return node;
|
|
1671
901
|
}
|
|
1672
902
|
function collectLocalBindings(node, locals) {
|
|
1673
903
|
switch (node.type) {
|
|
@@ -1713,6 +943,10 @@ function collectLocalBindings(node, locals) {
|
|
|
1713
943
|
collectLocalBindings(node.left, locals);
|
|
1714
944
|
collectLocalBindings(node.right, locals);
|
|
1715
945
|
return;
|
|
946
|
+
case "range":
|
|
947
|
+
collectLocalBindings(node.from, locals);
|
|
948
|
+
collectLocalBindings(node.to, locals);
|
|
949
|
+
return;
|
|
1716
950
|
case "conditional":
|
|
1717
951
|
collectLocalBindings(node.condition, locals);
|
|
1718
952
|
for (const part of node.thenBlock)
|
|
@@ -1729,11 +963,20 @@ function collectLocalBindings(node, locals) {
|
|
|
1729
963
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1730
964
|
collectLocalBindings(node.body, locals);
|
|
1731
965
|
return;
|
|
966
|
+
case "whileArrayComprehension":
|
|
967
|
+
collectLocalBindings(node.condition, locals);
|
|
968
|
+
collectLocalBindings(node.body, locals);
|
|
969
|
+
return;
|
|
1732
970
|
case "objectComprehension":
|
|
1733
971
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1734
972
|
collectLocalBindings(node.key, locals);
|
|
1735
973
|
collectLocalBindings(node.value, locals);
|
|
1736
974
|
return;
|
|
975
|
+
case "whileObjectComprehension":
|
|
976
|
+
collectLocalBindings(node.condition, locals);
|
|
977
|
+
collectLocalBindings(node.key, locals);
|
|
978
|
+
collectLocalBindings(node.value, locals);
|
|
979
|
+
return;
|
|
1737
980
|
default:
|
|
1738
981
|
return;
|
|
1739
982
|
}
|
|
@@ -1825,6 +1068,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
|
|
|
1825
1068
|
collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
|
|
1826
1069
|
collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
|
|
1827
1070
|
return;
|
|
1071
|
+
case "range":
|
|
1072
|
+
collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
|
|
1073
|
+
collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
|
|
1074
|
+
return;
|
|
1828
1075
|
case "conditional":
|
|
1829
1076
|
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1830
1077
|
for (const part of node.thenBlock)
|
|
@@ -1841,11 +1088,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
|
|
|
1841
1088
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1842
1089
|
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1843
1090
|
return;
|
|
1091
|
+
case "whileArrayComprehension":
|
|
1092
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1093
|
+
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1094
|
+
return;
|
|
1844
1095
|
case "objectComprehension":
|
|
1845
1096
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1846
1097
|
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1847
1098
|
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1848
1099
|
return;
|
|
1100
|
+
case "whileObjectComprehension":
|
|
1101
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1102
|
+
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1103
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1104
|
+
return;
|
|
1849
1105
|
default:
|
|
1850
1106
|
return;
|
|
1851
1107
|
}
|
|
@@ -1920,6 +1176,12 @@ function renameLocalNames(node, map) {
|
|
|
1920
1176
|
left: renameLocalNames(node.left, map),
|
|
1921
1177
|
right: renameLocalNames(node.right, map)
|
|
1922
1178
|
};
|
|
1179
|
+
case "range":
|
|
1180
|
+
return {
|
|
1181
|
+
type: "range",
|
|
1182
|
+
from: renameLocalNames(node.from, map),
|
|
1183
|
+
to: renameLocalNames(node.to, map)
|
|
1184
|
+
};
|
|
1923
1185
|
case "assign": {
|
|
1924
1186
|
const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
|
|
1925
1187
|
return {
|
|
@@ -1949,6 +1211,12 @@ function renameLocalNames(node, map) {
|
|
|
1949
1211
|
binding: renameLocalNamesBinding(node.binding, map),
|
|
1950
1212
|
body: renameLocalNames(node.body, map)
|
|
1951
1213
|
};
|
|
1214
|
+
case "whileArrayComprehension":
|
|
1215
|
+
return {
|
|
1216
|
+
type: "whileArrayComprehension",
|
|
1217
|
+
condition: renameLocalNames(node.condition, map),
|
|
1218
|
+
body: renameLocalNames(node.body, map)
|
|
1219
|
+
};
|
|
1952
1220
|
case "objectComprehension":
|
|
1953
1221
|
return {
|
|
1954
1222
|
type: "objectComprehension",
|
|
@@ -1956,34 +1224,31 @@ function renameLocalNames(node, map) {
|
|
|
1956
1224
|
key: renameLocalNames(node.key, map),
|
|
1957
1225
|
value: renameLocalNames(node.value, map)
|
|
1958
1226
|
};
|
|
1227
|
+
case "whileObjectComprehension":
|
|
1228
|
+
return {
|
|
1229
|
+
type: "whileObjectComprehension",
|
|
1230
|
+
condition: renameLocalNames(node.condition, map),
|
|
1231
|
+
key: renameLocalNames(node.key, map),
|
|
1232
|
+
value: renameLocalNames(node.value, map)
|
|
1233
|
+
};
|
|
1959
1234
|
default:
|
|
1960
1235
|
return node;
|
|
1961
1236
|
}
|
|
1962
1237
|
}
|
|
1963
1238
|
function renameLocalNamesBinding(binding, map) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
type: "binding:
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
type: "binding:keyValueIn",
|
|
1977
|
-
key: map.get(binding.key) ?? binding.key,
|
|
1978
|
-
value: map.get(binding.value) ?? binding.value,
|
|
1979
|
-
source: renameLocalNames(binding.source, map)
|
|
1980
|
-
};
|
|
1239
|
+
const source = renameLocalNames(binding.source, map);
|
|
1240
|
+
switch (binding.type) {
|
|
1241
|
+
case "binding:bareIn":
|
|
1242
|
+
return { type: "binding:bareIn", source };
|
|
1243
|
+
case "binding:bareOf":
|
|
1244
|
+
return { type: "binding:bareOf", source };
|
|
1245
|
+
case "binding:valueIn":
|
|
1246
|
+
return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
|
|
1247
|
+
case "binding:keyValueIn":
|
|
1248
|
+
return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
|
|
1249
|
+
case "binding:keyOf":
|
|
1250
|
+
return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
|
|
1981
1251
|
}
|
|
1982
|
-
return {
|
|
1983
|
-
type: "binding:keyOf",
|
|
1984
|
-
key: map.get(binding.key) ?? binding.key,
|
|
1985
|
-
source: renameLocalNames(binding.source, map)
|
|
1986
|
-
};
|
|
1987
1252
|
}
|
|
1988
1253
|
function renameLocalNamesElse(elseBranch, map) {
|
|
1989
1254
|
if (elseBranch.type === "else") {
|
|
@@ -2030,9 +1295,9 @@ function compile(source, options) {
|
|
|
2030
1295
|
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
2031
1296
|
if (options?.minifyNames)
|
|
2032
1297
|
lowered = minifyLocalNamesIR(lowered);
|
|
2033
|
-
const
|
|
1298
|
+
const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
|
|
2034
1299
|
return encodeIR(lowered, {
|
|
2035
|
-
|
|
1300
|
+
...domainMaps,
|
|
2036
1301
|
dedupeValues: options?.dedupeValues,
|
|
2037
1302
|
dedupeMinBytes: options?.dedupeMinBytes
|
|
2038
1303
|
});
|
|
@@ -2158,6 +1423,9 @@ semantics.addOperation("toIR", {
|
|
|
2158
1423
|
ExistenceExpr_or(left, _or, right) {
|
|
2159
1424
|
return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
|
|
2160
1425
|
},
|
|
1426
|
+
ExistenceExpr_nor(left, _nor, right) {
|
|
1427
|
+
return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
|
|
1428
|
+
},
|
|
2161
1429
|
BitExpr_and(left, _op, right) {
|
|
2162
1430
|
return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
|
|
2163
1431
|
},
|
|
@@ -2167,6 +1435,9 @@ semantics.addOperation("toIR", {
|
|
|
2167
1435
|
BitExpr_or(left, _op, right) {
|
|
2168
1436
|
return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
|
|
2169
1437
|
},
|
|
1438
|
+
RangeExpr_range(left, _op, right) {
|
|
1439
|
+
return { type: "range", from: left.toIR(), to: right.toIR() };
|
|
1440
|
+
},
|
|
2170
1441
|
CompareExpr_binary(left, op, right) {
|
|
2171
1442
|
const map = {
|
|
2172
1443
|
"==": "eq",
|
|
@@ -2207,6 +1478,9 @@ semantics.addOperation("toIR", {
|
|
|
2207
1478
|
UnaryExpr_not(_op, value) {
|
|
2208
1479
|
return { type: "unary", op: "not", value: value.toIR() };
|
|
2209
1480
|
},
|
|
1481
|
+
UnaryExpr_logicalNot(_not, value) {
|
|
1482
|
+
return { type: "unary", op: "logicalNot", value: value.toIR() };
|
|
1483
|
+
},
|
|
2210
1484
|
UnaryExpr_delete(_del, place) {
|
|
2211
1485
|
return { type: "unary", op: "delete", value: place.toIR() };
|
|
2212
1486
|
},
|
|
@@ -2260,14 +1534,6 @@ semantics.addOperation("toIR", {
|
|
|
2260
1534
|
ConditionalElse_else(_else, block) {
|
|
2261
1535
|
return { type: "else", block: block.toIR() };
|
|
2262
1536
|
},
|
|
2263
|
-
DoExpr(_do, block, _end) {
|
|
2264
|
-
const body = block.toIR();
|
|
2265
|
-
if (body.length === 0)
|
|
2266
|
-
return { type: "undefined" };
|
|
2267
|
-
if (body.length === 1)
|
|
2268
|
-
return body[0];
|
|
2269
|
-
return { type: "program", body };
|
|
2270
|
-
},
|
|
2271
1537
|
WhileExpr(_while, condition, _do, block, _end) {
|
|
2272
1538
|
return {
|
|
2273
1539
|
type: "while",
|
|
@@ -2282,30 +1548,44 @@ semantics.addOperation("toIR", {
|
|
|
2282
1548
|
body: block.toIR()
|
|
2283
1549
|
};
|
|
2284
1550
|
},
|
|
2285
|
-
BindingExpr(iterOrExpr) {
|
|
2286
|
-
const node = iterOrExpr.toIR();
|
|
2287
|
-
if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
|
|
2288
|
-
return node;
|
|
2289
|
-
}
|
|
2290
|
-
return { type: "binding:expr", source: node };
|
|
2291
|
-
},
|
|
2292
1551
|
Array_empty(_open, _close) {
|
|
2293
1552
|
return { type: "array", items: [] };
|
|
2294
1553
|
},
|
|
2295
|
-
|
|
1554
|
+
Array_forComprehension(_open, body, _for, binding, _close) {
|
|
2296
1555
|
return {
|
|
2297
1556
|
type: "arrayComprehension",
|
|
2298
1557
|
binding: binding.toIR(),
|
|
2299
1558
|
body: body.toIR()
|
|
2300
1559
|
};
|
|
2301
1560
|
},
|
|
1561
|
+
Array_whileComprehension(_open, body, _while, condition, _close) {
|
|
1562
|
+
return {
|
|
1563
|
+
type: "whileArrayComprehension",
|
|
1564
|
+
condition: condition.toIR(),
|
|
1565
|
+
body: body.toIR()
|
|
1566
|
+
};
|
|
1567
|
+
},
|
|
1568
|
+
Array_inComprehension(_open, body, _in, source, _close) {
|
|
1569
|
+
return {
|
|
1570
|
+
type: "arrayComprehension",
|
|
1571
|
+
binding: { type: "binding:bareIn", source: source.toIR() },
|
|
1572
|
+
body: body.toIR()
|
|
1573
|
+
};
|
|
1574
|
+
},
|
|
1575
|
+
Array_ofComprehension(_open, body, _of, source, _close) {
|
|
1576
|
+
return {
|
|
1577
|
+
type: "arrayComprehension",
|
|
1578
|
+
binding: { type: "binding:bareOf", source: source.toIR() },
|
|
1579
|
+
body: body.toIR()
|
|
1580
|
+
};
|
|
1581
|
+
},
|
|
2302
1582
|
Array_values(_open, items, _close) {
|
|
2303
1583
|
return { type: "array", items: normalizeList(items.toIR()) };
|
|
2304
1584
|
},
|
|
2305
1585
|
Object_empty(_open, _close) {
|
|
2306
1586
|
return { type: "object", entries: [] };
|
|
2307
1587
|
},
|
|
2308
|
-
|
|
1588
|
+
Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
|
|
2309
1589
|
return {
|
|
2310
1590
|
type: "objectComprehension",
|
|
2311
1591
|
binding: binding.toIR(),
|
|
@@ -2313,6 +1593,30 @@ semantics.addOperation("toIR", {
|
|
|
2313
1593
|
value: value.toIR()
|
|
2314
1594
|
};
|
|
2315
1595
|
},
|
|
1596
|
+
Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
|
|
1597
|
+
return {
|
|
1598
|
+
type: "whileObjectComprehension",
|
|
1599
|
+
condition: condition.toIR(),
|
|
1600
|
+
key: key.toIR(),
|
|
1601
|
+
value: value.toIR()
|
|
1602
|
+
};
|
|
1603
|
+
},
|
|
1604
|
+
Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
|
|
1605
|
+
return {
|
|
1606
|
+
type: "objectComprehension",
|
|
1607
|
+
binding: { type: "binding:bareIn", source: source.toIR() },
|
|
1608
|
+
key: key.toIR(),
|
|
1609
|
+
value: value.toIR()
|
|
1610
|
+
};
|
|
1611
|
+
},
|
|
1612
|
+
Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
|
|
1613
|
+
return {
|
|
1614
|
+
type: "objectComprehension",
|
|
1615
|
+
binding: { type: "binding:bareOf", source: source.toIR() },
|
|
1616
|
+
key: key.toIR(),
|
|
1617
|
+
value: value.toIR()
|
|
1618
|
+
};
|
|
1619
|
+
},
|
|
2316
1620
|
Object_pairs(_open, pairs, _close) {
|
|
2317
1621
|
return {
|
|
2318
1622
|
type: "object",
|
|
@@ -2341,6 +1645,40 @@ semantics.addOperation("toIR", {
|
|
|
2341
1645
|
source: source.toIR()
|
|
2342
1646
|
};
|
|
2343
1647
|
},
|
|
1648
|
+
IterBinding_bareIn(_in, source) {
|
|
1649
|
+
return {
|
|
1650
|
+
type: "binding:bareIn",
|
|
1651
|
+
source: source.toIR()
|
|
1652
|
+
};
|
|
1653
|
+
},
|
|
1654
|
+
IterBinding_bareOf(_of, source) {
|
|
1655
|
+
return {
|
|
1656
|
+
type: "binding:bareOf",
|
|
1657
|
+
source: source.toIR()
|
|
1658
|
+
};
|
|
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
|
+
},
|
|
2344
1682
|
Pair(key, _colon, value) {
|
|
2345
1683
|
return { key: key.toIR(), value: value.toIR() };
|
|
2346
1684
|
},
|
|
@@ -2422,28 +1760,29 @@ semantics.addOperation("toIR", {
|
|
|
2422
1760
|
var DIGITS2 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
|
|
2423
1761
|
var digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
|
|
2424
1762
|
var OPCODES = {
|
|
2425
|
-
do:
|
|
2426
|
-
add:
|
|
2427
|
-
sub:
|
|
2428
|
-
mul:
|
|
2429
|
-
div:
|
|
2430
|
-
eq:
|
|
2431
|
-
neq:
|
|
2432
|
-
lt:
|
|
2433
|
-
lte:
|
|
2434
|
-
gt:
|
|
2435
|
-
gte:
|
|
2436
|
-
and:
|
|
2437
|
-
or:
|
|
2438
|
-
xor:
|
|
2439
|
-
not:
|
|
2440
|
-
boolean:
|
|
2441
|
-
number:
|
|
2442
|
-
string:
|
|
2443
|
-
array:
|
|
2444
|
-
object:
|
|
2445
|
-
mod:
|
|
2446
|
-
neg:
|
|
1763
|
+
do: "",
|
|
1764
|
+
add: "ad",
|
|
1765
|
+
sub: "sb",
|
|
1766
|
+
mul: "ml",
|
|
1767
|
+
div: "dv",
|
|
1768
|
+
eq: "eq",
|
|
1769
|
+
neq: "nq",
|
|
1770
|
+
lt: "lt",
|
|
1771
|
+
lte: "le",
|
|
1772
|
+
gt: "gt",
|
|
1773
|
+
gte: "ge",
|
|
1774
|
+
and: "an",
|
|
1775
|
+
or: "or",
|
|
1776
|
+
xor: "xr",
|
|
1777
|
+
not: "nt",
|
|
1778
|
+
boolean: "bt",
|
|
1779
|
+
number: "nm",
|
|
1780
|
+
string: "st",
|
|
1781
|
+
array: "ar",
|
|
1782
|
+
object: "ob",
|
|
1783
|
+
mod: "md",
|
|
1784
|
+
neg: "ng",
|
|
1785
|
+
range: "rn"
|
|
2447
1786
|
};
|
|
2448
1787
|
function decodePrefix(text, start, end) {
|
|
2449
1788
|
let value = 0;
|
|
@@ -2467,7 +1806,6 @@ class CursorInterpreter {
|
|
|
2467
1806
|
pos = 0;
|
|
2468
1807
|
state;
|
|
2469
1808
|
selfStack;
|
|
2470
|
-
opcodeMarkers;
|
|
2471
1809
|
pointerCache = new Map;
|
|
2472
1810
|
gasLimit;
|
|
2473
1811
|
gas = 0;
|
|
@@ -2482,28 +1820,22 @@ class CursorInterpreter {
|
|
|
2482
1820
|
this.state = {
|
|
2483
1821
|
vars: ctx.vars ?? {},
|
|
2484
1822
|
refs: {
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
1823
|
+
tr: true,
|
|
1824
|
+
fl: false,
|
|
1825
|
+
nl: null,
|
|
1826
|
+
un: undefined,
|
|
1827
|
+
nan: NaN,
|
|
1828
|
+
inf: Infinity,
|
|
1829
|
+
nif: -Infinity,
|
|
1830
|
+
...ctx.refs
|
|
2493
1831
|
}
|
|
2494
1832
|
};
|
|
2495
1833
|
this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
|
|
2496
1834
|
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;
|
|
2502
|
-
}
|
|
2503
1835
|
if (ctx.opcodes) {
|
|
2504
|
-
for (const [
|
|
1836
|
+
for (const [key, op] of Object.entries(ctx.opcodes)) {
|
|
2505
1837
|
if (op)
|
|
2506
|
-
this.customOpcodes.set(
|
|
1838
|
+
this.customOpcodes.set(key, op);
|
|
2507
1839
|
}
|
|
2508
1840
|
}
|
|
2509
1841
|
}
|
|
@@ -2564,6 +1896,22 @@ class CursorInterpreter {
|
|
|
2564
1896
|
const end = this.pos;
|
|
2565
1897
|
return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
|
|
2566
1898
|
}
|
|
1899
|
+
advanceByBytes(start, byteCount) {
|
|
1900
|
+
if (byteCount <= 0)
|
|
1901
|
+
return start;
|
|
1902
|
+
let bytes = 0;
|
|
1903
|
+
let index = start;
|
|
1904
|
+
for (const char of this.text.slice(start)) {
|
|
1905
|
+
const charBytes = Buffer.byteLength(char);
|
|
1906
|
+
if (bytes + charBytes > byteCount)
|
|
1907
|
+
break;
|
|
1908
|
+
bytes += charBytes;
|
|
1909
|
+
index += char.length;
|
|
1910
|
+
if (bytes === byteCount)
|
|
1911
|
+
return index;
|
|
1912
|
+
}
|
|
1913
|
+
throw new Error("String container overflows input");
|
|
1914
|
+
}
|
|
2567
1915
|
ensure(char) {
|
|
2568
1916
|
if (this.text[this.pos] !== char)
|
|
2569
1917
|
throw new Error(`Expected '${char}' at ${this.pos}`);
|
|
@@ -2604,36 +1952,34 @@ class CursorInterpreter {
|
|
|
2604
1952
|
const significand = this.evalValue();
|
|
2605
1953
|
if (typeof significand !== "number")
|
|
2606
1954
|
throw new Error("Decimal significand must be numeric");
|
|
2607
|
-
return significand
|
|
1955
|
+
return parseFloat(`${significand}e${power}`);
|
|
2608
1956
|
}
|
|
2609
1957
|
case ":":
|
|
2610
1958
|
this.pos += 1;
|
|
2611
1959
|
return prefix.raw;
|
|
2612
1960
|
case "%":
|
|
2613
1961
|
this.pos += 1;
|
|
2614
|
-
return
|
|
1962
|
+
return { __opcode: prefix.raw };
|
|
2615
1963
|
case "@":
|
|
2616
1964
|
this.pos += 1;
|
|
2617
1965
|
return this.readSelf(prefix.value);
|
|
2618
1966
|
case "'":
|
|
2619
1967
|
this.pos += 1;
|
|
2620
|
-
return this.state.refs[prefix.
|
|
1968
|
+
return this.state.refs[prefix.raw];
|
|
2621
1969
|
case "$":
|
|
2622
1970
|
this.pos += 1;
|
|
2623
1971
|
return this.state.vars[prefix.raw];
|
|
2624
1972
|
case ",": {
|
|
2625
1973
|
this.pos += 1;
|
|
2626
1974
|
const start = this.pos;
|
|
2627
|
-
const end = start
|
|
2628
|
-
if (end > this.text.length)
|
|
2629
|
-
throw new Error("String container overflows input");
|
|
1975
|
+
const end = this.advanceByBytes(start, prefix.value);
|
|
2630
1976
|
const value = this.text.slice(start, end);
|
|
2631
1977
|
this.pos = end;
|
|
2632
1978
|
return value;
|
|
2633
1979
|
}
|
|
2634
1980
|
case "^": {
|
|
2635
1981
|
this.pos += 1;
|
|
2636
|
-
const target = this.pos
|
|
1982
|
+
const target = this.advanceByBytes(this.pos, prefix.value);
|
|
2637
1983
|
if (this.pointerCache.has(target))
|
|
2638
1984
|
return this.pointerCache.get(target);
|
|
2639
1985
|
const save = this.pos;
|
|
@@ -2650,6 +1996,14 @@ class CursorInterpreter {
|
|
|
2650
1996
|
this.writePlace(place, value);
|
|
2651
1997
|
return value;
|
|
2652
1998
|
}
|
|
1999
|
+
case "/": {
|
|
2000
|
+
this.pos += 1;
|
|
2001
|
+
const place = this.readPlace();
|
|
2002
|
+
const oldValue = this.readPlaceValue(place);
|
|
2003
|
+
const newValue = this.evalValue();
|
|
2004
|
+
this.writePlace(place, newValue);
|
|
2005
|
+
return oldValue;
|
|
2006
|
+
}
|
|
2653
2007
|
case "~": {
|
|
2654
2008
|
this.pos += 1;
|
|
2655
2009
|
const place = this.readPlace();
|
|
@@ -2677,7 +2031,7 @@ class CursorInterpreter {
|
|
|
2677
2031
|
case "<":
|
|
2678
2032
|
return this.evalLoopLike(tag);
|
|
2679
2033
|
case "#":
|
|
2680
|
-
return this.
|
|
2034
|
+
return this.evalWhileLike();
|
|
2681
2035
|
default:
|
|
2682
2036
|
throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
|
|
2683
2037
|
}
|
|
@@ -2699,7 +2053,9 @@ class CursorInterpreter {
|
|
|
2699
2053
|
}
|
|
2700
2054
|
this.ensure(")");
|
|
2701
2055
|
if (typeof callee === "object" && callee && "__opcode" in callee) {
|
|
2702
|
-
|
|
2056
|
+
const marker = callee;
|
|
2057
|
+
const opArgs = marker.__receiver !== undefined ? [marker.__receiver, ...args] : args;
|
|
2058
|
+
return this.applyOpcode(marker.__opcode, opArgs);
|
|
2703
2059
|
}
|
|
2704
2060
|
return this.navigate(callee, args);
|
|
2705
2061
|
}
|
|
@@ -2890,15 +2246,11 @@ class CursorInterpreter {
|
|
|
2890
2246
|
return entries.map(([key]) => ({ key, value: key }));
|
|
2891
2247
|
return entries.map(([key, value]) => ({ key, value }));
|
|
2892
2248
|
}
|
|
2893
|
-
if (typeof iterable === "
|
|
2894
|
-
const
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
else
|
|
2899
|
-
out.push({ key: index, value: index + 1 });
|
|
2900
|
-
}
|
|
2901
|
-
return out;
|
|
2249
|
+
if (typeof iterable === "string") {
|
|
2250
|
+
const entries = Array.from(iterable);
|
|
2251
|
+
if (keysOnly)
|
|
2252
|
+
return entries.map((_value, index) => ({ key: index, value: index }));
|
|
2253
|
+
return entries.map((value, index) => ({ key: index, value }));
|
|
2902
2254
|
}
|
|
2903
2255
|
return [];
|
|
2904
2256
|
}
|
|
@@ -2943,21 +2295,32 @@ class CursorInterpreter {
|
|
|
2943
2295
|
}
|
|
2944
2296
|
return last;
|
|
2945
2297
|
}
|
|
2946
|
-
|
|
2298
|
+
evalWhileLike() {
|
|
2299
|
+
this.pos += 1;
|
|
2300
|
+
const open = this.text[this.pos];
|
|
2301
|
+
if (!open || !"([{".includes(open))
|
|
2302
|
+
throw new Error(`Expected opener after '#' at ${this.pos}`);
|
|
2303
|
+
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
2947
2304
|
this.pos += 1;
|
|
2948
|
-
this.ensure("(");
|
|
2949
2305
|
const condStart = this.pos;
|
|
2950
2306
|
const condValue = this.evalValue();
|
|
2951
2307
|
const bodyStart = this.pos;
|
|
2952
|
-
const bodyValueCount = 1;
|
|
2308
|
+
const bodyValueCount = open === "{" ? 2 : 1;
|
|
2953
2309
|
let cursor = bodyStart;
|
|
2954
2310
|
for (let index = 0;index < bodyValueCount; index += 1) {
|
|
2955
2311
|
cursor = this.skipValueFrom(cursor);
|
|
2956
2312
|
}
|
|
2957
2313
|
const bodyEnd = cursor;
|
|
2958
2314
|
this.pos = bodyEnd;
|
|
2959
|
-
this.ensure(
|
|
2315
|
+
this.ensure(close);
|
|
2960
2316
|
const afterClose = this.pos;
|
|
2317
|
+
if (open === "[")
|
|
2318
|
+
return this.evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
2319
|
+
if (open === "{")
|
|
2320
|
+
return this.evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
2321
|
+
return this.evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
2322
|
+
}
|
|
2323
|
+
evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
2961
2324
|
let last = undefined;
|
|
2962
2325
|
let currentCond = condValue;
|
|
2963
2326
|
while (isDefined(currentCond)) {
|
|
@@ -2978,6 +2341,58 @@ class CursorInterpreter {
|
|
|
2978
2341
|
this.pos = afterClose;
|
|
2979
2342
|
return last;
|
|
2980
2343
|
}
|
|
2344
|
+
evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
2345
|
+
const out = [];
|
|
2346
|
+
let currentCond = condValue;
|
|
2347
|
+
while (isDefined(currentCond)) {
|
|
2348
|
+
this.tick();
|
|
2349
|
+
this.selfStack.push(currentCond);
|
|
2350
|
+
const value = this.evalBodySlice(bodyStart, bodyEnd);
|
|
2351
|
+
this.selfStack.pop();
|
|
2352
|
+
const control = this.handleLoopControl(value);
|
|
2353
|
+
if (control) {
|
|
2354
|
+
if (control.depth > 1)
|
|
2355
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
2356
|
+
if (control.kind === "break")
|
|
2357
|
+
break;
|
|
2358
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2359
|
+
continue;
|
|
2360
|
+
}
|
|
2361
|
+
if (isDefined(value))
|
|
2362
|
+
out.push(value);
|
|
2363
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2364
|
+
}
|
|
2365
|
+
this.pos = afterClose;
|
|
2366
|
+
return out;
|
|
2367
|
+
}
|
|
2368
|
+
evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
2369
|
+
const result = {};
|
|
2370
|
+
let currentCond = condValue;
|
|
2371
|
+
while (isDefined(currentCond)) {
|
|
2372
|
+
this.tick();
|
|
2373
|
+
this.selfStack.push(currentCond);
|
|
2374
|
+
const save = this.pos;
|
|
2375
|
+
this.pos = bodyStart;
|
|
2376
|
+
const key = this.evalValue();
|
|
2377
|
+
const value = this.evalValue();
|
|
2378
|
+
this.pos = save;
|
|
2379
|
+
this.selfStack.pop();
|
|
2380
|
+
const control = this.handleLoopControl(value);
|
|
2381
|
+
if (control) {
|
|
2382
|
+
if (control.depth > 1)
|
|
2383
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
2384
|
+
if (control.kind === "break")
|
|
2385
|
+
break;
|
|
2386
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
if (isDefined(value))
|
|
2390
|
+
result[String(key)] = value;
|
|
2391
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2392
|
+
}
|
|
2393
|
+
this.pos = afterClose;
|
|
2394
|
+
return result;
|
|
2395
|
+
}
|
|
2981
2396
|
evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
|
|
2982
2397
|
const items = this.iterate(iterable, keysOnly);
|
|
2983
2398
|
const out = [];
|
|
@@ -3048,10 +2463,19 @@ class CursorInterpreter {
|
|
|
3048
2463
|
case OPCODES.add:
|
|
3049
2464
|
if (args[0] === undefined || args[1] === undefined)
|
|
3050
2465
|
return;
|
|
3051
|
-
if (
|
|
3052
|
-
return
|
|
2466
|
+
if (Array.isArray(args[0]) && Array.isArray(args[1])) {
|
|
2467
|
+
return [...args[0], ...args[1]];
|
|
3053
2468
|
}
|
|
3054
|
-
|
|
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] };
|
|
2471
|
+
}
|
|
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;
|
|
3055
2479
|
case OPCODES.sub:
|
|
3056
2480
|
if (args[0] === undefined || args[1] === undefined)
|
|
3057
2481
|
return;
|
|
@@ -3120,6 +2544,98 @@ class CursorInterpreter {
|
|
|
3120
2544
|
return Array.isArray(args[0]) ? args[0] : undefined;
|
|
3121
2545
|
case OPCODES.object:
|
|
3122
2546
|
return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
|
|
2547
|
+
case OPCODES.range: {
|
|
2548
|
+
const from = Number(args[0]);
|
|
2549
|
+
const to = Number(args[1]);
|
|
2550
|
+
const step = to >= from ? 1 : -1;
|
|
2551
|
+
const out = [];
|
|
2552
|
+
for (let v = from;step > 0 ? v <= to : v >= to; v += step)
|
|
2553
|
+
out.push(v);
|
|
2554
|
+
return out;
|
|
2555
|
+
}
|
|
2556
|
+
case "array:push": {
|
|
2557
|
+
const target = args[0];
|
|
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);
|
|
2638
|
+
}
|
|
3123
2639
|
default:
|
|
3124
2640
|
throw new Error(`Unknown opcode ${id}`);
|
|
3125
2641
|
}
|
|
@@ -3129,10 +2645,103 @@ class CursorInterpreter {
|
|
|
3129
2645
|
for (const key of keys) {
|
|
3130
2646
|
if (current === undefined || current === null)
|
|
3131
2647
|
return;
|
|
3132
|
-
current = current
|
|
2648
|
+
current = this.readProperty(current, key);
|
|
2649
|
+
if (current === undefined)
|
|
2650
|
+
return;
|
|
3133
2651
|
}
|
|
3134
2652
|
return current;
|
|
3135
2653
|
}
|
|
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
|
+
}
|
|
2661
|
+
const index = this.parseIndexKey(key);
|
|
2662
|
+
if (Array.isArray(target)) {
|
|
2663
|
+
if (index !== undefined)
|
|
2664
|
+
return target[index];
|
|
2665
|
+
if (typeof key === "string")
|
|
2666
|
+
return this.resolveArrayMethod(target, key);
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
if (typeof target === "string") {
|
|
2670
|
+
if (index !== undefined)
|
|
2671
|
+
return Array.from(target)[index];
|
|
2672
|
+
if (typeof key === "string")
|
|
2673
|
+
return this.resolveStringMethod(target, key);
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
if (this.isPlainObject(target)) {
|
|
2677
|
+
const prop = String(key);
|
|
2678
|
+
if (!Object.prototype.hasOwnProperty.call(target, prop))
|
|
2679
|
+
return;
|
|
2680
|
+
return target[prop];
|
|
2681
|
+
}
|
|
2682
|
+
return;
|
|
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
|
+
}
|
|
2718
|
+
canWriteProperty(target, key) {
|
|
2719
|
+
const index = this.parseIndexKey(key);
|
|
2720
|
+
if (Array.isArray(target)) {
|
|
2721
|
+
if (index === undefined)
|
|
2722
|
+
return;
|
|
2723
|
+
return { kind: "array", index };
|
|
2724
|
+
}
|
|
2725
|
+
if (this.isPlainObject(target))
|
|
2726
|
+
return { kind: "object" };
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
parseIndexKey(key) {
|
|
2730
|
+
if (typeof key === "number" && Number.isInteger(key) && key >= 0)
|
|
2731
|
+
return key;
|
|
2732
|
+
if (typeof key !== "string" || key.length === 0)
|
|
2733
|
+
return;
|
|
2734
|
+
if (!/^(0|[1-9]\d*)$/.test(key))
|
|
2735
|
+
return;
|
|
2736
|
+
const index = Number(key);
|
|
2737
|
+
return Number.isSafeInteger(index) ? index : undefined;
|
|
2738
|
+
}
|
|
2739
|
+
isPlainObject(value) {
|
|
2740
|
+
if (!value || typeof value !== "object")
|
|
2741
|
+
return false;
|
|
2742
|
+
const proto = Object.getPrototypeOf(value);
|
|
2743
|
+
return proto === Object.prototype || proto === null;
|
|
2744
|
+
}
|
|
3136
2745
|
readPlace() {
|
|
3137
2746
|
this.skipNonCode();
|
|
3138
2747
|
const direct = this.readRootVarOrRefIfPresent();
|
|
@@ -3187,13 +2796,13 @@ class CursorInterpreter {
|
|
|
3187
2796
|
}
|
|
3188
2797
|
this.pos += 1;
|
|
3189
2798
|
return {
|
|
3190
|
-
root:
|
|
2799
|
+
root: prefix.raw,
|
|
3191
2800
|
isRef: tag === "'"
|
|
3192
2801
|
};
|
|
3193
2802
|
}
|
|
3194
2803
|
writePlace(place, value) {
|
|
3195
2804
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3196
|
-
const rootKey =
|
|
2805
|
+
const rootKey = place.root;
|
|
3197
2806
|
if (place.keys.length === 0) {
|
|
3198
2807
|
rootTable[rootKey] = value;
|
|
3199
2808
|
return;
|
|
@@ -3204,17 +2813,48 @@ class CursorInterpreter {
|
|
|
3204
2813
|
rootTable[rootKey] = target;
|
|
3205
2814
|
}
|
|
3206
2815
|
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3207
|
-
const key =
|
|
3208
|
-
const
|
|
2816
|
+
const key = place.keys[index];
|
|
2817
|
+
const access2 = this.canWriteProperty(target, key);
|
|
2818
|
+
if (!access2)
|
|
2819
|
+
return;
|
|
2820
|
+
if (access2.kind === "array") {
|
|
2821
|
+
const next2 = target[access2.index];
|
|
2822
|
+
if (!next2 || typeof next2 !== "object")
|
|
2823
|
+
target[access2.index] = {};
|
|
2824
|
+
target = target[access2.index];
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
const prop = String(key);
|
|
2828
|
+
const next = target[prop];
|
|
3209
2829
|
if (!next || typeof next !== "object")
|
|
3210
|
-
target[
|
|
3211
|
-
target = target[
|
|
2830
|
+
target[prop] = {};
|
|
2831
|
+
target = target[prop];
|
|
2832
|
+
}
|
|
2833
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
2834
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
2835
|
+
if (!access)
|
|
2836
|
+
return;
|
|
2837
|
+
if (access.kind === "array") {
|
|
2838
|
+
target[access.index] = value;
|
|
2839
|
+
return;
|
|
3212
2840
|
}
|
|
3213
|
-
target[String(
|
|
2841
|
+
target[String(lastKey)] = value;
|
|
2842
|
+
}
|
|
2843
|
+
readPlaceValue(place) {
|
|
2844
|
+
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
2845
|
+
let current = rootTable[place.root];
|
|
2846
|
+
for (const key of place.keys) {
|
|
2847
|
+
if (current === undefined || current === null)
|
|
2848
|
+
return;
|
|
2849
|
+
current = this.readProperty(current, key);
|
|
2850
|
+
if (current === undefined)
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
return current;
|
|
3214
2854
|
}
|
|
3215
2855
|
deletePlace(place) {
|
|
3216
2856
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3217
|
-
const rootKey =
|
|
2857
|
+
const rootKey = place.root;
|
|
3218
2858
|
if (place.keys.length === 0) {
|
|
3219
2859
|
delete rootTable[rootKey];
|
|
3220
2860
|
return;
|
|
@@ -3223,11 +2863,29 @@ class CursorInterpreter {
|
|
|
3223
2863
|
if (!target || typeof target !== "object")
|
|
3224
2864
|
return;
|
|
3225
2865
|
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3226
|
-
|
|
2866
|
+
const key = place.keys[index];
|
|
2867
|
+
const access2 = this.canWriteProperty(target, key);
|
|
2868
|
+
if (!access2)
|
|
2869
|
+
return;
|
|
2870
|
+
if (access2.kind === "array") {
|
|
2871
|
+
target = target[access2.index];
|
|
2872
|
+
if (!target || typeof target !== "object")
|
|
2873
|
+
return;
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
target = target[String(key)];
|
|
3227
2877
|
if (!target || typeof target !== "object")
|
|
3228
2878
|
return;
|
|
3229
2879
|
}
|
|
3230
|
-
|
|
2880
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
2881
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
2882
|
+
if (!access)
|
|
2883
|
+
return;
|
|
2884
|
+
if (access.kind === "array") {
|
|
2885
|
+
delete target[access.index];
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
delete target[String(lastKey)];
|
|
3231
2889
|
}
|
|
3232
2890
|
skipValue() {
|
|
3233
2891
|
this.pos = this.skipValueFrom(this.pos);
|
|
@@ -3243,12 +2901,12 @@ class CursorInterpreter {
|
|
|
3243
2901
|
return startPos;
|
|
3244
2902
|
}
|
|
3245
2903
|
if (tag === ",") {
|
|
3246
|
-
this.pos
|
|
2904
|
+
this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
3247
2905
|
const end2 = this.pos;
|
|
3248
2906
|
this.pos = save;
|
|
3249
2907
|
return end2;
|
|
3250
2908
|
}
|
|
3251
|
-
if (tag === "=") {
|
|
2909
|
+
if (tag === "=" || tag === "/") {
|
|
3252
2910
|
this.pos += 1;
|
|
3253
2911
|
this.skipValue();
|
|
3254
2912
|
this.skipValue();
|
|
@@ -3283,7 +2941,8 @@ class CursorInterpreter {
|
|
|
3283
2941
|
if (opener && "([{".includes(opener)) {
|
|
3284
2942
|
const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
|
|
3285
2943
|
if (prefix.value > 0) {
|
|
3286
|
-
this.pos
|
|
2944
|
+
const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
2945
|
+
this.pos = bodyEnd + 1;
|
|
3287
2946
|
const end3 = this.pos;
|
|
3288
2947
|
this.pos = save;
|
|
3289
2948
|
return end3;
|
|
@@ -3315,19 +2974,63 @@ function evaluateRexc(text, ctx = {}) {
|
|
|
3315
2974
|
// rex-repl.ts
|
|
3316
2975
|
var req = createRequire2(import.meta.url);
|
|
3317
2976
|
var { version } = req("./package.json");
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
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;
|
|
3331
3034
|
function highlightLine(line) {
|
|
3332
3035
|
let result = "";
|
|
3333
3036
|
let lastIndex = 0;
|
|
@@ -3340,14 +3043,18 @@ function highlightLine(line) {
|
|
|
3340
3043
|
result += C.gray + text + C.reset;
|
|
3341
3044
|
} else if (g.dstring || g.sstring) {
|
|
3342
3045
|
result += C.green + text + C.reset;
|
|
3046
|
+
} else if (g.objKey) {
|
|
3047
|
+
result += C.magenta + text + C.reset;
|
|
3343
3048
|
} else if (g.keyword) {
|
|
3344
|
-
result += C.
|
|
3049
|
+
result += C.keyword + text + C.reset;
|
|
3345
3050
|
} else if (g.literal) {
|
|
3346
3051
|
result += C.yellow + text + C.reset;
|
|
3347
3052
|
} else if (g.typePred) {
|
|
3348
3053
|
result += C.cyan + text + C.reset;
|
|
3349
3054
|
} else if (g.num) {
|
|
3350
3055
|
result += C.cyan + text + C.reset;
|
|
3056
|
+
} else if (g.identifier) {
|
|
3057
|
+
result += C.blue + text + C.reset;
|
|
3351
3058
|
} else {
|
|
3352
3059
|
result += text;
|
|
3353
3060
|
}
|
|
@@ -3410,7 +3117,7 @@ function highlightRexc(text) {
|
|
|
3410
3117
|
i++;
|
|
3411
3118
|
break;
|
|
3412
3119
|
case "%":
|
|
3413
|
-
out += C.
|
|
3120
|
+
out += C.keyword + prefix + tag + C.reset;
|
|
3414
3121
|
i++;
|
|
3415
3122
|
break;
|
|
3416
3123
|
case "$":
|
|
@@ -3436,6 +3143,7 @@ function highlightRexc(text) {
|
|
|
3436
3143
|
break;
|
|
3437
3144
|
}
|
|
3438
3145
|
case "=":
|
|
3146
|
+
case "/":
|
|
3439
3147
|
case "~":
|
|
3440
3148
|
out += C.red + prefix + tag + C.reset;
|
|
3441
3149
|
i++;
|
|
@@ -3447,11 +3155,11 @@ function highlightRexc(text) {
|
|
|
3447
3155
|
case ">":
|
|
3448
3156
|
case "<":
|
|
3449
3157
|
case "#":
|
|
3450
|
-
out += C.
|
|
3158
|
+
out += C.keyword + prefix + tag + C.reset;
|
|
3451
3159
|
i++;
|
|
3452
3160
|
break;
|
|
3453
3161
|
case ";":
|
|
3454
|
-
out += C.
|
|
3162
|
+
out += C.keyword + prefix + tag + C.reset;
|
|
3455
3163
|
i++;
|
|
3456
3164
|
break;
|
|
3457
3165
|
case "^":
|
|
@@ -3475,6 +3183,23 @@ function highlightRexc(text) {
|
|
|
3475
3183
|
}
|
|
3476
3184
|
return out;
|
|
3477
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
|
+
}
|
|
3478
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;
|
|
3479
3204
|
function highlightJSON(json) {
|
|
3480
3205
|
let result = "";
|
|
@@ -3539,23 +3264,18 @@ function isIncomplete(buffer) {
|
|
|
3539
3264
|
const trimmed = buffer.trimEnd();
|
|
3540
3265
|
if (/[+\-*/%&|^=<>]$/.test(trimmed))
|
|
3541
3266
|
return true;
|
|
3542
|
-
if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
|
|
3267
|
+
if (/\b(?:and|or|nor|do|in|of)\s*$/.test(trimmed))
|
|
3543
3268
|
return true;
|
|
3544
3269
|
return false;
|
|
3545
3270
|
}
|
|
3546
|
-
function
|
|
3547
|
-
let text;
|
|
3548
|
-
try {
|
|
3549
|
-
text = stringify(value, { maxWidth: 60 });
|
|
3550
|
-
} catch {
|
|
3551
|
-
text = String(value);
|
|
3552
|
-
}
|
|
3553
|
-
return `${C.gray}→${C.reset} ${highlightLine(text)}`;
|
|
3554
|
-
}
|
|
3555
|
-
function formatVarState(vars) {
|
|
3271
|
+
function formatVarState(vars, format) {
|
|
3556
3272
|
const entries = Object.entries(vars);
|
|
3557
3273
|
if (entries.length === 0)
|
|
3558
3274
|
return "";
|
|
3275
|
+
if (format === "json") {
|
|
3276
|
+
const rendered = highlightJSON(formatJson(vars, 2));
|
|
3277
|
+
return `${C.dim} vars:${C.reset} ${rendered}`;
|
|
3278
|
+
}
|
|
3559
3279
|
const MAX_LINE = 70;
|
|
3560
3280
|
const MAX_VALUE = 30;
|
|
3561
3281
|
const parts = [];
|
|
@@ -3580,6 +3300,18 @@ function formatVarState(vars) {
|
|
|
3580
3300
|
}
|
|
3581
3301
|
return `${C.dim} ${parts.join(", ")}${C.reset}`;
|
|
3582
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
|
+
}
|
|
3583
3315
|
var KEYWORDS = [
|
|
3584
3316
|
"when",
|
|
3585
3317
|
"unless",
|
|
@@ -3591,6 +3323,7 @@ var KEYWORDS = [
|
|
|
3591
3323
|
"of",
|
|
3592
3324
|
"and",
|
|
3593
3325
|
"or",
|
|
3326
|
+
"nor",
|
|
3594
3327
|
"else",
|
|
3595
3328
|
"break",
|
|
3596
3329
|
"continue",
|
|
@@ -3608,8 +3341,35 @@ var KEYWORDS = [
|
|
|
3608
3341
|
"array",
|
|
3609
3342
|
"boolean"
|
|
3610
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
|
+
];
|
|
3611
3360
|
function completer(state) {
|
|
3612
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
|
+
}
|
|
3613
3373
|
const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
|
|
3614
3374
|
const partial = match ? match[0] : "";
|
|
3615
3375
|
if (!partial)
|
|
@@ -3620,45 +3380,92 @@ function completer(state) {
|
|
|
3620
3380
|
return [hits, partial];
|
|
3621
3381
|
};
|
|
3622
3382
|
}
|
|
3623
|
-
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) {
|
|
3624
3420
|
function toggleLabel(on) {
|
|
3625
3421
|
return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
|
|
3626
3422
|
}
|
|
3627
3423
|
switch (cmd) {
|
|
3628
3424
|
case ".help":
|
|
3629
3425
|
console.log([
|
|
3630
|
-
`${C.
|
|
3631
|
-
" .help
|
|
3632
|
-
" .
|
|
3633
|
-
" .
|
|
3634
|
-
" .
|
|
3635
|
-
" .
|
|
3636
|
-
" .
|
|
3637
|
-
" .
|
|
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",
|
|
3638
3441
|
"",
|
|
3639
3442
|
"Enter Rex expressions to evaluate them.",
|
|
3640
3443
|
"Multi-line: open brackets or do/end blocks continue on the next line.",
|
|
3641
3444
|
"Ctrl-C cancels multi-line input.",
|
|
3642
|
-
"Ctrl-D exits."
|
|
3445
|
+
"Ctrl-D exits.",
|
|
3446
|
+
"",
|
|
3447
|
+
"Outputs are printed as labeled blocks when enabled."
|
|
3643
3448
|
].join(`
|
|
3644
3449
|
`));
|
|
3645
|
-
return
|
|
3646
|
-
case ".
|
|
3647
|
-
state.
|
|
3648
|
-
console.log(`${C.dim}
|
|
3649
|
-
return
|
|
3650
|
-
case ".
|
|
3651
|
-
state.
|
|
3652
|
-
console.log(`${C.dim}
|
|
3653
|
-
return
|
|
3654
|
-
case ".
|
|
3655
|
-
state.
|
|
3656
|
-
console.log(`${C.dim}
|
|
3657
|
-
return
|
|
3658
|
-
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!": {
|
|
3659
3464
|
const entries = Object.entries(state.vars);
|
|
3660
3465
|
if (entries.length === 0) {
|
|
3661
3466
|
console.log(`${C.dim} (no variables)${C.reset}`);
|
|
3467
|
+
} else if (state.outputFormat === "json") {
|
|
3468
|
+
console.log(highlightJSON(formatJson(state.vars, 2)));
|
|
3662
3469
|
} else {
|
|
3663
3470
|
for (const [key, val] of entries) {
|
|
3664
3471
|
let valStr;
|
|
@@ -3670,43 +3477,121 @@ function handleDotCommand(cmd, state, rl) {
|
|
|
3670
3477
|
console.log(` ${key} = ${highlightLine(valStr)}`);
|
|
3671
3478
|
}
|
|
3672
3479
|
}
|
|
3673
|
-
return
|
|
3480
|
+
return "handled";
|
|
3674
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";
|
|
3675
3503
|
case ".clear":
|
|
3676
3504
|
state.vars = {};
|
|
3677
3505
|
state.refs = {};
|
|
3678
3506
|
console.log(`${C.dim} Variables cleared.${C.reset}`);
|
|
3679
|
-
return
|
|
3507
|
+
return "handled";
|
|
3680
3508
|
case ".exit":
|
|
3681
3509
|
rl.close();
|
|
3682
|
-
return
|
|
3510
|
+
return "handled-noprompt";
|
|
3683
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
|
+
}
|
|
3684
3545
|
if (cmd.startsWith(".")) {
|
|
3685
3546
|
console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
|
|
3686
|
-
return
|
|
3547
|
+
return "handled";
|
|
3687
3548
|
}
|
|
3688
|
-
return
|
|
3549
|
+
return "unhandled";
|
|
3689
3550
|
}
|
|
3690
3551
|
}
|
|
3691
3552
|
var GAS_LIMIT = 1e7;
|
|
3553
|
+
var HISTORY_LIMIT = 1000;
|
|
3554
|
+
var HISTORY_PATH = resolve(homedir(), ".rex_history");
|
|
3692
3555
|
async function startRepl() {
|
|
3693
|
-
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
|
+
};
|
|
3694
3567
|
let multiLineBuffer = "";
|
|
3695
3568
|
const PRIMARY_PROMPT = "rex> ";
|
|
3696
3569
|
const CONT_PROMPT = "... ";
|
|
3697
|
-
const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
|
|
3698
|
-
const STYLED_CONT = `${C.dim}...${C.reset} `;
|
|
3699
3570
|
let currentPrompt = PRIMARY_PROMPT;
|
|
3700
|
-
let styledPrompt =
|
|
3701
|
-
|
|
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`);
|
|
3702
3581
|
const rl = readline.createInterface({
|
|
3703
3582
|
input: process.stdin,
|
|
3704
3583
|
output: process.stdout,
|
|
3705
3584
|
prompt: PRIMARY_PROMPT,
|
|
3706
|
-
historySize:
|
|
3585
|
+
historySize: HISTORY_LIMIT,
|
|
3707
3586
|
completer: completer(state),
|
|
3708
3587
|
terminal: true
|
|
3709
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 {}
|
|
3710
3595
|
process.stdin.on("keypress", () => {
|
|
3711
3596
|
process.nextTick(() => {
|
|
3712
3597
|
if (!rl.line && rl.line !== "")
|
|
@@ -3728,7 +3613,7 @@ async function startRepl() {
|
|
|
3728
3613
|
if (multiLineBuffer) {
|
|
3729
3614
|
multiLineBuffer = "";
|
|
3730
3615
|
currentPrompt = PRIMARY_PROMPT;
|
|
3731
|
-
styledPrompt =
|
|
3616
|
+
styledPrompt = styledPrimary;
|
|
3732
3617
|
rl.setPrompt(PRIMARY_PROMPT);
|
|
3733
3618
|
process.stdout.write(`
|
|
3734
3619
|
`);
|
|
@@ -3740,17 +3625,70 @@ async function startRepl() {
|
|
|
3740
3625
|
});
|
|
3741
3626
|
function resetPrompt() {
|
|
3742
3627
|
currentPrompt = PRIMARY_PROMPT;
|
|
3743
|
-
styledPrompt =
|
|
3628
|
+
styledPrompt = styledPrimary;
|
|
3744
3629
|
rl.setPrompt(PRIMARY_PROMPT);
|
|
3745
3630
|
rl.prompt();
|
|
3746
3631
|
}
|
|
3747
|
-
|
|
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) => {
|
|
3748
3683
|
const trimmed = line.trim();
|
|
3749
3684
|
if (!multiLineBuffer && trimmed.startsWith(".")) {
|
|
3750
|
-
|
|
3685
|
+
const result = await handleDotCommand(trimmed, state, rl, runSource, updatePromptStyles);
|
|
3686
|
+
if (result === "handled") {
|
|
3751
3687
|
rl.prompt();
|
|
3752
3688
|
return;
|
|
3753
3689
|
}
|
|
3690
|
+
if (result === "handled-noprompt")
|
|
3691
|
+
return;
|
|
3754
3692
|
}
|
|
3755
3693
|
multiLineBuffer += (multiLineBuffer ? `
|
|
3756
3694
|
` : "") + line;
|
|
@@ -3761,60 +3699,35 @@ async function startRepl() {
|
|
|
3761
3699
|
}
|
|
3762
3700
|
if (isIncomplete(multiLineBuffer)) {
|
|
3763
3701
|
currentPrompt = CONT_PROMPT;
|
|
3764
|
-
styledPrompt =
|
|
3702
|
+
styledPrompt = styledCont;
|
|
3765
3703
|
rl.setPrompt(CONT_PROMPT);
|
|
3766
3704
|
rl.prompt();
|
|
3767
3705
|
return;
|
|
3768
3706
|
}
|
|
3769
3707
|
const source = multiLineBuffer;
|
|
3770
3708
|
multiLineBuffer = "";
|
|
3771
|
-
|
|
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
|
-
}
|
|
3709
|
+
runSource(source);
|
|
3806
3710
|
resetPrompt();
|
|
3807
3711
|
});
|
|
3808
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 {}
|
|
3809
3720
|
process.exit(0);
|
|
3810
3721
|
});
|
|
3811
3722
|
rl.prompt();
|
|
3812
3723
|
}
|
|
3813
3724
|
export {
|
|
3814
3725
|
startRepl,
|
|
3726
|
+
setColorEnabled,
|
|
3815
3727
|
isIncomplete,
|
|
3816
3728
|
highlightRexc,
|
|
3817
3729
|
highlightLine,
|
|
3818
3730
|
highlightJSON,
|
|
3731
|
+
highlightAuto,
|
|
3819
3732
|
formatVarState
|
|
3820
3733
|
};
|