@creationix/rex 0.3.0 → 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 = 5;
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",
@@ -151,12 +152,33 @@ function encodeBareOrLengthString(value) {
151
152
  return `${value}:`;
152
153
  return `${encodeUint(byteLength(value))},${value}`;
153
154
  }
155
+ var DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
156
+ function splitDecimal(num) {
157
+ const match = num.toExponential().match(DEC_PARTS);
158
+ if (!match)
159
+ throw new Error(`Failed to split decimal for ${num}`);
160
+ const [, b1, b2 = "", e1] = match;
161
+ const base = Number.parseInt(b1 + b2, 10);
162
+ const exp = Number.parseInt(e1, 10) - b2.length;
163
+ return { base, exp };
164
+ }
165
+ function encodeDecimal(significand, power) {
166
+ return `${encodeZigzag(power)}*${encodeInt(significand)}`;
167
+ }
154
168
  function encodeNumberNode(node) {
155
169
  const numberValue = node.value;
156
- if (!Number.isFinite(numberValue))
157
- throw new Error(`Cannot encode non-finite number: ${node.raw}`);
158
- if (Number.isInteger(numberValue))
159
- return encodeInt(numberValue);
170
+ if (Number.isNaN(numberValue))
171
+ return "nan'";
172
+ if (numberValue === Infinity)
173
+ return "inf'";
174
+ if (numberValue === -Infinity)
175
+ return "nif'";
176
+ if (Number.isInteger(numberValue)) {
177
+ const { base, exp } = splitDecimal(numberValue);
178
+ if (exp >= 0 && exp <= 4)
179
+ return encodeInt(numberValue);
180
+ return encodeDecimal(base, exp);
181
+ }
160
182
  const raw = node.raw.toLowerCase();
161
183
  const sign = raw.startsWith("-") ? -1 : 1;
162
184
  const unsigned = sign < 0 ? raw.slice(1) : raw;
@@ -179,10 +201,10 @@ function encodeNumberNode(node) {
179
201
  significand /= 10;
180
202
  power += 1;
181
203
  }
182
- return `${encodeZigzag(power)}*${encodeInt(significand)}`;
204
+ return encodeDecimal(significand, power);
183
205
  }
184
206
  function encodeOpcode(opcode) {
185
- return `${encodeUint(OPCODE_IDS[opcode])}%`;
207
+ return `${OPCODE_IDS[opcode]}%`;
186
208
  }
187
209
  function encodeCallParts(parts) {
188
210
  return `(${parts.join("")})`;
@@ -199,7 +221,7 @@ function addOptionalPrefix(encoded) {
199
221
  let payload = encoded;
200
222
  if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
201
223
  payload = encoded.slice(2, -1);
202
- } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
224
+ } else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
203
225
  payload = encoded.slice(2, -1);
204
226
  } else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
205
227
  payload = encoded.slice(1, -1);
@@ -210,7 +232,7 @@ function addOptionalPrefix(encoded) {
210
232
  }
211
233
  function encodeBlockExpression(block) {
212
234
  if (block.length === 0)
213
- return "4'";
235
+ return "un'";
214
236
  if (block.length === 1)
215
237
  return encodeNode(block[0]);
216
238
  return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
@@ -227,9 +249,13 @@ function encodeConditionalElse(elseBranch) {
227
249
  };
228
250
  return encodeNode(nested);
229
251
  }
252
+ function encodeDomainLookup(shortCode, tag) {
253
+ return `${shortCode}${tag}`;
254
+ }
230
255
  function encodeNavigation(node) {
231
256
  const domainRefs = activeEncodeOptions?.domainRefs;
232
- if (domainRefs && node.target.type === "identifier") {
257
+ const domainOpcodes = activeEncodeOptions?.domainOpcodes;
258
+ if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
233
259
  const staticPath = [node.target.name];
234
260
  for (const segment of node.segments) {
235
261
  if (segment.type !== "static")
@@ -238,14 +264,17 @@ function encodeNavigation(node) {
238
264
  }
239
265
  for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
240
266
  const dottedName = staticPath.slice(0, pathLength).join(".");
241
- const domainRef = domainRefs[dottedName];
242
- if (domainRef === undefined)
267
+ const refCode = domainRefs?.[dottedName];
268
+ const opcodeCode = domainOpcodes?.[dottedName];
269
+ const shortCode = refCode ?? opcodeCode;
270
+ if (shortCode === undefined)
243
271
  continue;
272
+ const tag = refCode !== undefined ? "'" : "%";
244
273
  const consumedStaticSegments = pathLength - 1;
245
274
  if (consumedStaticSegments === node.segments.length) {
246
- return `${encodeUint(domainRef)}'`;
275
+ return encodeDomainLookup(shortCode, tag);
247
276
  }
248
- const parts2 = [`${encodeUint(domainRef)}'`];
277
+ const parts2 = [encodeDomainLookup(shortCode, tag)];
249
278
  for (const segment of node.segments.slice(consumedStaticSegments)) {
250
279
  if (segment.type === "static")
251
280
  parts2.push(encodeBareOrLengthString(segment.key));
@@ -271,9 +300,12 @@ function encodeWhile(node) {
271
300
  }
272
301
  function encodeFor(node) {
273
302
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
274
- if (node.binding.type === "binding:expr") {
303
+ if (node.binding.type === "binding:bareIn") {
275
304
  return `>(${encodeNode(node.binding.source)}${body})`;
276
305
  }
306
+ if (node.binding.type === "binding:bareOf") {
307
+ return `<(${encodeNode(node.binding.source)}${body})`;
308
+ }
277
309
  if (node.binding.type === "binding:valueIn") {
278
310
  return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
279
311
  }
@@ -284,30 +316,47 @@ function encodeFor(node) {
284
316
  }
285
317
  function encodeArrayComprehension(node) {
286
318
  const body = addOptionalPrefix(encodeNode(node.body));
287
- if (node.binding.type === "binding:expr") {
319
+ if (node.binding.type === "binding:bareIn") {
288
320
  return `>[${encodeNode(node.binding.source)}${body}]`;
289
321
  }
322
+ if (node.binding.type === "binding:bareOf") {
323
+ return `<[${encodeNode(node.binding.source)}${body}]`;
324
+ }
290
325
  if (node.binding.type === "binding:valueIn") {
291
326
  return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
292
327
  }
293
328
  if (node.binding.type === "binding:keyValueIn") {
294
329
  return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
295
330
  }
296
- return `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
331
+ return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
297
332
  }
298
333
  function encodeObjectComprehension(node) {
299
334
  const key = addOptionalPrefix(encodeNode(node.key));
300
335
  const value = addOptionalPrefix(encodeNode(node.value));
301
- if (node.binding.type === "binding:expr") {
336
+ if (node.binding.type === "binding:bareIn") {
302
337
  return `>{${encodeNode(node.binding.source)}${key}${value}}`;
303
338
  }
339
+ if (node.binding.type === "binding:bareOf") {
340
+ return `<{${encodeNode(node.binding.source)}${key}${value}}`;
341
+ }
304
342
  if (node.binding.type === "binding:valueIn") {
305
343
  return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
306
344
  }
307
345
  if (node.binding.type === "binding:keyValueIn") {
308
346
  return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
309
347
  }
310
- 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}}`;
311
360
  }
312
361
  var activeEncodeOptions;
313
362
  function encodeNode(node) {
@@ -317,7 +366,10 @@ function encodeNode(node) {
317
366
  case "identifier": {
318
367
  const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
319
368
  if (domainRef !== undefined)
320
- return `${encodeUint(domainRef)}'`;
369
+ return `${domainRef}'`;
370
+ const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
371
+ if (domainOpcode !== undefined)
372
+ return `${domainOpcode}%`;
321
373
  return `${node.name}$`;
322
374
  }
323
375
  case "self":
@@ -330,11 +382,11 @@ function encodeNode(node) {
330
382
  return `${encodeUint(node.depth - 1)}@`;
331
383
  }
332
384
  case "boolean":
333
- return node.value ? "1'" : "2'";
385
+ return node.value ? "tr'" : "fl'";
334
386
  case "null":
335
- return "3'";
387
+ return "nl'";
336
388
  case "undefined":
337
- return "4'";
389
+ return "un'";
338
390
  case "number":
339
391
  return encodeNumberNode(node);
340
392
  case "string":
@@ -345,12 +397,16 @@ function encodeNode(node) {
345
397
  }
346
398
  case "arrayComprehension":
347
399
  return encodeArrayComprehension(node);
400
+ case "whileArrayComprehension":
401
+ return encodeWhileArrayComprehension(node);
348
402
  case "object": {
349
403
  const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
350
404
  return `{${body}}`;
351
405
  }
352
406
  case "objectComprehension":
353
407
  return encodeObjectComprehension(node);
408
+ case "whileObjectComprehension":
409
+ return encodeWhileObjectComprehension(node);
354
410
  case "key":
355
411
  return encodeBareOrLengthString(node.name);
356
412
  case "group":
@@ -360,6 +416,10 @@ function encodeNode(node) {
360
416
  return `~${encodeNode(node.value)}`;
361
417
  if (node.op === "neg")
362
418
  return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
419
+ if (node.op === "logicalNot") {
420
+ const val = encodeNode(node.value);
421
+ return `!(${val}tr')`;
422
+ }
363
423
  return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
364
424
  case "binary":
365
425
  if (node.op === "and") {
@@ -378,12 +438,19 @@ function encodeNode(node) {
378
438
  }).join("");
379
439
  return `|(${body})`;
380
440
  }
441
+ if (node.op === "nor") {
442
+ const left = encodeNode(node.left);
443
+ const right = addOptionalPrefix(encodeNode(node.right));
444
+ return `!(${left}${right})`;
445
+ }
381
446
  return encodeCallParts([
382
447
  encodeOpcode(BINARY_TO_OPCODE[node.op]),
383
448
  encodeNode(node.left),
384
449
  encodeNode(node.right)
385
450
  ]);
386
451
  case "assign": {
452
+ if (node.op === ":=")
453
+ return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
387
454
  if (node.op === "=")
388
455
  return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
389
456
  const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
@@ -394,8 +461,12 @@ function encodeNode(node) {
394
461
  }
395
462
  case "navigation":
396
463
  return encodeNavigation(node);
397
- 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
+ }
398
468
  return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
469
+ }
399
470
  case "conditional": {
400
471
  const opener = node.head === "when" ? "?(" : "!(";
401
472
  const cond = encodeNode(node.condition);
@@ -403,6 +474,8 @@ function encodeNode(node) {
403
474
  const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
404
475
  return `${opener}${cond}${thenExpr}${elseExpr})`;
405
476
  }
477
+ case "range":
478
+ return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
406
479
  case "for":
407
480
  return encodeFor(node);
408
481
  case "while":
@@ -439,6 +512,18 @@ function isPlainObject(value) {
439
512
  function isBareKeyName(key) {
440
513
  return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
441
514
  }
515
+ function isNumericKey(key) {
516
+ if (key === "")
517
+ return false;
518
+ return String(Number(key)) === key && Number.isFinite(Number(key));
519
+ }
520
+ function stringifyKey(key) {
521
+ if (isBareKeyName(key))
522
+ return key;
523
+ if (isNumericKey(key))
524
+ return key;
525
+ return stringifyString(key);
526
+ }
442
527
  function stringifyString(value) {
443
528
  return JSON.stringify(value);
444
529
  }
@@ -450,8 +535,12 @@ function stringifyInline(value) {
450
535
  if (typeof value === "boolean")
451
536
  return value ? "true" : "false";
452
537
  if (typeof value === "number") {
453
- if (!Number.isFinite(value))
454
- throw new Error("Rex stringify() cannot encode non-finite numbers");
538
+ if (Number.isNaN(value))
539
+ return "nan";
540
+ if (value === Infinity)
541
+ return "inf";
542
+ if (value === -Infinity)
543
+ return "-inf";
455
544
  return String(value);
456
545
  }
457
546
  if (typeof value === "string")
@@ -465,7 +554,7 @@ function stringifyInline(value) {
465
554
  const entries = Object.entries(value);
466
555
  if (entries.length === 0)
467
556
  return "{}";
468
- const body = entries.map(([key, item]) => `${isBareKeyName(key) ? key : stringifyString(key)}: ${stringifyInline(item)}`).join(" ");
557
+ const body = entries.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`).join(" ");
469
558
  return `{${body}}`;
470
559
  }
471
560
  throw new Error(`Rex stringify() cannot encode value of type ${typeof value}`);
@@ -502,7 +591,7 @@ ${indent}]`;
502
591
  if (entries.length === 0)
503
592
  return "{}";
504
593
  const lines = entries.map(([key, item]) => {
505
- const keyText = isBareKeyName(key) ? key : stringifyString(key);
594
+ const keyText = stringifyKey(key);
506
595
  const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
507
596
  return `${childIndent}${keyText}: ${rendered}`;
508
597
  });
@@ -517,78 +606,55 @@ function domainRefsFromConfig(config) {
517
606
  if (!config || typeof config !== "object" || Array.isArray(config)) {
518
607
  throw new Error("Domain config must be an object");
519
608
  }
520
- const refs = {};
521
- for (const section of Object.values(config)) {
522
- if (!section || typeof section !== "object" || Array.isArray(section))
523
- continue;
524
- mapConfigEntries(section, refs);
525
- }
526
- return refs;
527
- }
528
- function decodeDomainRefKey(refText) {
529
- if (!refText)
530
- throw new Error("Domain ref key cannot be empty");
531
- if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
532
- throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
533
- }
534
- if (refText.length > 1 && refText[0] === "0") {
535
- throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
536
- }
537
- if (/^[1-9]$/.test(refText)) {
538
- throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
539
- }
540
- let value = 0;
541
- for (const char of refText) {
542
- const digit = DOMAIN_DIGIT_INDEX.get(char);
543
- if (digit === undefined)
544
- throw new Error(`Invalid domain ref key '${refText}'`);
545
- value = value * 64 + digit;
546
- if (value > Number.MAX_SAFE_INTEGER) {
547
- throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
548
- }
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);
549
615
  }
550
- if (value < FIRST_NON_RESERVED_REF) {
551
- throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
616
+ const functionsSection = configObj.functions;
617
+ if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
618
+ mapConfigEntries(functionsSection, domainOpcodes);
552
619
  }
553
- return value;
620
+ return { domainRefs, domainOpcodes };
554
621
  }
555
622
  function mapConfigEntries(entries, refs) {
556
623
  const sourceKindByRoot = new Map;
557
624
  for (const root of Object.keys(refs)) {
558
625
  sourceKindByRoot.set(root, "explicit");
559
626
  }
560
- for (const [refText, rawEntry] of Object.entries(entries)) {
627
+ for (const [shortCode, rawEntry] of Object.entries(entries)) {
561
628
  const entry = rawEntry;
562
629
  if (!entry || typeof entry !== "object")
563
630
  continue;
564
631
  if (!Array.isArray(entry.names))
565
632
  continue;
566
- const refId = decodeDomainRefKey(refText);
567
633
  for (const rawName of entry.names) {
568
634
  if (typeof rawName !== "string")
569
635
  continue;
570
- const existingNameRef = refs[rawName];
571
- if (existingNameRef !== undefined && existingNameRef !== refId) {
572
- 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}`);
573
639
  }
574
- refs[rawName] = refId;
640
+ refs[rawName] = shortCode;
575
641
  const root = rawName.split(".")[0];
576
642
  if (!root)
577
643
  continue;
578
644
  const currentKind = rawName.includes(".") ? "implicit" : "explicit";
579
645
  const existing = refs[root];
580
646
  if (existing !== undefined) {
581
- if (existing === refId)
647
+ if (existing === shortCode)
582
648
  continue;
583
649
  const existingKind = sourceKindByRoot.get(root) ?? "explicit";
584
650
  if (currentKind === "explicit") {
585
- throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
651
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
586
652
  }
587
653
  if (existingKind === "explicit")
588
654
  continue;
589
655
  continue;
590
656
  }
591
- refs[root] = refId;
657
+ refs[root] = shortCode;
592
658
  sourceKindByRoot.set(root, currentKind);
593
659
  }
594
660
  }
@@ -872,6 +938,21 @@ function dropBindingNames(env, binding) {
872
938
  clearBinding(env, binding.key);
873
939
  }
874
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
+ }
875
956
  function collectReads(node, out) {
876
957
  switch (node.type) {
877
958
  case "identifier":
@@ -894,11 +975,20 @@ function collectReads(node, out) {
894
975
  collectReads(node.binding.source, out);
895
976
  collectReads(node.body, out);
896
977
  return;
978
+ case "whileArrayComprehension":
979
+ collectReads(node.condition, out);
980
+ collectReads(node.body, out);
981
+ return;
897
982
  case "objectComprehension":
898
983
  collectReads(node.binding.source, out);
899
984
  collectReads(node.key, out);
900
985
  collectReads(node.value, out);
901
986
  return;
987
+ case "whileObjectComprehension":
988
+ collectReads(node.condition, out);
989
+ collectReads(node.key, out);
990
+ collectReads(node.value, out);
991
+ return;
902
992
  case "unary":
903
993
  collectReads(node.value, out);
904
994
  return;
@@ -935,6 +1025,10 @@ function collectReads(node, out) {
935
1025
  for (const part of node.body)
936
1026
  collectReads(part, out);
937
1027
  return;
1028
+ case "range":
1029
+ collectReads(node.from, out);
1030
+ collectReads(node.to, out);
1031
+ return;
938
1032
  case "program":
939
1033
  for (const part of node.body)
940
1034
  collectReads(part, out);
@@ -979,6 +1073,8 @@ function isPureNode(node) {
979
1073
  return node.op !== "delete" && isPureNode(node.value);
980
1074
  case "binary":
981
1075
  return isPureNode(node.left) && isPureNode(node.right);
1076
+ case "range":
1077
+ return isPureNode(node.from) && isPureNode(node.to);
982
1078
  default:
983
1079
  return false;
984
1080
  }
@@ -1043,6 +1139,8 @@ function hasIdentifierRead(node, name, asPlace = false) {
1043
1139
  return hasIdentifierRead(node.value, name, node.op === "delete");
1044
1140
  case "binary":
1045
1141
  return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1142
+ case "range":
1143
+ return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
1046
1144
  case "assign":
1047
1145
  return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1048
1146
  default:
@@ -1065,6 +1163,8 @@ function countIdentifierReads(node, name, asPlace = false) {
1065
1163
  return countIdentifierReads(node.value, name, node.op === "delete");
1066
1164
  case "binary":
1067
1165
  return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1166
+ case "range":
1167
+ return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
1068
1168
  case "assign":
1069
1169
  return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1070
1170
  default:
@@ -1119,6 +1219,12 @@ function replaceIdentifier(node, name, replacement, asPlace = false) {
1119
1219
  place: replaceIdentifier(node.place, name, replacement, true),
1120
1220
  value: replaceIdentifier(node.value, name, replacement)
1121
1221
  };
1222
+ case "range":
1223
+ return {
1224
+ type: "range",
1225
+ from: replaceIdentifier(node.from, name, replacement),
1226
+ to: replaceIdentifier(node.to, name, replacement)
1227
+ };
1122
1228
  default:
1123
1229
  return node;
1124
1230
  }
@@ -1159,7 +1265,16 @@ function inlineAdjacentPureAssignments(block) {
1159
1265
  return out;
1160
1266
  }
1161
1267
  function toNumberNode(value) {
1162
- return { type: "number", raw: String(value), value };
1268
+ let raw;
1269
+ if (Number.isNaN(value))
1270
+ raw = "nan";
1271
+ else if (value === Infinity)
1272
+ raw = "inf";
1273
+ else if (value === -Infinity)
1274
+ raw = "-inf";
1275
+ else
1276
+ raw = String(value);
1277
+ return { type: "number", raw, value };
1163
1278
  }
1164
1279
  function toStringNode(value) {
1165
1280
  return { type: "string", raw: JSON.stringify(value) };
@@ -1171,7 +1286,7 @@ function toLiteralNode(value) {
1171
1286
  return { type: "null" };
1172
1287
  if (typeof value === "boolean")
1173
1288
  return { type: "boolean", value };
1174
- if (typeof value === "number" && Number.isFinite(value))
1289
+ if (typeof value === "number")
1175
1290
  return toNumberNode(value);
1176
1291
  if (typeof value === "string")
1177
1292
  return toStringNode(value);
@@ -1254,6 +1369,9 @@ function foldUnary(op, value) {
1254
1369
  return ~value;
1255
1370
  return;
1256
1371
  }
1372
+ if (op === "logicalNot") {
1373
+ return value === undefined ? true : undefined;
1374
+ }
1257
1375
  return;
1258
1376
  }
1259
1377
  function foldBinary(op, left, right) {
@@ -1424,6 +1542,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1424
1542
  }
1425
1543
  return { type: "binary", op: node.op, left, right };
1426
1544
  }
1545
+ case "range":
1546
+ return {
1547
+ type: "range",
1548
+ from: optimizeNode(node.from, env, currentDepth),
1549
+ to: optimizeNode(node.to, env, currentDepth)
1550
+ };
1427
1551
  case "navigation": {
1428
1552
  const target = optimizeNode(node.target, env, currentDepth);
1429
1553
  const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
@@ -1534,31 +1658,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1534
1658
  }
1535
1659
  case "for": {
1536
1660
  const sourceEnv = cloneOptimizeEnv(env);
1537
- const binding = (() => {
1538
- if (node.binding.type === "binding:expr") {
1539
- return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
1540
- }
1541
- if (node.binding.type === "binding:valueIn") {
1542
- return {
1543
- type: "binding:valueIn",
1544
- value: node.binding.value,
1545
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1546
- };
1547
- }
1548
- if (node.binding.type === "binding:keyValueIn") {
1549
- return {
1550
- type: "binding:keyValueIn",
1551
- key: node.binding.key,
1552
- value: node.binding.value,
1553
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1554
- };
1555
- }
1556
- return {
1557
- type: "binding:keyOf",
1558
- key: node.binding.key,
1559
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1560
- };
1561
- })();
1661
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1562
1662
  const bodyEnv = cloneOptimizeEnv(env);
1563
1663
  dropBindingNames(bodyEnv, binding);
1564
1664
  return {
@@ -1569,20 +1669,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1569
1669
  }
1570
1670
  case "arrayComprehension": {
1571
1671
  const sourceEnv = cloneOptimizeEnv(env);
1572
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1573
- type: "binding:valueIn",
1574
- value: node.binding.value,
1575
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1576
- } : node.binding.type === "binding:keyValueIn" ? {
1577
- type: "binding:keyValueIn",
1578
- key: node.binding.key,
1579
- value: node.binding.value,
1580
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1581
- } : {
1582
- type: "binding:keyOf",
1583
- key: node.binding.key,
1584
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1585
- };
1672
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1586
1673
  const bodyEnv = cloneOptimizeEnv(env);
1587
1674
  dropBindingNames(bodyEnv, binding);
1588
1675
  return {
@@ -1591,22 +1678,15 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1591
1678
  body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1592
1679
  };
1593
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
+ };
1594
1687
  case "objectComprehension": {
1595
1688
  const sourceEnv = cloneOptimizeEnv(env);
1596
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1597
- type: "binding:valueIn",
1598
- value: node.binding.value,
1599
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1600
- } : node.binding.type === "binding:keyValueIn" ? {
1601
- type: "binding:keyValueIn",
1602
- key: node.binding.key,
1603
- value: node.binding.value,
1604
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1605
- } : {
1606
- type: "binding:keyOf",
1607
- key: node.binding.key,
1608
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1609
- };
1689
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1610
1690
  const bodyEnv = cloneOptimizeEnv(env);
1611
1691
  dropBindingNames(bodyEnv, binding);
1612
1692
  return {
@@ -1616,6 +1696,13 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1616
1696
  value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1617
1697
  };
1618
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
+ };
1619
1706
  default:
1620
1707
  return node;
1621
1708
  }
@@ -1667,6 +1754,10 @@ function collectLocalBindings(node, locals) {
1667
1754
  collectLocalBindings(node.left, locals);
1668
1755
  collectLocalBindings(node.right, locals);
1669
1756
  return;
1757
+ case "range":
1758
+ collectLocalBindings(node.from, locals);
1759
+ collectLocalBindings(node.to, locals);
1760
+ return;
1670
1761
  case "conditional":
1671
1762
  collectLocalBindings(node.condition, locals);
1672
1763
  for (const part of node.thenBlock)
@@ -1683,11 +1774,20 @@ function collectLocalBindings(node, locals) {
1683
1774
  collectLocalBindingFromBinding(node.binding, locals);
1684
1775
  collectLocalBindings(node.body, locals);
1685
1776
  return;
1777
+ case "whileArrayComprehension":
1778
+ collectLocalBindings(node.condition, locals);
1779
+ collectLocalBindings(node.body, locals);
1780
+ return;
1686
1781
  case "objectComprehension":
1687
1782
  collectLocalBindingFromBinding(node.binding, locals);
1688
1783
  collectLocalBindings(node.key, locals);
1689
1784
  collectLocalBindings(node.value, locals);
1690
1785
  return;
1786
+ case "whileObjectComprehension":
1787
+ collectLocalBindings(node.condition, locals);
1788
+ collectLocalBindings(node.key, locals);
1789
+ collectLocalBindings(node.value, locals);
1790
+ return;
1691
1791
  default:
1692
1792
  return;
1693
1793
  }
@@ -1779,6 +1879,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1779
1879
  collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
1780
1880
  collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
1781
1881
  return;
1882
+ case "range":
1883
+ collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
1884
+ collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
1885
+ return;
1782
1886
  case "conditional":
1783
1887
  collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1784
1888
  for (const part of node.thenBlock)
@@ -1795,11 +1899,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1795
1899
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1796
1900
  collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1797
1901
  return;
1902
+ case "whileArrayComprehension":
1903
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1904
+ collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1905
+ return;
1798
1906
  case "objectComprehension":
1799
1907
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1800
1908
  collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1801
1909
  collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1802
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;
1803
1916
  default:
1804
1917
  return;
1805
1918
  }
@@ -1874,6 +1987,12 @@ function renameLocalNames(node, map) {
1874
1987
  left: renameLocalNames(node.left, map),
1875
1988
  right: renameLocalNames(node.right, map)
1876
1989
  };
1990
+ case "range":
1991
+ return {
1992
+ type: "range",
1993
+ from: renameLocalNames(node.from, map),
1994
+ to: renameLocalNames(node.to, map)
1995
+ };
1877
1996
  case "assign": {
1878
1997
  const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
1879
1998
  return {
@@ -1903,6 +2022,12 @@ function renameLocalNames(node, map) {
1903
2022
  binding: renameLocalNamesBinding(node.binding, map),
1904
2023
  body: renameLocalNames(node.body, map)
1905
2024
  };
2025
+ case "whileArrayComprehension":
2026
+ return {
2027
+ type: "whileArrayComprehension",
2028
+ condition: renameLocalNames(node.condition, map),
2029
+ body: renameLocalNames(node.body, map)
2030
+ };
1906
2031
  case "objectComprehension":
1907
2032
  return {
1908
2033
  type: "objectComprehension",
@@ -1910,34 +2035,31 @@ function renameLocalNames(node, map) {
1910
2035
  key: renameLocalNames(node.key, map),
1911
2036
  value: renameLocalNames(node.value, map)
1912
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
+ };
1913
2045
  default:
1914
2046
  return node;
1915
2047
  }
1916
2048
  }
1917
2049
  function renameLocalNamesBinding(binding, map) {
1918
- if (binding.type === "binding:expr") {
1919
- return { type: "binding:expr", source: renameLocalNames(binding.source, map) };
1920
- }
1921
- if (binding.type === "binding:valueIn") {
1922
- return {
1923
- type: "binding:valueIn",
1924
- value: map.get(binding.value) ?? binding.value,
1925
- source: renameLocalNames(binding.source, map)
1926
- };
1927
- }
1928
- if (binding.type === "binding:keyValueIn") {
1929
- return {
1930
- type: "binding:keyValueIn",
1931
- key: map.get(binding.key) ?? binding.key,
1932
- value: map.get(binding.value) ?? binding.value,
1933
- source: renameLocalNames(binding.source, map)
1934
- };
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 };
1935
2062
  }
1936
- return {
1937
- type: "binding:keyOf",
1938
- key: map.get(binding.key) ?? binding.key,
1939
- source: renameLocalNames(binding.source, map)
1940
- };
1941
2063
  }
1942
2064
  function renameLocalNamesElse(elseBranch, map) {
1943
2065
  if (elseBranch.type === "else") {
@@ -1984,14 +2106,20 @@ function compile(source, options) {
1984
2106
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
1985
2107
  if (options?.minifyNames)
1986
2108
  lowered = minifyLocalNamesIR(lowered);
1987
- const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2109
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1988
2110
  return encodeIR(lowered, {
1989
- domainRefs,
2111
+ ...domainMaps,
1990
2112
  dedupeValues: options?.dedupeValues,
1991
2113
  dedupeMinBytes: options?.dedupeMinBytes
1992
2114
  });
1993
2115
  }
1994
2116
  function parseNumber(raw) {
2117
+ if (raw === "nan")
2118
+ return NaN;
2119
+ if (raw === "inf")
2120
+ return Infinity;
2121
+ if (raw === "-inf")
2122
+ return -Infinity;
1995
2123
  if (/^-?0x/i.test(raw))
1996
2124
  return parseInt(raw, 16);
1997
2125
  if (/^-?0b/i.test(raw)) {
@@ -2106,6 +2234,9 @@ semantics.addOperation("toIR", {
2106
2234
  ExistenceExpr_or(left, _or, right) {
2107
2235
  return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
2108
2236
  },
2237
+ ExistenceExpr_nor(left, _nor, right) {
2238
+ return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
2239
+ },
2109
2240
  BitExpr_and(left, _op, right) {
2110
2241
  return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
2111
2242
  },
@@ -2115,6 +2246,9 @@ semantics.addOperation("toIR", {
2115
2246
  BitExpr_or(left, _op, right) {
2116
2247
  return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
2117
2248
  },
2249
+ RangeExpr_range(left, _op, right) {
2250
+ return { type: "range", from: left.toIR(), to: right.toIR() };
2251
+ },
2118
2252
  CompareExpr_binary(left, op, right) {
2119
2253
  const map = {
2120
2254
  "==": "eq",
@@ -2155,6 +2289,9 @@ semantics.addOperation("toIR", {
2155
2289
  UnaryExpr_not(_op, value) {
2156
2290
  return { type: "unary", op: "not", value: value.toIR() };
2157
2291
  },
2292
+ UnaryExpr_logicalNot(_not, value) {
2293
+ return { type: "unary", op: "logicalNot", value: value.toIR() };
2294
+ },
2158
2295
  UnaryExpr_delete(_del, place) {
2159
2296
  return { type: "unary", op: "delete", value: place.toIR() };
2160
2297
  },
@@ -2208,14 +2345,6 @@ semantics.addOperation("toIR", {
2208
2345
  ConditionalElse_else(_else, block) {
2209
2346
  return { type: "else", block: block.toIR() };
2210
2347
  },
2211
- DoExpr(_do, block, _end) {
2212
- const body = block.toIR();
2213
- if (body.length === 0)
2214
- return { type: "undefined" };
2215
- if (body.length === 1)
2216
- return body[0];
2217
- return { type: "program", body };
2218
- },
2219
2348
  WhileExpr(_while, condition, _do, block, _end) {
2220
2349
  return {
2221
2350
  type: "while",
@@ -2230,30 +2359,44 @@ semantics.addOperation("toIR", {
2230
2359
  body: block.toIR()
2231
2360
  };
2232
2361
  },
2233
- BindingExpr(iterOrExpr) {
2234
- const node = iterOrExpr.toIR();
2235
- if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
2236
- return node;
2237
- }
2238
- return { type: "binding:expr", source: node };
2239
- },
2240
2362
  Array_empty(_open, _close) {
2241
2363
  return { type: "array", items: [] };
2242
2364
  },
2243
- Array_comprehension(_open, binding, _semi, body, _close) {
2365
+ Array_forComprehension(_open, body, _for, binding, _close) {
2244
2366
  return {
2245
2367
  type: "arrayComprehension",
2246
2368
  binding: binding.toIR(),
2247
2369
  body: body.toIR()
2248
2370
  };
2249
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
+ },
2250
2393
  Array_values(_open, items, _close) {
2251
2394
  return { type: "array", items: normalizeList(items.toIR()) };
2252
2395
  },
2253
2396
  Object_empty(_open, _close) {
2254
2397
  return { type: "object", entries: [] };
2255
2398
  },
2256
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
2399
+ Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
2257
2400
  return {
2258
2401
  type: "objectComprehension",
2259
2402
  binding: binding.toIR(),
@@ -2261,6 +2404,30 @@ semantics.addOperation("toIR", {
2261
2404
  value: value.toIR()
2262
2405
  };
2263
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
+ },
2264
2431
  Object_pairs(_open, pairs, _close) {
2265
2432
  return {
2266
2433
  type: "object",
@@ -2289,6 +2456,18 @@ semantics.addOperation("toIR", {
2289
2456
  source: source.toIR()
2290
2457
  };
2291
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
+ },
2292
2471
  Pair(key, _colon, value) {
2293
2472
  return { key: key.toIR(), value: value.toIR() };
2294
2473
  },
@@ -2352,6 +2531,9 @@ semantics.addOperation("toIR", {
2352
2531
  BooleanKw(_kw) {
2353
2532
  return { type: "identifier", name: "boolean" };
2354
2533
  },
2534
+ SizeKw(_kw) {
2535
+ return { type: "identifier", name: "size" };
2536
+ },
2355
2537
  identifier(_a, _b) {
2356
2538
  return { type: "identifier", name: this.sourceString };
2357
2539
  },
@@ -2370,28 +2552,30 @@ semantics.addOperation("toIR", {
2370
2552
  var DIGITS2 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
2371
2553
  var digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
2372
2554
  var OPCODES = {
2373
- do: 0,
2374
- add: 1,
2375
- sub: 2,
2376
- mul: 3,
2377
- div: 4,
2378
- eq: 5,
2379
- neq: 6,
2380
- lt: 7,
2381
- lte: 8,
2382
- gt: 9,
2383
- gte: 10,
2384
- and: 11,
2385
- or: 12,
2386
- xor: 13,
2387
- not: 14,
2388
- boolean: 15,
2389
- number: 16,
2390
- string: 17,
2391
- array: 18,
2392
- object: 19,
2393
- mod: 20,
2394
- 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"
2395
2579
  };
2396
2580
  function decodePrefix(text, start, end) {
2397
2581
  let value = 0;
@@ -2415,7 +2599,6 @@ class CursorInterpreter {
2415
2599
  pos = 0;
2416
2600
  state;
2417
2601
  selfStack;
2418
- opcodeMarkers;
2419
2602
  pointerCache = new Map;
2420
2603
  gasLimit;
2421
2604
  gas = 0;
@@ -2430,25 +2613,22 @@ class CursorInterpreter {
2430
2613
  this.state = {
2431
2614
  vars: ctx.vars ?? {},
2432
2615
  refs: {
2433
- 0: ctx.refs?.[0],
2434
- 1: ctx.refs?.[1] ?? true,
2435
- 2: ctx.refs?.[2] ?? false,
2436
- 3: ctx.refs?.[3] ?? null,
2437
- 4: ctx.refs?.[4] ?? undefined
2616
+ tr: true,
2617
+ fl: false,
2618
+ nl: null,
2619
+ un: undefined,
2620
+ nan: NaN,
2621
+ inf: Infinity,
2622
+ nif: -Infinity,
2623
+ ...ctx.refs
2438
2624
  }
2439
2625
  };
2440
2626
  this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
2441
2627
  this.gasLimit = ctx.gasLimit ?? 0;
2442
- this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
2443
- for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
2444
- const id = Number(idText);
2445
- if (Number.isInteger(id))
2446
- this.state.refs[id] = value;
2447
- }
2448
2628
  if (ctx.opcodes) {
2449
- for (const [idText, op] of Object.entries(ctx.opcodes)) {
2629
+ for (const [key, op] of Object.entries(ctx.opcodes)) {
2450
2630
  if (op)
2451
- this.customOpcodes.set(Number(idText), op);
2631
+ this.customOpcodes.set(key, op);
2452
2632
  }
2453
2633
  }
2454
2634
  }
@@ -2509,6 +2689,22 @@ class CursorInterpreter {
2509
2689
  const end = this.pos;
2510
2690
  return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
2511
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
+ }
2512
2708
  ensure(char) {
2513
2709
  if (this.text[this.pos] !== char)
2514
2710
  throw new Error(`Expected '${char}' at ${this.pos}`);
@@ -2549,36 +2745,34 @@ class CursorInterpreter {
2549
2745
  const significand = this.evalValue();
2550
2746
  if (typeof significand !== "number")
2551
2747
  throw new Error("Decimal significand must be numeric");
2552
- return significand * 10 ** power;
2748
+ return parseFloat(`${significand}e${power}`);
2553
2749
  }
2554
2750
  case ":":
2555
2751
  this.pos += 1;
2556
2752
  return prefix.raw;
2557
2753
  case "%":
2558
2754
  this.pos += 1;
2559
- return this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
2755
+ return { __opcode: prefix.raw };
2560
2756
  case "@":
2561
2757
  this.pos += 1;
2562
2758
  return this.readSelf(prefix.value);
2563
2759
  case "'":
2564
2760
  this.pos += 1;
2565
- return this.state.refs[prefix.value];
2761
+ return this.state.refs[prefix.raw];
2566
2762
  case "$":
2567
2763
  this.pos += 1;
2568
2764
  return this.state.vars[prefix.raw];
2569
2765
  case ",": {
2570
2766
  this.pos += 1;
2571
2767
  const start = this.pos;
2572
- const end = start + prefix.value;
2573
- if (end > this.text.length)
2574
- throw new Error("String container overflows input");
2768
+ const end = this.advanceByBytes(start, prefix.value);
2575
2769
  const value = this.text.slice(start, end);
2576
2770
  this.pos = end;
2577
2771
  return value;
2578
2772
  }
2579
2773
  case "^": {
2580
2774
  this.pos += 1;
2581
- const target = this.pos + prefix.value;
2775
+ const target = this.advanceByBytes(this.pos, prefix.value);
2582
2776
  if (this.pointerCache.has(target))
2583
2777
  return this.pointerCache.get(target);
2584
2778
  const save = this.pos;
@@ -2595,6 +2789,14 @@ class CursorInterpreter {
2595
2789
  this.writePlace(place, value);
2596
2790
  return value;
2597
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
+ }
2598
2800
  case "~": {
2599
2801
  this.pos += 1;
2600
2802
  const place = this.readPlace();
@@ -2622,7 +2824,7 @@ class CursorInterpreter {
2622
2824
  case "<":
2623
2825
  return this.evalLoopLike(tag);
2624
2826
  case "#":
2625
- return this.evalWhileLoop();
2827
+ return this.evalWhileLike();
2626
2828
  default:
2627
2829
  throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
2628
2830
  }
@@ -2835,15 +3037,11 @@ class CursorInterpreter {
2835
3037
  return entries.map(([key]) => ({ key, value: key }));
2836
3038
  return entries.map(([key, value]) => ({ key, value }));
2837
3039
  }
2838
- if (typeof iterable === "number" && Number.isFinite(iterable) && iterable > 0) {
2839
- const out = [];
2840
- for (let index = 0;index < Math.floor(iterable); index += 1) {
2841
- if (keysOnly)
2842
- out.push({ key: index, value: index });
2843
- else
2844
- out.push({ key: index, value: index + 1 });
2845
- }
2846
- 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 }));
2847
3045
  }
2848
3046
  return [];
2849
3047
  }
@@ -2888,21 +3086,32 @@ class CursorInterpreter {
2888
3086
  }
2889
3087
  return last;
2890
3088
  }
2891
- 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 === "[" ? "]" : "}";
2892
3095
  this.pos += 1;
2893
- this.ensure("(");
2894
3096
  const condStart = this.pos;
2895
3097
  const condValue = this.evalValue();
2896
3098
  const bodyStart = this.pos;
2897
- const bodyValueCount = 1;
3099
+ const bodyValueCount = open === "{" ? 2 : 1;
2898
3100
  let cursor = bodyStart;
2899
3101
  for (let index = 0;index < bodyValueCount; index += 1) {
2900
3102
  cursor = this.skipValueFrom(cursor);
2901
3103
  }
2902
3104
  const bodyEnd = cursor;
2903
3105
  this.pos = bodyEnd;
2904
- this.ensure(")");
3106
+ this.ensure(close);
2905
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) {
2906
3115
  let last = undefined;
2907
3116
  let currentCond = condValue;
2908
3117
  while (isDefined(currentCond)) {
@@ -2923,6 +3132,58 @@ class CursorInterpreter {
2923
3132
  this.pos = afterClose;
2924
3133
  return last;
2925
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
+ }
2926
3187
  evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
2927
3188
  const items = this.iterate(iterable, keysOnly);
2928
3189
  const out = [];
@@ -2991,25 +3252,39 @@ class CursorInterpreter {
2991
3252
  case OPCODES.do:
2992
3253
  return args.length ? args[args.length - 1] : undefined;
2993
3254
  case OPCODES.add:
3255
+ if (args[0] === undefined || args[1] === undefined)
3256
+ return;
2994
3257
  if (typeof args[0] === "string" || typeof args[1] === "string") {
2995
- return String(args[0] ?? "") + String(args[1] ?? "");
3258
+ return String(args[0]) + String(args[1]);
2996
3259
  }
2997
- return Number(args[0] ?? 0) + Number(args[1] ?? 0);
3260
+ return Number(args[0]) + Number(args[1]);
2998
3261
  case OPCODES.sub:
2999
- return Number(args[0] ?? 0) - Number(args[1] ?? 0);
3262
+ if (args[0] === undefined || args[1] === undefined)
3263
+ return;
3264
+ return Number(args[0]) - Number(args[1]);
3000
3265
  case OPCODES.mul:
3001
- return Number(args[0] ?? 0) * Number(args[1] ?? 0);
3266
+ if (args[0] === undefined || args[1] === undefined)
3267
+ return;
3268
+ return Number(args[0]) * Number(args[1]);
3002
3269
  case OPCODES.div:
3003
- return Number(args[0] ?? 0) / Number(args[1] ?? 0);
3270
+ if (args[0] === undefined || args[1] === undefined)
3271
+ return;
3272
+ return Number(args[0]) / Number(args[1]);
3004
3273
  case OPCODES.mod:
3005
- return Number(args[0] ?? 0) % Number(args[1] ?? 0);
3274
+ if (args[0] === undefined || args[1] === undefined)
3275
+ return;
3276
+ return Number(args[0]) % Number(args[1]);
3006
3277
  case OPCODES.neg:
3007
- return -Number(args[0] ?? 0);
3278
+ if (args[0] === undefined)
3279
+ return;
3280
+ return -Number(args[0]);
3008
3281
  case OPCODES.not: {
3009
3282
  const value = args[0];
3283
+ if (value === undefined)
3284
+ return;
3010
3285
  if (typeof value === "boolean")
3011
3286
  return !value;
3012
- return ~Number(value ?? 0);
3287
+ return ~Number(value);
3013
3288
  }
3014
3289
  case OPCODES.and: {
3015
3290
  const [a, b] = args;
@@ -3051,6 +3326,25 @@ class CursorInterpreter {
3051
3326
  return Array.isArray(args[0]) ? args[0] : undefined;
3052
3327
  case OPCODES.object:
3053
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
+ }
3054
3348
  default:
3055
3349
  throw new Error(`Unknown opcode ${id}`);
3056
3350
  }
@@ -3060,10 +3354,59 @@ class CursorInterpreter {
3060
3354
  for (const key of keys) {
3061
3355
  if (current === undefined || current === null)
3062
3356
  return;
3063
- current = current[String(key)];
3357
+ current = this.readProperty(current, key);
3358
+ if (current === undefined)
3359
+ return;
3064
3360
  }
3065
3361
  return current;
3066
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
+ }
3067
3410
  readPlace() {
3068
3411
  this.skipNonCode();
3069
3412
  const direct = this.readRootVarOrRefIfPresent();
@@ -3118,13 +3461,13 @@ class CursorInterpreter {
3118
3461
  }
3119
3462
  this.pos += 1;
3120
3463
  return {
3121
- root: tag === "$" ? prefix.raw : prefix.value,
3464
+ root: prefix.raw,
3122
3465
  isRef: tag === "'"
3123
3466
  };
3124
3467
  }
3125
3468
  writePlace(place, value) {
3126
3469
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3127
- const rootKey = String(place.root);
3470
+ const rootKey = place.root;
3128
3471
  if (place.keys.length === 0) {
3129
3472
  rootTable[rootKey] = value;
3130
3473
  return;
@@ -3135,17 +3478,48 @@ class CursorInterpreter {
3135
3478
  rootTable[rootKey] = target;
3136
3479
  }
3137
3480
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3138
- const key = String(place.keys[index]);
3139
- 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];
3140
3494
  if (!next || typeof next !== "object")
3141
- target[key] = {};
3142
- target = target[key];
3495
+ target[prop] = {};
3496
+ target = target[prop];
3497
+ }
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;
3143
3517
  }
3144
- target[String(place.keys[place.keys.length - 1])] = value;
3518
+ return current;
3145
3519
  }
3146
3520
  deletePlace(place) {
3147
3521
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3148
- const rootKey = String(place.root);
3522
+ const rootKey = place.root;
3149
3523
  if (place.keys.length === 0) {
3150
3524
  delete rootTable[rootKey];
3151
3525
  return;
@@ -3154,11 +3528,29 @@ class CursorInterpreter {
3154
3528
  if (!target || typeof target !== "object")
3155
3529
  return;
3156
3530
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3157
- 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)];
3158
3542
  if (!target || typeof target !== "object")
3159
3543
  return;
3160
3544
  }
3161
- 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)];
3162
3554
  }
3163
3555
  skipValue() {
3164
3556
  this.pos = this.skipValueFrom(this.pos);
@@ -3174,12 +3566,12 @@ class CursorInterpreter {
3174
3566
  return startPos;
3175
3567
  }
3176
3568
  if (tag === ",") {
3177
- this.pos += 1 + prefix.value;
3569
+ this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
3178
3570
  const end2 = this.pos;
3179
3571
  this.pos = save;
3180
3572
  return end2;
3181
3573
  }
3182
- if (tag === "=") {
3574
+ if (tag === "=" || tag === "/") {
3183
3575
  this.pos += 1;
3184
3576
  this.skipValue();
3185
3577
  this.skipValue();
@@ -3214,7 +3606,8 @@ class CursorInterpreter {
3214
3606
  if (opener && "([{".includes(opener)) {
3215
3607
  const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
3216
3608
  if (prefix.value > 0) {
3217
- this.pos += 1 + prefix.value + 1;
3609
+ const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
3610
+ this.pos = bodyEnd + 1;
3218
3611
  const end3 = this.pos;
3219
3612
  this.pos = save;
3220
3613
  return end3;
@@ -3258,7 +3651,7 @@ var C = {
3258
3651
  gray: "\x1B[90m",
3259
3652
  boldBlue: "\x1B[1;34m"
3260
3653
  };
3261
- 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)(?![a-zA-Z0-9_-]))|(?<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;
3262
3655
  function highlightLine(line) {
3263
3656
  let result = "";
3264
3657
  let lastIndex = 0;
@@ -3367,6 +3760,7 @@ function highlightRexc(text) {
3367
3760
  break;
3368
3761
  }
3369
3762
  case "=":
3763
+ case "/":
3370
3764
  case "~":
3371
3765
  out += C.red + prefix + tag + C.reset;
3372
3766
  i++;
@@ -3406,6 +3800,33 @@ function highlightRexc(text) {
3406
3800
  }
3407
3801
  return out;
3408
3802
  }
3803
+ var JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3804
+ function highlightJSON(json) {
3805
+ let result = "";
3806
+ let lastIndex = 0;
3807
+ JSON_TOKEN_RE.lastIndex = 0;
3808
+ for (const m of json.matchAll(JSON_TOKEN_RE)) {
3809
+ result += json.slice(lastIndex, m.index);
3810
+ const text = m[0];
3811
+ const g = m.groups;
3812
+ if (g.key) {
3813
+ result += C.cyan + g.key + C.reset + ":";
3814
+ } else if (g.string) {
3815
+ result += C.green + text + C.reset;
3816
+ } else if (g.number) {
3817
+ result += C.yellow + text + C.reset;
3818
+ } else if (g.bool) {
3819
+ result += C.yellow + text + C.reset;
3820
+ } else if (g.null) {
3821
+ result += C.dim + text + C.reset;
3822
+ } else {
3823
+ result += text;
3824
+ }
3825
+ lastIndex = m.index + text.length;
3826
+ }
3827
+ result += json.slice(lastIndex);
3828
+ return result;
3829
+ }
3409
3830
  function stripStringsAndComments(source) {
3410
3831
  return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, (m) => " ".repeat(m.length));
3411
3832
  }
@@ -3443,7 +3864,7 @@ function isIncomplete(buffer) {
3443
3864
  const trimmed = buffer.trimEnd();
3444
3865
  if (/[+\-*/%&|^=<>]$/.test(trimmed))
3445
3866
  return true;
3446
- if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
3867
+ if (/\b(?:and|or|nor|do|in|of)\s*$/.test(trimmed))
3447
3868
  return true;
3448
3869
  return false;
3449
3870
  }
@@ -3495,6 +3916,7 @@ var KEYWORDS = [
3495
3916
  "of",
3496
3917
  "and",
3497
3918
  "or",
3919
+ "nor",
3498
3920
  "else",
3499
3921
  "break",
3500
3922
  "continue",
@@ -3504,6 +3926,8 @@ var KEYWORDS = [
3504
3926
  "false",
3505
3927
  "null",
3506
3928
  "undefined",
3929
+ "nan",
3930
+ "inf",
3507
3931
  "string",
3508
3932
  "number",
3509
3933
  "object",
@@ -3680,7 +4104,7 @@ async function startRepl() {
3680
4104
  const ir = parseToIR(source);
3681
4105
  const lowered = state.optimize ? optimizeIR(ir) : ir;
3682
4106
  if (state.showIR) {
3683
- console.log(`${C.dim} IR: ${JSON.stringify(lowered)}${C.reset}`);
4107
+ console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3684
4108
  }
3685
4109
  const rexc = compile(source, { optimize: state.optimize });
3686
4110
  if (state.showRexc) {
@@ -3717,5 +4141,6 @@ export {
3717
4141
  isIncomplete,
3718
4142
  highlightRexc,
3719
4143
  highlightLine,
4144
+ highlightJSON,
3720
4145
  formatVarState
3721
4146
  };