@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-cli.js
CHANGED
|
@@ -115,11 +115,11 @@ function encodeDecimal(significand, power) {
|
|
|
115
115
|
function encodeNumberNode(node) {
|
|
116
116
|
const numberValue = node.value;
|
|
117
117
|
if (Number.isNaN(numberValue))
|
|
118
|
-
return "
|
|
118
|
+
return "nan'";
|
|
119
119
|
if (numberValue === Infinity)
|
|
120
|
-
return "
|
|
120
|
+
return "inf'";
|
|
121
121
|
if (numberValue === -Infinity)
|
|
122
|
-
return "
|
|
122
|
+
return "nif'";
|
|
123
123
|
if (Number.isInteger(numberValue)) {
|
|
124
124
|
const { base, exp } = splitDecimal(numberValue);
|
|
125
125
|
if (exp >= 0 && exp <= 4)
|
|
@@ -151,7 +151,7 @@ function encodeNumberNode(node) {
|
|
|
151
151
|
return encodeDecimal(significand, power);
|
|
152
152
|
}
|
|
153
153
|
function encodeOpcode(opcode) {
|
|
154
|
-
return `${
|
|
154
|
+
return `${OPCODE_IDS[opcode]}%`;
|
|
155
155
|
}
|
|
156
156
|
function encodeCallParts(parts) {
|
|
157
157
|
return `(${parts.join("")})`;
|
|
@@ -168,7 +168,7 @@ function addOptionalPrefix(encoded) {
|
|
|
168
168
|
let payload = encoded;
|
|
169
169
|
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
|
|
170
170
|
payload = encoded.slice(2, -1);
|
|
171
|
-
} else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
171
|
+
} else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
|
|
172
172
|
payload = encoded.slice(2, -1);
|
|
173
173
|
} else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
|
|
174
174
|
payload = encoded.slice(1, -1);
|
|
@@ -179,7 +179,7 @@ function addOptionalPrefix(encoded) {
|
|
|
179
179
|
}
|
|
180
180
|
function encodeBlockExpression(block) {
|
|
181
181
|
if (block.length === 0)
|
|
182
|
-
return "
|
|
182
|
+
return "un'";
|
|
183
183
|
if (block.length === 1)
|
|
184
184
|
return encodeNode(block[0]);
|
|
185
185
|
return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
|
|
@@ -196,9 +196,13 @@ function encodeConditionalElse(elseBranch) {
|
|
|
196
196
|
};
|
|
197
197
|
return encodeNode(nested);
|
|
198
198
|
}
|
|
199
|
+
function encodeDomainLookup(shortCode, tag) {
|
|
200
|
+
return `${shortCode}${tag}`;
|
|
201
|
+
}
|
|
199
202
|
function encodeNavigation(node) {
|
|
200
203
|
const domainRefs = activeEncodeOptions?.domainRefs;
|
|
201
|
-
|
|
204
|
+
const domainOpcodes = activeEncodeOptions?.domainOpcodes;
|
|
205
|
+
if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
|
|
202
206
|
const staticPath = [node.target.name];
|
|
203
207
|
for (const segment of node.segments) {
|
|
204
208
|
if (segment.type !== "static")
|
|
@@ -207,14 +211,17 @@ function encodeNavigation(node) {
|
|
|
207
211
|
}
|
|
208
212
|
for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
|
|
209
213
|
const dottedName = staticPath.slice(0, pathLength).join(".");
|
|
210
|
-
const
|
|
211
|
-
|
|
214
|
+
const refCode = domainRefs?.[dottedName];
|
|
215
|
+
const opcodeCode = domainOpcodes?.[dottedName];
|
|
216
|
+
const shortCode = refCode ?? opcodeCode;
|
|
217
|
+
if (shortCode === undefined)
|
|
212
218
|
continue;
|
|
219
|
+
const tag = refCode !== undefined ? "'" : "%";
|
|
213
220
|
const consumedStaticSegments = pathLength - 1;
|
|
214
221
|
if (consumedStaticSegments === node.segments.length) {
|
|
215
|
-
return
|
|
222
|
+
return encodeDomainLookup(shortCode, tag);
|
|
216
223
|
}
|
|
217
|
-
const parts2 = [
|
|
224
|
+
const parts2 = [encodeDomainLookup(shortCode, tag)];
|
|
218
225
|
for (const segment of node.segments.slice(consumedStaticSegments)) {
|
|
219
226
|
if (segment.type === "static")
|
|
220
227
|
parts2.push(encodeBareOrLengthString(segment.key));
|
|
@@ -240,9 +247,12 @@ function encodeWhile(node) {
|
|
|
240
247
|
}
|
|
241
248
|
function encodeFor(node) {
|
|
242
249
|
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
243
|
-
if (node.binding.type === "binding:
|
|
250
|
+
if (node.binding.type === "binding:bareIn") {
|
|
244
251
|
return `>(${encodeNode(node.binding.source)}${body})`;
|
|
245
252
|
}
|
|
253
|
+
if (node.binding.type === "binding:bareOf") {
|
|
254
|
+
return `<(${encodeNode(node.binding.source)}${body})`;
|
|
255
|
+
}
|
|
246
256
|
if (node.binding.type === "binding:valueIn") {
|
|
247
257
|
return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
|
|
248
258
|
}
|
|
@@ -253,30 +263,47 @@ function encodeFor(node) {
|
|
|
253
263
|
}
|
|
254
264
|
function encodeArrayComprehension(node) {
|
|
255
265
|
const body = addOptionalPrefix(encodeNode(node.body));
|
|
256
|
-
if (node.binding.type === "binding:
|
|
266
|
+
if (node.binding.type === "binding:bareIn") {
|
|
257
267
|
return `>[${encodeNode(node.binding.source)}${body}]`;
|
|
258
268
|
}
|
|
269
|
+
if (node.binding.type === "binding:bareOf") {
|
|
270
|
+
return `<[${encodeNode(node.binding.source)}${body}]`;
|
|
271
|
+
}
|
|
259
272
|
if (node.binding.type === "binding:valueIn") {
|
|
260
273
|
return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
|
|
261
274
|
}
|
|
262
275
|
if (node.binding.type === "binding:keyValueIn") {
|
|
263
276
|
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
|
|
264
277
|
}
|
|
265
|
-
return
|
|
278
|
+
return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
|
|
266
279
|
}
|
|
267
280
|
function encodeObjectComprehension(node) {
|
|
268
281
|
const key = addOptionalPrefix(encodeNode(node.key));
|
|
269
282
|
const value = addOptionalPrefix(encodeNode(node.value));
|
|
270
|
-
if (node.binding.type === "binding:
|
|
283
|
+
if (node.binding.type === "binding:bareIn") {
|
|
271
284
|
return `>{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
272
285
|
}
|
|
286
|
+
if (node.binding.type === "binding:bareOf") {
|
|
287
|
+
return `<{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
288
|
+
}
|
|
273
289
|
if (node.binding.type === "binding:valueIn") {
|
|
274
290
|
return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
|
|
275
291
|
}
|
|
276
292
|
if (node.binding.type === "binding:keyValueIn") {
|
|
277
293
|
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
|
|
278
294
|
}
|
|
279
|
-
return
|
|
295
|
+
return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
|
|
296
|
+
}
|
|
297
|
+
function encodeWhileArrayComprehension(node) {
|
|
298
|
+
const cond = encodeNode(node.condition);
|
|
299
|
+
const body = addOptionalPrefix(encodeNode(node.body));
|
|
300
|
+
return `#[${cond}${body}]`;
|
|
301
|
+
}
|
|
302
|
+
function encodeWhileObjectComprehension(node) {
|
|
303
|
+
const cond = encodeNode(node.condition);
|
|
304
|
+
const key = addOptionalPrefix(encodeNode(node.key));
|
|
305
|
+
const value = addOptionalPrefix(encodeNode(node.value));
|
|
306
|
+
return `#{${cond}${key}${value}}`;
|
|
280
307
|
}
|
|
281
308
|
function encodeNode(node) {
|
|
282
309
|
switch (node.type) {
|
|
@@ -285,7 +312,10 @@ function encodeNode(node) {
|
|
|
285
312
|
case "identifier": {
|
|
286
313
|
const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
|
|
287
314
|
if (domainRef !== undefined)
|
|
288
|
-
return `${
|
|
315
|
+
return `${domainRef}'`;
|
|
316
|
+
const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
|
|
317
|
+
if (domainOpcode !== undefined)
|
|
318
|
+
return `${domainOpcode}%`;
|
|
289
319
|
return `${node.name}$`;
|
|
290
320
|
}
|
|
291
321
|
case "self":
|
|
@@ -298,11 +328,11 @@ function encodeNode(node) {
|
|
|
298
328
|
return `${encodeUint(node.depth - 1)}@`;
|
|
299
329
|
}
|
|
300
330
|
case "boolean":
|
|
301
|
-
return node.value ? "
|
|
331
|
+
return node.value ? "tr'" : "fl'";
|
|
302
332
|
case "null":
|
|
303
|
-
return "
|
|
333
|
+
return "nl'";
|
|
304
334
|
case "undefined":
|
|
305
|
-
return "
|
|
335
|
+
return "un'";
|
|
306
336
|
case "number":
|
|
307
337
|
return encodeNumberNode(node);
|
|
308
338
|
case "string":
|
|
@@ -313,12 +343,16 @@ function encodeNode(node) {
|
|
|
313
343
|
}
|
|
314
344
|
case "arrayComprehension":
|
|
315
345
|
return encodeArrayComprehension(node);
|
|
346
|
+
case "whileArrayComprehension":
|
|
347
|
+
return encodeWhileArrayComprehension(node);
|
|
316
348
|
case "object": {
|
|
317
349
|
const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
|
|
318
350
|
return `{${body}}`;
|
|
319
351
|
}
|
|
320
352
|
case "objectComprehension":
|
|
321
353
|
return encodeObjectComprehension(node);
|
|
354
|
+
case "whileObjectComprehension":
|
|
355
|
+
return encodeWhileObjectComprehension(node);
|
|
322
356
|
case "key":
|
|
323
357
|
return encodeBareOrLengthString(node.name);
|
|
324
358
|
case "group":
|
|
@@ -328,6 +362,10 @@ function encodeNode(node) {
|
|
|
328
362
|
return `~${encodeNode(node.value)}`;
|
|
329
363
|
if (node.op === "neg")
|
|
330
364
|
return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
|
|
365
|
+
if (node.op === "logicalNot") {
|
|
366
|
+
const val = encodeNode(node.value);
|
|
367
|
+
return `!(${val}tr')`;
|
|
368
|
+
}
|
|
331
369
|
return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
|
|
332
370
|
case "binary":
|
|
333
371
|
if (node.op === "and") {
|
|
@@ -346,12 +384,19 @@ function encodeNode(node) {
|
|
|
346
384
|
}).join("");
|
|
347
385
|
return `|(${body})`;
|
|
348
386
|
}
|
|
387
|
+
if (node.op === "nor") {
|
|
388
|
+
const left = encodeNode(node.left);
|
|
389
|
+
const right = addOptionalPrefix(encodeNode(node.right));
|
|
390
|
+
return `!(${left}${right})`;
|
|
391
|
+
}
|
|
349
392
|
return encodeCallParts([
|
|
350
393
|
encodeOpcode(BINARY_TO_OPCODE[node.op]),
|
|
351
394
|
encodeNode(node.left),
|
|
352
395
|
encodeNode(node.right)
|
|
353
396
|
]);
|
|
354
397
|
case "assign": {
|
|
398
|
+
if (node.op === ":=")
|
|
399
|
+
return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
355
400
|
if (node.op === "=")
|
|
356
401
|
return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
357
402
|
const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
|
|
@@ -362,8 +407,12 @@ function encodeNode(node) {
|
|
|
362
407
|
}
|
|
363
408
|
case "navigation":
|
|
364
409
|
return encodeNavigation(node);
|
|
365
|
-
case "call":
|
|
410
|
+
case "call": {
|
|
411
|
+
if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
|
|
412
|
+
return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
|
|
413
|
+
}
|
|
366
414
|
return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
|
|
415
|
+
}
|
|
367
416
|
case "conditional": {
|
|
368
417
|
const opener = node.head === "when" ? "?(" : "!(";
|
|
369
418
|
const cond = encodeNode(node.condition);
|
|
@@ -371,6 +420,8 @@ function encodeNode(node) {
|
|
|
371
420
|
const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
|
|
372
421
|
return `${opener}${cond}${thenExpr}${elseExpr})`;
|
|
373
422
|
}
|
|
423
|
+
case "range":
|
|
424
|
+
return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
|
|
374
425
|
case "for":
|
|
375
426
|
return encodeFor(node);
|
|
376
427
|
case "while":
|
|
@@ -390,11 +441,30 @@ function collectLogicalChain(node, op) {
|
|
|
390
441
|
return [node];
|
|
391
442
|
return [...collectLogicalChain(node.left, op), ...collectLogicalChain(node.right, op)];
|
|
392
443
|
}
|
|
444
|
+
function formatParseError(source, match) {
|
|
445
|
+
const message = match.message ?? "Parse failed";
|
|
446
|
+
const pos = match.getRightmostFailurePosition?.();
|
|
447
|
+
if (typeof pos !== "number" || !Number.isFinite(pos))
|
|
448
|
+
return message;
|
|
449
|
+
const safePos = Math.max(0, Math.min(source.length, pos));
|
|
450
|
+
const lineStart = source.lastIndexOf(`
|
|
451
|
+
`, safePos - 1) + 1;
|
|
452
|
+
const lineEndIndex = source.indexOf(`
|
|
453
|
+
`, safePos);
|
|
454
|
+
const lineEnd = lineEndIndex === -1 ? source.length : lineEndIndex;
|
|
455
|
+
const lineText = source.slice(lineStart, lineEnd);
|
|
456
|
+
const lineNumber = source.slice(0, lineStart).split(`
|
|
457
|
+
`).length;
|
|
458
|
+
const columnNumber = safePos - lineStart + 1;
|
|
459
|
+
const caret = `${" ".repeat(Math.max(0, columnNumber - 1))}^`;
|
|
460
|
+
return `${message}
|
|
461
|
+
${lineText}
|
|
462
|
+
${caret}`;
|
|
463
|
+
}
|
|
393
464
|
function parseToIR(source) {
|
|
394
465
|
const match = grammar.match(source);
|
|
395
466
|
if (!match.succeeded()) {
|
|
396
|
-
|
|
397
|
-
throw new Error(failure.message ?? "Parse failed");
|
|
467
|
+
throw new Error(formatParseError(source, match));
|
|
398
468
|
}
|
|
399
469
|
return semantics(match).toIR();
|
|
400
470
|
}
|
|
@@ -546,78 +616,55 @@ function domainRefsFromConfig(config) {
|
|
|
546
616
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
547
617
|
throw new Error("Domain config must be an object");
|
|
548
618
|
}
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
return refs;
|
|
556
|
-
}
|
|
557
|
-
function decodeDomainRefKey(refText) {
|
|
558
|
-
if (!refText)
|
|
559
|
-
throw new Error("Domain ref key cannot be empty");
|
|
560
|
-
if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
|
|
561
|
-
throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
|
|
619
|
+
const configObj = config;
|
|
620
|
+
const domainRefs = {};
|
|
621
|
+
const domainOpcodes = {};
|
|
622
|
+
const dataSection = configObj.data;
|
|
623
|
+
if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
|
|
624
|
+
mapConfigEntries(dataSection, domainRefs);
|
|
562
625
|
}
|
|
563
|
-
|
|
564
|
-
|
|
626
|
+
const functionsSection = configObj.functions;
|
|
627
|
+
if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
|
|
628
|
+
mapConfigEntries(functionsSection, domainOpcodes);
|
|
565
629
|
}
|
|
566
|
-
|
|
567
|
-
throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
|
|
568
|
-
}
|
|
569
|
-
let value = 0;
|
|
570
|
-
for (const char of refText) {
|
|
571
|
-
const digit = DOMAIN_DIGIT_INDEX.get(char);
|
|
572
|
-
if (digit === undefined)
|
|
573
|
-
throw new Error(`Invalid domain ref key '${refText}'`);
|
|
574
|
-
value = value * 64 + digit;
|
|
575
|
-
if (value > Number.MAX_SAFE_INTEGER) {
|
|
576
|
-
throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (value < FIRST_NON_RESERVED_REF) {
|
|
580
|
-
throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
|
|
581
|
-
}
|
|
582
|
-
return value;
|
|
630
|
+
return { domainRefs, domainOpcodes };
|
|
583
631
|
}
|
|
584
632
|
function mapConfigEntries(entries, refs) {
|
|
585
633
|
const sourceKindByRoot = new Map;
|
|
586
634
|
for (const root of Object.keys(refs)) {
|
|
587
635
|
sourceKindByRoot.set(root, "explicit");
|
|
588
636
|
}
|
|
589
|
-
for (const [
|
|
637
|
+
for (const [shortCode, rawEntry] of Object.entries(entries)) {
|
|
590
638
|
const entry = rawEntry;
|
|
591
639
|
if (!entry || typeof entry !== "object")
|
|
592
640
|
continue;
|
|
593
641
|
if (!Array.isArray(entry.names))
|
|
594
642
|
continue;
|
|
595
|
-
const refId = decodeDomainRefKey(refText);
|
|
596
643
|
for (const rawName of entry.names) {
|
|
597
644
|
if (typeof rawName !== "string")
|
|
598
645
|
continue;
|
|
599
|
-
const
|
|
600
|
-
if (
|
|
601
|
-
throw new Error(`Conflicting refs for '${rawName}': ${
|
|
646
|
+
const existingRef = refs[rawName];
|
|
647
|
+
if (existingRef !== undefined && existingRef !== shortCode) {
|
|
648
|
+
throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
|
|
602
649
|
}
|
|
603
|
-
refs[rawName] =
|
|
650
|
+
refs[rawName] = shortCode;
|
|
604
651
|
const root = rawName.split(".")[0];
|
|
605
652
|
if (!root)
|
|
606
653
|
continue;
|
|
607
654
|
const currentKind = rawName.includes(".") ? "implicit" : "explicit";
|
|
608
655
|
const existing = refs[root];
|
|
609
656
|
if (existing !== undefined) {
|
|
610
|
-
if (existing ===
|
|
657
|
+
if (existing === shortCode)
|
|
611
658
|
continue;
|
|
612
659
|
const existingKind = sourceKindByRoot.get(root) ?? "explicit";
|
|
613
660
|
if (currentKind === "explicit") {
|
|
614
|
-
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${
|
|
661
|
+
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
|
|
615
662
|
}
|
|
616
663
|
if (existingKind === "explicit")
|
|
617
664
|
continue;
|
|
618
665
|
continue;
|
|
619
666
|
}
|
|
620
|
-
refs[root] =
|
|
667
|
+
refs[root] = shortCode;
|
|
621
668
|
sourceKindByRoot.set(root, currentKind);
|
|
622
669
|
}
|
|
623
670
|
}
|
|
@@ -761,20 +808,11 @@ function gatherEncodedValueSpans(text) {
|
|
|
761
808
|
}
|
|
762
809
|
return spans;
|
|
763
810
|
}
|
|
764
|
-
function buildPointerToken(pointerStart, targetStart) {
|
|
765
|
-
|
|
811
|
+
function buildPointerToken(pointerStart, targetStart, occurrenceSize) {
|
|
812
|
+
const offset = targetStart - pointerStart - occurrenceSize;
|
|
766
813
|
if (offset < 0)
|
|
767
814
|
return;
|
|
768
|
-
|
|
769
|
-
const prefix = encodeUint(offset);
|
|
770
|
-
const recalculated = targetStart - (pointerStart + prefix.length + 1);
|
|
771
|
-
if (recalculated === offset)
|
|
772
|
-
return `${prefix}^`;
|
|
773
|
-
offset = recalculated;
|
|
774
|
-
if (offset < 0)
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
return;
|
|
815
|
+
return `${encodeUint(offset)}^`;
|
|
778
816
|
}
|
|
779
817
|
function buildDedupeCandidateTable(encoded, minBytes) {
|
|
780
818
|
const spans = gatherEncodedValueSpans(encoded);
|
|
@@ -810,854 +848,43 @@ function dedupeLargeEncodedValues(encoded, minBytes = 4) {
|
|
|
810
848
|
continue;
|
|
811
849
|
const canonical = occurrences[occurrences.length - 1];
|
|
812
850
|
for (let index = occurrences.length - 2;index >= 0; index -= 1) {
|
|
813
|
-
const occurrence = occurrences[index];
|
|
814
|
-
if (occurrence.span.end > canonical.span.start)
|
|
815
|
-
continue;
|
|
816
|
-
if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
|
|
817
|
-
continue;
|
|
818
|
-
const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
|
|
819
|
-
const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart);
|
|
820
|
-
if (!pointerToken)
|
|
821
|
-
continue;
|
|
822
|
-
if (pointerToken.length >= occurrence.sizeBytes)
|
|
823
|
-
continue;
|
|
824
|
-
current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
|
|
825
|
-
replaced = true;
|
|
826
|
-
break;
|
|
827
|
-
}
|
|
828
|
-
if (replaced)
|
|
829
|
-
break;
|
|
830
|
-
}
|
|
831
|
-
if (!replaced)
|
|
832
|
-
return current;
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
function encodeIR(node, options) {
|
|
836
|
-
const previous = activeEncodeOptions;
|
|
837
|
-
activeEncodeOptions = options;
|
|
838
|
-
try {
|
|
839
|
-
const encoded = encodeNode(node);
|
|
840
|
-
if (options?.dedupeValues) {
|
|
841
|
-
return dedupeLargeEncodedValues(encoded, options.dedupeMinBytes ?? 4);
|
|
842
|
-
}
|
|
843
|
-
return encoded;
|
|
844
|
-
} finally {
|
|
845
|
-
activeEncodeOptions = previous;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function cloneNode(node) {
|
|
849
|
-
return structuredClone(node);
|
|
850
|
-
}
|
|
851
|
-
function emptyOptimizeEnv() {
|
|
852
|
-
return { constants: {}, selfCaptures: {} };
|
|
853
|
-
}
|
|
854
|
-
function cloneOptimizeEnv(env) {
|
|
855
|
-
return {
|
|
856
|
-
constants: { ...env.constants },
|
|
857
|
-
selfCaptures: { ...env.selfCaptures }
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
function clearOptimizeEnv(env) {
|
|
861
|
-
for (const key of Object.keys(env.constants))
|
|
862
|
-
delete env.constants[key];
|
|
863
|
-
for (const key of Object.keys(env.selfCaptures))
|
|
864
|
-
delete env.selfCaptures[key];
|
|
865
|
-
}
|
|
866
|
-
function clearBinding(env, name) {
|
|
867
|
-
delete env.constants[name];
|
|
868
|
-
delete env.selfCaptures[name];
|
|
869
|
-
}
|
|
870
|
-
function selfTargetFromNode(node, currentDepth) {
|
|
871
|
-
if (node.type === "self")
|
|
872
|
-
return currentDepth;
|
|
873
|
-
if (node.type === "selfDepth") {
|
|
874
|
-
const target = currentDepth - (node.depth - 1);
|
|
875
|
-
if (target >= 1)
|
|
876
|
-
return target;
|
|
877
|
-
}
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
function selfNodeFromTarget(targetDepth, currentDepth) {
|
|
881
|
-
const relDepth = currentDepth - targetDepth + 1;
|
|
882
|
-
if (!Number.isInteger(relDepth) || relDepth < 1)
|
|
883
|
-
return;
|
|
884
|
-
if (relDepth === 1)
|
|
885
|
-
return { type: "self" };
|
|
886
|
-
return { type: "selfDepth", depth: relDepth };
|
|
887
|
-
}
|
|
888
|
-
function dropBindingNames(env, binding) {
|
|
889
|
-
if (binding.type === "binding:valueIn") {
|
|
890
|
-
clearBinding(env, binding.value);
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
if (binding.type === "binding:keyValueIn") {
|
|
894
|
-
clearBinding(env, binding.key);
|
|
895
|
-
clearBinding(env, binding.value);
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
if (binding.type === "binding:keyOf") {
|
|
899
|
-
clearBinding(env, binding.key);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
function collectReads(node, out) {
|
|
903
|
-
switch (node.type) {
|
|
904
|
-
case "identifier":
|
|
905
|
-
out.add(node.name);
|
|
906
|
-
return;
|
|
907
|
-
case "group":
|
|
908
|
-
collectReads(node.expression, out);
|
|
909
|
-
return;
|
|
910
|
-
case "array":
|
|
911
|
-
for (const item of node.items)
|
|
912
|
-
collectReads(item, out);
|
|
913
|
-
return;
|
|
914
|
-
case "object":
|
|
915
|
-
for (const entry of node.entries) {
|
|
916
|
-
collectReads(entry.key, out);
|
|
917
|
-
collectReads(entry.value, out);
|
|
918
|
-
}
|
|
919
|
-
return;
|
|
920
|
-
case "arrayComprehension":
|
|
921
|
-
collectReads(node.binding.source, out);
|
|
922
|
-
collectReads(node.body, out);
|
|
923
|
-
return;
|
|
924
|
-
case "objectComprehension":
|
|
925
|
-
collectReads(node.binding.source, out);
|
|
926
|
-
collectReads(node.key, out);
|
|
927
|
-
collectReads(node.value, out);
|
|
928
|
-
return;
|
|
929
|
-
case "unary":
|
|
930
|
-
collectReads(node.value, out);
|
|
931
|
-
return;
|
|
932
|
-
case "binary":
|
|
933
|
-
collectReads(node.left, out);
|
|
934
|
-
collectReads(node.right, out);
|
|
935
|
-
return;
|
|
936
|
-
case "assign":
|
|
937
|
-
if (!(node.op === "=" && node.place.type === "identifier"))
|
|
938
|
-
collectReads(node.place, out);
|
|
939
|
-
collectReads(node.value, out);
|
|
940
|
-
return;
|
|
941
|
-
case "navigation":
|
|
942
|
-
collectReads(node.target, out);
|
|
943
|
-
for (const segment of node.segments) {
|
|
944
|
-
if (segment.type === "dynamic")
|
|
945
|
-
collectReads(segment.key, out);
|
|
946
|
-
}
|
|
947
|
-
return;
|
|
948
|
-
case "call":
|
|
949
|
-
collectReads(node.callee, out);
|
|
950
|
-
for (const arg of node.args)
|
|
951
|
-
collectReads(arg, out);
|
|
952
|
-
return;
|
|
953
|
-
case "conditional":
|
|
954
|
-
collectReads(node.condition, out);
|
|
955
|
-
for (const part of node.thenBlock)
|
|
956
|
-
collectReads(part, out);
|
|
957
|
-
if (node.elseBranch)
|
|
958
|
-
collectReadsElse(node.elseBranch, out);
|
|
959
|
-
return;
|
|
960
|
-
case "for":
|
|
961
|
-
collectReads(node.binding.source, out);
|
|
962
|
-
for (const part of node.body)
|
|
963
|
-
collectReads(part, out);
|
|
964
|
-
return;
|
|
965
|
-
case "program":
|
|
966
|
-
for (const part of node.body)
|
|
967
|
-
collectReads(part, out);
|
|
968
|
-
return;
|
|
969
|
-
default:
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
function collectReadsElse(elseBranch, out) {
|
|
974
|
-
if (elseBranch.type === "else") {
|
|
975
|
-
for (const part of elseBranch.block)
|
|
976
|
-
collectReads(part, out);
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
collectReads(elseBranch.condition, out);
|
|
980
|
-
for (const part of elseBranch.thenBlock)
|
|
981
|
-
collectReads(part, out);
|
|
982
|
-
if (elseBranch.elseBranch)
|
|
983
|
-
collectReadsElse(elseBranch.elseBranch, out);
|
|
984
|
-
}
|
|
985
|
-
function isPureNode(node) {
|
|
986
|
-
switch (node.type) {
|
|
987
|
-
case "identifier":
|
|
988
|
-
case "self":
|
|
989
|
-
case "selfDepth":
|
|
990
|
-
case "boolean":
|
|
991
|
-
case "null":
|
|
992
|
-
case "undefined":
|
|
993
|
-
case "number":
|
|
994
|
-
case "string":
|
|
995
|
-
case "key":
|
|
996
|
-
return true;
|
|
997
|
-
case "group":
|
|
998
|
-
return isPureNode(node.expression);
|
|
999
|
-
case "array":
|
|
1000
|
-
return node.items.every((item) => isPureNode(item));
|
|
1001
|
-
case "object":
|
|
1002
|
-
return node.entries.every((entry) => isPureNode(entry.key) && isPureNode(entry.value));
|
|
1003
|
-
case "navigation":
|
|
1004
|
-
return isPureNode(node.target) && node.segments.every((segment) => segment.type === "static" || isPureNode(segment.key));
|
|
1005
|
-
case "unary":
|
|
1006
|
-
return node.op !== "delete" && isPureNode(node.value);
|
|
1007
|
-
case "binary":
|
|
1008
|
-
return isPureNode(node.left) && isPureNode(node.right);
|
|
1009
|
-
default:
|
|
1010
|
-
return false;
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
function eliminateDeadAssignments(block) {
|
|
1014
|
-
const needed = new Set;
|
|
1015
|
-
const out = [];
|
|
1016
|
-
for (let index = block.length - 1;index >= 0; index -= 1) {
|
|
1017
|
-
const node = block[index];
|
|
1018
|
-
if (node.type === "conditional") {
|
|
1019
|
-
let rewritten = node;
|
|
1020
|
-
if (node.condition.type === "assign" && node.condition.op === "=" && node.condition.place.type === "identifier") {
|
|
1021
|
-
const name = node.condition.place.name;
|
|
1022
|
-
const branchReads = new Set;
|
|
1023
|
-
for (const part of node.thenBlock)
|
|
1024
|
-
collectReads(part, branchReads);
|
|
1025
|
-
if (node.elseBranch)
|
|
1026
|
-
collectReadsElse(node.elseBranch, branchReads);
|
|
1027
|
-
if (!needed.has(name) && !branchReads.has(name)) {
|
|
1028
|
-
rewritten = {
|
|
1029
|
-
type: "conditional",
|
|
1030
|
-
head: node.head,
|
|
1031
|
-
condition: node.condition.value,
|
|
1032
|
-
thenBlock: node.thenBlock,
|
|
1033
|
-
elseBranch: node.elseBranch
|
|
1034
|
-
};
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
collectReads(rewritten, needed);
|
|
1038
|
-
out.push(rewritten);
|
|
1039
|
-
continue;
|
|
1040
|
-
}
|
|
1041
|
-
if (node.type === "assign" && node.op === "=" && node.place.type === "identifier") {
|
|
1042
|
-
collectReads(node.value, needed);
|
|
1043
|
-
const name = node.place.name;
|
|
1044
|
-
const canDrop = !needed.has(name) && isPureNode(node.value);
|
|
1045
|
-
needed.delete(name);
|
|
1046
|
-
if (canDrop)
|
|
1047
|
-
continue;
|
|
1048
|
-
out.push(node);
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
collectReads(node, needed);
|
|
1052
|
-
out.push(node);
|
|
1053
|
-
}
|
|
1054
|
-
out.reverse();
|
|
1055
|
-
return out;
|
|
1056
|
-
}
|
|
1057
|
-
function hasIdentifierRead(node, name, asPlace = false) {
|
|
1058
|
-
if (node.type === "identifier")
|
|
1059
|
-
return !asPlace && node.name === name;
|
|
1060
|
-
switch (node.type) {
|
|
1061
|
-
case "group":
|
|
1062
|
-
return hasIdentifierRead(node.expression, name);
|
|
1063
|
-
case "array":
|
|
1064
|
-
return node.items.some((item) => hasIdentifierRead(item, name));
|
|
1065
|
-
case "object":
|
|
1066
|
-
return node.entries.some((entry) => hasIdentifierRead(entry.key, name) || hasIdentifierRead(entry.value, name));
|
|
1067
|
-
case "navigation":
|
|
1068
|
-
return hasIdentifierRead(node.target, name) || node.segments.some((segment) => segment.type === "dynamic" && hasIdentifierRead(segment.key, name));
|
|
1069
|
-
case "unary":
|
|
1070
|
-
return hasIdentifierRead(node.value, name, node.op === "delete");
|
|
1071
|
-
case "binary":
|
|
1072
|
-
return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
|
|
1073
|
-
case "assign":
|
|
1074
|
-
return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
|
|
1075
|
-
default:
|
|
1076
|
-
return false;
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
function countIdentifierReads(node, name, asPlace = false) {
|
|
1080
|
-
if (node.type === "identifier")
|
|
1081
|
-
return !asPlace && node.name === name ? 1 : 0;
|
|
1082
|
-
switch (node.type) {
|
|
1083
|
-
case "group":
|
|
1084
|
-
return countIdentifierReads(node.expression, name);
|
|
1085
|
-
case "array":
|
|
1086
|
-
return node.items.reduce((sum, item) => sum + countIdentifierReads(item, name), 0);
|
|
1087
|
-
case "object":
|
|
1088
|
-
return node.entries.reduce((sum, entry) => sum + countIdentifierReads(entry.key, name) + countIdentifierReads(entry.value, name), 0);
|
|
1089
|
-
case "navigation":
|
|
1090
|
-
return countIdentifierReads(node.target, name) + node.segments.reduce((sum, segment) => sum + (segment.type === "dynamic" ? countIdentifierReads(segment.key, name) : 0), 0);
|
|
1091
|
-
case "unary":
|
|
1092
|
-
return countIdentifierReads(node.value, name, node.op === "delete");
|
|
1093
|
-
case "binary":
|
|
1094
|
-
return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
|
|
1095
|
-
case "assign":
|
|
1096
|
-
return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
|
|
1097
|
-
default:
|
|
1098
|
-
return 0;
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
function replaceIdentifier(node, name, replacement, asPlace = false) {
|
|
1102
|
-
if (node.type === "identifier") {
|
|
1103
|
-
if (!asPlace && node.name === name)
|
|
1104
|
-
return cloneNode(replacement);
|
|
1105
|
-
return node;
|
|
1106
|
-
}
|
|
1107
|
-
switch (node.type) {
|
|
1108
|
-
case "group":
|
|
1109
|
-
return {
|
|
1110
|
-
type: "group",
|
|
1111
|
-
expression: replaceIdentifier(node.expression, name, replacement)
|
|
1112
|
-
};
|
|
1113
|
-
case "array":
|
|
1114
|
-
return { type: "array", items: node.items.map((item) => replaceIdentifier(item, name, replacement)) };
|
|
1115
|
-
case "object":
|
|
1116
|
-
return {
|
|
1117
|
-
type: "object",
|
|
1118
|
-
entries: node.entries.map((entry) => ({
|
|
1119
|
-
key: replaceIdentifier(entry.key, name, replacement),
|
|
1120
|
-
value: replaceIdentifier(entry.value, name, replacement)
|
|
1121
|
-
}))
|
|
1122
|
-
};
|
|
1123
|
-
case "navigation":
|
|
1124
|
-
return {
|
|
1125
|
-
type: "navigation",
|
|
1126
|
-
target: replaceIdentifier(node.target, name, replacement),
|
|
1127
|
-
segments: node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: replaceIdentifier(segment.key, name, replacement) })
|
|
1128
|
-
};
|
|
1129
|
-
case "unary":
|
|
1130
|
-
return {
|
|
1131
|
-
type: "unary",
|
|
1132
|
-
op: node.op,
|
|
1133
|
-
value: replaceIdentifier(node.value, name, replacement, node.op === "delete")
|
|
1134
|
-
};
|
|
1135
|
-
case "binary":
|
|
1136
|
-
return {
|
|
1137
|
-
type: "binary",
|
|
1138
|
-
op: node.op,
|
|
1139
|
-
left: replaceIdentifier(node.left, name, replacement),
|
|
1140
|
-
right: replaceIdentifier(node.right, name, replacement)
|
|
1141
|
-
};
|
|
1142
|
-
case "assign":
|
|
1143
|
-
return {
|
|
1144
|
-
type: "assign",
|
|
1145
|
-
op: node.op,
|
|
1146
|
-
place: replaceIdentifier(node.place, name, replacement, true),
|
|
1147
|
-
value: replaceIdentifier(node.value, name, replacement)
|
|
1148
|
-
};
|
|
1149
|
-
default:
|
|
1150
|
-
return node;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
function isSafeInlineTargetNode(node) {
|
|
1154
|
-
if (isPureNode(node))
|
|
1155
|
-
return true;
|
|
1156
|
-
if (node.type === "assign" && node.op === "=") {
|
|
1157
|
-
return isPureNode(node.place) && isPureNode(node.value);
|
|
1158
|
-
}
|
|
1159
|
-
return false;
|
|
1160
|
-
}
|
|
1161
|
-
function inlineAdjacentPureAssignments(block) {
|
|
1162
|
-
const out = [...block];
|
|
1163
|
-
let changed = true;
|
|
1164
|
-
while (changed) {
|
|
1165
|
-
changed = false;
|
|
1166
|
-
for (let index = 0;index < out.length - 1; index += 1) {
|
|
1167
|
-
const current = out[index];
|
|
1168
|
-
if (current.type !== "assign" || current.op !== "=" || current.place.type !== "identifier")
|
|
1169
|
-
continue;
|
|
1170
|
-
if (!isPureNode(current.value))
|
|
1171
|
-
continue;
|
|
1172
|
-
const name = current.place.name;
|
|
1173
|
-
if (hasIdentifierRead(current.value, name))
|
|
1174
|
-
continue;
|
|
1175
|
-
const next = out[index + 1];
|
|
1176
|
-
if (!isSafeInlineTargetNode(next))
|
|
1177
|
-
continue;
|
|
1178
|
-
if (countIdentifierReads(next, name) !== 1)
|
|
1179
|
-
continue;
|
|
1180
|
-
out[index + 1] = replaceIdentifier(next, name, current.value);
|
|
1181
|
-
out.splice(index, 1);
|
|
1182
|
-
changed = true;
|
|
1183
|
-
break;
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
return out;
|
|
1187
|
-
}
|
|
1188
|
-
function toNumberNode(value) {
|
|
1189
|
-
let raw;
|
|
1190
|
-
if (Number.isNaN(value))
|
|
1191
|
-
raw = "nan";
|
|
1192
|
-
else if (value === Infinity)
|
|
1193
|
-
raw = "inf";
|
|
1194
|
-
else if (value === -Infinity)
|
|
1195
|
-
raw = "-inf";
|
|
1196
|
-
else
|
|
1197
|
-
raw = String(value);
|
|
1198
|
-
return { type: "number", raw, value };
|
|
1199
|
-
}
|
|
1200
|
-
function toStringNode(value) {
|
|
1201
|
-
return { type: "string", raw: JSON.stringify(value) };
|
|
1202
|
-
}
|
|
1203
|
-
function toLiteralNode(value) {
|
|
1204
|
-
if (value === undefined)
|
|
1205
|
-
return { type: "undefined" };
|
|
1206
|
-
if (value === null)
|
|
1207
|
-
return { type: "null" };
|
|
1208
|
-
if (typeof value === "boolean")
|
|
1209
|
-
return { type: "boolean", value };
|
|
1210
|
-
if (typeof value === "number")
|
|
1211
|
-
return toNumberNode(value);
|
|
1212
|
-
if (typeof value === "string")
|
|
1213
|
-
return toStringNode(value);
|
|
1214
|
-
if (Array.isArray(value)) {
|
|
1215
|
-
const items = [];
|
|
1216
|
-
for (const item of value) {
|
|
1217
|
-
const lowered = toLiteralNode(item);
|
|
1218
|
-
if (!lowered)
|
|
1219
|
-
return;
|
|
1220
|
-
items.push(lowered);
|
|
1221
|
-
}
|
|
1222
|
-
return { type: "array", items };
|
|
1223
|
-
}
|
|
1224
|
-
if (value && typeof value === "object") {
|
|
1225
|
-
const entries = [];
|
|
1226
|
-
for (const [key, entryValue] of Object.entries(value)) {
|
|
1227
|
-
const loweredValue = toLiteralNode(entryValue);
|
|
1228
|
-
if (!loweredValue)
|
|
1229
|
-
return;
|
|
1230
|
-
entries.push({ key: { type: "key", name: key }, value: loweredValue });
|
|
1231
|
-
}
|
|
1232
|
-
return { type: "object", entries };
|
|
1233
|
-
}
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
function constValue(node) {
|
|
1237
|
-
switch (node.type) {
|
|
1238
|
-
case "undefined":
|
|
1239
|
-
return;
|
|
1240
|
-
case "null":
|
|
1241
|
-
return null;
|
|
1242
|
-
case "boolean":
|
|
1243
|
-
return node.value;
|
|
1244
|
-
case "number":
|
|
1245
|
-
return node.value;
|
|
1246
|
-
case "string":
|
|
1247
|
-
return decodeStringLiteral(node.raw);
|
|
1248
|
-
case "key":
|
|
1249
|
-
return node.name;
|
|
1250
|
-
case "array": {
|
|
1251
|
-
const out = [];
|
|
1252
|
-
for (const item of node.items) {
|
|
1253
|
-
const value = constValue(item);
|
|
1254
|
-
if (value === undefined && item.type !== "undefined")
|
|
1255
|
-
return;
|
|
1256
|
-
out.push(value);
|
|
1257
|
-
}
|
|
1258
|
-
return out;
|
|
1259
|
-
}
|
|
1260
|
-
case "object": {
|
|
1261
|
-
const out = {};
|
|
1262
|
-
for (const entry of node.entries) {
|
|
1263
|
-
const key = constValue(entry.key);
|
|
1264
|
-
if (key === undefined && entry.key.type !== "undefined")
|
|
1265
|
-
return;
|
|
1266
|
-
const value = constValue(entry.value);
|
|
1267
|
-
if (value === undefined && entry.value.type !== "undefined")
|
|
1268
|
-
return;
|
|
1269
|
-
out[String(key)] = value;
|
|
1270
|
-
}
|
|
1271
|
-
return out;
|
|
1272
|
-
}
|
|
1273
|
-
default:
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
function isDefinedValue(value) {
|
|
1278
|
-
return value !== undefined;
|
|
1279
|
-
}
|
|
1280
|
-
function foldUnary(op, value) {
|
|
1281
|
-
if (op === "neg") {
|
|
1282
|
-
if (typeof value !== "number")
|
|
1283
|
-
return;
|
|
1284
|
-
return -value;
|
|
1285
|
-
}
|
|
1286
|
-
if (op === "not") {
|
|
1287
|
-
if (typeof value === "boolean")
|
|
1288
|
-
return !value;
|
|
1289
|
-
if (typeof value === "number")
|
|
1290
|
-
return ~value;
|
|
1291
|
-
return;
|
|
1292
|
-
}
|
|
1293
|
-
return;
|
|
1294
|
-
}
|
|
1295
|
-
function foldBinary(op, left, right) {
|
|
1296
|
-
if (op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod") {
|
|
1297
|
-
if (typeof left !== "number" || typeof right !== "number")
|
|
1298
|
-
return;
|
|
1299
|
-
if (op === "add")
|
|
1300
|
-
return left + right;
|
|
1301
|
-
if (op === "sub")
|
|
1302
|
-
return left - right;
|
|
1303
|
-
if (op === "mul")
|
|
1304
|
-
return left * right;
|
|
1305
|
-
if (op === "div")
|
|
1306
|
-
return left / right;
|
|
1307
|
-
return left % right;
|
|
1308
|
-
}
|
|
1309
|
-
if (op === "bitAnd" || op === "bitOr" || op === "bitXor") {
|
|
1310
|
-
if (typeof left !== "number" || typeof right !== "number")
|
|
1311
|
-
return;
|
|
1312
|
-
if (op === "bitAnd")
|
|
1313
|
-
return left & right;
|
|
1314
|
-
if (op === "bitOr")
|
|
1315
|
-
return left | right;
|
|
1316
|
-
return left ^ right;
|
|
1317
|
-
}
|
|
1318
|
-
if (op === "eq")
|
|
1319
|
-
return left === right ? left : undefined;
|
|
1320
|
-
if (op === "neq")
|
|
1321
|
-
return left !== right ? left : undefined;
|
|
1322
|
-
if (op === "gt" || op === "gte" || op === "lt" || op === "lte") {
|
|
1323
|
-
if (typeof left !== "number" || typeof right !== "number")
|
|
1324
|
-
return;
|
|
1325
|
-
if (op === "gt")
|
|
1326
|
-
return left > right ? left : undefined;
|
|
1327
|
-
if (op === "gte")
|
|
1328
|
-
return left >= right ? left : undefined;
|
|
1329
|
-
if (op === "lt")
|
|
1330
|
-
return left < right ? left : undefined;
|
|
1331
|
-
return left <= right ? left : undefined;
|
|
1332
|
-
}
|
|
1333
|
-
if (op === "and")
|
|
1334
|
-
return isDefinedValue(left) ? right : undefined;
|
|
1335
|
-
if (op === "or")
|
|
1336
|
-
return isDefinedValue(left) ? left : right;
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
function optimizeElse(elseBranch, env, currentDepth) {
|
|
1340
|
-
if (!elseBranch)
|
|
1341
|
-
return;
|
|
1342
|
-
if (elseBranch.type === "else") {
|
|
1343
|
-
return { type: "else", block: optimizeBlock(elseBranch.block, cloneOptimizeEnv(env), currentDepth) };
|
|
1344
|
-
}
|
|
1345
|
-
const optimizedCondition = optimizeNode(elseBranch.condition, env, currentDepth);
|
|
1346
|
-
const foldedCondition = constValue(optimizedCondition);
|
|
1347
|
-
if (foldedCondition !== undefined || optimizedCondition.type === "undefined") {
|
|
1348
|
-
const passes = elseBranch.head === "when" ? isDefinedValue(foldedCondition) : !isDefinedValue(foldedCondition);
|
|
1349
|
-
if (passes) {
|
|
1350
|
-
return {
|
|
1351
|
-
type: "else",
|
|
1352
|
-
block: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth)
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
return optimizeElse(elseBranch.elseBranch, env, currentDepth);
|
|
1356
|
-
}
|
|
1357
|
-
return {
|
|
1358
|
-
type: "elseChain",
|
|
1359
|
-
head: elseBranch.head,
|
|
1360
|
-
condition: optimizedCondition,
|
|
1361
|
-
thenBlock: optimizeBlock(elseBranch.thenBlock, cloneOptimizeEnv(env), currentDepth),
|
|
1362
|
-
elseBranch: optimizeElse(elseBranch.elseBranch, cloneOptimizeEnv(env), currentDepth)
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
function optimizeBlock(block, env, currentDepth) {
|
|
1366
|
-
const out = [];
|
|
1367
|
-
for (const node of block) {
|
|
1368
|
-
const optimized = optimizeNode(node, env, currentDepth);
|
|
1369
|
-
out.push(optimized);
|
|
1370
|
-
if (optimized.type === "break" || optimized.type === "continue")
|
|
1371
|
-
break;
|
|
1372
|
-
if (optimized.type === "assign" && optimized.op === "=" && optimized.place.type === "identifier") {
|
|
1373
|
-
const selfTarget = selfTargetFromNode(optimized.value, currentDepth);
|
|
1374
|
-
if (selfTarget !== undefined) {
|
|
1375
|
-
env.selfCaptures[optimized.place.name] = selfTarget;
|
|
1376
|
-
delete env.constants[optimized.place.name];
|
|
1377
|
-
continue;
|
|
1378
|
-
}
|
|
1379
|
-
const folded = constValue(optimized.value);
|
|
1380
|
-
if (folded !== undefined || optimized.value.type === "undefined") {
|
|
1381
|
-
env.constants[optimized.place.name] = cloneNode(optimized.value);
|
|
1382
|
-
delete env.selfCaptures[optimized.place.name];
|
|
1383
|
-
} else {
|
|
1384
|
-
clearBinding(env, optimized.place.name);
|
|
1385
|
-
}
|
|
1386
|
-
continue;
|
|
1387
|
-
}
|
|
1388
|
-
if (optimized.type === "unary" && optimized.op === "delete" && optimized.value.type === "identifier") {
|
|
1389
|
-
clearBinding(env, optimized.value.name);
|
|
1390
|
-
continue;
|
|
1391
|
-
}
|
|
1392
|
-
if (optimized.type === "assign" && optimized.place.type === "identifier") {
|
|
1393
|
-
clearBinding(env, optimized.place.name);
|
|
1394
|
-
continue;
|
|
1395
|
-
}
|
|
1396
|
-
if (optimized.type === "assign" || optimized.type === "for" || optimized.type === "call") {
|
|
1397
|
-
clearOptimizeEnv(env);
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
return inlineAdjacentPureAssignments(eliminateDeadAssignments(out));
|
|
1401
|
-
}
|
|
1402
|
-
function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
1403
|
-
switch (node.type) {
|
|
1404
|
-
case "program": {
|
|
1405
|
-
const body = optimizeBlock(node.body, cloneOptimizeEnv(env), currentDepth);
|
|
1406
|
-
if (body.length === 0)
|
|
1407
|
-
return { type: "undefined" };
|
|
1408
|
-
if (body.length === 1)
|
|
1409
|
-
return body[0];
|
|
1410
|
-
return { type: "program", body };
|
|
1411
|
-
}
|
|
1412
|
-
case "identifier": {
|
|
1413
|
-
if (asPlace)
|
|
1414
|
-
return node;
|
|
1415
|
-
const selfTarget = env.selfCaptures[node.name];
|
|
1416
|
-
if (selfTarget !== undefined) {
|
|
1417
|
-
const rewritten = selfNodeFromTarget(selfTarget, currentDepth);
|
|
1418
|
-
if (rewritten)
|
|
1419
|
-
return rewritten;
|
|
1420
|
-
}
|
|
1421
|
-
const replacement = env.constants[node.name];
|
|
1422
|
-
return replacement ? cloneNode(replacement) : node;
|
|
1423
|
-
}
|
|
1424
|
-
case "group": {
|
|
1425
|
-
return optimizeNode(node.expression, env, currentDepth);
|
|
1426
|
-
}
|
|
1427
|
-
case "array": {
|
|
1428
|
-
return { type: "array", items: node.items.map((item) => optimizeNode(item, env, currentDepth)) };
|
|
1429
|
-
}
|
|
1430
|
-
case "object": {
|
|
1431
|
-
return {
|
|
1432
|
-
type: "object",
|
|
1433
|
-
entries: node.entries.map((entry) => ({
|
|
1434
|
-
key: optimizeNode(entry.key, env, currentDepth),
|
|
1435
|
-
value: optimizeNode(entry.value, env, currentDepth)
|
|
1436
|
-
}))
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
case "unary": {
|
|
1440
|
-
const value = optimizeNode(node.value, env, currentDepth, node.op === "delete");
|
|
1441
|
-
const foldedValue = constValue(value);
|
|
1442
|
-
if (foldedValue !== undefined || value.type === "undefined") {
|
|
1443
|
-
const folded = foldUnary(node.op, foldedValue);
|
|
1444
|
-
const literal = folded === undefined ? undefined : toLiteralNode(folded);
|
|
1445
|
-
if (literal)
|
|
1446
|
-
return literal;
|
|
1447
|
-
}
|
|
1448
|
-
return { type: "unary", op: node.op, value };
|
|
1449
|
-
}
|
|
1450
|
-
case "binary": {
|
|
1451
|
-
const left = optimizeNode(node.left, env, currentDepth);
|
|
1452
|
-
const right = optimizeNode(node.right, env, currentDepth);
|
|
1453
|
-
const leftValue = constValue(left);
|
|
1454
|
-
const rightValue = constValue(right);
|
|
1455
|
-
if ((leftValue !== undefined || left.type === "undefined") && (rightValue !== undefined || right.type === "undefined")) {
|
|
1456
|
-
const folded = foldBinary(node.op, leftValue, rightValue);
|
|
1457
|
-
const literal = folded === undefined ? undefined : toLiteralNode(folded);
|
|
1458
|
-
if (literal)
|
|
1459
|
-
return literal;
|
|
1460
|
-
}
|
|
1461
|
-
return { type: "binary", op: node.op, left, right };
|
|
1462
|
-
}
|
|
1463
|
-
case "navigation": {
|
|
1464
|
-
const target = optimizeNode(node.target, env, currentDepth);
|
|
1465
|
-
const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
|
|
1466
|
-
const targetValue = constValue(target);
|
|
1467
|
-
if (targetValue !== undefined || target.type === "undefined") {
|
|
1468
|
-
let current = targetValue;
|
|
1469
|
-
let foldable = true;
|
|
1470
|
-
for (const segment of segments) {
|
|
1471
|
-
if (!foldable)
|
|
1472
|
-
break;
|
|
1473
|
-
const key = segment.type === "static" ? segment.key : constValue(segment.key);
|
|
1474
|
-
if (segment.type === "dynamic" && key === undefined && segment.key.type !== "undefined") {
|
|
1475
|
-
foldable = false;
|
|
1476
|
-
break;
|
|
1477
|
-
}
|
|
1478
|
-
if (current === null || current === undefined) {
|
|
1479
|
-
current = undefined;
|
|
1480
|
-
continue;
|
|
1481
|
-
}
|
|
1482
|
-
current = current[String(key)];
|
|
1483
|
-
}
|
|
1484
|
-
if (foldable) {
|
|
1485
|
-
const literal = toLiteralNode(current);
|
|
1486
|
-
if (literal)
|
|
1487
|
-
return literal;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
return {
|
|
1491
|
-
type: "navigation",
|
|
1492
|
-
target,
|
|
1493
|
-
segments
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
case "call": {
|
|
1497
|
-
return {
|
|
1498
|
-
type: "call",
|
|
1499
|
-
callee: optimizeNode(node.callee, env, currentDepth),
|
|
1500
|
-
args: node.args.map((arg) => optimizeNode(arg, env, currentDepth))
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
case "assign": {
|
|
1504
|
-
return {
|
|
1505
|
-
type: "assign",
|
|
1506
|
-
op: node.op,
|
|
1507
|
-
place: optimizeNode(node.place, env, currentDepth, true),
|
|
1508
|
-
value: optimizeNode(node.value, env, currentDepth)
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
case "conditional": {
|
|
1512
|
-
const condition = optimizeNode(node.condition, env, currentDepth);
|
|
1513
|
-
const thenEnv = cloneOptimizeEnv(env);
|
|
1514
|
-
if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
|
|
1515
|
-
thenEnv.selfCaptures[condition.place.name] = currentDepth;
|
|
1516
|
-
delete thenEnv.constants[condition.place.name];
|
|
1517
|
-
}
|
|
1518
|
-
const conditionValue = constValue(condition);
|
|
1519
|
-
if (conditionValue !== undefined || condition.type === "undefined") {
|
|
1520
|
-
const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
|
|
1521
|
-
if (passes) {
|
|
1522
|
-
const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
|
|
1523
|
-
if (thenBlock2.length === 0)
|
|
1524
|
-
return { type: "undefined" };
|
|
1525
|
-
if (thenBlock2.length === 1)
|
|
1526
|
-
return thenBlock2[0];
|
|
1527
|
-
return { type: "program", body: thenBlock2 };
|
|
1528
|
-
}
|
|
1529
|
-
if (!node.elseBranch)
|
|
1530
|
-
return { type: "undefined" };
|
|
1531
|
-
const loweredElse = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
|
|
1532
|
-
if (!loweredElse)
|
|
1533
|
-
return { type: "undefined" };
|
|
1534
|
-
if (loweredElse.type === "else") {
|
|
1535
|
-
if (loweredElse.block.length === 0)
|
|
1536
|
-
return { type: "undefined" };
|
|
1537
|
-
if (loweredElse.block.length === 1)
|
|
1538
|
-
return loweredElse.block[0];
|
|
1539
|
-
return { type: "program", body: loweredElse.block };
|
|
1540
|
-
}
|
|
1541
|
-
return {
|
|
1542
|
-
type: "conditional",
|
|
1543
|
-
head: loweredElse.head,
|
|
1544
|
-
condition: loweredElse.condition,
|
|
1545
|
-
thenBlock: loweredElse.thenBlock,
|
|
1546
|
-
elseBranch: loweredElse.elseBranch
|
|
1547
|
-
};
|
|
1548
|
-
}
|
|
1549
|
-
const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
|
|
1550
|
-
const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
|
|
1551
|
-
let finalCondition = condition;
|
|
1552
|
-
if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
|
|
1553
|
-
const name = condition.place.name;
|
|
1554
|
-
const reads = new Set;
|
|
1555
|
-
for (const part of thenBlock)
|
|
1556
|
-
collectReads(part, reads);
|
|
1557
|
-
if (elseBranch)
|
|
1558
|
-
collectReadsElse(elseBranch, reads);
|
|
1559
|
-
if (!reads.has(name)) {
|
|
1560
|
-
finalCondition = condition.value;
|
|
1561
|
-
}
|
|
851
|
+
const occurrence = occurrences[index];
|
|
852
|
+
if (occurrence.span.end > canonical.span.start)
|
|
853
|
+
continue;
|
|
854
|
+
if (current.slice(occurrence.span.start, occurrence.span.end) !== value)
|
|
855
|
+
continue;
|
|
856
|
+
const canonicalCurrentStart = current.length - canonical.offsetFromEnd - canonical.sizeBytes;
|
|
857
|
+
const pointerToken = buildPointerToken(occurrence.span.start, canonicalCurrentStart, occurrence.sizeBytes);
|
|
858
|
+
if (!pointerToken)
|
|
859
|
+
continue;
|
|
860
|
+
if (pointerToken.length >= occurrence.sizeBytes)
|
|
861
|
+
continue;
|
|
862
|
+
current = `${current.slice(0, occurrence.span.start)}${pointerToken}${current.slice(occurrence.span.end)}`;
|
|
863
|
+
replaced = true;
|
|
864
|
+
break;
|
|
1562
865
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
head: node.head,
|
|
1566
|
-
condition: finalCondition,
|
|
1567
|
-
thenBlock,
|
|
1568
|
-
elseBranch
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
1571
|
-
case "for": {
|
|
1572
|
-
const sourceEnv = cloneOptimizeEnv(env);
|
|
1573
|
-
const binding = (() => {
|
|
1574
|
-
if (node.binding.type === "binding:expr") {
|
|
1575
|
-
return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
|
|
1576
|
-
}
|
|
1577
|
-
if (node.binding.type === "binding:valueIn") {
|
|
1578
|
-
return {
|
|
1579
|
-
type: "binding:valueIn",
|
|
1580
|
-
value: node.binding.value,
|
|
1581
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1582
|
-
};
|
|
1583
|
-
}
|
|
1584
|
-
if (node.binding.type === "binding:keyValueIn") {
|
|
1585
|
-
return {
|
|
1586
|
-
type: "binding:keyValueIn",
|
|
1587
|
-
key: node.binding.key,
|
|
1588
|
-
value: node.binding.value,
|
|
1589
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1590
|
-
};
|
|
1591
|
-
}
|
|
1592
|
-
return {
|
|
1593
|
-
type: "binding:keyOf",
|
|
1594
|
-
key: node.binding.key,
|
|
1595
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1596
|
-
};
|
|
1597
|
-
})();
|
|
1598
|
-
const bodyEnv = cloneOptimizeEnv(env);
|
|
1599
|
-
dropBindingNames(bodyEnv, binding);
|
|
1600
|
-
return {
|
|
1601
|
-
type: "for",
|
|
1602
|
-
binding,
|
|
1603
|
-
body: optimizeBlock(node.body, bodyEnv, currentDepth + 1)
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
case "arrayComprehension": {
|
|
1607
|
-
const sourceEnv = cloneOptimizeEnv(env);
|
|
1608
|
-
const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
|
|
1609
|
-
type: "binding:valueIn",
|
|
1610
|
-
value: node.binding.value,
|
|
1611
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1612
|
-
} : node.binding.type === "binding:keyValueIn" ? {
|
|
1613
|
-
type: "binding:keyValueIn",
|
|
1614
|
-
key: node.binding.key,
|
|
1615
|
-
value: node.binding.value,
|
|
1616
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1617
|
-
} : {
|
|
1618
|
-
type: "binding:keyOf",
|
|
1619
|
-
key: node.binding.key,
|
|
1620
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1621
|
-
};
|
|
1622
|
-
const bodyEnv = cloneOptimizeEnv(env);
|
|
1623
|
-
dropBindingNames(bodyEnv, binding);
|
|
1624
|
-
return {
|
|
1625
|
-
type: "arrayComprehension",
|
|
1626
|
-
binding,
|
|
1627
|
-
body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
|
|
1628
|
-
};
|
|
866
|
+
if (replaced)
|
|
867
|
+
break;
|
|
1629
868
|
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
} : {
|
|
1642
|
-
type: "binding:keyOf",
|
|
1643
|
-
key: node.binding.key,
|
|
1644
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1645
|
-
};
|
|
1646
|
-
const bodyEnv = cloneOptimizeEnv(env);
|
|
1647
|
-
dropBindingNames(bodyEnv, binding);
|
|
1648
|
-
return {
|
|
1649
|
-
type: "objectComprehension",
|
|
1650
|
-
binding,
|
|
1651
|
-
key: optimizeNode(node.key, bodyEnv, currentDepth + 1),
|
|
1652
|
-
value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
|
|
1653
|
-
};
|
|
869
|
+
if (!replaced)
|
|
870
|
+
return current;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function encodeIR(node, options) {
|
|
874
|
+
const previous = activeEncodeOptions;
|
|
875
|
+
activeEncodeOptions = options;
|
|
876
|
+
try {
|
|
877
|
+
const encoded = encodeNode(node);
|
|
878
|
+
if (options?.dedupeValues) {
|
|
879
|
+
return dedupeLargeEncodedValues(encoded, options.dedupeMinBytes ?? 4);
|
|
1654
880
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
881
|
+
return encoded;
|
|
882
|
+
} finally {
|
|
883
|
+
activeEncodeOptions = previous;
|
|
1657
884
|
}
|
|
1658
885
|
}
|
|
1659
886
|
function optimizeIR(node) {
|
|
1660
|
-
return
|
|
887
|
+
return node;
|
|
1661
888
|
}
|
|
1662
889
|
function collectLocalBindings(node, locals) {
|
|
1663
890
|
switch (node.type) {
|
|
@@ -1703,6 +930,10 @@ function collectLocalBindings(node, locals) {
|
|
|
1703
930
|
collectLocalBindings(node.left, locals);
|
|
1704
931
|
collectLocalBindings(node.right, locals);
|
|
1705
932
|
return;
|
|
933
|
+
case "range":
|
|
934
|
+
collectLocalBindings(node.from, locals);
|
|
935
|
+
collectLocalBindings(node.to, locals);
|
|
936
|
+
return;
|
|
1706
937
|
case "conditional":
|
|
1707
938
|
collectLocalBindings(node.condition, locals);
|
|
1708
939
|
for (const part of node.thenBlock)
|
|
@@ -1719,11 +950,20 @@ function collectLocalBindings(node, locals) {
|
|
|
1719
950
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1720
951
|
collectLocalBindings(node.body, locals);
|
|
1721
952
|
return;
|
|
953
|
+
case "whileArrayComprehension":
|
|
954
|
+
collectLocalBindings(node.condition, locals);
|
|
955
|
+
collectLocalBindings(node.body, locals);
|
|
956
|
+
return;
|
|
1722
957
|
case "objectComprehension":
|
|
1723
958
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1724
959
|
collectLocalBindings(node.key, locals);
|
|
1725
960
|
collectLocalBindings(node.value, locals);
|
|
1726
961
|
return;
|
|
962
|
+
case "whileObjectComprehension":
|
|
963
|
+
collectLocalBindings(node.condition, locals);
|
|
964
|
+
collectLocalBindings(node.key, locals);
|
|
965
|
+
collectLocalBindings(node.value, locals);
|
|
966
|
+
return;
|
|
1727
967
|
default:
|
|
1728
968
|
return;
|
|
1729
969
|
}
|
|
@@ -1815,6 +1055,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
|
|
|
1815
1055
|
collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
|
|
1816
1056
|
collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
|
|
1817
1057
|
return;
|
|
1058
|
+
case "range":
|
|
1059
|
+
collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
|
|
1060
|
+
collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
|
|
1061
|
+
return;
|
|
1818
1062
|
case "conditional":
|
|
1819
1063
|
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1820
1064
|
for (const part of node.thenBlock)
|
|
@@ -1831,11 +1075,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
|
|
|
1831
1075
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1832
1076
|
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1833
1077
|
return;
|
|
1078
|
+
case "whileArrayComprehension":
|
|
1079
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1080
|
+
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1081
|
+
return;
|
|
1834
1082
|
case "objectComprehension":
|
|
1835
1083
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1836
1084
|
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1837
1085
|
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1838
1086
|
return;
|
|
1087
|
+
case "whileObjectComprehension":
|
|
1088
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1089
|
+
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1090
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1091
|
+
return;
|
|
1839
1092
|
default:
|
|
1840
1093
|
return;
|
|
1841
1094
|
}
|
|
@@ -1910,6 +1163,12 @@ function renameLocalNames(node, map) {
|
|
|
1910
1163
|
left: renameLocalNames(node.left, map),
|
|
1911
1164
|
right: renameLocalNames(node.right, map)
|
|
1912
1165
|
};
|
|
1166
|
+
case "range":
|
|
1167
|
+
return {
|
|
1168
|
+
type: "range",
|
|
1169
|
+
from: renameLocalNames(node.from, map),
|
|
1170
|
+
to: renameLocalNames(node.to, map)
|
|
1171
|
+
};
|
|
1913
1172
|
case "assign": {
|
|
1914
1173
|
const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
|
|
1915
1174
|
return {
|
|
@@ -1939,6 +1198,12 @@ function renameLocalNames(node, map) {
|
|
|
1939
1198
|
binding: renameLocalNamesBinding(node.binding, map),
|
|
1940
1199
|
body: renameLocalNames(node.body, map)
|
|
1941
1200
|
};
|
|
1201
|
+
case "whileArrayComprehension":
|
|
1202
|
+
return {
|
|
1203
|
+
type: "whileArrayComprehension",
|
|
1204
|
+
condition: renameLocalNames(node.condition, map),
|
|
1205
|
+
body: renameLocalNames(node.body, map)
|
|
1206
|
+
};
|
|
1942
1207
|
case "objectComprehension":
|
|
1943
1208
|
return {
|
|
1944
1209
|
type: "objectComprehension",
|
|
@@ -1946,34 +1211,31 @@ function renameLocalNames(node, map) {
|
|
|
1946
1211
|
key: renameLocalNames(node.key, map),
|
|
1947
1212
|
value: renameLocalNames(node.value, map)
|
|
1948
1213
|
};
|
|
1214
|
+
case "whileObjectComprehension":
|
|
1215
|
+
return {
|
|
1216
|
+
type: "whileObjectComprehension",
|
|
1217
|
+
condition: renameLocalNames(node.condition, map),
|
|
1218
|
+
key: renameLocalNames(node.key, map),
|
|
1219
|
+
value: renameLocalNames(node.value, map)
|
|
1220
|
+
};
|
|
1949
1221
|
default:
|
|
1950
1222
|
return node;
|
|
1951
1223
|
}
|
|
1952
1224
|
}
|
|
1953
1225
|
function renameLocalNamesBinding(binding, map) {
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
type: "binding:
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1226
|
+
const source = renameLocalNames(binding.source, map);
|
|
1227
|
+
switch (binding.type) {
|
|
1228
|
+
case "binding:bareIn":
|
|
1229
|
+
return { type: "binding:bareIn", source };
|
|
1230
|
+
case "binding:bareOf":
|
|
1231
|
+
return { type: "binding:bareOf", source };
|
|
1232
|
+
case "binding:valueIn":
|
|
1233
|
+
return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
|
|
1234
|
+
case "binding:keyValueIn":
|
|
1235
|
+
return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
|
|
1236
|
+
case "binding:keyOf":
|
|
1237
|
+
return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
|
|
1963
1238
|
}
|
|
1964
|
-
if (binding.type === "binding:keyValueIn") {
|
|
1965
|
-
return {
|
|
1966
|
-
type: "binding:keyValueIn",
|
|
1967
|
-
key: map.get(binding.key) ?? binding.key,
|
|
1968
|
-
value: map.get(binding.value) ?? binding.value,
|
|
1969
|
-
source: renameLocalNames(binding.source, map)
|
|
1970
|
-
};
|
|
1971
|
-
}
|
|
1972
|
-
return {
|
|
1973
|
-
type: "binding:keyOf",
|
|
1974
|
-
key: map.get(binding.key) ?? binding.key,
|
|
1975
|
-
source: renameLocalNames(binding.source, map)
|
|
1976
|
-
};
|
|
1977
1239
|
}
|
|
1978
1240
|
function renameLocalNamesElse(elseBranch, map) {
|
|
1979
1241
|
if (elseBranch.type === "else") {
|
|
@@ -2020,9 +1282,9 @@ function compile(source, options) {
|
|
|
2020
1282
|
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
2021
1283
|
if (options?.minifyNames)
|
|
2022
1284
|
lowered = minifyLocalNamesIR(lowered);
|
|
2023
|
-
const
|
|
1285
|
+
const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
|
|
2024
1286
|
return encodeIR(lowered, {
|
|
2025
|
-
|
|
1287
|
+
...domainMaps,
|
|
2026
1288
|
dedupeValues: options?.dedupeValues,
|
|
2027
1289
|
dedupeMinBytes: options?.dedupeMinBytes
|
|
2028
1290
|
});
|
|
@@ -2105,7 +1367,7 @@ function buildPostfix(base, steps) {
|
|
|
2105
1367
|
flushSegments();
|
|
2106
1368
|
return current;
|
|
2107
1369
|
}
|
|
2108
|
-
var require2, rexGrammarModule, rexGrammar, grammar, semantics, DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", OPCODE_IDS,
|
|
1370
|
+
var require2, rexGrammarModule, rexGrammar, grammar, semantics, DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", OPCODE_IDS, KEYWORD_OPCODES, BINARY_TO_OPCODE, ASSIGN_COMPOUND_TO_OPCODE, DEC_PARTS, activeEncodeOptions, DIGIT_SET, DIGIT_INDEX;
|
|
2109
1371
|
var init_rex = __esm(() => {
|
|
2110
1372
|
require2 = createRequire(import.meta.url);
|
|
2111
1373
|
rexGrammarModule = require2("./rex.ohm-bundle.cjs");
|
|
@@ -2113,30 +1375,31 @@ var init_rex = __esm(() => {
|
|
|
2113
1375
|
grammar = rexGrammar;
|
|
2114
1376
|
semantics = rexGrammar.createSemantics();
|
|
2115
1377
|
OPCODE_IDS = {
|
|
2116
|
-
do:
|
|
2117
|
-
add:
|
|
2118
|
-
sub:
|
|
2119
|
-
mul:
|
|
2120
|
-
div:
|
|
2121
|
-
eq:
|
|
2122
|
-
neq:
|
|
2123
|
-
lt:
|
|
2124
|
-
lte:
|
|
2125
|
-
gt:
|
|
2126
|
-
gte:
|
|
2127
|
-
and:
|
|
2128
|
-
or:
|
|
2129
|
-
xor:
|
|
2130
|
-
not:
|
|
2131
|
-
boolean:
|
|
2132
|
-
number:
|
|
2133
|
-
string:
|
|
2134
|
-
array:
|
|
2135
|
-
object:
|
|
2136
|
-
mod:
|
|
2137
|
-
neg:
|
|
1378
|
+
do: "",
|
|
1379
|
+
add: "ad",
|
|
1380
|
+
sub: "sb",
|
|
1381
|
+
mul: "ml",
|
|
1382
|
+
div: "dv",
|
|
1383
|
+
eq: "eq",
|
|
1384
|
+
neq: "nq",
|
|
1385
|
+
lt: "lt",
|
|
1386
|
+
lte: "le",
|
|
1387
|
+
gt: "gt",
|
|
1388
|
+
gte: "ge",
|
|
1389
|
+
and: "an",
|
|
1390
|
+
or: "or",
|
|
1391
|
+
xor: "xr",
|
|
1392
|
+
not: "nt",
|
|
1393
|
+
boolean: "bt",
|
|
1394
|
+
number: "nm",
|
|
1395
|
+
string: "st",
|
|
1396
|
+
array: "ar",
|
|
1397
|
+
object: "ob",
|
|
1398
|
+
mod: "md",
|
|
1399
|
+
neg: "ng",
|
|
1400
|
+
range: "rn"
|
|
2138
1401
|
};
|
|
2139
|
-
|
|
1402
|
+
KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object"]);
|
|
2140
1403
|
BINARY_TO_OPCODE = {
|
|
2141
1404
|
add: "add",
|
|
2142
1405
|
sub: "sub",
|
|
@@ -2211,6 +1474,9 @@ var init_rex = __esm(() => {
|
|
|
2211
1474
|
ExistenceExpr_or(left, _or, right) {
|
|
2212
1475
|
return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
|
|
2213
1476
|
},
|
|
1477
|
+
ExistenceExpr_nor(left, _nor, right) {
|
|
1478
|
+
return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
|
|
1479
|
+
},
|
|
2214
1480
|
BitExpr_and(left, _op, right) {
|
|
2215
1481
|
return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
|
|
2216
1482
|
},
|
|
@@ -2220,6 +1486,9 @@ var init_rex = __esm(() => {
|
|
|
2220
1486
|
BitExpr_or(left, _op, right) {
|
|
2221
1487
|
return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
|
|
2222
1488
|
},
|
|
1489
|
+
RangeExpr_range(left, _op, right) {
|
|
1490
|
+
return { type: "range", from: left.toIR(), to: right.toIR() };
|
|
1491
|
+
},
|
|
2223
1492
|
CompareExpr_binary(left, op, right) {
|
|
2224
1493
|
const map = {
|
|
2225
1494
|
"==": "eq",
|
|
@@ -2260,6 +1529,9 @@ var init_rex = __esm(() => {
|
|
|
2260
1529
|
UnaryExpr_not(_op, value) {
|
|
2261
1530
|
return { type: "unary", op: "not", value: value.toIR() };
|
|
2262
1531
|
},
|
|
1532
|
+
UnaryExpr_logicalNot(_not, value) {
|
|
1533
|
+
return { type: "unary", op: "logicalNot", value: value.toIR() };
|
|
1534
|
+
},
|
|
2263
1535
|
UnaryExpr_delete(_del, place) {
|
|
2264
1536
|
return { type: "unary", op: "delete", value: place.toIR() };
|
|
2265
1537
|
},
|
|
@@ -2313,14 +1585,6 @@ var init_rex = __esm(() => {
|
|
|
2313
1585
|
ConditionalElse_else(_else, block) {
|
|
2314
1586
|
return { type: "else", block: block.toIR() };
|
|
2315
1587
|
},
|
|
2316
|
-
DoExpr(_do, block, _end) {
|
|
2317
|
-
const body = block.toIR();
|
|
2318
|
-
if (body.length === 0)
|
|
2319
|
-
return { type: "undefined" };
|
|
2320
|
-
if (body.length === 1)
|
|
2321
|
-
return body[0];
|
|
2322
|
-
return { type: "program", body };
|
|
2323
|
-
},
|
|
2324
1588
|
WhileExpr(_while, condition, _do, block, _end) {
|
|
2325
1589
|
return {
|
|
2326
1590
|
type: "while",
|
|
@@ -2335,30 +1599,44 @@ var init_rex = __esm(() => {
|
|
|
2335
1599
|
body: block.toIR()
|
|
2336
1600
|
};
|
|
2337
1601
|
},
|
|
2338
|
-
BindingExpr(iterOrExpr) {
|
|
2339
|
-
const node = iterOrExpr.toIR();
|
|
2340
|
-
if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
|
|
2341
|
-
return node;
|
|
2342
|
-
}
|
|
2343
|
-
return { type: "binding:expr", source: node };
|
|
2344
|
-
},
|
|
2345
1602
|
Array_empty(_open, _close) {
|
|
2346
1603
|
return { type: "array", items: [] };
|
|
2347
1604
|
},
|
|
2348
|
-
|
|
1605
|
+
Array_forComprehension(_open, body, _for, binding, _close) {
|
|
2349
1606
|
return {
|
|
2350
1607
|
type: "arrayComprehension",
|
|
2351
1608
|
binding: binding.toIR(),
|
|
2352
1609
|
body: body.toIR()
|
|
2353
1610
|
};
|
|
2354
1611
|
},
|
|
1612
|
+
Array_whileComprehension(_open, body, _while, condition, _close) {
|
|
1613
|
+
return {
|
|
1614
|
+
type: "whileArrayComprehension",
|
|
1615
|
+
condition: condition.toIR(),
|
|
1616
|
+
body: body.toIR()
|
|
1617
|
+
};
|
|
1618
|
+
},
|
|
1619
|
+
Array_inComprehension(_open, body, _in, source, _close) {
|
|
1620
|
+
return {
|
|
1621
|
+
type: "arrayComprehension",
|
|
1622
|
+
binding: { type: "binding:bareIn", source: source.toIR() },
|
|
1623
|
+
body: body.toIR()
|
|
1624
|
+
};
|
|
1625
|
+
},
|
|
1626
|
+
Array_ofComprehension(_open, body, _of, source, _close) {
|
|
1627
|
+
return {
|
|
1628
|
+
type: "arrayComprehension",
|
|
1629
|
+
binding: { type: "binding:bareOf", source: source.toIR() },
|
|
1630
|
+
body: body.toIR()
|
|
1631
|
+
};
|
|
1632
|
+
},
|
|
2355
1633
|
Array_values(_open, items, _close) {
|
|
2356
1634
|
return { type: "array", items: normalizeList(items.toIR()) };
|
|
2357
1635
|
},
|
|
2358
1636
|
Object_empty(_open, _close) {
|
|
2359
1637
|
return { type: "object", entries: [] };
|
|
2360
1638
|
},
|
|
2361
|
-
|
|
1639
|
+
Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
|
|
2362
1640
|
return {
|
|
2363
1641
|
type: "objectComprehension",
|
|
2364
1642
|
binding: binding.toIR(),
|
|
@@ -2366,6 +1644,30 @@ var init_rex = __esm(() => {
|
|
|
2366
1644
|
value: value.toIR()
|
|
2367
1645
|
};
|
|
2368
1646
|
},
|
|
1647
|
+
Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
|
|
1648
|
+
return {
|
|
1649
|
+
type: "whileObjectComprehension",
|
|
1650
|
+
condition: condition.toIR(),
|
|
1651
|
+
key: key.toIR(),
|
|
1652
|
+
value: value.toIR()
|
|
1653
|
+
};
|
|
1654
|
+
},
|
|
1655
|
+
Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
|
|
1656
|
+
return {
|
|
1657
|
+
type: "objectComprehension",
|
|
1658
|
+
binding: { type: "binding:bareIn", source: source.toIR() },
|
|
1659
|
+
key: key.toIR(),
|
|
1660
|
+
value: value.toIR()
|
|
1661
|
+
};
|
|
1662
|
+
},
|
|
1663
|
+
Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
|
|
1664
|
+
return {
|
|
1665
|
+
type: "objectComprehension",
|
|
1666
|
+
binding: { type: "binding:bareOf", source: source.toIR() },
|
|
1667
|
+
key: key.toIR(),
|
|
1668
|
+
value: value.toIR()
|
|
1669
|
+
};
|
|
1670
|
+
},
|
|
2369
1671
|
Object_pairs(_open, pairs, _close) {
|
|
2370
1672
|
return {
|
|
2371
1673
|
type: "object",
|
|
@@ -2394,6 +1696,40 @@ var init_rex = __esm(() => {
|
|
|
2394
1696
|
source: source.toIR()
|
|
2395
1697
|
};
|
|
2396
1698
|
},
|
|
1699
|
+
IterBinding_bareIn(_in, source) {
|
|
1700
|
+
return {
|
|
1701
|
+
type: "binding:bareIn",
|
|
1702
|
+
source: source.toIR()
|
|
1703
|
+
};
|
|
1704
|
+
},
|
|
1705
|
+
IterBinding_bareOf(_of, source) {
|
|
1706
|
+
return {
|
|
1707
|
+
type: "binding:bareOf",
|
|
1708
|
+
source: source.toIR()
|
|
1709
|
+
};
|
|
1710
|
+
},
|
|
1711
|
+
IterBindingComprehension_keyValueIn(key, _comma, value, _in, source) {
|
|
1712
|
+
return {
|
|
1713
|
+
type: "binding:keyValueIn",
|
|
1714
|
+
key: key.sourceString,
|
|
1715
|
+
value: value.sourceString,
|
|
1716
|
+
source: source.toIR()
|
|
1717
|
+
};
|
|
1718
|
+
},
|
|
1719
|
+
IterBindingComprehension_valueIn(value, _in, source) {
|
|
1720
|
+
return {
|
|
1721
|
+
type: "binding:valueIn",
|
|
1722
|
+
value: value.sourceString,
|
|
1723
|
+
source: source.toIR()
|
|
1724
|
+
};
|
|
1725
|
+
},
|
|
1726
|
+
IterBindingComprehension_keyOf(key, _of, source) {
|
|
1727
|
+
return {
|
|
1728
|
+
type: "binding:keyOf",
|
|
1729
|
+
key: key.sourceString,
|
|
1730
|
+
source: source.toIR()
|
|
1731
|
+
};
|
|
1732
|
+
},
|
|
2397
1733
|
Pair(key, _colon, value) {
|
|
2398
1734
|
return { key: key.toIR(), value: value.toIR() };
|
|
2399
1735
|
},
|
|
@@ -2495,7 +1831,6 @@ class CursorInterpreter {
|
|
|
2495
1831
|
pos = 0;
|
|
2496
1832
|
state;
|
|
2497
1833
|
selfStack;
|
|
2498
|
-
opcodeMarkers;
|
|
2499
1834
|
pointerCache = new Map;
|
|
2500
1835
|
gasLimit;
|
|
2501
1836
|
gas = 0;
|
|
@@ -2510,28 +1845,22 @@ class CursorInterpreter {
|
|
|
2510
1845
|
this.state = {
|
|
2511
1846
|
vars: ctx.vars ?? {},
|
|
2512
1847
|
refs: {
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
1848
|
+
tr: true,
|
|
1849
|
+
fl: false,
|
|
1850
|
+
nl: null,
|
|
1851
|
+
un: undefined,
|
|
1852
|
+
nan: NaN,
|
|
1853
|
+
inf: Infinity,
|
|
1854
|
+
nif: -Infinity,
|
|
1855
|
+
...ctx.refs
|
|
2521
1856
|
}
|
|
2522
1857
|
};
|
|
2523
1858
|
this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
|
|
2524
1859
|
this.gasLimit = ctx.gasLimit ?? 0;
|
|
2525
|
-
this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
|
|
2526
|
-
for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
|
|
2527
|
-
const id = Number(idText);
|
|
2528
|
-
if (Number.isInteger(id))
|
|
2529
|
-
this.state.refs[id] = value;
|
|
2530
|
-
}
|
|
2531
1860
|
if (ctx.opcodes) {
|
|
2532
|
-
for (const [
|
|
1861
|
+
for (const [key, op] of Object.entries(ctx.opcodes)) {
|
|
2533
1862
|
if (op)
|
|
2534
|
-
this.customOpcodes.set(
|
|
1863
|
+
this.customOpcodes.set(key, op);
|
|
2535
1864
|
}
|
|
2536
1865
|
}
|
|
2537
1866
|
}
|
|
@@ -2592,6 +1921,22 @@ class CursorInterpreter {
|
|
|
2592
1921
|
const end = this.pos;
|
|
2593
1922
|
return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
|
|
2594
1923
|
}
|
|
1924
|
+
advanceByBytes(start, byteCount) {
|
|
1925
|
+
if (byteCount <= 0)
|
|
1926
|
+
return start;
|
|
1927
|
+
let bytes = 0;
|
|
1928
|
+
let index = start;
|
|
1929
|
+
for (const char of this.text.slice(start)) {
|
|
1930
|
+
const charBytes = Buffer.byteLength(char);
|
|
1931
|
+
if (bytes + charBytes > byteCount)
|
|
1932
|
+
break;
|
|
1933
|
+
bytes += charBytes;
|
|
1934
|
+
index += char.length;
|
|
1935
|
+
if (bytes === byteCount)
|
|
1936
|
+
return index;
|
|
1937
|
+
}
|
|
1938
|
+
throw new Error("String container overflows input");
|
|
1939
|
+
}
|
|
2595
1940
|
ensure(char) {
|
|
2596
1941
|
if (this.text[this.pos] !== char)
|
|
2597
1942
|
throw new Error(`Expected '${char}' at ${this.pos}`);
|
|
@@ -2632,36 +1977,34 @@ class CursorInterpreter {
|
|
|
2632
1977
|
const significand = this.evalValue();
|
|
2633
1978
|
if (typeof significand !== "number")
|
|
2634
1979
|
throw new Error("Decimal significand must be numeric");
|
|
2635
|
-
return significand
|
|
1980
|
+
return parseFloat(`${significand}e${power}`);
|
|
2636
1981
|
}
|
|
2637
1982
|
case ":":
|
|
2638
1983
|
this.pos += 1;
|
|
2639
1984
|
return prefix.raw;
|
|
2640
1985
|
case "%":
|
|
2641
1986
|
this.pos += 1;
|
|
2642
|
-
return
|
|
1987
|
+
return { __opcode: prefix.raw };
|
|
2643
1988
|
case "@":
|
|
2644
1989
|
this.pos += 1;
|
|
2645
1990
|
return this.readSelf(prefix.value);
|
|
2646
1991
|
case "'":
|
|
2647
1992
|
this.pos += 1;
|
|
2648
|
-
return this.state.refs[prefix.
|
|
1993
|
+
return this.state.refs[prefix.raw];
|
|
2649
1994
|
case "$":
|
|
2650
1995
|
this.pos += 1;
|
|
2651
1996
|
return this.state.vars[prefix.raw];
|
|
2652
1997
|
case ",": {
|
|
2653
1998
|
this.pos += 1;
|
|
2654
1999
|
const start = this.pos;
|
|
2655
|
-
const end = start
|
|
2656
|
-
if (end > this.text.length)
|
|
2657
|
-
throw new Error("String container overflows input");
|
|
2000
|
+
const end = this.advanceByBytes(start, prefix.value);
|
|
2658
2001
|
const value = this.text.slice(start, end);
|
|
2659
2002
|
this.pos = end;
|
|
2660
2003
|
return value;
|
|
2661
2004
|
}
|
|
2662
2005
|
case "^": {
|
|
2663
2006
|
this.pos += 1;
|
|
2664
|
-
const target = this.pos
|
|
2007
|
+
const target = this.advanceByBytes(this.pos, prefix.value);
|
|
2665
2008
|
if (this.pointerCache.has(target))
|
|
2666
2009
|
return this.pointerCache.get(target);
|
|
2667
2010
|
const save = this.pos;
|
|
@@ -2678,6 +2021,14 @@ class CursorInterpreter {
|
|
|
2678
2021
|
this.writePlace(place, value);
|
|
2679
2022
|
return value;
|
|
2680
2023
|
}
|
|
2024
|
+
case "/": {
|
|
2025
|
+
this.pos += 1;
|
|
2026
|
+
const place = this.readPlace();
|
|
2027
|
+
const oldValue = this.readPlaceValue(place);
|
|
2028
|
+
const newValue = this.evalValue();
|
|
2029
|
+
this.writePlace(place, newValue);
|
|
2030
|
+
return oldValue;
|
|
2031
|
+
}
|
|
2681
2032
|
case "~": {
|
|
2682
2033
|
this.pos += 1;
|
|
2683
2034
|
const place = this.readPlace();
|
|
@@ -2705,7 +2056,7 @@ class CursorInterpreter {
|
|
|
2705
2056
|
case "<":
|
|
2706
2057
|
return this.evalLoopLike(tag);
|
|
2707
2058
|
case "#":
|
|
2708
|
-
return this.
|
|
2059
|
+
return this.evalWhileLike();
|
|
2709
2060
|
default:
|
|
2710
2061
|
throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
|
|
2711
2062
|
}
|
|
@@ -2727,7 +2078,9 @@ class CursorInterpreter {
|
|
|
2727
2078
|
}
|
|
2728
2079
|
this.ensure(")");
|
|
2729
2080
|
if (typeof callee === "object" && callee && "__opcode" in callee) {
|
|
2730
|
-
|
|
2081
|
+
const marker = callee;
|
|
2082
|
+
const opArgs = marker.__receiver !== undefined ? [marker.__receiver, ...args] : args;
|
|
2083
|
+
return this.applyOpcode(marker.__opcode, opArgs);
|
|
2731
2084
|
}
|
|
2732
2085
|
return this.navigate(callee, args);
|
|
2733
2086
|
}
|
|
@@ -2918,15 +2271,11 @@ class CursorInterpreter {
|
|
|
2918
2271
|
return entries.map(([key]) => ({ key, value: key }));
|
|
2919
2272
|
return entries.map(([key, value]) => ({ key, value }));
|
|
2920
2273
|
}
|
|
2921
|
-
if (typeof iterable === "
|
|
2922
|
-
const
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
else
|
|
2927
|
-
out.push({ key: index, value: index + 1 });
|
|
2928
|
-
}
|
|
2929
|
-
return out;
|
|
2274
|
+
if (typeof iterable === "string") {
|
|
2275
|
+
const entries = Array.from(iterable);
|
|
2276
|
+
if (keysOnly)
|
|
2277
|
+
return entries.map((_value, index) => ({ key: index, value: index }));
|
|
2278
|
+
return entries.map((value, index) => ({ key: index, value }));
|
|
2930
2279
|
}
|
|
2931
2280
|
return [];
|
|
2932
2281
|
}
|
|
@@ -2971,21 +2320,32 @@ class CursorInterpreter {
|
|
|
2971
2320
|
}
|
|
2972
2321
|
return last;
|
|
2973
2322
|
}
|
|
2974
|
-
|
|
2323
|
+
evalWhileLike() {
|
|
2324
|
+
this.pos += 1;
|
|
2325
|
+
const open = this.text[this.pos];
|
|
2326
|
+
if (!open || !"([{".includes(open))
|
|
2327
|
+
throw new Error(`Expected opener after '#' at ${this.pos}`);
|
|
2328
|
+
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
2975
2329
|
this.pos += 1;
|
|
2976
|
-
this.ensure("(");
|
|
2977
2330
|
const condStart = this.pos;
|
|
2978
2331
|
const condValue = this.evalValue();
|
|
2979
2332
|
const bodyStart = this.pos;
|
|
2980
|
-
const bodyValueCount = 1;
|
|
2333
|
+
const bodyValueCount = open === "{" ? 2 : 1;
|
|
2981
2334
|
let cursor = bodyStart;
|
|
2982
2335
|
for (let index = 0;index < bodyValueCount; index += 1) {
|
|
2983
2336
|
cursor = this.skipValueFrom(cursor);
|
|
2984
2337
|
}
|
|
2985
2338
|
const bodyEnd = cursor;
|
|
2986
2339
|
this.pos = bodyEnd;
|
|
2987
|
-
this.ensure(
|
|
2340
|
+
this.ensure(close);
|
|
2988
2341
|
const afterClose = this.pos;
|
|
2342
|
+
if (open === "[")
|
|
2343
|
+
return this.evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
2344
|
+
if (open === "{")
|
|
2345
|
+
return this.evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
2346
|
+
return this.evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
2347
|
+
}
|
|
2348
|
+
evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
2989
2349
|
let last = undefined;
|
|
2990
2350
|
let currentCond = condValue;
|
|
2991
2351
|
while (isDefined(currentCond)) {
|
|
@@ -3006,6 +2366,58 @@ class CursorInterpreter {
|
|
|
3006
2366
|
this.pos = afterClose;
|
|
3007
2367
|
return last;
|
|
3008
2368
|
}
|
|
2369
|
+
evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
2370
|
+
const out = [];
|
|
2371
|
+
let currentCond = condValue;
|
|
2372
|
+
while (isDefined(currentCond)) {
|
|
2373
|
+
this.tick();
|
|
2374
|
+
this.selfStack.push(currentCond);
|
|
2375
|
+
const value = this.evalBodySlice(bodyStart, bodyEnd);
|
|
2376
|
+
this.selfStack.pop();
|
|
2377
|
+
const control = this.handleLoopControl(value);
|
|
2378
|
+
if (control) {
|
|
2379
|
+
if (control.depth > 1)
|
|
2380
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
2381
|
+
if (control.kind === "break")
|
|
2382
|
+
break;
|
|
2383
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2384
|
+
continue;
|
|
2385
|
+
}
|
|
2386
|
+
if (isDefined(value))
|
|
2387
|
+
out.push(value);
|
|
2388
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2389
|
+
}
|
|
2390
|
+
this.pos = afterClose;
|
|
2391
|
+
return out;
|
|
2392
|
+
}
|
|
2393
|
+
evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
2394
|
+
const result = {};
|
|
2395
|
+
let currentCond = condValue;
|
|
2396
|
+
while (isDefined(currentCond)) {
|
|
2397
|
+
this.tick();
|
|
2398
|
+
this.selfStack.push(currentCond);
|
|
2399
|
+
const save = this.pos;
|
|
2400
|
+
this.pos = bodyStart;
|
|
2401
|
+
const key = this.evalValue();
|
|
2402
|
+
const value = this.evalValue();
|
|
2403
|
+
this.pos = save;
|
|
2404
|
+
this.selfStack.pop();
|
|
2405
|
+
const control = this.handleLoopControl(value);
|
|
2406
|
+
if (control) {
|
|
2407
|
+
if (control.depth > 1)
|
|
2408
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
2409
|
+
if (control.kind === "break")
|
|
2410
|
+
break;
|
|
2411
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2412
|
+
continue;
|
|
2413
|
+
}
|
|
2414
|
+
if (isDefined(value))
|
|
2415
|
+
result[String(key)] = value;
|
|
2416
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
2417
|
+
}
|
|
2418
|
+
this.pos = afterClose;
|
|
2419
|
+
return result;
|
|
2420
|
+
}
|
|
3009
2421
|
evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
|
|
3010
2422
|
const items = this.iterate(iterable, keysOnly);
|
|
3011
2423
|
const out = [];
|
|
@@ -3076,10 +2488,19 @@ class CursorInterpreter {
|
|
|
3076
2488
|
case OPCODES.add:
|
|
3077
2489
|
if (args[0] === undefined || args[1] === undefined)
|
|
3078
2490
|
return;
|
|
3079
|
-
if (
|
|
3080
|
-
return
|
|
2491
|
+
if (Array.isArray(args[0]) && Array.isArray(args[1])) {
|
|
2492
|
+
return [...args[0], ...args[1]];
|
|
2493
|
+
}
|
|
2494
|
+
if (args[0] && args[1] && typeof args[0] === "object" && typeof args[1] === "object" && !Array.isArray(args[0]) && !Array.isArray(args[1])) {
|
|
2495
|
+
return { ...args[0], ...args[1] };
|
|
3081
2496
|
}
|
|
3082
|
-
|
|
2497
|
+
if (typeof args[0] === "string" && typeof args[1] === "string") {
|
|
2498
|
+
return args[0] + args[1];
|
|
2499
|
+
}
|
|
2500
|
+
if (typeof args[0] === "number" && typeof args[1] === "number") {
|
|
2501
|
+
return args[0] + args[1];
|
|
2502
|
+
}
|
|
2503
|
+
return;
|
|
3083
2504
|
case OPCODES.sub:
|
|
3084
2505
|
if (args[0] === undefined || args[1] === undefined)
|
|
3085
2506
|
return;
|
|
@@ -3148,6 +2569,98 @@ class CursorInterpreter {
|
|
|
3148
2569
|
return Array.isArray(args[0]) ? args[0] : undefined;
|
|
3149
2570
|
case OPCODES.object:
|
|
3150
2571
|
return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
|
|
2572
|
+
case OPCODES.range: {
|
|
2573
|
+
const from = Number(args[0]);
|
|
2574
|
+
const to = Number(args[1]);
|
|
2575
|
+
const step = to >= from ? 1 : -1;
|
|
2576
|
+
const out = [];
|
|
2577
|
+
for (let v = from;step > 0 ? v <= to : v >= to; v += step)
|
|
2578
|
+
out.push(v);
|
|
2579
|
+
return out;
|
|
2580
|
+
}
|
|
2581
|
+
case "array:push": {
|
|
2582
|
+
const target = args[0];
|
|
2583
|
+
if (!Array.isArray(target))
|
|
2584
|
+
return;
|
|
2585
|
+
const next = target.slice();
|
|
2586
|
+
for (let i = 1;i < args.length; i += 1)
|
|
2587
|
+
next.push(args[i]);
|
|
2588
|
+
return next;
|
|
2589
|
+
}
|
|
2590
|
+
case "array:pop": {
|
|
2591
|
+
const target = args[0];
|
|
2592
|
+
if (!Array.isArray(target) || target.length === 0)
|
|
2593
|
+
return;
|
|
2594
|
+
return target[target.length - 1];
|
|
2595
|
+
}
|
|
2596
|
+
case "array:unshift": {
|
|
2597
|
+
const target = args[0];
|
|
2598
|
+
if (!Array.isArray(target))
|
|
2599
|
+
return;
|
|
2600
|
+
const next = target.slice();
|
|
2601
|
+
for (let i = args.length - 1;i >= 1; i -= 1)
|
|
2602
|
+
next.unshift(args[i]);
|
|
2603
|
+
return next;
|
|
2604
|
+
}
|
|
2605
|
+
case "array:shift": {
|
|
2606
|
+
const target = args[0];
|
|
2607
|
+
if (!Array.isArray(target) || target.length === 0)
|
|
2608
|
+
return;
|
|
2609
|
+
return target[0];
|
|
2610
|
+
}
|
|
2611
|
+
case "array:slice": {
|
|
2612
|
+
const target = args[0];
|
|
2613
|
+
if (!Array.isArray(target))
|
|
2614
|
+
return;
|
|
2615
|
+
const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
|
|
2616
|
+
const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
|
|
2617
|
+
return target.slice(start, end);
|
|
2618
|
+
}
|
|
2619
|
+
case "array:join": {
|
|
2620
|
+
const target = args[0];
|
|
2621
|
+
if (!Array.isArray(target))
|
|
2622
|
+
return;
|
|
2623
|
+
const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : ",";
|
|
2624
|
+
return target.map((item) => String(item)).join(sep);
|
|
2625
|
+
}
|
|
2626
|
+
case "string:split": {
|
|
2627
|
+
const target = args[0];
|
|
2628
|
+
if (typeof target !== "string")
|
|
2629
|
+
return;
|
|
2630
|
+
if (args.length < 2 || args[1] === undefined)
|
|
2631
|
+
return [target];
|
|
2632
|
+
return target.split(String(args[1]));
|
|
2633
|
+
}
|
|
2634
|
+
case "string:join": {
|
|
2635
|
+
const target = args[0];
|
|
2636
|
+
if (typeof target !== "string")
|
|
2637
|
+
return;
|
|
2638
|
+
const parts = Array.from(target);
|
|
2639
|
+
const sep = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
|
|
2640
|
+
return parts.join(sep);
|
|
2641
|
+
}
|
|
2642
|
+
case "string:slice": {
|
|
2643
|
+
const target = args[0];
|
|
2644
|
+
if (typeof target !== "string")
|
|
2645
|
+
return;
|
|
2646
|
+
const start = args.length > 1 && args[1] !== undefined ? Number(args[1]) : undefined;
|
|
2647
|
+
const end = args.length > 2 && args[2] !== undefined ? Number(args[2]) : undefined;
|
|
2648
|
+
return Array.from(target).slice(start, end).join("");
|
|
2649
|
+
}
|
|
2650
|
+
case "string:starts-with": {
|
|
2651
|
+
const target = args[0];
|
|
2652
|
+
if (typeof target !== "string")
|
|
2653
|
+
return;
|
|
2654
|
+
const prefix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
|
|
2655
|
+
return target.startsWith(prefix);
|
|
2656
|
+
}
|
|
2657
|
+
case "string:ends-with": {
|
|
2658
|
+
const target = args[0];
|
|
2659
|
+
if (typeof target !== "string")
|
|
2660
|
+
return;
|
|
2661
|
+
const suffix = args.length > 1 && args[1] !== undefined ? String(args[1]) : "";
|
|
2662
|
+
return target.endsWith(suffix);
|
|
2663
|
+
}
|
|
3151
2664
|
default:
|
|
3152
2665
|
throw new Error(`Unknown opcode ${id}`);
|
|
3153
2666
|
}
|
|
@@ -3157,10 +2670,103 @@ class CursorInterpreter {
|
|
|
3157
2670
|
for (const key of keys) {
|
|
3158
2671
|
if (current === undefined || current === null)
|
|
3159
2672
|
return;
|
|
3160
|
-
current = current
|
|
2673
|
+
current = this.readProperty(current, key);
|
|
2674
|
+
if (current === undefined)
|
|
2675
|
+
return;
|
|
3161
2676
|
}
|
|
3162
2677
|
return current;
|
|
3163
2678
|
}
|
|
2679
|
+
readProperty(target, key) {
|
|
2680
|
+
if (typeof key === "string" && key === "size") {
|
|
2681
|
+
if (Array.isArray(target))
|
|
2682
|
+
return target.length;
|
|
2683
|
+
if (typeof target === "string")
|
|
2684
|
+
return Array.from(target).length;
|
|
2685
|
+
}
|
|
2686
|
+
const index = this.parseIndexKey(key);
|
|
2687
|
+
if (Array.isArray(target)) {
|
|
2688
|
+
if (index !== undefined)
|
|
2689
|
+
return target[index];
|
|
2690
|
+
if (typeof key === "string")
|
|
2691
|
+
return this.resolveArrayMethod(target, key);
|
|
2692
|
+
return;
|
|
2693
|
+
}
|
|
2694
|
+
if (typeof target === "string") {
|
|
2695
|
+
if (index !== undefined)
|
|
2696
|
+
return Array.from(target)[index];
|
|
2697
|
+
if (typeof key === "string")
|
|
2698
|
+
return this.resolveStringMethod(target, key);
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
if (this.isPlainObject(target)) {
|
|
2702
|
+
const prop = String(key);
|
|
2703
|
+
if (!Object.prototype.hasOwnProperty.call(target, prop))
|
|
2704
|
+
return;
|
|
2705
|
+
return target[prop];
|
|
2706
|
+
}
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
resolveArrayMethod(target, key) {
|
|
2710
|
+
switch (key) {
|
|
2711
|
+
case "push":
|
|
2712
|
+
return { __opcode: "array:push", __receiver: target };
|
|
2713
|
+
case "pop":
|
|
2714
|
+
return { __opcode: "array:pop", __receiver: target };
|
|
2715
|
+
case "unshift":
|
|
2716
|
+
return { __opcode: "array:unshift", __receiver: target };
|
|
2717
|
+
case "shift":
|
|
2718
|
+
return { __opcode: "array:shift", __receiver: target };
|
|
2719
|
+
case "slice":
|
|
2720
|
+
return { __opcode: "array:slice", __receiver: target };
|
|
2721
|
+
case "join":
|
|
2722
|
+
return { __opcode: "array:join", __receiver: target };
|
|
2723
|
+
default:
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
resolveStringMethod(target, key) {
|
|
2728
|
+
switch (key) {
|
|
2729
|
+
case "split":
|
|
2730
|
+
return { __opcode: "string:split", __receiver: target };
|
|
2731
|
+
case "join":
|
|
2732
|
+
return { __opcode: "string:join", __receiver: target };
|
|
2733
|
+
case "slice":
|
|
2734
|
+
return { __opcode: "string:slice", __receiver: target };
|
|
2735
|
+
case "starts-with":
|
|
2736
|
+
return { __opcode: "string:starts-with", __receiver: target };
|
|
2737
|
+
case "ends-with":
|
|
2738
|
+
return { __opcode: "string:ends-with", __receiver: target };
|
|
2739
|
+
default:
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
canWriteProperty(target, key) {
|
|
2744
|
+
const index = this.parseIndexKey(key);
|
|
2745
|
+
if (Array.isArray(target)) {
|
|
2746
|
+
if (index === undefined)
|
|
2747
|
+
return;
|
|
2748
|
+
return { kind: "array", index };
|
|
2749
|
+
}
|
|
2750
|
+
if (this.isPlainObject(target))
|
|
2751
|
+
return { kind: "object" };
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
parseIndexKey(key) {
|
|
2755
|
+
if (typeof key === "number" && Number.isInteger(key) && key >= 0)
|
|
2756
|
+
return key;
|
|
2757
|
+
if (typeof key !== "string" || key.length === 0)
|
|
2758
|
+
return;
|
|
2759
|
+
if (!/^(0|[1-9]\d*)$/.test(key))
|
|
2760
|
+
return;
|
|
2761
|
+
const index = Number(key);
|
|
2762
|
+
return Number.isSafeInteger(index) ? index : undefined;
|
|
2763
|
+
}
|
|
2764
|
+
isPlainObject(value) {
|
|
2765
|
+
if (!value || typeof value !== "object")
|
|
2766
|
+
return false;
|
|
2767
|
+
const proto = Object.getPrototypeOf(value);
|
|
2768
|
+
return proto === Object.prototype || proto === null;
|
|
2769
|
+
}
|
|
3164
2770
|
readPlace() {
|
|
3165
2771
|
this.skipNonCode();
|
|
3166
2772
|
const direct = this.readRootVarOrRefIfPresent();
|
|
@@ -3215,13 +2821,13 @@ class CursorInterpreter {
|
|
|
3215
2821
|
}
|
|
3216
2822
|
this.pos += 1;
|
|
3217
2823
|
return {
|
|
3218
|
-
root:
|
|
2824
|
+
root: prefix.raw,
|
|
3219
2825
|
isRef: tag === "'"
|
|
3220
2826
|
};
|
|
3221
2827
|
}
|
|
3222
2828
|
writePlace(place, value) {
|
|
3223
2829
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3224
|
-
const rootKey =
|
|
2830
|
+
const rootKey = place.root;
|
|
3225
2831
|
if (place.keys.length === 0) {
|
|
3226
2832
|
rootTable[rootKey] = value;
|
|
3227
2833
|
return;
|
|
@@ -3232,17 +2838,48 @@ class CursorInterpreter {
|
|
|
3232
2838
|
rootTable[rootKey] = target;
|
|
3233
2839
|
}
|
|
3234
2840
|
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3235
|
-
const key =
|
|
3236
|
-
const
|
|
2841
|
+
const key = place.keys[index];
|
|
2842
|
+
const access2 = this.canWriteProperty(target, key);
|
|
2843
|
+
if (!access2)
|
|
2844
|
+
return;
|
|
2845
|
+
if (access2.kind === "array") {
|
|
2846
|
+
const next2 = target[access2.index];
|
|
2847
|
+
if (!next2 || typeof next2 !== "object")
|
|
2848
|
+
target[access2.index] = {};
|
|
2849
|
+
target = target[access2.index];
|
|
2850
|
+
continue;
|
|
2851
|
+
}
|
|
2852
|
+
const prop = String(key);
|
|
2853
|
+
const next = target[prop];
|
|
3237
2854
|
if (!next || typeof next !== "object")
|
|
3238
|
-
target[
|
|
3239
|
-
target = target[
|
|
2855
|
+
target[prop] = {};
|
|
2856
|
+
target = target[prop];
|
|
2857
|
+
}
|
|
2858
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
2859
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
2860
|
+
if (!access)
|
|
2861
|
+
return;
|
|
2862
|
+
if (access.kind === "array") {
|
|
2863
|
+
target[access.index] = value;
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
target[String(lastKey)] = value;
|
|
2867
|
+
}
|
|
2868
|
+
readPlaceValue(place) {
|
|
2869
|
+
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
2870
|
+
let current = rootTable[place.root];
|
|
2871
|
+
for (const key of place.keys) {
|
|
2872
|
+
if (current === undefined || current === null)
|
|
2873
|
+
return;
|
|
2874
|
+
current = this.readProperty(current, key);
|
|
2875
|
+
if (current === undefined)
|
|
2876
|
+
return;
|
|
3240
2877
|
}
|
|
3241
|
-
|
|
2878
|
+
return current;
|
|
3242
2879
|
}
|
|
3243
2880
|
deletePlace(place) {
|
|
3244
2881
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3245
|
-
const rootKey =
|
|
2882
|
+
const rootKey = place.root;
|
|
3246
2883
|
if (place.keys.length === 0) {
|
|
3247
2884
|
delete rootTable[rootKey];
|
|
3248
2885
|
return;
|
|
@@ -3251,11 +2888,29 @@ class CursorInterpreter {
|
|
|
3251
2888
|
if (!target || typeof target !== "object")
|
|
3252
2889
|
return;
|
|
3253
2890
|
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3254
|
-
|
|
2891
|
+
const key = place.keys[index];
|
|
2892
|
+
const access2 = this.canWriteProperty(target, key);
|
|
2893
|
+
if (!access2)
|
|
2894
|
+
return;
|
|
2895
|
+
if (access2.kind === "array") {
|
|
2896
|
+
target = target[access2.index];
|
|
2897
|
+
if (!target || typeof target !== "object")
|
|
2898
|
+
return;
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2901
|
+
target = target[String(key)];
|
|
3255
2902
|
if (!target || typeof target !== "object")
|
|
3256
2903
|
return;
|
|
3257
2904
|
}
|
|
3258
|
-
|
|
2905
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
2906
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
2907
|
+
if (!access)
|
|
2908
|
+
return;
|
|
2909
|
+
if (access.kind === "array") {
|
|
2910
|
+
delete target[access.index];
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
delete target[String(lastKey)];
|
|
3259
2914
|
}
|
|
3260
2915
|
skipValue() {
|
|
3261
2916
|
this.pos = this.skipValueFrom(this.pos);
|
|
@@ -3271,12 +2926,12 @@ class CursorInterpreter {
|
|
|
3271
2926
|
return startPos;
|
|
3272
2927
|
}
|
|
3273
2928
|
if (tag === ",") {
|
|
3274
|
-
this.pos
|
|
2929
|
+
this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
3275
2930
|
const end2 = this.pos;
|
|
3276
2931
|
this.pos = save;
|
|
3277
2932
|
return end2;
|
|
3278
2933
|
}
|
|
3279
|
-
if (tag === "=") {
|
|
2934
|
+
if (tag === "=" || tag === "/") {
|
|
3280
2935
|
this.pos += 1;
|
|
3281
2936
|
this.skipValue();
|
|
3282
2937
|
this.skipValue();
|
|
@@ -3311,7 +2966,8 @@ class CursorInterpreter {
|
|
|
3311
2966
|
if (opener && "([{".includes(opener)) {
|
|
3312
2967
|
const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
|
|
3313
2968
|
if (prefix.value > 0) {
|
|
3314
|
-
this.pos
|
|
2969
|
+
const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
2970
|
+
this.pos = bodyEnd + 1;
|
|
3315
2971
|
const end3 = this.pos;
|
|
3316
2972
|
this.pos = save;
|
|
3317
2973
|
return end3;
|
|
@@ -3347,28 +3003,29 @@ var init_rexc_interpreter = __esm(() => {
|
|
|
3347
3003
|
init_rex();
|
|
3348
3004
|
digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
|
|
3349
3005
|
OPCODES = {
|
|
3350
|
-
do:
|
|
3351
|
-
add:
|
|
3352
|
-
sub:
|
|
3353
|
-
mul:
|
|
3354
|
-
div:
|
|
3355
|
-
eq:
|
|
3356
|
-
neq:
|
|
3357
|
-
lt:
|
|
3358
|
-
lte:
|
|
3359
|
-
gt:
|
|
3360
|
-
gte:
|
|
3361
|
-
and:
|
|
3362
|
-
or:
|
|
3363
|
-
xor:
|
|
3364
|
-
not:
|
|
3365
|
-
boolean:
|
|
3366
|
-
number:
|
|
3367
|
-
string:
|
|
3368
|
-
array:
|
|
3369
|
-
object:
|
|
3370
|
-
mod:
|
|
3371
|
-
neg:
|
|
3006
|
+
do: "",
|
|
3007
|
+
add: "ad",
|
|
3008
|
+
sub: "sb",
|
|
3009
|
+
mul: "ml",
|
|
3010
|
+
div: "dv",
|
|
3011
|
+
eq: "eq",
|
|
3012
|
+
neq: "nq",
|
|
3013
|
+
lt: "lt",
|
|
3014
|
+
lte: "le",
|
|
3015
|
+
gt: "gt",
|
|
3016
|
+
gte: "ge",
|
|
3017
|
+
and: "an",
|
|
3018
|
+
or: "or",
|
|
3019
|
+
xor: "xr",
|
|
3020
|
+
not: "nt",
|
|
3021
|
+
boolean: "bt",
|
|
3022
|
+
number: "nm",
|
|
3023
|
+
string: "st",
|
|
3024
|
+
array: "ar",
|
|
3025
|
+
object: "ob",
|
|
3026
|
+
mod: "md",
|
|
3027
|
+
neg: "ng",
|
|
3028
|
+
range: "rn"
|
|
3372
3029
|
};
|
|
3373
3030
|
});
|
|
3374
3031
|
|
|
@@ -3376,14 +3033,73 @@ var init_rexc_interpreter = __esm(() => {
|
|
|
3376
3033
|
var exports_rex_repl = {};
|
|
3377
3034
|
__export(exports_rex_repl, {
|
|
3378
3035
|
startRepl: () => startRepl,
|
|
3036
|
+
setColorEnabled: () => setColorEnabled,
|
|
3379
3037
|
isIncomplete: () => isIncomplete,
|
|
3380
3038
|
highlightRexc: () => highlightRexc,
|
|
3381
3039
|
highlightLine: () => highlightLine,
|
|
3382
3040
|
highlightJSON: () => highlightJSON,
|
|
3041
|
+
highlightAuto: () => highlightAuto,
|
|
3383
3042
|
formatVarState: () => formatVarState
|
|
3384
3043
|
});
|
|
3385
3044
|
import * as readline from "node:readline";
|
|
3386
3045
|
import { createRequire as createRequire2 } from "node:module";
|
|
3046
|
+
import { readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
|
|
3047
|
+
import { resolve, dirname, basename } from "node:path";
|
|
3048
|
+
import { homedir } from "node:os";
|
|
3049
|
+
function createColors(enabled) {
|
|
3050
|
+
if (!enabled) {
|
|
3051
|
+
return {
|
|
3052
|
+
reset: "",
|
|
3053
|
+
bold: "",
|
|
3054
|
+
dim: "",
|
|
3055
|
+
red: "",
|
|
3056
|
+
green: "",
|
|
3057
|
+
yellow: "",
|
|
3058
|
+
blue: "",
|
|
3059
|
+
magenta: "",
|
|
3060
|
+
cyan: "",
|
|
3061
|
+
gray: "",
|
|
3062
|
+
keyword: ""
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
return {
|
|
3066
|
+
reset: "\x1B[0m",
|
|
3067
|
+
bold: "\x1B[1m",
|
|
3068
|
+
dim: "\x1B[2m",
|
|
3069
|
+
red: "\x1B[38;5;203m",
|
|
3070
|
+
green: "\x1B[38;5;114m",
|
|
3071
|
+
yellow: "\x1B[38;5;179m",
|
|
3072
|
+
blue: "\x1B[38;5;75m",
|
|
3073
|
+
magenta: "\x1B[38;5;141m",
|
|
3074
|
+
cyan: "\x1B[38;5;81m",
|
|
3075
|
+
gray: "\x1B[38;5;245m",
|
|
3076
|
+
keyword: "\x1B[1;38;5;208m"
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
function setColorEnabled(enabled) {
|
|
3080
|
+
colorEnabled = enabled;
|
|
3081
|
+
C = createColors(enabled);
|
|
3082
|
+
}
|
|
3083
|
+
function formatJson(value, indent = 2) {
|
|
3084
|
+
const normalized = normalizeJsonValue(value, false);
|
|
3085
|
+
const text = JSON.stringify(normalized, null, indent);
|
|
3086
|
+
return text ?? "null";
|
|
3087
|
+
}
|
|
3088
|
+
function normalizeJsonValue(value, inArray) {
|
|
3089
|
+
if (value === undefined)
|
|
3090
|
+
return inArray ? null : undefined;
|
|
3091
|
+
if (value === null || typeof value !== "object")
|
|
3092
|
+
return value;
|
|
3093
|
+
if (Array.isArray(value))
|
|
3094
|
+
return value.map((item) => normalizeJsonValue(item, true));
|
|
3095
|
+
const out = {};
|
|
3096
|
+
for (const [key, val] of Object.entries(value)) {
|
|
3097
|
+
const normalized = normalizeJsonValue(val, false);
|
|
3098
|
+
if (normalized !== undefined)
|
|
3099
|
+
out[key] = normalized;
|
|
3100
|
+
}
|
|
3101
|
+
return out;
|
|
3102
|
+
}
|
|
3387
3103
|
function highlightLine(line) {
|
|
3388
3104
|
let result = "";
|
|
3389
3105
|
let lastIndex = 0;
|
|
@@ -3396,14 +3112,18 @@ function highlightLine(line) {
|
|
|
3396
3112
|
result += C.gray + text + C.reset;
|
|
3397
3113
|
} else if (g.dstring || g.sstring) {
|
|
3398
3114
|
result += C.green + text + C.reset;
|
|
3115
|
+
} else if (g.objKey) {
|
|
3116
|
+
result += C.magenta + text + C.reset;
|
|
3399
3117
|
} else if (g.keyword) {
|
|
3400
|
-
result += C.
|
|
3118
|
+
result += C.keyword + text + C.reset;
|
|
3401
3119
|
} else if (g.literal) {
|
|
3402
3120
|
result += C.yellow + text + C.reset;
|
|
3403
3121
|
} else if (g.typePred) {
|
|
3404
3122
|
result += C.cyan + text + C.reset;
|
|
3405
3123
|
} else if (g.num) {
|
|
3406
3124
|
result += C.cyan + text + C.reset;
|
|
3125
|
+
} else if (g.identifier) {
|
|
3126
|
+
result += C.blue + text + C.reset;
|
|
3407
3127
|
} else {
|
|
3408
3128
|
result += text;
|
|
3409
3129
|
}
|
|
@@ -3465,7 +3185,7 @@ function highlightRexc(text) {
|
|
|
3465
3185
|
i++;
|
|
3466
3186
|
break;
|
|
3467
3187
|
case "%":
|
|
3468
|
-
out += C.
|
|
3188
|
+
out += C.keyword + prefix + tag + C.reset;
|
|
3469
3189
|
i++;
|
|
3470
3190
|
break;
|
|
3471
3191
|
case "$":
|
|
@@ -3491,6 +3211,7 @@ function highlightRexc(text) {
|
|
|
3491
3211
|
break;
|
|
3492
3212
|
}
|
|
3493
3213
|
case "=":
|
|
3214
|
+
case "/":
|
|
3494
3215
|
case "~":
|
|
3495
3216
|
out += C.red + prefix + tag + C.reset;
|
|
3496
3217
|
i++;
|
|
@@ -3502,11 +3223,11 @@ function highlightRexc(text) {
|
|
|
3502
3223
|
case ">":
|
|
3503
3224
|
case "<":
|
|
3504
3225
|
case "#":
|
|
3505
|
-
out += C.
|
|
3226
|
+
out += C.keyword + prefix + tag + C.reset;
|
|
3506
3227
|
i++;
|
|
3507
3228
|
break;
|
|
3508
3229
|
case ";":
|
|
3509
|
-
out += C.
|
|
3230
|
+
out += C.keyword + prefix + tag + C.reset;
|
|
3510
3231
|
i++;
|
|
3511
3232
|
break;
|
|
3512
3233
|
case "^":
|
|
@@ -3530,6 +3251,23 @@ function highlightRexc(text) {
|
|
|
3530
3251
|
}
|
|
3531
3252
|
return out;
|
|
3532
3253
|
}
|
|
3254
|
+
function highlightAuto(text, hint) {
|
|
3255
|
+
if (hint === "rexc")
|
|
3256
|
+
return highlightRexc(text);
|
|
3257
|
+
if (hint === "rex")
|
|
3258
|
+
return text.split(`
|
|
3259
|
+
`).map((line) => highlightLine(line)).join(`
|
|
3260
|
+
`);
|
|
3261
|
+
try {
|
|
3262
|
+
const match = grammar.match(text);
|
|
3263
|
+
if (match.succeeded()) {
|
|
3264
|
+
return text.split(`
|
|
3265
|
+
`).map((line) => highlightLine(line)).join(`
|
|
3266
|
+
`);
|
|
3267
|
+
}
|
|
3268
|
+
} catch {}
|
|
3269
|
+
return highlightRexc(text);
|
|
3270
|
+
}
|
|
3533
3271
|
function highlightJSON(json) {
|
|
3534
3272
|
let result = "";
|
|
3535
3273
|
let lastIndex = 0;
|
|
@@ -3593,23 +3331,18 @@ function isIncomplete(buffer) {
|
|
|
3593
3331
|
const trimmed = buffer.trimEnd();
|
|
3594
3332
|
if (/[+\-*/%&|^=<>]$/.test(trimmed))
|
|
3595
3333
|
return true;
|
|
3596
|
-
if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
|
|
3334
|
+
if (/\b(?:and|or|nor|do|in|of)\s*$/.test(trimmed))
|
|
3597
3335
|
return true;
|
|
3598
3336
|
return false;
|
|
3599
3337
|
}
|
|
3600
|
-
function
|
|
3601
|
-
let text;
|
|
3602
|
-
try {
|
|
3603
|
-
text = stringify(value, { maxWidth: 60 });
|
|
3604
|
-
} catch {
|
|
3605
|
-
text = String(value);
|
|
3606
|
-
}
|
|
3607
|
-
return `${C.gray}→${C.reset} ${highlightLine(text)}`;
|
|
3608
|
-
}
|
|
3609
|
-
function formatVarState(vars) {
|
|
3338
|
+
function formatVarState(vars, format) {
|
|
3610
3339
|
const entries = Object.entries(vars);
|
|
3611
3340
|
if (entries.length === 0)
|
|
3612
3341
|
return "";
|
|
3342
|
+
if (format === "json") {
|
|
3343
|
+
const rendered = highlightJSON(formatJson(vars, 2));
|
|
3344
|
+
return `${C.dim} vars:${C.reset} ${rendered}`;
|
|
3345
|
+
}
|
|
3613
3346
|
const MAX_LINE = 70;
|
|
3614
3347
|
const MAX_VALUE = 30;
|
|
3615
3348
|
const parts = [];
|
|
@@ -3634,8 +3367,31 @@ function formatVarState(vars) {
|
|
|
3634
3367
|
}
|
|
3635
3368
|
return `${C.dim} ${parts.join(", ")}${C.reset}`;
|
|
3636
3369
|
}
|
|
3370
|
+
function renderValue(value, format, kind) {
|
|
3371
|
+
if (format === "json") {
|
|
3372
|
+
return highlightJSON(formatJson(value, 2));
|
|
3373
|
+
}
|
|
3374
|
+
if (kind === "source") {
|
|
3375
|
+
return highlightAuto(String(value ?? ""));
|
|
3376
|
+
}
|
|
3377
|
+
if (kind === "rexc") {
|
|
3378
|
+
return highlightRexc(String(value ?? ""));
|
|
3379
|
+
}
|
|
3380
|
+
return highlightLine(stringify(value, { maxWidth: 120 }));
|
|
3381
|
+
}
|
|
3637
3382
|
function completer(state) {
|
|
3638
3383
|
return (line) => {
|
|
3384
|
+
if (line.startsWith(".file ")) {
|
|
3385
|
+
return completeFilePath(line, 6);
|
|
3386
|
+
}
|
|
3387
|
+
if (line.startsWith(".cat ")) {
|
|
3388
|
+
return completeFilePath(line, 5);
|
|
3389
|
+
}
|
|
3390
|
+
if (line.startsWith(".") && !line.includes(" ")) {
|
|
3391
|
+
const partial2 = line.trim();
|
|
3392
|
+
const matches = DOT_COMMANDS.filter((cmd) => cmd.startsWith(partial2));
|
|
3393
|
+
return [matches, line];
|
|
3394
|
+
}
|
|
3639
3395
|
const match = line.match(/[a-zA-Z_][a-zA-Z0-9_.-]*$/);
|
|
3640
3396
|
const partial = match ? match[0] : "";
|
|
3641
3397
|
if (!partial)
|
|
@@ -3646,45 +3402,92 @@ function completer(state) {
|
|
|
3646
3402
|
return [hits, partial];
|
|
3647
3403
|
};
|
|
3648
3404
|
}
|
|
3649
|
-
function
|
|
3405
|
+
function completeFilePath(line, prefixLength) {
|
|
3406
|
+
const raw = line.slice(prefixLength);
|
|
3407
|
+
const trimmed = raw.trimStart();
|
|
3408
|
+
const quote = trimmed.startsWith('"') || trimmed.startsWith("'") ? trimmed[0] : "";
|
|
3409
|
+
const pathPart = quote ? trimmed.slice(1) : trimmed;
|
|
3410
|
+
const endsWithSlash = pathPart.endsWith("/");
|
|
3411
|
+
const rawDir = endsWithSlash ? pathPart.slice(0, -1) : pathPart;
|
|
3412
|
+
const baseName = endsWithSlash ? "" : rawDir.includes("/") ? basename(rawDir) : rawDir;
|
|
3413
|
+
const dirPart = endsWithSlash ? rawDir || "." : rawDir.includes("/") ? dirname(rawDir) : ".";
|
|
3414
|
+
const dirPath = resolve(dirPart);
|
|
3415
|
+
let entries = [];
|
|
3416
|
+
try {
|
|
3417
|
+
entries = readdirSync(dirPath);
|
|
3418
|
+
} catch {
|
|
3419
|
+
return [[], ""];
|
|
3420
|
+
}
|
|
3421
|
+
const prefix = dirPart === "." ? "" : `${dirPart}/`;
|
|
3422
|
+
const matches = entries.filter((entry) => entry.startsWith(baseName)).map((entry) => {
|
|
3423
|
+
const fullPath = resolve(dirPath, entry);
|
|
3424
|
+
let suffix = "";
|
|
3425
|
+
try {
|
|
3426
|
+
if (statSync(fullPath).isDirectory())
|
|
3427
|
+
suffix = "/";
|
|
3428
|
+
} catch {
|
|
3429
|
+
suffix = "";
|
|
3430
|
+
}
|
|
3431
|
+
return `${quote}${prefix}${entry}${suffix}`;
|
|
3432
|
+
});
|
|
3433
|
+
return [matches, trimmed];
|
|
3434
|
+
}
|
|
3435
|
+
function stripQuotes(value) {
|
|
3436
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
3437
|
+
return value.slice(1, -1);
|
|
3438
|
+
}
|
|
3439
|
+
return value;
|
|
3440
|
+
}
|
|
3441
|
+
async function handleDotCommand(cmd, state, rl, runSource, updatePromptStyles) {
|
|
3650
3442
|
function toggleLabel(on) {
|
|
3651
3443
|
return on ? `${C.green}on${C.reset}` : `${C.dim}off${C.reset}`;
|
|
3652
3444
|
}
|
|
3653
3445
|
switch (cmd) {
|
|
3654
3446
|
case ".help":
|
|
3655
3447
|
console.log([
|
|
3656
|
-
`${C.
|
|
3657
|
-
" .help
|
|
3658
|
-
" .
|
|
3659
|
-
" .
|
|
3660
|
-
" .
|
|
3661
|
-
" .
|
|
3662
|
-
" .
|
|
3663
|
-
" .
|
|
3448
|
+
`${C.keyword}Rex REPL Commands:${C.reset}`,
|
|
3449
|
+
" .help Show this help message",
|
|
3450
|
+
" .file <path> Load and execute a Rex file",
|
|
3451
|
+
" .cat <path> Print a Rex/rexc file with highlighting",
|
|
3452
|
+
" .expr Toggle showing expression results",
|
|
3453
|
+
" .source Toggle showing input source",
|
|
3454
|
+
" .vars Toggle showing variable summary",
|
|
3455
|
+
" .vars! Show all current variables",
|
|
3456
|
+
" .clear Clear all variables",
|
|
3457
|
+
" .ir Toggle showing IR JSON after parsing",
|
|
3458
|
+
" .rexc Toggle showing compiled rexc before execution",
|
|
3459
|
+
" .opt Toggle IR optimizations",
|
|
3460
|
+
" .json Toggle JSON output format",
|
|
3461
|
+
" .color Toggle ANSI color output",
|
|
3462
|
+
" .exit Exit the REPL",
|
|
3664
3463
|
"",
|
|
3665
3464
|
"Enter Rex expressions to evaluate them.",
|
|
3666
3465
|
"Multi-line: open brackets or do/end blocks continue on the next line.",
|
|
3667
3466
|
"Ctrl-C cancels multi-line input.",
|
|
3668
|
-
"Ctrl-D exits."
|
|
3467
|
+
"Ctrl-D exits.",
|
|
3468
|
+
"",
|
|
3469
|
+
"Outputs are printed as labeled blocks when enabled."
|
|
3669
3470
|
].join(`
|
|
3670
3471
|
`));
|
|
3671
|
-
return
|
|
3672
|
-
case ".
|
|
3673
|
-
state.
|
|
3674
|
-
console.log(`${C.dim}
|
|
3675
|
-
return
|
|
3676
|
-
case ".
|
|
3677
|
-
state.
|
|
3678
|
-
console.log(`${C.dim}
|
|
3679
|
-
return
|
|
3680
|
-
case ".
|
|
3681
|
-
state.
|
|
3682
|
-
console.log(`${C.dim}
|
|
3683
|
-
return
|
|
3684
|
-
case ".vars": {
|
|
3472
|
+
return "handled";
|
|
3473
|
+
case ".expr":
|
|
3474
|
+
state.showExpr = !state.showExpr;
|
|
3475
|
+
console.log(`${C.dim} Expression output: ${toggleLabel(state.showExpr)}${C.reset}`);
|
|
3476
|
+
return "handled";
|
|
3477
|
+
case ".source":
|
|
3478
|
+
state.showSource = !state.showSource;
|
|
3479
|
+
console.log(`${C.dim} Source output: ${toggleLabel(state.showSource)}${C.reset}`);
|
|
3480
|
+
return "handled";
|
|
3481
|
+
case ".vars":
|
|
3482
|
+
state.showVars = !state.showVars;
|
|
3483
|
+
console.log(`${C.dim} Variable summary: ${toggleLabel(state.showVars)}${C.reset}`);
|
|
3484
|
+
return "handled";
|
|
3485
|
+
case ".vars!": {
|
|
3685
3486
|
const entries = Object.entries(state.vars);
|
|
3686
3487
|
if (entries.length === 0) {
|
|
3687
3488
|
console.log(`${C.dim} (no variables)${C.reset}`);
|
|
3489
|
+
} else if (state.outputFormat === "json") {
|
|
3490
|
+
console.log(highlightJSON(formatJson(state.vars, 2)));
|
|
3688
3491
|
} else {
|
|
3689
3492
|
for (const [key, val] of entries) {
|
|
3690
3493
|
let valStr;
|
|
@@ -3696,42 +3499,118 @@ function handleDotCommand(cmd, state, rl) {
|
|
|
3696
3499
|
console.log(` ${key} = ${highlightLine(valStr)}`);
|
|
3697
3500
|
}
|
|
3698
3501
|
}
|
|
3699
|
-
return
|
|
3502
|
+
return "handled";
|
|
3700
3503
|
}
|
|
3504
|
+
case ".ir":
|
|
3505
|
+
state.showIR = !state.showIR;
|
|
3506
|
+
console.log(`${C.dim} IR display: ${toggleLabel(state.showIR)}${C.reset}`);
|
|
3507
|
+
return "handled";
|
|
3508
|
+
case ".rexc":
|
|
3509
|
+
state.showRexc = !state.showRexc;
|
|
3510
|
+
console.log(`${C.dim} Rexc display: ${toggleLabel(state.showRexc)}${C.reset}`);
|
|
3511
|
+
return "handled";
|
|
3512
|
+
case ".opt":
|
|
3513
|
+
state.optimize = !state.optimize;
|
|
3514
|
+
console.log(`${C.dim} Optimizations: ${toggleLabel(state.optimize)}${C.reset}`);
|
|
3515
|
+
return "handled";
|
|
3516
|
+
case ".json":
|
|
3517
|
+
state.outputFormat = state.outputFormat === "json" ? "rex" : "json";
|
|
3518
|
+
console.log(`${C.dim} Output format: ${state.outputFormat}${C.reset}`);
|
|
3519
|
+
return "handled";
|
|
3520
|
+
case ".color":
|
|
3521
|
+
setColorEnabled(!colorEnabled);
|
|
3522
|
+
updatePromptStyles();
|
|
3523
|
+
console.log(`${C.dim} Color output: ${toggleLabel(colorEnabled)}${C.reset}`);
|
|
3524
|
+
return "handled";
|
|
3701
3525
|
case ".clear":
|
|
3702
3526
|
state.vars = {};
|
|
3703
3527
|
state.refs = {};
|
|
3704
3528
|
console.log(`${C.dim} Variables cleared.${C.reset}`);
|
|
3705
|
-
return
|
|
3529
|
+
return "handled";
|
|
3706
3530
|
case ".exit":
|
|
3707
3531
|
rl.close();
|
|
3708
|
-
return
|
|
3532
|
+
return "handled-noprompt";
|
|
3709
3533
|
default:
|
|
3534
|
+
if (cmd.startsWith(".cat ")) {
|
|
3535
|
+
const rawPath = cmd.slice(5).trim();
|
|
3536
|
+
if (!rawPath) {
|
|
3537
|
+
console.log(`${C.red} Missing file path. Usage: .cat <path>${C.reset}`);
|
|
3538
|
+
return "handled";
|
|
3539
|
+
}
|
|
3540
|
+
const filePath = resolve(stripQuotes(rawPath));
|
|
3541
|
+
try {
|
|
3542
|
+
const source = readFileSync(filePath, "utf8");
|
|
3543
|
+
const hint = filePath.endsWith(".rexc") ? "rexc" : filePath.endsWith(".rex") ? "rex" : undefined;
|
|
3544
|
+
console.log(highlightAuto(source, hint));
|
|
3545
|
+
} catch (error) {
|
|
3546
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3547
|
+
console.log(`${C.red} File error: ${message}${C.reset}`);
|
|
3548
|
+
}
|
|
3549
|
+
return "handled";
|
|
3550
|
+
}
|
|
3551
|
+
if (cmd.startsWith(".file ")) {
|
|
3552
|
+
const rawPath = cmd.slice(6).trim();
|
|
3553
|
+
if (!rawPath) {
|
|
3554
|
+
console.log(`${C.red} Missing file path. Usage: .file <path>${C.reset}`);
|
|
3555
|
+
return "handled";
|
|
3556
|
+
}
|
|
3557
|
+
const filePath = resolve(stripQuotes(rawPath));
|
|
3558
|
+
try {
|
|
3559
|
+
const source = readFileSync(filePath, "utf8");
|
|
3560
|
+
runSource(source);
|
|
3561
|
+
} catch (error) {
|
|
3562
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3563
|
+
console.log(`${C.red} File error: ${message}${C.reset}`);
|
|
3564
|
+
}
|
|
3565
|
+
return "handled";
|
|
3566
|
+
}
|
|
3710
3567
|
if (cmd.startsWith(".")) {
|
|
3711
3568
|
console.log(`${C.red} Unknown command: ${cmd}. Type .help for available commands.${C.reset}`);
|
|
3712
|
-
return
|
|
3569
|
+
return "handled";
|
|
3713
3570
|
}
|
|
3714
|
-
return
|
|
3571
|
+
return "unhandled";
|
|
3715
3572
|
}
|
|
3716
3573
|
}
|
|
3717
3574
|
async function startRepl() {
|
|
3718
|
-
const state = {
|
|
3575
|
+
const state = {
|
|
3576
|
+
vars: {},
|
|
3577
|
+
refs: {},
|
|
3578
|
+
showIR: false,
|
|
3579
|
+
showRexc: false,
|
|
3580
|
+
optimize: false,
|
|
3581
|
+
showExpr: true,
|
|
3582
|
+
showVars: false,
|
|
3583
|
+
showSource: false,
|
|
3584
|
+
outputFormat: "rex"
|
|
3585
|
+
};
|
|
3719
3586
|
let multiLineBuffer = "";
|
|
3720
3587
|
const PRIMARY_PROMPT = "rex> ";
|
|
3721
3588
|
const CONT_PROMPT = "... ";
|
|
3722
|
-
const STYLED_PRIMARY = `${C.boldBlue}rex${C.reset}> `;
|
|
3723
|
-
const STYLED_CONT = `${C.dim}...${C.reset} `;
|
|
3724
3589
|
let currentPrompt = PRIMARY_PROMPT;
|
|
3725
|
-
let styledPrompt =
|
|
3726
|
-
|
|
3590
|
+
let styledPrompt = "";
|
|
3591
|
+
let styledPrimary = "";
|
|
3592
|
+
let styledCont = "";
|
|
3593
|
+
function updatePromptStyles() {
|
|
3594
|
+
styledPrimary = `${C.keyword}rex${C.reset}> `;
|
|
3595
|
+
styledCont = `${C.dim}...${C.reset} `;
|
|
3596
|
+
styledPrompt = currentPrompt === PRIMARY_PROMPT ? styledPrimary : styledCont;
|
|
3597
|
+
}
|
|
3598
|
+
updatePromptStyles();
|
|
3599
|
+
console.log(`${C.keyword}Rex${C.reset} v${version} — type ${C.dim}.help${C.reset} for commands`);
|
|
3727
3600
|
const rl = readline.createInterface({
|
|
3728
3601
|
input: process.stdin,
|
|
3729
3602
|
output: process.stdout,
|
|
3730
3603
|
prompt: PRIMARY_PROMPT,
|
|
3731
|
-
historySize:
|
|
3604
|
+
historySize: HISTORY_LIMIT,
|
|
3732
3605
|
completer: completer(state),
|
|
3733
3606
|
terminal: true
|
|
3734
3607
|
});
|
|
3608
|
+
try {
|
|
3609
|
+
const historyText = readFileSync(HISTORY_PATH, "utf8");
|
|
3610
|
+
const lines = historyText.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
3611
|
+
const recent = lines.slice(-HISTORY_LIMIT);
|
|
3612
|
+
rl.history = recent.reverse();
|
|
3613
|
+
} catch {}
|
|
3735
3614
|
process.stdin.on("keypress", () => {
|
|
3736
3615
|
process.nextTick(() => {
|
|
3737
3616
|
if (!rl.line && rl.line !== "")
|
|
@@ -3753,7 +3632,7 @@ async function startRepl() {
|
|
|
3753
3632
|
if (multiLineBuffer) {
|
|
3754
3633
|
multiLineBuffer = "";
|
|
3755
3634
|
currentPrompt = PRIMARY_PROMPT;
|
|
3756
|
-
styledPrompt =
|
|
3635
|
+
styledPrompt = styledPrimary;
|
|
3757
3636
|
rl.setPrompt(PRIMARY_PROMPT);
|
|
3758
3637
|
process.stdout.write(`
|
|
3759
3638
|
`);
|
|
@@ -3765,50 +3644,33 @@ async function startRepl() {
|
|
|
3765
3644
|
});
|
|
3766
3645
|
function resetPrompt() {
|
|
3767
3646
|
currentPrompt = PRIMARY_PROMPT;
|
|
3768
|
-
styledPrompt =
|
|
3647
|
+
styledPrompt = styledPrimary;
|
|
3769
3648
|
rl.setPrompt(PRIMARY_PROMPT);
|
|
3770
3649
|
rl.prompt();
|
|
3771
3650
|
}
|
|
3772
|
-
|
|
3773
|
-
const trimmed = line.trim();
|
|
3774
|
-
if (!multiLineBuffer && trimmed.startsWith(".")) {
|
|
3775
|
-
if (handleDotCommand(trimmed, state, rl)) {
|
|
3776
|
-
rl.prompt();
|
|
3777
|
-
return;
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
multiLineBuffer += (multiLineBuffer ? `
|
|
3781
|
-
` : "") + line;
|
|
3782
|
-
if (multiLineBuffer.trim() === "") {
|
|
3783
|
-
multiLineBuffer = "";
|
|
3784
|
-
rl.prompt();
|
|
3785
|
-
return;
|
|
3786
|
-
}
|
|
3787
|
-
if (isIncomplete(multiLineBuffer)) {
|
|
3788
|
-
currentPrompt = CONT_PROMPT;
|
|
3789
|
-
styledPrompt = STYLED_CONT;
|
|
3790
|
-
rl.setPrompt(CONT_PROMPT);
|
|
3791
|
-
rl.prompt();
|
|
3792
|
-
return;
|
|
3793
|
-
}
|
|
3794
|
-
const source = multiLineBuffer;
|
|
3795
|
-
multiLineBuffer = "";
|
|
3651
|
+
function runSource(source) {
|
|
3796
3652
|
const match = grammar.match(source);
|
|
3797
3653
|
if (!match.succeeded()) {
|
|
3798
|
-
|
|
3799
|
-
|
|
3654
|
+
const message = formatParseError(source, match);
|
|
3655
|
+
console.log(`${C.red} ${message}${C.reset}`);
|
|
3800
3656
|
return;
|
|
3801
3657
|
}
|
|
3802
3658
|
try {
|
|
3803
|
-
const
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3659
|
+
const outputs = {};
|
|
3660
|
+
if (state.showSource)
|
|
3661
|
+
outputs.source = source;
|
|
3662
|
+
const isRex = grammar.match(source).succeeded();
|
|
3663
|
+
if (!isRex && state.showIR) {
|
|
3664
|
+
console.log(`${C.red} IR output is only available for Rex source.${C.reset}`);
|
|
3807
3665
|
}
|
|
3808
|
-
const rexc = compile(source, { optimize: state.optimize });
|
|
3809
|
-
if (state.
|
|
3810
|
-
|
|
3666
|
+
const rexc = isRex ? compile(source, { optimize: state.optimize }) : source;
|
|
3667
|
+
if (state.showIR && isRex) {
|
|
3668
|
+
const ir = parseToIR(source);
|
|
3669
|
+
const lowered = state.optimize ? optimizeIR(ir) : ir;
|
|
3670
|
+
outputs.ir = lowered;
|
|
3811
3671
|
}
|
|
3672
|
+
if (state.showRexc)
|
|
3673
|
+
outputs.rexc = rexc;
|
|
3812
3674
|
const result = evaluateRexc(rexc, {
|
|
3813
3675
|
vars: { ...state.vars },
|
|
3814
3676
|
refs: { ...state.refs },
|
|
@@ -3816,10 +3678,17 @@ async function startRepl() {
|
|
|
3816
3678
|
});
|
|
3817
3679
|
state.vars = result.state.vars;
|
|
3818
3680
|
state.refs = result.state.refs;
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
if (
|
|
3822
|
-
|
|
3681
|
+
if (state.showExpr)
|
|
3682
|
+
outputs.result = result.value;
|
|
3683
|
+
if (state.showVars)
|
|
3684
|
+
outputs.vars = state.vars;
|
|
3685
|
+
const order = ["source", "ir", "rexc", "vars", "result"];
|
|
3686
|
+
for (const key of order) {
|
|
3687
|
+
const value = outputs[key];
|
|
3688
|
+
if (value === undefined)
|
|
3689
|
+
continue;
|
|
3690
|
+
console.log(`${C.gray} ${key}:${C.reset} ${renderValue(value, state.outputFormat, key)}`);
|
|
3691
|
+
}
|
|
3823
3692
|
} catch (error) {
|
|
3824
3693
|
const message = error instanceof Error ? error.message : String(error);
|
|
3825
3694
|
if (message.includes("Gas limit exceeded")) {
|
|
@@ -3828,32 +3697,58 @@ async function startRepl() {
|
|
|
3828
3697
|
console.log(`${C.red} Error: ${message}${C.reset}`);
|
|
3829
3698
|
}
|
|
3830
3699
|
}
|
|
3700
|
+
}
|
|
3701
|
+
rl.on("line", async (line) => {
|
|
3702
|
+
const trimmed = line.trim();
|
|
3703
|
+
if (!multiLineBuffer && trimmed.startsWith(".")) {
|
|
3704
|
+
const result = await handleDotCommand(trimmed, state, rl, runSource, updatePromptStyles);
|
|
3705
|
+
if (result === "handled") {
|
|
3706
|
+
rl.prompt();
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3709
|
+
if (result === "handled-noprompt")
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
multiLineBuffer += (multiLineBuffer ? `
|
|
3713
|
+
` : "") + line;
|
|
3714
|
+
if (multiLineBuffer.trim() === "") {
|
|
3715
|
+
multiLineBuffer = "";
|
|
3716
|
+
rl.prompt();
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
if (isIncomplete(multiLineBuffer)) {
|
|
3720
|
+
currentPrompt = CONT_PROMPT;
|
|
3721
|
+
styledPrompt = styledCont;
|
|
3722
|
+
rl.setPrompt(CONT_PROMPT);
|
|
3723
|
+
rl.prompt();
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
const source = multiLineBuffer;
|
|
3727
|
+
multiLineBuffer = "";
|
|
3728
|
+
runSource(source);
|
|
3831
3729
|
resetPrompt();
|
|
3832
3730
|
});
|
|
3833
3731
|
rl.on("close", () => {
|
|
3732
|
+
try {
|
|
3733
|
+
const history = rl.history ?? [];
|
|
3734
|
+
const trimmed = history.slice().reverse().filter((line) => line.trim().length > 0).slice(-HISTORY_LIMIT);
|
|
3735
|
+
writeFileSync(HISTORY_PATH, `${trimmed.join(`
|
|
3736
|
+
`)}
|
|
3737
|
+
`, "utf8");
|
|
3738
|
+
} catch {}
|
|
3834
3739
|
process.exit(0);
|
|
3835
3740
|
});
|
|
3836
3741
|
rl.prompt();
|
|
3837
3742
|
}
|
|
3838
|
-
var req, version, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, GAS_LIMIT = 1e7;
|
|
3743
|
+
var req, version, colorEnabled, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, DOT_COMMANDS, GAS_LIMIT = 1e7, HISTORY_LIMIT = 1000, HISTORY_PATH;
|
|
3839
3744
|
var init_rex_repl = __esm(() => {
|
|
3840
3745
|
init_rex();
|
|
3841
3746
|
init_rexc_interpreter();
|
|
3842
3747
|
req = createRequire2(import.meta.url);
|
|
3843
3748
|
({ version } = req("./package.json"));
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
dim: "\x1B[2m",
|
|
3848
|
-
red: "\x1B[31m",
|
|
3849
|
-
green: "\x1B[32m",
|
|
3850
|
-
yellow: "\x1B[33m",
|
|
3851
|
-
blue: "\x1B[34m",
|
|
3852
|
-
cyan: "\x1B[36m",
|
|
3853
|
-
gray: "\x1B[90m",
|
|
3854
|
-
boldBlue: "\x1B[1;34m"
|
|
3855
|
-
};
|
|
3856
|
-
TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
|
|
3749
|
+
colorEnabled = process.stdout.isTTY;
|
|
3750
|
+
C = createColors(colorEnabled);
|
|
3751
|
+
TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<objKey>\b[A-Za-z_][A-Za-z0-9_-]*\b)(?=\s*:)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|nor|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)|(?<identifier>\b[A-Za-z_][A-Za-z0-9_.-]*\b)/g;
|
|
3857
3752
|
REXC_DIGITS = new Set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_");
|
|
3858
3753
|
JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
|
|
3859
3754
|
KEYWORDS = [
|
|
@@ -3867,6 +3762,7 @@ var init_rex_repl = __esm(() => {
|
|
|
3867
3762
|
"of",
|
|
3868
3763
|
"and",
|
|
3869
3764
|
"or",
|
|
3765
|
+
"nor",
|
|
3870
3766
|
"else",
|
|
3871
3767
|
"break",
|
|
3872
3768
|
"continue",
|
|
@@ -3884,20 +3780,49 @@ var init_rex_repl = __esm(() => {
|
|
|
3884
3780
|
"array",
|
|
3885
3781
|
"boolean"
|
|
3886
3782
|
];
|
|
3783
|
+
DOT_COMMANDS = [
|
|
3784
|
+
".help",
|
|
3785
|
+
".file",
|
|
3786
|
+
".cat",
|
|
3787
|
+
".expr",
|
|
3788
|
+
".source",
|
|
3789
|
+
".vars",
|
|
3790
|
+
".vars!",
|
|
3791
|
+
".clear",
|
|
3792
|
+
".ir",
|
|
3793
|
+
".rexc",
|
|
3794
|
+
".opt",
|
|
3795
|
+
".json",
|
|
3796
|
+
".color",
|
|
3797
|
+
".exit"
|
|
3798
|
+
];
|
|
3799
|
+
HISTORY_PATH = resolve(homedir(), ".rex_history");
|
|
3887
3800
|
});
|
|
3888
3801
|
|
|
3889
3802
|
// rex-cli.ts
|
|
3890
3803
|
init_rex();
|
|
3891
3804
|
init_rexc_interpreter();
|
|
3892
|
-
|
|
3805
|
+
init_rex_repl();
|
|
3806
|
+
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
3893
3807
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3894
3808
|
function parseArgs(argv) {
|
|
3895
3809
|
const options = {
|
|
3810
|
+
sources: [],
|
|
3896
3811
|
compile: false,
|
|
3897
3812
|
ir: false,
|
|
3898
3813
|
minifyNames: false,
|
|
3899
3814
|
dedupeValues: false,
|
|
3900
|
-
help: false
|
|
3815
|
+
help: false,
|
|
3816
|
+
cat: false,
|
|
3817
|
+
showSource: false,
|
|
3818
|
+
showExpr: true,
|
|
3819
|
+
showIR: false,
|
|
3820
|
+
showRexc: false,
|
|
3821
|
+
showVars: false,
|
|
3822
|
+
color: process.stdout.isTTY,
|
|
3823
|
+
colorExplicit: false,
|
|
3824
|
+
showExprExplicit: false,
|
|
3825
|
+
format: "rex"
|
|
3901
3826
|
};
|
|
3902
3827
|
for (let index = 0;index < argv.length; index += 1) {
|
|
3903
3828
|
const arg = argv[index];
|
|
@@ -3907,6 +3832,60 @@ function parseArgs(argv) {
|
|
|
3907
3832
|
options.help = true;
|
|
3908
3833
|
continue;
|
|
3909
3834
|
}
|
|
3835
|
+
if (arg === "--cat") {
|
|
3836
|
+
options.cat = true;
|
|
3837
|
+
continue;
|
|
3838
|
+
}
|
|
3839
|
+
if (arg === "--show-source") {
|
|
3840
|
+
options.showSource = true;
|
|
3841
|
+
continue;
|
|
3842
|
+
}
|
|
3843
|
+
if (arg === "--show-expr") {
|
|
3844
|
+
options.showExpr = true;
|
|
3845
|
+
options.showExprExplicit = true;
|
|
3846
|
+
continue;
|
|
3847
|
+
}
|
|
3848
|
+
if (arg === "--no-expr") {
|
|
3849
|
+
options.showExpr = false;
|
|
3850
|
+
options.showExprExplicit = true;
|
|
3851
|
+
continue;
|
|
3852
|
+
}
|
|
3853
|
+
if (arg === "--show-ir") {
|
|
3854
|
+
options.showIR = true;
|
|
3855
|
+
continue;
|
|
3856
|
+
}
|
|
3857
|
+
if (arg === "--show-rexc") {
|
|
3858
|
+
options.showRexc = true;
|
|
3859
|
+
continue;
|
|
3860
|
+
}
|
|
3861
|
+
if (arg === "--show-vars") {
|
|
3862
|
+
options.showVars = true;
|
|
3863
|
+
continue;
|
|
3864
|
+
}
|
|
3865
|
+
if (arg === "--json") {
|
|
3866
|
+
options.format = "json";
|
|
3867
|
+
continue;
|
|
3868
|
+
}
|
|
3869
|
+
if (arg === "--format") {
|
|
3870
|
+
const value = argv[index + 1];
|
|
3871
|
+
if (!value)
|
|
3872
|
+
throw new Error("Missing value for --format");
|
|
3873
|
+
if (value !== "rex" && value !== "json")
|
|
3874
|
+
throw new Error("--format must be 'rex' or 'json'");
|
|
3875
|
+
options.format = value;
|
|
3876
|
+
index += 1;
|
|
3877
|
+
continue;
|
|
3878
|
+
}
|
|
3879
|
+
if (arg === "--color") {
|
|
3880
|
+
options.color = true;
|
|
3881
|
+
options.colorExplicit = true;
|
|
3882
|
+
continue;
|
|
3883
|
+
}
|
|
3884
|
+
if (arg === "--no-color") {
|
|
3885
|
+
options.color = false;
|
|
3886
|
+
options.colorExplicit = true;
|
|
3887
|
+
continue;
|
|
3888
|
+
}
|
|
3910
3889
|
if (arg === "--compile" || arg === "-c") {
|
|
3911
3890
|
options.compile = true;
|
|
3912
3891
|
continue;
|
|
@@ -3938,7 +3917,7 @@ function parseArgs(argv) {
|
|
|
3938
3917
|
const value = argv[index + 1];
|
|
3939
3918
|
if (!value)
|
|
3940
3919
|
throw new Error("Missing value for --expr");
|
|
3941
|
-
options.expr
|
|
3920
|
+
options.sources.push({ type: "expr", value });
|
|
3942
3921
|
index += 1;
|
|
3943
3922
|
continue;
|
|
3944
3923
|
}
|
|
@@ -3946,7 +3925,7 @@ function parseArgs(argv) {
|
|
|
3946
3925
|
const value = argv[index + 1];
|
|
3947
3926
|
if (!value)
|
|
3948
3927
|
throw new Error("Missing value for --file");
|
|
3949
|
-
options.file
|
|
3928
|
+
options.sources.push({ type: "file", path: value });
|
|
3950
3929
|
index += 1;
|
|
3951
3930
|
continue;
|
|
3952
3931
|
}
|
|
@@ -3959,9 +3938,7 @@ function parseArgs(argv) {
|
|
|
3959
3938
|
continue;
|
|
3960
3939
|
}
|
|
3961
3940
|
if (!arg.startsWith("-")) {
|
|
3962
|
-
|
|
3963
|
-
throw new Error("Multiple file arguments provided");
|
|
3964
|
-
options.file = arg;
|
|
3941
|
+
options.sources.push({ type: "file", path: arg });
|
|
3965
3942
|
continue;
|
|
3966
3943
|
}
|
|
3967
3944
|
throw new Error(`Unknown option: ${arg}`);
|
|
@@ -3979,15 +3956,39 @@ function usage() {
|
|
|
3979
3956
|
" cat input.rex | rex Evaluate from stdin",
|
|
3980
3957
|
" rex -c input.rex Compile to rexc bytecode",
|
|
3981
3958
|
"",
|
|
3959
|
+
" Sources are concatenated in order, so flags and files can be mixed:",
|
|
3960
|
+
" rex -e 'max = 200' primes.rex Set max before running script",
|
|
3961
|
+
" rex primes.rex -e '42' Run script, then evaluate 42",
|
|
3962
|
+
"",
|
|
3982
3963
|
"Input:",
|
|
3983
3964
|
" <file> Evaluate/compile a Rex source file",
|
|
3984
3965
|
" -e, --expr <source> Evaluate/compile an inline expression",
|
|
3985
3966
|
" -f, --file <path> Evaluate/compile source from a file",
|
|
3986
3967
|
"",
|
|
3968
|
+
" Multiple -e and -f flags (and positional files) can be combined.",
|
|
3969
|
+
" They are concatenated in the order they appear on the command line.",
|
|
3970
|
+
"",
|
|
3987
3971
|
"Output mode:",
|
|
3988
|
-
" (default) Evaluate and output result
|
|
3989
|
-
" -c, --compile
|
|
3990
|
-
" --ir
|
|
3972
|
+
" (default) Evaluate and output result",
|
|
3973
|
+
" -c, --compile Show rexc only (same as --show-rexc --no-expr)",
|
|
3974
|
+
" --ir Show IR only (same as --show-ir --no-expr)",
|
|
3975
|
+
" --cat Print input with Rex highlighting",
|
|
3976
|
+
" --show-source Show input source text",
|
|
3977
|
+
" --show-expr Show expression result",
|
|
3978
|
+
" --no-expr Hide expression result",
|
|
3979
|
+
" --show-ir Show IR JSON",
|
|
3980
|
+
" --show-rexc Show rexc bytecode",
|
|
3981
|
+
" --show-vars Show variable state",
|
|
3982
|
+
"",
|
|
3983
|
+
"Output formatting:",
|
|
3984
|
+
" --format <type> Output format: rex|json (default: rex)",
|
|
3985
|
+
" --json Shortcut for --format json",
|
|
3986
|
+
" --color Force color output",
|
|
3987
|
+
" --no-color Disable color output",
|
|
3988
|
+
"",
|
|
3989
|
+
"TTY vs non-TTY:",
|
|
3990
|
+
" TTY output prints labeled blocks when multiple outputs are selected.",
|
|
3991
|
+
" Non-TTY output emits a single value (object if multiple outputs).",
|
|
3991
3992
|
"",
|
|
3992
3993
|
"Compile options:",
|
|
3993
3994
|
" -m, --minify-names Minify local variable names",
|
|
@@ -4008,12 +4009,17 @@ async function readStdin() {
|
|
|
4008
4009
|
return Buffer.concat(chunks).toString("utf8");
|
|
4009
4010
|
}
|
|
4010
4011
|
async function resolveSource(options) {
|
|
4011
|
-
if (options.
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4012
|
+
if (options.sources.length > 0) {
|
|
4013
|
+
const parts = [];
|
|
4014
|
+
for (const seg of options.sources) {
|
|
4015
|
+
if (seg.type === "expr")
|
|
4016
|
+
parts.push(seg.value);
|
|
4017
|
+
else
|
|
4018
|
+
parts.push(await readFile(seg.path, "utf8"));
|
|
4019
|
+
}
|
|
4020
|
+
return parts.join(`
|
|
4021
|
+
`);
|
|
4022
|
+
}
|
|
4017
4023
|
if (!process.stdin.isTTY) {
|
|
4018
4024
|
const piped = await readStdin();
|
|
4019
4025
|
if (piped.trim().length > 0)
|
|
@@ -4021,8 +4027,15 @@ async function resolveSource(options) {
|
|
|
4021
4027
|
}
|
|
4022
4028
|
throw new Error("No input provided. Use a file path, --expr, or pipe source via stdin.");
|
|
4023
4029
|
}
|
|
4030
|
+
function findFirstFilePath(sources) {
|
|
4031
|
+
for (const seg of sources) {
|
|
4032
|
+
if (seg.type === "file")
|
|
4033
|
+
return seg.path;
|
|
4034
|
+
}
|
|
4035
|
+
return;
|
|
4036
|
+
}
|
|
4024
4037
|
async function loadDomainConfigFromFolder(folderPath) {
|
|
4025
|
-
const configPath =
|
|
4038
|
+
const configPath = resolve2(folderPath, ".config.rex");
|
|
4026
4039
|
try {
|
|
4027
4040
|
return parse(await readFile(configPath, "utf8"));
|
|
4028
4041
|
} catch (error) {
|
|
@@ -4032,36 +4045,167 @@ async function loadDomainConfigFromFolder(folderPath) {
|
|
|
4032
4045
|
}
|
|
4033
4046
|
}
|
|
4034
4047
|
async function resolveDomainConfig(options) {
|
|
4035
|
-
const
|
|
4048
|
+
const filePath = findFirstFilePath(options.sources);
|
|
4049
|
+
const baseFolder = filePath ? dirname2(resolve2(filePath)) : process.cwd();
|
|
4036
4050
|
return loadDomainConfigFromFolder(baseFolder);
|
|
4037
4051
|
}
|
|
4052
|
+
function formatJson2(value, indent = 2) {
|
|
4053
|
+
const normalized = normalizeJsonValue2(value, false);
|
|
4054
|
+
const text = JSON.stringify(normalized, null, indent);
|
|
4055
|
+
return text ?? "null";
|
|
4056
|
+
}
|
|
4057
|
+
function normalizeJsonValue2(value, inArray) {
|
|
4058
|
+
if (value === undefined)
|
|
4059
|
+
return inArray ? null : undefined;
|
|
4060
|
+
if (value === null || typeof value !== "object")
|
|
4061
|
+
return value;
|
|
4062
|
+
if (Array.isArray(value)) {
|
|
4063
|
+
return value.map((item) => normalizeJsonValue2(item, true));
|
|
4064
|
+
}
|
|
4065
|
+
const out = {};
|
|
4066
|
+
for (const [key, val] of Object.entries(value)) {
|
|
4067
|
+
const normalized = normalizeJsonValue2(val, false);
|
|
4068
|
+
if (normalized !== undefined)
|
|
4069
|
+
out[key] = normalized;
|
|
4070
|
+
}
|
|
4071
|
+
return out;
|
|
4072
|
+
}
|
|
4073
|
+
function isRexSource(source) {
|
|
4074
|
+
try {
|
|
4075
|
+
return grammar.match(source).succeeded();
|
|
4076
|
+
} catch {
|
|
4077
|
+
return false;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4038
4080
|
async function main() {
|
|
4039
4081
|
const options = parseArgs(process.argv.slice(2));
|
|
4040
4082
|
if (options.help) {
|
|
4041
4083
|
console.log(usage());
|
|
4042
4084
|
return;
|
|
4043
4085
|
}
|
|
4044
|
-
|
|
4086
|
+
setColorEnabled(options.color);
|
|
4087
|
+
if (options.cat) {
|
|
4088
|
+
const source2 = await resolveSource(options);
|
|
4089
|
+
const hasRexc = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rexc"));
|
|
4090
|
+
const hasRex = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rex"));
|
|
4091
|
+
const hint = hasRexc && !hasRex ? "rexc" : hasRex && !hasRexc ? "rex" : undefined;
|
|
4092
|
+
const output2 = process.stdout.isTTY && options.color ? highlightAuto(source2, hint) : source2;
|
|
4093
|
+
if (options.out) {
|
|
4094
|
+
await writeFile(options.out, `${output2}
|
|
4095
|
+
`, "utf8");
|
|
4096
|
+
return;
|
|
4097
|
+
}
|
|
4098
|
+
console.log(output2);
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
const hasSource = options.sources.length > 0 || !process.stdin.isTTY;
|
|
4045
4102
|
if (!hasSource && !options.compile && !options.ir) {
|
|
4046
4103
|
const { startRepl: startRepl2 } = await Promise.resolve().then(() => (init_rex_repl(), exports_rex_repl));
|
|
4047
4104
|
await startRepl2();
|
|
4048
4105
|
return;
|
|
4049
4106
|
}
|
|
4050
4107
|
const source = await resolveSource(options);
|
|
4051
|
-
|
|
4108
|
+
const sourceIsRex = isRexSource(source);
|
|
4109
|
+
if (options.compile) {
|
|
4110
|
+
if (!sourceIsRex)
|
|
4111
|
+
throw new Error("--compile requires Rex source");
|
|
4112
|
+
options.showRexc = true;
|
|
4113
|
+
if (!options.showExprExplicit)
|
|
4114
|
+
options.showExpr = false;
|
|
4115
|
+
}
|
|
4052
4116
|
if (options.ir) {
|
|
4053
|
-
|
|
4054
|
-
|
|
4117
|
+
if (!sourceIsRex)
|
|
4118
|
+
throw new Error("--ir requires Rex source");
|
|
4119
|
+
options.showIR = true;
|
|
4120
|
+
if (!options.showExprExplicit)
|
|
4121
|
+
options.showExpr = false;
|
|
4122
|
+
}
|
|
4123
|
+
const outputFlags = [options.showSource, options.showExpr, options.showIR, options.showRexc, options.showVars].filter(Boolean).length;
|
|
4124
|
+
if (outputFlags === 0) {
|
|
4125
|
+
throw new Error("No output selected. Use --show-source, --show-expr, --show-ir, --show-rexc, or --show-vars.");
|
|
4126
|
+
}
|
|
4127
|
+
const humanMode = process.stdout.isTTY && options.color;
|
|
4128
|
+
const outputs = {};
|
|
4129
|
+
if (options.showSource)
|
|
4130
|
+
outputs.source = source;
|
|
4131
|
+
if (!sourceIsRex && options.showIR) {
|
|
4132
|
+
throw new Error("--show-ir is only available for Rex source");
|
|
4133
|
+
}
|
|
4134
|
+
if (options.showIR) {
|
|
4135
|
+
outputs.ir = parseToIR(source);
|
|
4136
|
+
}
|
|
4137
|
+
if (options.showRexc) {
|
|
4055
4138
|
const domainConfig = await resolveDomainConfig(options);
|
|
4056
|
-
|
|
4139
|
+
outputs.rexc = sourceIsRex ? compile(source, {
|
|
4057
4140
|
minifyNames: options.minifyNames,
|
|
4058
4141
|
dedupeValues: options.dedupeValues,
|
|
4059
4142
|
dedupeMinBytes: options.dedupeMinBytes,
|
|
4060
4143
|
domainConfig
|
|
4061
|
-
});
|
|
4144
|
+
}) : source;
|
|
4145
|
+
}
|
|
4146
|
+
if (options.showExpr || options.showVars) {
|
|
4147
|
+
const result = sourceIsRex ? evaluateSource(source) : evaluateRexc(source);
|
|
4148
|
+
if (options.showExpr)
|
|
4149
|
+
outputs.result = result.value;
|
|
4150
|
+
if (options.showVars)
|
|
4151
|
+
outputs.vars = result.state.vars;
|
|
4152
|
+
}
|
|
4153
|
+
const order = ["source", "ir", "rexc", "vars", "result"];
|
|
4154
|
+
const selected = order.filter((key) => outputs[key] !== undefined);
|
|
4155
|
+
function formatValue(value, kind) {
|
|
4156
|
+
if (kind === "source") {
|
|
4157
|
+
const raw2 = String(value ?? "");
|
|
4158
|
+
if (options.format === "json") {
|
|
4159
|
+
const json = formatJson2(raw2, 2);
|
|
4160
|
+
return humanMode && options.color ? highlightJSON(json) : json;
|
|
4161
|
+
}
|
|
4162
|
+
return humanMode && options.color ? highlightAuto(raw2) : raw2;
|
|
4163
|
+
}
|
|
4164
|
+
if (kind === "rexc" && humanMode) {
|
|
4165
|
+
const raw2 = String(value ?? "");
|
|
4166
|
+
return options.color ? highlightAuto(raw2, "rexc") : raw2;
|
|
4167
|
+
}
|
|
4168
|
+
if (options.format === "json") {
|
|
4169
|
+
const raw2 = formatJson2(value, 2);
|
|
4170
|
+
return humanMode && options.color ? highlightJSON(raw2) : raw2;
|
|
4171
|
+
}
|
|
4172
|
+
if (kind === "rexc") {
|
|
4173
|
+
const raw2 = stringify(String(value ?? ""));
|
|
4174
|
+
return humanMode && options.color ? highlightLine(raw2) : raw2;
|
|
4175
|
+
}
|
|
4176
|
+
const raw = stringify(value);
|
|
4177
|
+
return humanMode && options.color ? highlightLine(raw) : raw;
|
|
4178
|
+
}
|
|
4179
|
+
let output;
|
|
4180
|
+
if (humanMode) {
|
|
4181
|
+
const header = options.color ? "\x1B[90m" : "";
|
|
4182
|
+
const reset = options.color ? "\x1B[0m" : "";
|
|
4183
|
+
const lines = [];
|
|
4184
|
+
for (const key of order) {
|
|
4185
|
+
const value = outputs[key];
|
|
4186
|
+
if (value === undefined)
|
|
4187
|
+
continue;
|
|
4188
|
+
lines.push(`${header}${key}:${reset} ${formatValue(value, key)}`);
|
|
4189
|
+
}
|
|
4190
|
+
output = lines.join(`
|
|
4191
|
+
`);
|
|
4062
4192
|
} else {
|
|
4063
|
-
|
|
4064
|
-
|
|
4193
|
+
let value;
|
|
4194
|
+
if (selected.length === 1) {
|
|
4195
|
+
const only = selected[0];
|
|
4196
|
+
if (!only)
|
|
4197
|
+
throw new Error("No output selected.");
|
|
4198
|
+
value = outputs[only];
|
|
4199
|
+
} else {
|
|
4200
|
+
const out = {};
|
|
4201
|
+
for (const key of order) {
|
|
4202
|
+
const v = outputs[key];
|
|
4203
|
+
if (v !== undefined)
|
|
4204
|
+
out[key] = v;
|
|
4205
|
+
}
|
|
4206
|
+
value = out;
|
|
4207
|
+
}
|
|
4208
|
+
output = options.format === "json" ? formatJson2(value, 2) : stringify(value);
|
|
4065
4209
|
}
|
|
4066
4210
|
if (options.out) {
|
|
4067
4211
|
await writeFile(options.out, `${output}
|