@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/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: 0,
18
- add: 1,
19
- sub: 2,
20
- mul: 3,
21
- div: 4,
22
- eq: 5,
23
- neq: 6,
24
- lt: 7,
25
- lte: 8,
26
- gt: 9,
27
- gte: 10,
28
- and: 11,
29
- or: 12,
30
- xor: 13,
31
- not: 14,
32
- boolean: 15,
33
- number: 16,
34
- string: 17,
35
- array: 18,
36
- object: 19,
37
- mod: 20,
38
- neg: 21
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 FIRST_NON_RESERVED_REF = 8;
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 "5'";
171
+ return "nan'";
171
172
  if (numberValue === Infinity)
172
- return "6'";
173
+ return "inf'";
173
174
  if (numberValue === -Infinity)
174
- return "7'";
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 `${encodeUint(OPCODE_IDS[opcode])}%`;
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 "4'";
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
- if (domainRefs && node.target.type === "identifier") {
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 domainRef = domainRefs[dottedName];
263
- if (domainRef === undefined)
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 `${encodeUint(domainRef)}'`;
275
+ return encodeDomainLookup(shortCode, tag);
268
276
  }
269
- const parts2 = [`${encodeUint(domainRef)}'`];
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:expr") {
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:expr") {
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 `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
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:expr") {
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 `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
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 `${encodeUint(domainRef)}'`;
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 ? "1'" : "2'";
385
+ return node.value ? "tr'" : "fl'";
355
386
  case "null":
356
- return "3'";
387
+ return "nl'";
357
388
  case "undefined":
358
- return "4'";
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 refs = {};
558
- for (const section of Object.values(config)) {
559
- if (!section || typeof section !== "object" || Array.isArray(section))
560
- continue;
561
- mapConfigEntries(section, refs);
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
- return refs;
564
- }
565
- function decodeDomainRefKey(refText) {
566
- if (!refText)
567
- throw new Error("Domain ref key cannot be empty");
568
- if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
569
- throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
570
- }
571
- if (refText.length > 1 && refText[0] === "0") {
572
- throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
616
+ const functionsSection = configObj.functions;
617
+ if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
618
+ mapConfigEntries(functionsSection, domainOpcodes);
573
619
  }
574
- if (/^[1-9]$/.test(refText)) {
575
- throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
576
- }
577
- let value = 0;
578
- for (const char of refText) {
579
- const digit = DOMAIN_DIGIT_INDEX.get(char);
580
- if (digit === undefined)
581
- throw new Error(`Invalid domain ref key '${refText}'`);
582
- value = value * 64 + digit;
583
- if (value > Number.MAX_SAFE_INTEGER) {
584
- throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
585
- }
586
- }
587
- if (value < FIRST_NON_RESERVED_REF) {
588
- throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
589
- }
590
- return value;
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 [refText, rawEntry] of Object.entries(entries)) {
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 existingNameRef = refs[rawName];
608
- if (existingNameRef !== undefined && existingNameRef !== refId) {
609
- throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
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] = refId;
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 === refId)
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 ${refId}`);
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] = refId;
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 = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1619
- type: "binding:valueIn",
1620
- value: node.binding.value,
1621
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1622
- } : node.binding.type === "binding:keyValueIn" ? {
1623
- type: "binding:keyValueIn",
1624
- key: node.binding.key,
1625
- value: node.binding.value,
1626
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1627
- } : {
1628
- type: "binding:keyOf",
1629
- key: node.binding.key,
1630
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1631
- };
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 = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1643
- type: "binding:valueIn",
1644
- value: node.binding.value,
1645
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1646
- } : node.binding.type === "binding:keyValueIn" ? {
1647
- type: "binding:keyValueIn",
1648
- key: node.binding.key,
1649
- value: node.binding.value,
1650
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1651
- } : {
1652
- type: "binding:keyOf",
1653
- key: node.binding.key,
1654
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1655
- };
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
- if (binding.type === "binding:expr") {
1965
- return { type: "binding:expr", source: renameLocalNames(binding.source, map) };
1966
- }
1967
- if (binding.type === "binding:valueIn") {
1968
- return {
1969
- type: "binding:valueIn",
1970
- value: map.get(binding.value) ?? binding.value,
1971
- source: renameLocalNames(binding.source, map)
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 domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2109
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2034
2110
  return encodeIR(lowered, {
2035
- domainRefs,
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
- Array_comprehension(_open, binding, _semi, body, _close) {
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
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
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: 0,
2426
- add: 1,
2427
- sub: 2,
2428
- mul: 3,
2429
- div: 4,
2430
- eq: 5,
2431
- neq: 6,
2432
- lt: 7,
2433
- lte: 8,
2434
- gt: 9,
2435
- gte: 10,
2436
- and: 11,
2437
- or: 12,
2438
- xor: 13,
2439
- not: 14,
2440
- boolean: 15,
2441
- number: 16,
2442
- string: 17,
2443
- array: 18,
2444
- object: 19,
2445
- mod: 20,
2446
- neg: 21
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
- 0: ctx.refs?.[0],
2486
- 1: ctx.refs?.[1] ?? true,
2487
- 2: ctx.refs?.[2] ?? false,
2488
- 3: ctx.refs?.[3] ?? null,
2489
- 4: ctx.refs?.[4] ?? undefined,
2490
- 5: ctx.refs?.[5] ?? NaN,
2491
- 6: ctx.refs?.[6] ?? Infinity,
2492
- 7: ctx.refs?.[7] ?? -Infinity
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 [idText, op] of Object.entries(ctx.opcodes)) {
2629
+ for (const [key, op] of Object.entries(ctx.opcodes)) {
2505
2630
  if (op)
2506
- this.customOpcodes.set(Number(idText), op);
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 * 10 ** power;
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 this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
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.value];
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 + prefix.value;
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 + prefix.value;
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.evalWhileLoop();
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 === "number" && Number.isFinite(iterable) && iterable > 0) {
2894
- const out = [];
2895
- for (let index = 0;index < Math.floor(iterable); index += 1) {
2896
- if (keysOnly)
2897
- out.push({ key: index, value: index });
2898
- else
2899
- out.push({ key: index, value: index + 1 });
2900
- }
2901
- return out;
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
- evalWhileLoop() {
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[String(key)];
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: tag === "$" ? prefix.raw : prefix.value,
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 = String(place.root);
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 = String(place.keys[index]);
3208
- const next = target[key];
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[key] = {};
3211
- target = target[key];
3495
+ target[prop] = {};
3496
+ target = target[prop];
3212
3497
  }
3213
- target[String(place.keys[place.keys.length - 1])] = value;
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 = String(place.root);
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
- target = target[String(place.keys[index])];
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
- delete target[String(place.keys[place.keys.length - 1])];
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 += 1 + prefix.value;
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 += 1 + prefix.value + 1;
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",