@creationix/rex 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/rex-cli.js +615 -269
- package/rex-cli.ts +32 -11
- package/rex-repl.js +582 -256
- package/rex-repl.ts +4 -3
- package/rex.js +310 -180
- package/rex.ohm +50 -22
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +26 -8
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +342 -201
- package/rexc-interpreter.ts +262 -85
package/rex-repl.js
CHANGED
|
@@ -14,31 +14,32 @@ function byteLength(value) {
|
|
|
14
14
|
return Buffer.byteLength(value, "utf8");
|
|
15
15
|
}
|
|
16
16
|
var OPCODE_IDS = {
|
|
17
|
-
do:
|
|
18
|
-
add:
|
|
19
|
-
sub:
|
|
20
|
-
mul:
|
|
21
|
-
div:
|
|
22
|
-
eq:
|
|
23
|
-
neq:
|
|
24
|
-
lt:
|
|
25
|
-
lte:
|
|
26
|
-
gt:
|
|
27
|
-
gte:
|
|
28
|
-
and:
|
|
29
|
-
or:
|
|
30
|
-
xor:
|
|
31
|
-
not:
|
|
32
|
-
boolean:
|
|
33
|
-
number:
|
|
34
|
-
string:
|
|
35
|
-
array:
|
|
36
|
-
object:
|
|
37
|
-
mod:
|
|
38
|
-
neg:
|
|
17
|
+
do: "",
|
|
18
|
+
add: "ad",
|
|
19
|
+
sub: "sb",
|
|
20
|
+
mul: "ml",
|
|
21
|
+
div: "dv",
|
|
22
|
+
eq: "eq",
|
|
23
|
+
neq: "nq",
|
|
24
|
+
lt: "lt",
|
|
25
|
+
lte: "le",
|
|
26
|
+
gt: "gt",
|
|
27
|
+
gte: "ge",
|
|
28
|
+
and: "an",
|
|
29
|
+
or: "or",
|
|
30
|
+
xor: "xr",
|
|
31
|
+
not: "nt",
|
|
32
|
+
boolean: "bt",
|
|
33
|
+
number: "nm",
|
|
34
|
+
string: "st",
|
|
35
|
+
array: "ar",
|
|
36
|
+
object: "ob",
|
|
37
|
+
mod: "md",
|
|
38
|
+
neg: "ng",
|
|
39
|
+
range: "rn",
|
|
40
|
+
size: "sz"
|
|
39
41
|
};
|
|
40
|
-
var
|
|
41
|
-
var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
|
|
42
|
+
var KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object", "size"]);
|
|
42
43
|
var BINARY_TO_OPCODE = {
|
|
43
44
|
add: "add",
|
|
44
45
|
sub: "sub",
|
|
@@ -167,11 +168,11 @@ function encodeDecimal(significand, power) {
|
|
|
167
168
|
function encodeNumberNode(node) {
|
|
168
169
|
const numberValue = node.value;
|
|
169
170
|
if (Number.isNaN(numberValue))
|
|
170
|
-
return "
|
|
171
|
+
return "nan'";
|
|
171
172
|
if (numberValue === Infinity)
|
|
172
|
-
return "
|
|
173
|
+
return "inf'";
|
|
173
174
|
if (numberValue === -Infinity)
|
|
174
|
-
return "
|
|
175
|
+
return "nif'";
|
|
175
176
|
if (Number.isInteger(numberValue)) {
|
|
176
177
|
const { base, exp } = splitDecimal(numberValue);
|
|
177
178
|
if (exp >= 0 && exp <= 4)
|
|
@@ -203,7 +204,7 @@ function encodeNumberNode(node) {
|
|
|
203
204
|
return encodeDecimal(significand, power);
|
|
204
205
|
}
|
|
205
206
|
function encodeOpcode(opcode) {
|
|
206
|
-
return `${
|
|
207
|
+
return `${OPCODE_IDS[opcode]}%`;
|
|
207
208
|
}
|
|
208
209
|
function encodeCallParts(parts) {
|
|
209
210
|
return `(${parts.join("")})`;
|
|
@@ -220,7 +221,7 @@ function addOptionalPrefix(encoded) {
|
|
|
220
221
|
let payload = encoded;
|
|
221
222
|
if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
|
|
222
223
|
payload = encoded.slice(2, -1);
|
|
223
|
-
} else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
|
|
224
|
+
} else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
|
|
224
225
|
payload = encoded.slice(2, -1);
|
|
225
226
|
} else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
|
|
226
227
|
payload = encoded.slice(1, -1);
|
|
@@ -231,7 +232,7 @@ function addOptionalPrefix(encoded) {
|
|
|
231
232
|
}
|
|
232
233
|
function encodeBlockExpression(block) {
|
|
233
234
|
if (block.length === 0)
|
|
234
|
-
return "
|
|
235
|
+
return "un'";
|
|
235
236
|
if (block.length === 1)
|
|
236
237
|
return encodeNode(block[0]);
|
|
237
238
|
return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
|
|
@@ -248,9 +249,13 @@ function encodeConditionalElse(elseBranch) {
|
|
|
248
249
|
};
|
|
249
250
|
return encodeNode(nested);
|
|
250
251
|
}
|
|
252
|
+
function encodeDomainLookup(shortCode, tag) {
|
|
253
|
+
return `${shortCode}${tag}`;
|
|
254
|
+
}
|
|
251
255
|
function encodeNavigation(node) {
|
|
252
256
|
const domainRefs = activeEncodeOptions?.domainRefs;
|
|
253
|
-
|
|
257
|
+
const domainOpcodes = activeEncodeOptions?.domainOpcodes;
|
|
258
|
+
if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
|
|
254
259
|
const staticPath = [node.target.name];
|
|
255
260
|
for (const segment of node.segments) {
|
|
256
261
|
if (segment.type !== "static")
|
|
@@ -259,14 +264,17 @@ function encodeNavigation(node) {
|
|
|
259
264
|
}
|
|
260
265
|
for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
|
|
261
266
|
const dottedName = staticPath.slice(0, pathLength).join(".");
|
|
262
|
-
const
|
|
263
|
-
|
|
267
|
+
const refCode = domainRefs?.[dottedName];
|
|
268
|
+
const opcodeCode = domainOpcodes?.[dottedName];
|
|
269
|
+
const shortCode = refCode ?? opcodeCode;
|
|
270
|
+
if (shortCode === undefined)
|
|
264
271
|
continue;
|
|
272
|
+
const tag = refCode !== undefined ? "'" : "%";
|
|
265
273
|
const consumedStaticSegments = pathLength - 1;
|
|
266
274
|
if (consumedStaticSegments === node.segments.length) {
|
|
267
|
-
return
|
|
275
|
+
return encodeDomainLookup(shortCode, tag);
|
|
268
276
|
}
|
|
269
|
-
const parts2 = [
|
|
277
|
+
const parts2 = [encodeDomainLookup(shortCode, tag)];
|
|
270
278
|
for (const segment of node.segments.slice(consumedStaticSegments)) {
|
|
271
279
|
if (segment.type === "static")
|
|
272
280
|
parts2.push(encodeBareOrLengthString(segment.key));
|
|
@@ -292,9 +300,12 @@ function encodeWhile(node) {
|
|
|
292
300
|
}
|
|
293
301
|
function encodeFor(node) {
|
|
294
302
|
const body = addOptionalPrefix(encodeBlockExpression(node.body));
|
|
295
|
-
if (node.binding.type === "binding:
|
|
303
|
+
if (node.binding.type === "binding:bareIn") {
|
|
296
304
|
return `>(${encodeNode(node.binding.source)}${body})`;
|
|
297
305
|
}
|
|
306
|
+
if (node.binding.type === "binding:bareOf") {
|
|
307
|
+
return `<(${encodeNode(node.binding.source)}${body})`;
|
|
308
|
+
}
|
|
298
309
|
if (node.binding.type === "binding:valueIn") {
|
|
299
310
|
return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
|
|
300
311
|
}
|
|
@@ -305,30 +316,47 @@ function encodeFor(node) {
|
|
|
305
316
|
}
|
|
306
317
|
function encodeArrayComprehension(node) {
|
|
307
318
|
const body = addOptionalPrefix(encodeNode(node.body));
|
|
308
|
-
if (node.binding.type === "binding:
|
|
319
|
+
if (node.binding.type === "binding:bareIn") {
|
|
309
320
|
return `>[${encodeNode(node.binding.source)}${body}]`;
|
|
310
321
|
}
|
|
322
|
+
if (node.binding.type === "binding:bareOf") {
|
|
323
|
+
return `<[${encodeNode(node.binding.source)}${body}]`;
|
|
324
|
+
}
|
|
311
325
|
if (node.binding.type === "binding:valueIn") {
|
|
312
326
|
return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
|
|
313
327
|
}
|
|
314
328
|
if (node.binding.type === "binding:keyValueIn") {
|
|
315
329
|
return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
|
|
316
330
|
}
|
|
317
|
-
return
|
|
331
|
+
return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
|
|
318
332
|
}
|
|
319
333
|
function encodeObjectComprehension(node) {
|
|
320
334
|
const key = addOptionalPrefix(encodeNode(node.key));
|
|
321
335
|
const value = addOptionalPrefix(encodeNode(node.value));
|
|
322
|
-
if (node.binding.type === "binding:
|
|
336
|
+
if (node.binding.type === "binding:bareIn") {
|
|
323
337
|
return `>{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
324
338
|
}
|
|
339
|
+
if (node.binding.type === "binding:bareOf") {
|
|
340
|
+
return `<{${encodeNode(node.binding.source)}${key}${value}}`;
|
|
341
|
+
}
|
|
325
342
|
if (node.binding.type === "binding:valueIn") {
|
|
326
343
|
return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
|
|
327
344
|
}
|
|
328
345
|
if (node.binding.type === "binding:keyValueIn") {
|
|
329
346
|
return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
|
|
330
347
|
}
|
|
331
|
-
return
|
|
348
|
+
return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
|
|
349
|
+
}
|
|
350
|
+
function encodeWhileArrayComprehension(node) {
|
|
351
|
+
const cond = encodeNode(node.condition);
|
|
352
|
+
const body = addOptionalPrefix(encodeNode(node.body));
|
|
353
|
+
return `#[${cond}${body}]`;
|
|
354
|
+
}
|
|
355
|
+
function encodeWhileObjectComprehension(node) {
|
|
356
|
+
const cond = encodeNode(node.condition);
|
|
357
|
+
const key = addOptionalPrefix(encodeNode(node.key));
|
|
358
|
+
const value = addOptionalPrefix(encodeNode(node.value));
|
|
359
|
+
return `#{${cond}${key}${value}}`;
|
|
332
360
|
}
|
|
333
361
|
var activeEncodeOptions;
|
|
334
362
|
function encodeNode(node) {
|
|
@@ -338,7 +366,10 @@ function encodeNode(node) {
|
|
|
338
366
|
case "identifier": {
|
|
339
367
|
const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
|
|
340
368
|
if (domainRef !== undefined)
|
|
341
|
-
return `${
|
|
369
|
+
return `${domainRef}'`;
|
|
370
|
+
const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
|
|
371
|
+
if (domainOpcode !== undefined)
|
|
372
|
+
return `${domainOpcode}%`;
|
|
342
373
|
return `${node.name}$`;
|
|
343
374
|
}
|
|
344
375
|
case "self":
|
|
@@ -351,11 +382,11 @@ function encodeNode(node) {
|
|
|
351
382
|
return `${encodeUint(node.depth - 1)}@`;
|
|
352
383
|
}
|
|
353
384
|
case "boolean":
|
|
354
|
-
return node.value ? "
|
|
385
|
+
return node.value ? "tr'" : "fl'";
|
|
355
386
|
case "null":
|
|
356
|
-
return "
|
|
387
|
+
return "nl'";
|
|
357
388
|
case "undefined":
|
|
358
|
-
return "
|
|
389
|
+
return "un'";
|
|
359
390
|
case "number":
|
|
360
391
|
return encodeNumberNode(node);
|
|
361
392
|
case "string":
|
|
@@ -366,12 +397,16 @@ function encodeNode(node) {
|
|
|
366
397
|
}
|
|
367
398
|
case "arrayComprehension":
|
|
368
399
|
return encodeArrayComprehension(node);
|
|
400
|
+
case "whileArrayComprehension":
|
|
401
|
+
return encodeWhileArrayComprehension(node);
|
|
369
402
|
case "object": {
|
|
370
403
|
const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
|
|
371
404
|
return `{${body}}`;
|
|
372
405
|
}
|
|
373
406
|
case "objectComprehension":
|
|
374
407
|
return encodeObjectComprehension(node);
|
|
408
|
+
case "whileObjectComprehension":
|
|
409
|
+
return encodeWhileObjectComprehension(node);
|
|
375
410
|
case "key":
|
|
376
411
|
return encodeBareOrLengthString(node.name);
|
|
377
412
|
case "group":
|
|
@@ -381,6 +416,10 @@ function encodeNode(node) {
|
|
|
381
416
|
return `~${encodeNode(node.value)}`;
|
|
382
417
|
if (node.op === "neg")
|
|
383
418
|
return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
|
|
419
|
+
if (node.op === "logicalNot") {
|
|
420
|
+
const val = encodeNode(node.value);
|
|
421
|
+
return `!(${val}tr')`;
|
|
422
|
+
}
|
|
384
423
|
return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
|
|
385
424
|
case "binary":
|
|
386
425
|
if (node.op === "and") {
|
|
@@ -399,12 +438,19 @@ function encodeNode(node) {
|
|
|
399
438
|
}).join("");
|
|
400
439
|
return `|(${body})`;
|
|
401
440
|
}
|
|
441
|
+
if (node.op === "nor") {
|
|
442
|
+
const left = encodeNode(node.left);
|
|
443
|
+
const right = addOptionalPrefix(encodeNode(node.right));
|
|
444
|
+
return `!(${left}${right})`;
|
|
445
|
+
}
|
|
402
446
|
return encodeCallParts([
|
|
403
447
|
encodeOpcode(BINARY_TO_OPCODE[node.op]),
|
|
404
448
|
encodeNode(node.left),
|
|
405
449
|
encodeNode(node.right)
|
|
406
450
|
]);
|
|
407
451
|
case "assign": {
|
|
452
|
+
if (node.op === ":=")
|
|
453
|
+
return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
408
454
|
if (node.op === "=")
|
|
409
455
|
return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
|
|
410
456
|
const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
|
|
@@ -415,8 +461,12 @@ function encodeNode(node) {
|
|
|
415
461
|
}
|
|
416
462
|
case "navigation":
|
|
417
463
|
return encodeNavigation(node);
|
|
418
|
-
case "call":
|
|
464
|
+
case "call": {
|
|
465
|
+
if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
|
|
466
|
+
return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
|
|
467
|
+
}
|
|
419
468
|
return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
|
|
469
|
+
}
|
|
420
470
|
case "conditional": {
|
|
421
471
|
const opener = node.head === "when" ? "?(" : "!(";
|
|
422
472
|
const cond = encodeNode(node.condition);
|
|
@@ -424,6 +474,8 @@ function encodeNode(node) {
|
|
|
424
474
|
const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
|
|
425
475
|
return `${opener}${cond}${thenExpr}${elseExpr})`;
|
|
426
476
|
}
|
|
477
|
+
case "range":
|
|
478
|
+
return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
|
|
427
479
|
case "for":
|
|
428
480
|
return encodeFor(node);
|
|
429
481
|
case "while":
|
|
@@ -554,78 +606,55 @@ function domainRefsFromConfig(config) {
|
|
|
554
606
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
555
607
|
throw new Error("Domain config must be an object");
|
|
556
608
|
}
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
609
|
+
const configObj = config;
|
|
610
|
+
const domainRefs = {};
|
|
611
|
+
const domainOpcodes = {};
|
|
612
|
+
const dataSection = configObj.data;
|
|
613
|
+
if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
|
|
614
|
+
mapConfigEntries(dataSection, domainRefs);
|
|
562
615
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (!refText)
|
|
567
|
-
throw new Error("Domain ref key cannot be empty");
|
|
568
|
-
if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
|
|
569
|
-
throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
|
|
570
|
-
}
|
|
571
|
-
if (refText.length > 1 && refText[0] === "0") {
|
|
572
|
-
throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
|
|
616
|
+
const functionsSection = configObj.functions;
|
|
617
|
+
if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
|
|
618
|
+
mapConfigEntries(functionsSection, domainOpcodes);
|
|
573
619
|
}
|
|
574
|
-
|
|
575
|
-
throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
|
|
576
|
-
}
|
|
577
|
-
let value = 0;
|
|
578
|
-
for (const char of refText) {
|
|
579
|
-
const digit = DOMAIN_DIGIT_INDEX.get(char);
|
|
580
|
-
if (digit === undefined)
|
|
581
|
-
throw new Error(`Invalid domain ref key '${refText}'`);
|
|
582
|
-
value = value * 64 + digit;
|
|
583
|
-
if (value > Number.MAX_SAFE_INTEGER) {
|
|
584
|
-
throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (value < FIRST_NON_RESERVED_REF) {
|
|
588
|
-
throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
|
|
589
|
-
}
|
|
590
|
-
return value;
|
|
620
|
+
return { domainRefs, domainOpcodes };
|
|
591
621
|
}
|
|
592
622
|
function mapConfigEntries(entries, refs) {
|
|
593
623
|
const sourceKindByRoot = new Map;
|
|
594
624
|
for (const root of Object.keys(refs)) {
|
|
595
625
|
sourceKindByRoot.set(root, "explicit");
|
|
596
626
|
}
|
|
597
|
-
for (const [
|
|
627
|
+
for (const [shortCode, rawEntry] of Object.entries(entries)) {
|
|
598
628
|
const entry = rawEntry;
|
|
599
629
|
if (!entry || typeof entry !== "object")
|
|
600
630
|
continue;
|
|
601
631
|
if (!Array.isArray(entry.names))
|
|
602
632
|
continue;
|
|
603
|
-
const refId = decodeDomainRefKey(refText);
|
|
604
633
|
for (const rawName of entry.names) {
|
|
605
634
|
if (typeof rawName !== "string")
|
|
606
635
|
continue;
|
|
607
|
-
const
|
|
608
|
-
if (
|
|
609
|
-
throw new Error(`Conflicting refs for '${rawName}': ${
|
|
636
|
+
const existingRef = refs[rawName];
|
|
637
|
+
if (existingRef !== undefined && existingRef !== shortCode) {
|
|
638
|
+
throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
|
|
610
639
|
}
|
|
611
|
-
refs[rawName] =
|
|
640
|
+
refs[rawName] = shortCode;
|
|
612
641
|
const root = rawName.split(".")[0];
|
|
613
642
|
if (!root)
|
|
614
643
|
continue;
|
|
615
644
|
const currentKind = rawName.includes(".") ? "implicit" : "explicit";
|
|
616
645
|
const existing = refs[root];
|
|
617
646
|
if (existing !== undefined) {
|
|
618
|
-
if (existing ===
|
|
647
|
+
if (existing === shortCode)
|
|
619
648
|
continue;
|
|
620
649
|
const existingKind = sourceKindByRoot.get(root) ?? "explicit";
|
|
621
650
|
if (currentKind === "explicit") {
|
|
622
|
-
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${
|
|
651
|
+
throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
|
|
623
652
|
}
|
|
624
653
|
if (existingKind === "explicit")
|
|
625
654
|
continue;
|
|
626
655
|
continue;
|
|
627
656
|
}
|
|
628
|
-
refs[root] =
|
|
657
|
+
refs[root] = shortCode;
|
|
629
658
|
sourceKindByRoot.set(root, currentKind);
|
|
630
659
|
}
|
|
631
660
|
}
|
|
@@ -909,6 +938,21 @@ function dropBindingNames(env, binding) {
|
|
|
909
938
|
clearBinding(env, binding.key);
|
|
910
939
|
}
|
|
911
940
|
}
|
|
941
|
+
function optimizeBinding(binding, sourceEnv, currentDepth) {
|
|
942
|
+
const source = optimizeNode(binding.source, sourceEnv, currentDepth);
|
|
943
|
+
switch (binding.type) {
|
|
944
|
+
case "binding:bareIn":
|
|
945
|
+
return { type: "binding:bareIn", source };
|
|
946
|
+
case "binding:bareOf":
|
|
947
|
+
return { type: "binding:bareOf", source };
|
|
948
|
+
case "binding:valueIn":
|
|
949
|
+
return { type: "binding:valueIn", value: binding.value, source };
|
|
950
|
+
case "binding:keyValueIn":
|
|
951
|
+
return { type: "binding:keyValueIn", key: binding.key, value: binding.value, source };
|
|
952
|
+
case "binding:keyOf":
|
|
953
|
+
return { type: "binding:keyOf", key: binding.key, source };
|
|
954
|
+
}
|
|
955
|
+
}
|
|
912
956
|
function collectReads(node, out) {
|
|
913
957
|
switch (node.type) {
|
|
914
958
|
case "identifier":
|
|
@@ -931,11 +975,20 @@ function collectReads(node, out) {
|
|
|
931
975
|
collectReads(node.binding.source, out);
|
|
932
976
|
collectReads(node.body, out);
|
|
933
977
|
return;
|
|
978
|
+
case "whileArrayComprehension":
|
|
979
|
+
collectReads(node.condition, out);
|
|
980
|
+
collectReads(node.body, out);
|
|
981
|
+
return;
|
|
934
982
|
case "objectComprehension":
|
|
935
983
|
collectReads(node.binding.source, out);
|
|
936
984
|
collectReads(node.key, out);
|
|
937
985
|
collectReads(node.value, out);
|
|
938
986
|
return;
|
|
987
|
+
case "whileObjectComprehension":
|
|
988
|
+
collectReads(node.condition, out);
|
|
989
|
+
collectReads(node.key, out);
|
|
990
|
+
collectReads(node.value, out);
|
|
991
|
+
return;
|
|
939
992
|
case "unary":
|
|
940
993
|
collectReads(node.value, out);
|
|
941
994
|
return;
|
|
@@ -972,6 +1025,10 @@ function collectReads(node, out) {
|
|
|
972
1025
|
for (const part of node.body)
|
|
973
1026
|
collectReads(part, out);
|
|
974
1027
|
return;
|
|
1028
|
+
case "range":
|
|
1029
|
+
collectReads(node.from, out);
|
|
1030
|
+
collectReads(node.to, out);
|
|
1031
|
+
return;
|
|
975
1032
|
case "program":
|
|
976
1033
|
for (const part of node.body)
|
|
977
1034
|
collectReads(part, out);
|
|
@@ -1016,6 +1073,8 @@ function isPureNode(node) {
|
|
|
1016
1073
|
return node.op !== "delete" && isPureNode(node.value);
|
|
1017
1074
|
case "binary":
|
|
1018
1075
|
return isPureNode(node.left) && isPureNode(node.right);
|
|
1076
|
+
case "range":
|
|
1077
|
+
return isPureNode(node.from) && isPureNode(node.to);
|
|
1019
1078
|
default:
|
|
1020
1079
|
return false;
|
|
1021
1080
|
}
|
|
@@ -1080,6 +1139,8 @@ function hasIdentifierRead(node, name, asPlace = false) {
|
|
|
1080
1139
|
return hasIdentifierRead(node.value, name, node.op === "delete");
|
|
1081
1140
|
case "binary":
|
|
1082
1141
|
return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
|
|
1142
|
+
case "range":
|
|
1143
|
+
return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
|
|
1083
1144
|
case "assign":
|
|
1084
1145
|
return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
|
|
1085
1146
|
default:
|
|
@@ -1102,6 +1163,8 @@ function countIdentifierReads(node, name, asPlace = false) {
|
|
|
1102
1163
|
return countIdentifierReads(node.value, name, node.op === "delete");
|
|
1103
1164
|
case "binary":
|
|
1104
1165
|
return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
|
|
1166
|
+
case "range":
|
|
1167
|
+
return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
|
|
1105
1168
|
case "assign":
|
|
1106
1169
|
return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
|
|
1107
1170
|
default:
|
|
@@ -1156,6 +1219,12 @@ function replaceIdentifier(node, name, replacement, asPlace = false) {
|
|
|
1156
1219
|
place: replaceIdentifier(node.place, name, replacement, true),
|
|
1157
1220
|
value: replaceIdentifier(node.value, name, replacement)
|
|
1158
1221
|
};
|
|
1222
|
+
case "range":
|
|
1223
|
+
return {
|
|
1224
|
+
type: "range",
|
|
1225
|
+
from: replaceIdentifier(node.from, name, replacement),
|
|
1226
|
+
to: replaceIdentifier(node.to, name, replacement)
|
|
1227
|
+
};
|
|
1159
1228
|
default:
|
|
1160
1229
|
return node;
|
|
1161
1230
|
}
|
|
@@ -1300,6 +1369,9 @@ function foldUnary(op, value) {
|
|
|
1300
1369
|
return ~value;
|
|
1301
1370
|
return;
|
|
1302
1371
|
}
|
|
1372
|
+
if (op === "logicalNot") {
|
|
1373
|
+
return value === undefined ? true : undefined;
|
|
1374
|
+
}
|
|
1303
1375
|
return;
|
|
1304
1376
|
}
|
|
1305
1377
|
function foldBinary(op, left, right) {
|
|
@@ -1470,6 +1542,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
|
1470
1542
|
}
|
|
1471
1543
|
return { type: "binary", op: node.op, left, right };
|
|
1472
1544
|
}
|
|
1545
|
+
case "range":
|
|
1546
|
+
return {
|
|
1547
|
+
type: "range",
|
|
1548
|
+
from: optimizeNode(node.from, env, currentDepth),
|
|
1549
|
+
to: optimizeNode(node.to, env, currentDepth)
|
|
1550
|
+
};
|
|
1473
1551
|
case "navigation": {
|
|
1474
1552
|
const target = optimizeNode(node.target, env, currentDepth);
|
|
1475
1553
|
const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
|
|
@@ -1580,31 +1658,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
|
1580
1658
|
}
|
|
1581
1659
|
case "for": {
|
|
1582
1660
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1583
|
-
const binding = (
|
|
1584
|
-
if (node.binding.type === "binding:expr") {
|
|
1585
|
-
return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
|
|
1586
|
-
}
|
|
1587
|
-
if (node.binding.type === "binding:valueIn") {
|
|
1588
|
-
return {
|
|
1589
|
-
type: "binding:valueIn",
|
|
1590
|
-
value: node.binding.value,
|
|
1591
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
if (node.binding.type === "binding:keyValueIn") {
|
|
1595
|
-
return {
|
|
1596
|
-
type: "binding:keyValueIn",
|
|
1597
|
-
key: node.binding.key,
|
|
1598
|
-
value: node.binding.value,
|
|
1599
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
return {
|
|
1603
|
-
type: "binding:keyOf",
|
|
1604
|
-
key: node.binding.key,
|
|
1605
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1606
|
-
};
|
|
1607
|
-
})();
|
|
1661
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1608
1662
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1609
1663
|
dropBindingNames(bodyEnv, binding);
|
|
1610
1664
|
return {
|
|
@@ -1615,20 +1669,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
|
1615
1669
|
}
|
|
1616
1670
|
case "arrayComprehension": {
|
|
1617
1671
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1618
|
-
const binding =
|
|
1619
|
-
type: "binding:valueIn",
|
|
1620
|
-
value: node.binding.value,
|
|
1621
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1622
|
-
} : node.binding.type === "binding:keyValueIn" ? {
|
|
1623
|
-
type: "binding:keyValueIn",
|
|
1624
|
-
key: node.binding.key,
|
|
1625
|
-
value: node.binding.value,
|
|
1626
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1627
|
-
} : {
|
|
1628
|
-
type: "binding:keyOf",
|
|
1629
|
-
key: node.binding.key,
|
|
1630
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1631
|
-
};
|
|
1672
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1632
1673
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1633
1674
|
dropBindingNames(bodyEnv, binding);
|
|
1634
1675
|
return {
|
|
@@ -1637,22 +1678,15 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
|
1637
1678
|
body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
|
|
1638
1679
|
};
|
|
1639
1680
|
}
|
|
1681
|
+
case "whileArrayComprehension":
|
|
1682
|
+
return {
|
|
1683
|
+
type: "whileArrayComprehension",
|
|
1684
|
+
condition: optimizeNode(node.condition, env, currentDepth),
|
|
1685
|
+
body: optimizeNode(node.body, env, currentDepth + 1)
|
|
1686
|
+
};
|
|
1640
1687
|
case "objectComprehension": {
|
|
1641
1688
|
const sourceEnv = cloneOptimizeEnv(env);
|
|
1642
|
-
const binding =
|
|
1643
|
-
type: "binding:valueIn",
|
|
1644
|
-
value: node.binding.value,
|
|
1645
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1646
|
-
} : node.binding.type === "binding:keyValueIn" ? {
|
|
1647
|
-
type: "binding:keyValueIn",
|
|
1648
|
-
key: node.binding.key,
|
|
1649
|
-
value: node.binding.value,
|
|
1650
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1651
|
-
} : {
|
|
1652
|
-
type: "binding:keyOf",
|
|
1653
|
-
key: node.binding.key,
|
|
1654
|
-
source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
|
|
1655
|
-
};
|
|
1689
|
+
const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
|
|
1656
1690
|
const bodyEnv = cloneOptimizeEnv(env);
|
|
1657
1691
|
dropBindingNames(bodyEnv, binding);
|
|
1658
1692
|
return {
|
|
@@ -1662,6 +1696,13 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
|
|
|
1662
1696
|
value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
|
|
1663
1697
|
};
|
|
1664
1698
|
}
|
|
1699
|
+
case "whileObjectComprehension":
|
|
1700
|
+
return {
|
|
1701
|
+
type: "whileObjectComprehension",
|
|
1702
|
+
condition: optimizeNode(node.condition, env, currentDepth),
|
|
1703
|
+
key: optimizeNode(node.key, env, currentDepth + 1),
|
|
1704
|
+
value: optimizeNode(node.value, env, currentDepth + 1)
|
|
1705
|
+
};
|
|
1665
1706
|
default:
|
|
1666
1707
|
return node;
|
|
1667
1708
|
}
|
|
@@ -1713,6 +1754,10 @@ function collectLocalBindings(node, locals) {
|
|
|
1713
1754
|
collectLocalBindings(node.left, locals);
|
|
1714
1755
|
collectLocalBindings(node.right, locals);
|
|
1715
1756
|
return;
|
|
1757
|
+
case "range":
|
|
1758
|
+
collectLocalBindings(node.from, locals);
|
|
1759
|
+
collectLocalBindings(node.to, locals);
|
|
1760
|
+
return;
|
|
1716
1761
|
case "conditional":
|
|
1717
1762
|
collectLocalBindings(node.condition, locals);
|
|
1718
1763
|
for (const part of node.thenBlock)
|
|
@@ -1729,11 +1774,20 @@ function collectLocalBindings(node, locals) {
|
|
|
1729
1774
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1730
1775
|
collectLocalBindings(node.body, locals);
|
|
1731
1776
|
return;
|
|
1777
|
+
case "whileArrayComprehension":
|
|
1778
|
+
collectLocalBindings(node.condition, locals);
|
|
1779
|
+
collectLocalBindings(node.body, locals);
|
|
1780
|
+
return;
|
|
1732
1781
|
case "objectComprehension":
|
|
1733
1782
|
collectLocalBindingFromBinding(node.binding, locals);
|
|
1734
1783
|
collectLocalBindings(node.key, locals);
|
|
1735
1784
|
collectLocalBindings(node.value, locals);
|
|
1736
1785
|
return;
|
|
1786
|
+
case "whileObjectComprehension":
|
|
1787
|
+
collectLocalBindings(node.condition, locals);
|
|
1788
|
+
collectLocalBindings(node.key, locals);
|
|
1789
|
+
collectLocalBindings(node.value, locals);
|
|
1790
|
+
return;
|
|
1737
1791
|
default:
|
|
1738
1792
|
return;
|
|
1739
1793
|
}
|
|
@@ -1825,6 +1879,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
|
|
|
1825
1879
|
collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
|
|
1826
1880
|
collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
|
|
1827
1881
|
return;
|
|
1882
|
+
case "range":
|
|
1883
|
+
collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
|
|
1884
|
+
collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
|
|
1885
|
+
return;
|
|
1828
1886
|
case "conditional":
|
|
1829
1887
|
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1830
1888
|
for (const part of node.thenBlock)
|
|
@@ -1841,11 +1899,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
|
|
|
1841
1899
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1842
1900
|
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1843
1901
|
return;
|
|
1902
|
+
case "whileArrayComprehension":
|
|
1903
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1904
|
+
collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
|
|
1905
|
+
return;
|
|
1844
1906
|
case "objectComprehension":
|
|
1845
1907
|
collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
|
|
1846
1908
|
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1847
1909
|
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1848
1910
|
return;
|
|
1911
|
+
case "whileObjectComprehension":
|
|
1912
|
+
collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
|
|
1913
|
+
collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
|
|
1914
|
+
collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
|
|
1915
|
+
return;
|
|
1849
1916
|
default:
|
|
1850
1917
|
return;
|
|
1851
1918
|
}
|
|
@@ -1920,6 +1987,12 @@ function renameLocalNames(node, map) {
|
|
|
1920
1987
|
left: renameLocalNames(node.left, map),
|
|
1921
1988
|
right: renameLocalNames(node.right, map)
|
|
1922
1989
|
};
|
|
1990
|
+
case "range":
|
|
1991
|
+
return {
|
|
1992
|
+
type: "range",
|
|
1993
|
+
from: renameLocalNames(node.from, map),
|
|
1994
|
+
to: renameLocalNames(node.to, map)
|
|
1995
|
+
};
|
|
1923
1996
|
case "assign": {
|
|
1924
1997
|
const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
|
|
1925
1998
|
return {
|
|
@@ -1949,6 +2022,12 @@ function renameLocalNames(node, map) {
|
|
|
1949
2022
|
binding: renameLocalNamesBinding(node.binding, map),
|
|
1950
2023
|
body: renameLocalNames(node.body, map)
|
|
1951
2024
|
};
|
|
2025
|
+
case "whileArrayComprehension":
|
|
2026
|
+
return {
|
|
2027
|
+
type: "whileArrayComprehension",
|
|
2028
|
+
condition: renameLocalNames(node.condition, map),
|
|
2029
|
+
body: renameLocalNames(node.body, map)
|
|
2030
|
+
};
|
|
1952
2031
|
case "objectComprehension":
|
|
1953
2032
|
return {
|
|
1954
2033
|
type: "objectComprehension",
|
|
@@ -1956,34 +2035,31 @@ function renameLocalNames(node, map) {
|
|
|
1956
2035
|
key: renameLocalNames(node.key, map),
|
|
1957
2036
|
value: renameLocalNames(node.value, map)
|
|
1958
2037
|
};
|
|
2038
|
+
case "whileObjectComprehension":
|
|
2039
|
+
return {
|
|
2040
|
+
type: "whileObjectComprehension",
|
|
2041
|
+
condition: renameLocalNames(node.condition, map),
|
|
2042
|
+
key: renameLocalNames(node.key, map),
|
|
2043
|
+
value: renameLocalNames(node.value, map)
|
|
2044
|
+
};
|
|
1959
2045
|
default:
|
|
1960
2046
|
return node;
|
|
1961
2047
|
}
|
|
1962
2048
|
}
|
|
1963
2049
|
function renameLocalNamesBinding(binding, map) {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
type: "binding:
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2050
|
+
const source = renameLocalNames(binding.source, map);
|
|
2051
|
+
switch (binding.type) {
|
|
2052
|
+
case "binding:bareIn":
|
|
2053
|
+
return { type: "binding:bareIn", source };
|
|
2054
|
+
case "binding:bareOf":
|
|
2055
|
+
return { type: "binding:bareOf", source };
|
|
2056
|
+
case "binding:valueIn":
|
|
2057
|
+
return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
|
|
2058
|
+
case "binding:keyValueIn":
|
|
2059
|
+
return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
|
|
2060
|
+
case "binding:keyOf":
|
|
2061
|
+
return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
|
|
1973
2062
|
}
|
|
1974
|
-
if (binding.type === "binding:keyValueIn") {
|
|
1975
|
-
return {
|
|
1976
|
-
type: "binding:keyValueIn",
|
|
1977
|
-
key: map.get(binding.key) ?? binding.key,
|
|
1978
|
-
value: map.get(binding.value) ?? binding.value,
|
|
1979
|
-
source: renameLocalNames(binding.source, map)
|
|
1980
|
-
};
|
|
1981
|
-
}
|
|
1982
|
-
return {
|
|
1983
|
-
type: "binding:keyOf",
|
|
1984
|
-
key: map.get(binding.key) ?? binding.key,
|
|
1985
|
-
source: renameLocalNames(binding.source, map)
|
|
1986
|
-
};
|
|
1987
2063
|
}
|
|
1988
2064
|
function renameLocalNamesElse(elseBranch, map) {
|
|
1989
2065
|
if (elseBranch.type === "else") {
|
|
@@ -2030,9 +2106,9 @@ function compile(source, options) {
|
|
|
2030
2106
|
let lowered = options?.optimize ? optimizeIR(ir) : ir;
|
|
2031
2107
|
if (options?.minifyNames)
|
|
2032
2108
|
lowered = minifyLocalNamesIR(lowered);
|
|
2033
|
-
const
|
|
2109
|
+
const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
|
|
2034
2110
|
return encodeIR(lowered, {
|
|
2035
|
-
|
|
2111
|
+
...domainMaps,
|
|
2036
2112
|
dedupeValues: options?.dedupeValues,
|
|
2037
2113
|
dedupeMinBytes: options?.dedupeMinBytes
|
|
2038
2114
|
});
|
|
@@ -2158,6 +2234,9 @@ semantics.addOperation("toIR", {
|
|
|
2158
2234
|
ExistenceExpr_or(left, _or, right) {
|
|
2159
2235
|
return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
|
|
2160
2236
|
},
|
|
2237
|
+
ExistenceExpr_nor(left, _nor, right) {
|
|
2238
|
+
return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
|
|
2239
|
+
},
|
|
2161
2240
|
BitExpr_and(left, _op, right) {
|
|
2162
2241
|
return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
|
|
2163
2242
|
},
|
|
@@ -2167,6 +2246,9 @@ semantics.addOperation("toIR", {
|
|
|
2167
2246
|
BitExpr_or(left, _op, right) {
|
|
2168
2247
|
return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
|
|
2169
2248
|
},
|
|
2249
|
+
RangeExpr_range(left, _op, right) {
|
|
2250
|
+
return { type: "range", from: left.toIR(), to: right.toIR() };
|
|
2251
|
+
},
|
|
2170
2252
|
CompareExpr_binary(left, op, right) {
|
|
2171
2253
|
const map = {
|
|
2172
2254
|
"==": "eq",
|
|
@@ -2207,6 +2289,9 @@ semantics.addOperation("toIR", {
|
|
|
2207
2289
|
UnaryExpr_not(_op, value) {
|
|
2208
2290
|
return { type: "unary", op: "not", value: value.toIR() };
|
|
2209
2291
|
},
|
|
2292
|
+
UnaryExpr_logicalNot(_not, value) {
|
|
2293
|
+
return { type: "unary", op: "logicalNot", value: value.toIR() };
|
|
2294
|
+
},
|
|
2210
2295
|
UnaryExpr_delete(_del, place) {
|
|
2211
2296
|
return { type: "unary", op: "delete", value: place.toIR() };
|
|
2212
2297
|
},
|
|
@@ -2260,14 +2345,6 @@ semantics.addOperation("toIR", {
|
|
|
2260
2345
|
ConditionalElse_else(_else, block) {
|
|
2261
2346
|
return { type: "else", block: block.toIR() };
|
|
2262
2347
|
},
|
|
2263
|
-
DoExpr(_do, block, _end) {
|
|
2264
|
-
const body = block.toIR();
|
|
2265
|
-
if (body.length === 0)
|
|
2266
|
-
return { type: "undefined" };
|
|
2267
|
-
if (body.length === 1)
|
|
2268
|
-
return body[0];
|
|
2269
|
-
return { type: "program", body };
|
|
2270
|
-
},
|
|
2271
2348
|
WhileExpr(_while, condition, _do, block, _end) {
|
|
2272
2349
|
return {
|
|
2273
2350
|
type: "while",
|
|
@@ -2282,30 +2359,44 @@ semantics.addOperation("toIR", {
|
|
|
2282
2359
|
body: block.toIR()
|
|
2283
2360
|
};
|
|
2284
2361
|
},
|
|
2285
|
-
BindingExpr(iterOrExpr) {
|
|
2286
|
-
const node = iterOrExpr.toIR();
|
|
2287
|
-
if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
|
|
2288
|
-
return node;
|
|
2289
|
-
}
|
|
2290
|
-
return { type: "binding:expr", source: node };
|
|
2291
|
-
},
|
|
2292
2362
|
Array_empty(_open, _close) {
|
|
2293
2363
|
return { type: "array", items: [] };
|
|
2294
2364
|
},
|
|
2295
|
-
|
|
2365
|
+
Array_forComprehension(_open, body, _for, binding, _close) {
|
|
2296
2366
|
return {
|
|
2297
2367
|
type: "arrayComprehension",
|
|
2298
2368
|
binding: binding.toIR(),
|
|
2299
2369
|
body: body.toIR()
|
|
2300
2370
|
};
|
|
2301
2371
|
},
|
|
2372
|
+
Array_whileComprehension(_open, body, _while, condition, _close) {
|
|
2373
|
+
return {
|
|
2374
|
+
type: "whileArrayComprehension",
|
|
2375
|
+
condition: condition.toIR(),
|
|
2376
|
+
body: body.toIR()
|
|
2377
|
+
};
|
|
2378
|
+
},
|
|
2379
|
+
Array_inComprehension(_open, body, _in, source, _close) {
|
|
2380
|
+
return {
|
|
2381
|
+
type: "arrayComprehension",
|
|
2382
|
+
binding: { type: "binding:bareIn", source: source.toIR() },
|
|
2383
|
+
body: body.toIR()
|
|
2384
|
+
};
|
|
2385
|
+
},
|
|
2386
|
+
Array_ofComprehension(_open, body, _of, source, _close) {
|
|
2387
|
+
return {
|
|
2388
|
+
type: "arrayComprehension",
|
|
2389
|
+
binding: { type: "binding:bareOf", source: source.toIR() },
|
|
2390
|
+
body: body.toIR()
|
|
2391
|
+
};
|
|
2392
|
+
},
|
|
2302
2393
|
Array_values(_open, items, _close) {
|
|
2303
2394
|
return { type: "array", items: normalizeList(items.toIR()) };
|
|
2304
2395
|
},
|
|
2305
2396
|
Object_empty(_open, _close) {
|
|
2306
2397
|
return { type: "object", entries: [] };
|
|
2307
2398
|
},
|
|
2308
|
-
|
|
2399
|
+
Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
|
|
2309
2400
|
return {
|
|
2310
2401
|
type: "objectComprehension",
|
|
2311
2402
|
binding: binding.toIR(),
|
|
@@ -2313,6 +2404,30 @@ semantics.addOperation("toIR", {
|
|
|
2313
2404
|
value: value.toIR()
|
|
2314
2405
|
};
|
|
2315
2406
|
},
|
|
2407
|
+
Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
|
|
2408
|
+
return {
|
|
2409
|
+
type: "whileObjectComprehension",
|
|
2410
|
+
condition: condition.toIR(),
|
|
2411
|
+
key: key.toIR(),
|
|
2412
|
+
value: value.toIR()
|
|
2413
|
+
};
|
|
2414
|
+
},
|
|
2415
|
+
Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
|
|
2416
|
+
return {
|
|
2417
|
+
type: "objectComprehension",
|
|
2418
|
+
binding: { type: "binding:bareIn", source: source.toIR() },
|
|
2419
|
+
key: key.toIR(),
|
|
2420
|
+
value: value.toIR()
|
|
2421
|
+
};
|
|
2422
|
+
},
|
|
2423
|
+
Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
|
|
2424
|
+
return {
|
|
2425
|
+
type: "objectComprehension",
|
|
2426
|
+
binding: { type: "binding:bareOf", source: source.toIR() },
|
|
2427
|
+
key: key.toIR(),
|
|
2428
|
+
value: value.toIR()
|
|
2429
|
+
};
|
|
2430
|
+
},
|
|
2316
2431
|
Object_pairs(_open, pairs, _close) {
|
|
2317
2432
|
return {
|
|
2318
2433
|
type: "object",
|
|
@@ -2341,6 +2456,18 @@ semantics.addOperation("toIR", {
|
|
|
2341
2456
|
source: source.toIR()
|
|
2342
2457
|
};
|
|
2343
2458
|
},
|
|
2459
|
+
IterBinding_bareIn(_in, source) {
|
|
2460
|
+
return {
|
|
2461
|
+
type: "binding:bareIn",
|
|
2462
|
+
source: source.toIR()
|
|
2463
|
+
};
|
|
2464
|
+
},
|
|
2465
|
+
IterBinding_bareOf(_of, source) {
|
|
2466
|
+
return {
|
|
2467
|
+
type: "binding:bareOf",
|
|
2468
|
+
source: source.toIR()
|
|
2469
|
+
};
|
|
2470
|
+
},
|
|
2344
2471
|
Pair(key, _colon, value) {
|
|
2345
2472
|
return { key: key.toIR(), value: value.toIR() };
|
|
2346
2473
|
},
|
|
@@ -2404,6 +2531,9 @@ semantics.addOperation("toIR", {
|
|
|
2404
2531
|
BooleanKw(_kw) {
|
|
2405
2532
|
return { type: "identifier", name: "boolean" };
|
|
2406
2533
|
},
|
|
2534
|
+
SizeKw(_kw) {
|
|
2535
|
+
return { type: "identifier", name: "size" };
|
|
2536
|
+
},
|
|
2407
2537
|
identifier(_a, _b) {
|
|
2408
2538
|
return { type: "identifier", name: this.sourceString };
|
|
2409
2539
|
},
|
|
@@ -2422,28 +2552,30 @@ semantics.addOperation("toIR", {
|
|
|
2422
2552
|
var DIGITS2 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
|
|
2423
2553
|
var digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
|
|
2424
2554
|
var OPCODES = {
|
|
2425
|
-
do:
|
|
2426
|
-
add:
|
|
2427
|
-
sub:
|
|
2428
|
-
mul:
|
|
2429
|
-
div:
|
|
2430
|
-
eq:
|
|
2431
|
-
neq:
|
|
2432
|
-
lt:
|
|
2433
|
-
lte:
|
|
2434
|
-
gt:
|
|
2435
|
-
gte:
|
|
2436
|
-
and:
|
|
2437
|
-
or:
|
|
2438
|
-
xor:
|
|
2439
|
-
not:
|
|
2440
|
-
boolean:
|
|
2441
|
-
number:
|
|
2442
|
-
string:
|
|
2443
|
-
array:
|
|
2444
|
-
object:
|
|
2445
|
-
mod:
|
|
2446
|
-
neg:
|
|
2555
|
+
do: "",
|
|
2556
|
+
add: "ad",
|
|
2557
|
+
sub: "sb",
|
|
2558
|
+
mul: "ml",
|
|
2559
|
+
div: "dv",
|
|
2560
|
+
eq: "eq",
|
|
2561
|
+
neq: "nq",
|
|
2562
|
+
lt: "lt",
|
|
2563
|
+
lte: "le",
|
|
2564
|
+
gt: "gt",
|
|
2565
|
+
gte: "ge",
|
|
2566
|
+
and: "an",
|
|
2567
|
+
or: "or",
|
|
2568
|
+
xor: "xr",
|
|
2569
|
+
not: "nt",
|
|
2570
|
+
boolean: "bt",
|
|
2571
|
+
number: "nm",
|
|
2572
|
+
string: "st",
|
|
2573
|
+
array: "ar",
|
|
2574
|
+
object: "ob",
|
|
2575
|
+
mod: "md",
|
|
2576
|
+
neg: "ng",
|
|
2577
|
+
range: "rn",
|
|
2578
|
+
size: "sz"
|
|
2447
2579
|
};
|
|
2448
2580
|
function decodePrefix(text, start, end) {
|
|
2449
2581
|
let value = 0;
|
|
@@ -2467,7 +2599,6 @@ class CursorInterpreter {
|
|
|
2467
2599
|
pos = 0;
|
|
2468
2600
|
state;
|
|
2469
2601
|
selfStack;
|
|
2470
|
-
opcodeMarkers;
|
|
2471
2602
|
pointerCache = new Map;
|
|
2472
2603
|
gasLimit;
|
|
2473
2604
|
gas = 0;
|
|
@@ -2482,28 +2613,22 @@ class CursorInterpreter {
|
|
|
2482
2613
|
this.state = {
|
|
2483
2614
|
vars: ctx.vars ?? {},
|
|
2484
2615
|
refs: {
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2616
|
+
tr: true,
|
|
2617
|
+
fl: false,
|
|
2618
|
+
nl: null,
|
|
2619
|
+
un: undefined,
|
|
2620
|
+
nan: NaN,
|
|
2621
|
+
inf: Infinity,
|
|
2622
|
+
nif: -Infinity,
|
|
2623
|
+
...ctx.refs
|
|
2493
2624
|
}
|
|
2494
2625
|
};
|
|
2495
2626
|
this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
|
|
2496
2627
|
this.gasLimit = ctx.gasLimit ?? 0;
|
|
2497
|
-
this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
|
|
2498
|
-
for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
|
|
2499
|
-
const id = Number(idText);
|
|
2500
|
-
if (Number.isInteger(id))
|
|
2501
|
-
this.state.refs[id] = value;
|
|
2502
|
-
}
|
|
2503
2628
|
if (ctx.opcodes) {
|
|
2504
|
-
for (const [
|
|
2629
|
+
for (const [key, op] of Object.entries(ctx.opcodes)) {
|
|
2505
2630
|
if (op)
|
|
2506
|
-
this.customOpcodes.set(
|
|
2631
|
+
this.customOpcodes.set(key, op);
|
|
2507
2632
|
}
|
|
2508
2633
|
}
|
|
2509
2634
|
}
|
|
@@ -2564,6 +2689,22 @@ class CursorInterpreter {
|
|
|
2564
2689
|
const end = this.pos;
|
|
2565
2690
|
return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
|
|
2566
2691
|
}
|
|
2692
|
+
advanceByBytes(start, byteCount) {
|
|
2693
|
+
if (byteCount <= 0)
|
|
2694
|
+
return start;
|
|
2695
|
+
let bytes = 0;
|
|
2696
|
+
let index = start;
|
|
2697
|
+
for (const char of this.text.slice(start)) {
|
|
2698
|
+
const charBytes = Buffer.byteLength(char);
|
|
2699
|
+
if (bytes + charBytes > byteCount)
|
|
2700
|
+
break;
|
|
2701
|
+
bytes += charBytes;
|
|
2702
|
+
index += char.length;
|
|
2703
|
+
if (bytes === byteCount)
|
|
2704
|
+
return index;
|
|
2705
|
+
}
|
|
2706
|
+
throw new Error("String container overflows input");
|
|
2707
|
+
}
|
|
2567
2708
|
ensure(char) {
|
|
2568
2709
|
if (this.text[this.pos] !== char)
|
|
2569
2710
|
throw new Error(`Expected '${char}' at ${this.pos}`);
|
|
@@ -2604,36 +2745,34 @@ class CursorInterpreter {
|
|
|
2604
2745
|
const significand = this.evalValue();
|
|
2605
2746
|
if (typeof significand !== "number")
|
|
2606
2747
|
throw new Error("Decimal significand must be numeric");
|
|
2607
|
-
return significand
|
|
2748
|
+
return parseFloat(`${significand}e${power}`);
|
|
2608
2749
|
}
|
|
2609
2750
|
case ":":
|
|
2610
2751
|
this.pos += 1;
|
|
2611
2752
|
return prefix.raw;
|
|
2612
2753
|
case "%":
|
|
2613
2754
|
this.pos += 1;
|
|
2614
|
-
return
|
|
2755
|
+
return { __opcode: prefix.raw };
|
|
2615
2756
|
case "@":
|
|
2616
2757
|
this.pos += 1;
|
|
2617
2758
|
return this.readSelf(prefix.value);
|
|
2618
2759
|
case "'":
|
|
2619
2760
|
this.pos += 1;
|
|
2620
|
-
return this.state.refs[prefix.
|
|
2761
|
+
return this.state.refs[prefix.raw];
|
|
2621
2762
|
case "$":
|
|
2622
2763
|
this.pos += 1;
|
|
2623
2764
|
return this.state.vars[prefix.raw];
|
|
2624
2765
|
case ",": {
|
|
2625
2766
|
this.pos += 1;
|
|
2626
2767
|
const start = this.pos;
|
|
2627
|
-
const end = start
|
|
2628
|
-
if (end > this.text.length)
|
|
2629
|
-
throw new Error("String container overflows input");
|
|
2768
|
+
const end = this.advanceByBytes(start, prefix.value);
|
|
2630
2769
|
const value = this.text.slice(start, end);
|
|
2631
2770
|
this.pos = end;
|
|
2632
2771
|
return value;
|
|
2633
2772
|
}
|
|
2634
2773
|
case "^": {
|
|
2635
2774
|
this.pos += 1;
|
|
2636
|
-
const target = this.pos
|
|
2775
|
+
const target = this.advanceByBytes(this.pos, prefix.value);
|
|
2637
2776
|
if (this.pointerCache.has(target))
|
|
2638
2777
|
return this.pointerCache.get(target);
|
|
2639
2778
|
const save = this.pos;
|
|
@@ -2650,6 +2789,14 @@ class CursorInterpreter {
|
|
|
2650
2789
|
this.writePlace(place, value);
|
|
2651
2790
|
return value;
|
|
2652
2791
|
}
|
|
2792
|
+
case "/": {
|
|
2793
|
+
this.pos += 1;
|
|
2794
|
+
const place = this.readPlace();
|
|
2795
|
+
const oldValue = this.readPlaceValue(place);
|
|
2796
|
+
const newValue = this.evalValue();
|
|
2797
|
+
this.writePlace(place, newValue);
|
|
2798
|
+
return oldValue;
|
|
2799
|
+
}
|
|
2653
2800
|
case "~": {
|
|
2654
2801
|
this.pos += 1;
|
|
2655
2802
|
const place = this.readPlace();
|
|
@@ -2677,7 +2824,7 @@ class CursorInterpreter {
|
|
|
2677
2824
|
case "<":
|
|
2678
2825
|
return this.evalLoopLike(tag);
|
|
2679
2826
|
case "#":
|
|
2680
|
-
return this.
|
|
2827
|
+
return this.evalWhileLike();
|
|
2681
2828
|
default:
|
|
2682
2829
|
throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
|
|
2683
2830
|
}
|
|
@@ -2890,15 +3037,11 @@ class CursorInterpreter {
|
|
|
2890
3037
|
return entries.map(([key]) => ({ key, value: key }));
|
|
2891
3038
|
return entries.map(([key, value]) => ({ key, value }));
|
|
2892
3039
|
}
|
|
2893
|
-
if (typeof iterable === "
|
|
2894
|
-
const
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
else
|
|
2899
|
-
out.push({ key: index, value: index + 1 });
|
|
2900
|
-
}
|
|
2901
|
-
return out;
|
|
3040
|
+
if (typeof iterable === "string") {
|
|
3041
|
+
const entries = Array.from(iterable);
|
|
3042
|
+
if (keysOnly)
|
|
3043
|
+
return entries.map((_value, index) => ({ key: index, value: index }));
|
|
3044
|
+
return entries.map((value, index) => ({ key: index, value }));
|
|
2902
3045
|
}
|
|
2903
3046
|
return [];
|
|
2904
3047
|
}
|
|
@@ -2943,21 +3086,32 @@ class CursorInterpreter {
|
|
|
2943
3086
|
}
|
|
2944
3087
|
return last;
|
|
2945
3088
|
}
|
|
2946
|
-
|
|
3089
|
+
evalWhileLike() {
|
|
3090
|
+
this.pos += 1;
|
|
3091
|
+
const open = this.text[this.pos];
|
|
3092
|
+
if (!open || !"([{".includes(open))
|
|
3093
|
+
throw new Error(`Expected opener after '#' at ${this.pos}`);
|
|
3094
|
+
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
2947
3095
|
this.pos += 1;
|
|
2948
|
-
this.ensure("(");
|
|
2949
3096
|
const condStart = this.pos;
|
|
2950
3097
|
const condValue = this.evalValue();
|
|
2951
3098
|
const bodyStart = this.pos;
|
|
2952
|
-
const bodyValueCount = 1;
|
|
3099
|
+
const bodyValueCount = open === "{" ? 2 : 1;
|
|
2953
3100
|
let cursor = bodyStart;
|
|
2954
3101
|
for (let index = 0;index < bodyValueCount; index += 1) {
|
|
2955
3102
|
cursor = this.skipValueFrom(cursor);
|
|
2956
3103
|
}
|
|
2957
3104
|
const bodyEnd = cursor;
|
|
2958
3105
|
this.pos = bodyEnd;
|
|
2959
|
-
this.ensure(
|
|
3106
|
+
this.ensure(close);
|
|
2960
3107
|
const afterClose = this.pos;
|
|
3108
|
+
if (open === "[")
|
|
3109
|
+
return this.evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
3110
|
+
if (open === "{")
|
|
3111
|
+
return this.evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
3112
|
+
return this.evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
3113
|
+
}
|
|
3114
|
+
evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
2961
3115
|
let last = undefined;
|
|
2962
3116
|
let currentCond = condValue;
|
|
2963
3117
|
while (isDefined(currentCond)) {
|
|
@@ -2978,6 +3132,58 @@ class CursorInterpreter {
|
|
|
2978
3132
|
this.pos = afterClose;
|
|
2979
3133
|
return last;
|
|
2980
3134
|
}
|
|
3135
|
+
evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
3136
|
+
const out = [];
|
|
3137
|
+
let currentCond = condValue;
|
|
3138
|
+
while (isDefined(currentCond)) {
|
|
3139
|
+
this.tick();
|
|
3140
|
+
this.selfStack.push(currentCond);
|
|
3141
|
+
const value = this.evalBodySlice(bodyStart, bodyEnd);
|
|
3142
|
+
this.selfStack.pop();
|
|
3143
|
+
const control = this.handleLoopControl(value);
|
|
3144
|
+
if (control) {
|
|
3145
|
+
if (control.depth > 1)
|
|
3146
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
3147
|
+
if (control.kind === "break")
|
|
3148
|
+
break;
|
|
3149
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
if (isDefined(value))
|
|
3153
|
+
out.push(value);
|
|
3154
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
3155
|
+
}
|
|
3156
|
+
this.pos = afterClose;
|
|
3157
|
+
return out;
|
|
3158
|
+
}
|
|
3159
|
+
evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
|
|
3160
|
+
const result = {};
|
|
3161
|
+
let currentCond = condValue;
|
|
3162
|
+
while (isDefined(currentCond)) {
|
|
3163
|
+
this.tick();
|
|
3164
|
+
this.selfStack.push(currentCond);
|
|
3165
|
+
const save = this.pos;
|
|
3166
|
+
this.pos = bodyStart;
|
|
3167
|
+
const key = this.evalValue();
|
|
3168
|
+
const value = this.evalValue();
|
|
3169
|
+
this.pos = save;
|
|
3170
|
+
this.selfStack.pop();
|
|
3171
|
+
const control = this.handleLoopControl(value);
|
|
3172
|
+
if (control) {
|
|
3173
|
+
if (control.depth > 1)
|
|
3174
|
+
return { kind: control.kind, depth: control.depth - 1 };
|
|
3175
|
+
if (control.kind === "break")
|
|
3176
|
+
break;
|
|
3177
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
3178
|
+
continue;
|
|
3179
|
+
}
|
|
3180
|
+
if (isDefined(value))
|
|
3181
|
+
result[String(key)] = value;
|
|
3182
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
3183
|
+
}
|
|
3184
|
+
this.pos = afterClose;
|
|
3185
|
+
return result;
|
|
3186
|
+
}
|
|
2981
3187
|
evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
|
|
2982
3188
|
const items = this.iterate(iterable, keysOnly);
|
|
2983
3189
|
const out = [];
|
|
@@ -3120,6 +3326,25 @@ class CursorInterpreter {
|
|
|
3120
3326
|
return Array.isArray(args[0]) ? args[0] : undefined;
|
|
3121
3327
|
case OPCODES.object:
|
|
3122
3328
|
return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
|
|
3329
|
+
case OPCODES.range: {
|
|
3330
|
+
const from = Number(args[0]);
|
|
3331
|
+
const to = Number(args[1]);
|
|
3332
|
+
const step = to >= from ? 1 : -1;
|
|
3333
|
+
const out = [];
|
|
3334
|
+
for (let v = from;step > 0 ? v <= to : v >= to; v += step)
|
|
3335
|
+
out.push(v);
|
|
3336
|
+
return out;
|
|
3337
|
+
}
|
|
3338
|
+
case OPCODES.size: {
|
|
3339
|
+
const target = args[0];
|
|
3340
|
+
if (Array.isArray(target))
|
|
3341
|
+
return target.length;
|
|
3342
|
+
if (typeof target === "string")
|
|
3343
|
+
return Array.from(target).length;
|
|
3344
|
+
if (target && typeof target === "object")
|
|
3345
|
+
return Object.keys(target).length;
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3123
3348
|
default:
|
|
3124
3349
|
throw new Error(`Unknown opcode ${id}`);
|
|
3125
3350
|
}
|
|
@@ -3129,10 +3354,59 @@ class CursorInterpreter {
|
|
|
3129
3354
|
for (const key of keys) {
|
|
3130
3355
|
if (current === undefined || current === null)
|
|
3131
3356
|
return;
|
|
3132
|
-
current = current
|
|
3357
|
+
current = this.readProperty(current, key);
|
|
3358
|
+
if (current === undefined)
|
|
3359
|
+
return;
|
|
3133
3360
|
}
|
|
3134
3361
|
return current;
|
|
3135
3362
|
}
|
|
3363
|
+
readProperty(target, key) {
|
|
3364
|
+
const index = this.parseIndexKey(key);
|
|
3365
|
+
if (Array.isArray(target)) {
|
|
3366
|
+
if (index === undefined)
|
|
3367
|
+
return;
|
|
3368
|
+
return target[index];
|
|
3369
|
+
}
|
|
3370
|
+
if (typeof target === "string") {
|
|
3371
|
+
if (index === undefined)
|
|
3372
|
+
return;
|
|
3373
|
+
return Array.from(target)[index];
|
|
3374
|
+
}
|
|
3375
|
+
if (this.isPlainObject(target)) {
|
|
3376
|
+
const prop = String(key);
|
|
3377
|
+
if (!Object.prototype.hasOwnProperty.call(target, prop))
|
|
3378
|
+
return;
|
|
3379
|
+
return target[prop];
|
|
3380
|
+
}
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
canWriteProperty(target, key) {
|
|
3384
|
+
const index = this.parseIndexKey(key);
|
|
3385
|
+
if (Array.isArray(target)) {
|
|
3386
|
+
if (index === undefined)
|
|
3387
|
+
return;
|
|
3388
|
+
return { kind: "array", index };
|
|
3389
|
+
}
|
|
3390
|
+
if (this.isPlainObject(target))
|
|
3391
|
+
return { kind: "object" };
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
parseIndexKey(key) {
|
|
3395
|
+
if (typeof key === "number" && Number.isInteger(key) && key >= 0)
|
|
3396
|
+
return key;
|
|
3397
|
+
if (typeof key !== "string" || key.length === 0)
|
|
3398
|
+
return;
|
|
3399
|
+
if (!/^(0|[1-9]\d*)$/.test(key))
|
|
3400
|
+
return;
|
|
3401
|
+
const index = Number(key);
|
|
3402
|
+
return Number.isSafeInteger(index) ? index : undefined;
|
|
3403
|
+
}
|
|
3404
|
+
isPlainObject(value) {
|
|
3405
|
+
if (!value || typeof value !== "object")
|
|
3406
|
+
return false;
|
|
3407
|
+
const proto = Object.getPrototypeOf(value);
|
|
3408
|
+
return proto === Object.prototype || proto === null;
|
|
3409
|
+
}
|
|
3136
3410
|
readPlace() {
|
|
3137
3411
|
this.skipNonCode();
|
|
3138
3412
|
const direct = this.readRootVarOrRefIfPresent();
|
|
@@ -3187,13 +3461,13 @@ class CursorInterpreter {
|
|
|
3187
3461
|
}
|
|
3188
3462
|
this.pos += 1;
|
|
3189
3463
|
return {
|
|
3190
|
-
root:
|
|
3464
|
+
root: prefix.raw,
|
|
3191
3465
|
isRef: tag === "'"
|
|
3192
3466
|
};
|
|
3193
3467
|
}
|
|
3194
3468
|
writePlace(place, value) {
|
|
3195
3469
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3196
|
-
const rootKey =
|
|
3470
|
+
const rootKey = place.root;
|
|
3197
3471
|
if (place.keys.length === 0) {
|
|
3198
3472
|
rootTable[rootKey] = value;
|
|
3199
3473
|
return;
|
|
@@ -3204,17 +3478,48 @@ class CursorInterpreter {
|
|
|
3204
3478
|
rootTable[rootKey] = target;
|
|
3205
3479
|
}
|
|
3206
3480
|
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3207
|
-
const key =
|
|
3208
|
-
const
|
|
3481
|
+
const key = place.keys[index];
|
|
3482
|
+
const access2 = this.canWriteProperty(target, key);
|
|
3483
|
+
if (!access2)
|
|
3484
|
+
return;
|
|
3485
|
+
if (access2.kind === "array") {
|
|
3486
|
+
const next2 = target[access2.index];
|
|
3487
|
+
if (!next2 || typeof next2 !== "object")
|
|
3488
|
+
target[access2.index] = {};
|
|
3489
|
+
target = target[access2.index];
|
|
3490
|
+
continue;
|
|
3491
|
+
}
|
|
3492
|
+
const prop = String(key);
|
|
3493
|
+
const next = target[prop];
|
|
3209
3494
|
if (!next || typeof next !== "object")
|
|
3210
|
-
target[
|
|
3211
|
-
target = target[
|
|
3495
|
+
target[prop] = {};
|
|
3496
|
+
target = target[prop];
|
|
3212
3497
|
}
|
|
3213
|
-
|
|
3498
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
3499
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
3500
|
+
if (!access)
|
|
3501
|
+
return;
|
|
3502
|
+
if (access.kind === "array") {
|
|
3503
|
+
target[access.index] = value;
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
target[String(lastKey)] = value;
|
|
3507
|
+
}
|
|
3508
|
+
readPlaceValue(place) {
|
|
3509
|
+
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3510
|
+
let current = rootTable[place.root];
|
|
3511
|
+
for (const key of place.keys) {
|
|
3512
|
+
if (current === undefined || current === null)
|
|
3513
|
+
return;
|
|
3514
|
+
current = this.readProperty(current, key);
|
|
3515
|
+
if (current === undefined)
|
|
3516
|
+
return;
|
|
3517
|
+
}
|
|
3518
|
+
return current;
|
|
3214
3519
|
}
|
|
3215
3520
|
deletePlace(place) {
|
|
3216
3521
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
3217
|
-
const rootKey =
|
|
3522
|
+
const rootKey = place.root;
|
|
3218
3523
|
if (place.keys.length === 0) {
|
|
3219
3524
|
delete rootTable[rootKey];
|
|
3220
3525
|
return;
|
|
@@ -3223,11 +3528,29 @@ class CursorInterpreter {
|
|
|
3223
3528
|
if (!target || typeof target !== "object")
|
|
3224
3529
|
return;
|
|
3225
3530
|
for (let index = 0;index < place.keys.length - 1; index += 1) {
|
|
3226
|
-
|
|
3531
|
+
const key = place.keys[index];
|
|
3532
|
+
const access2 = this.canWriteProperty(target, key);
|
|
3533
|
+
if (!access2)
|
|
3534
|
+
return;
|
|
3535
|
+
if (access2.kind === "array") {
|
|
3536
|
+
target = target[access2.index];
|
|
3537
|
+
if (!target || typeof target !== "object")
|
|
3538
|
+
return;
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
target = target[String(key)];
|
|
3227
3542
|
if (!target || typeof target !== "object")
|
|
3228
3543
|
return;
|
|
3229
3544
|
}
|
|
3230
|
-
|
|
3545
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
3546
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
3547
|
+
if (!access)
|
|
3548
|
+
return;
|
|
3549
|
+
if (access.kind === "array") {
|
|
3550
|
+
delete target[access.index];
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
delete target[String(lastKey)];
|
|
3231
3554
|
}
|
|
3232
3555
|
skipValue() {
|
|
3233
3556
|
this.pos = this.skipValueFrom(this.pos);
|
|
@@ -3243,12 +3566,12 @@ class CursorInterpreter {
|
|
|
3243
3566
|
return startPos;
|
|
3244
3567
|
}
|
|
3245
3568
|
if (tag === ",") {
|
|
3246
|
-
this.pos
|
|
3569
|
+
this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
3247
3570
|
const end2 = this.pos;
|
|
3248
3571
|
this.pos = save;
|
|
3249
3572
|
return end2;
|
|
3250
3573
|
}
|
|
3251
|
-
if (tag === "=") {
|
|
3574
|
+
if (tag === "=" || tag === "/") {
|
|
3252
3575
|
this.pos += 1;
|
|
3253
3576
|
this.skipValue();
|
|
3254
3577
|
this.skipValue();
|
|
@@ -3283,7 +3606,8 @@ class CursorInterpreter {
|
|
|
3283
3606
|
if (opener && "([{".includes(opener)) {
|
|
3284
3607
|
const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
|
|
3285
3608
|
if (prefix.value > 0) {
|
|
3286
|
-
this.pos
|
|
3609
|
+
const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
3610
|
+
this.pos = bodyEnd + 1;
|
|
3287
3611
|
const end3 = this.pos;
|
|
3288
3612
|
this.pos = save;
|
|
3289
3613
|
return end3;
|
|
@@ -3327,7 +3651,7 @@ var C = {
|
|
|
3327
3651
|
gray: "\x1B[90m",
|
|
3328
3652
|
boldBlue: "\x1B[1;34m"
|
|
3329
3653
|
};
|
|
3330
|
-
var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
|
|
3654
|
+
var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|nor|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
|
|
3331
3655
|
function highlightLine(line) {
|
|
3332
3656
|
let result = "";
|
|
3333
3657
|
let lastIndex = 0;
|
|
@@ -3436,6 +3760,7 @@ function highlightRexc(text) {
|
|
|
3436
3760
|
break;
|
|
3437
3761
|
}
|
|
3438
3762
|
case "=":
|
|
3763
|
+
case "/":
|
|
3439
3764
|
case "~":
|
|
3440
3765
|
out += C.red + prefix + tag + C.reset;
|
|
3441
3766
|
i++;
|
|
@@ -3539,7 +3864,7 @@ function isIncomplete(buffer) {
|
|
|
3539
3864
|
const trimmed = buffer.trimEnd();
|
|
3540
3865
|
if (/[+\-*/%&|^=<>]$/.test(trimmed))
|
|
3541
3866
|
return true;
|
|
3542
|
-
if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
|
|
3867
|
+
if (/\b(?:and|or|nor|do|in|of)\s*$/.test(trimmed))
|
|
3543
3868
|
return true;
|
|
3544
3869
|
return false;
|
|
3545
3870
|
}
|
|
@@ -3591,6 +3916,7 @@ var KEYWORDS = [
|
|
|
3591
3916
|
"of",
|
|
3592
3917
|
"and",
|
|
3593
3918
|
"or",
|
|
3919
|
+
"nor",
|
|
3594
3920
|
"else",
|
|
3595
3921
|
"break",
|
|
3596
3922
|
"continue",
|