@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-cli.js CHANGED
@@ -100,12 +100,32 @@ function encodeBareOrLengthString(value) {
100
100
  return `${value}:`;
101
101
  return `${encodeUint(byteLength(value))},${value}`;
102
102
  }
103
+ function splitDecimal(num) {
104
+ const match = num.toExponential().match(DEC_PARTS);
105
+ if (!match)
106
+ throw new Error(`Failed to split decimal for ${num}`);
107
+ const [, b1, b2 = "", e1] = match;
108
+ const base = Number.parseInt(b1 + b2, 10);
109
+ const exp = Number.parseInt(e1, 10) - b2.length;
110
+ return { base, exp };
111
+ }
112
+ function encodeDecimal(significand, power) {
113
+ return `${encodeZigzag(power)}*${encodeInt(significand)}`;
114
+ }
103
115
  function encodeNumberNode(node) {
104
116
  const numberValue = node.value;
105
- if (!Number.isFinite(numberValue))
106
- throw new Error(`Cannot encode non-finite number: ${node.raw}`);
107
- if (Number.isInteger(numberValue))
108
- return encodeInt(numberValue);
117
+ if (Number.isNaN(numberValue))
118
+ return "nan'";
119
+ if (numberValue === Infinity)
120
+ return "inf'";
121
+ if (numberValue === -Infinity)
122
+ return "nif'";
123
+ if (Number.isInteger(numberValue)) {
124
+ const { base, exp } = splitDecimal(numberValue);
125
+ if (exp >= 0 && exp <= 4)
126
+ return encodeInt(numberValue);
127
+ return encodeDecimal(base, exp);
128
+ }
109
129
  const raw = node.raw.toLowerCase();
110
130
  const sign = raw.startsWith("-") ? -1 : 1;
111
131
  const unsigned = sign < 0 ? raw.slice(1) : raw;
@@ -128,10 +148,10 @@ function encodeNumberNode(node) {
128
148
  significand /= 10;
129
149
  power += 1;
130
150
  }
131
- return `${encodeZigzag(power)}*${encodeInt(significand)}`;
151
+ return encodeDecimal(significand, power);
132
152
  }
133
153
  function encodeOpcode(opcode) {
134
- return `${encodeUint(OPCODE_IDS[opcode])}%`;
154
+ return `${OPCODE_IDS[opcode]}%`;
135
155
  }
136
156
  function encodeCallParts(parts) {
137
157
  return `(${parts.join("")})`;
@@ -148,7 +168,7 @@ function addOptionalPrefix(encoded) {
148
168
  let payload = encoded;
149
169
  if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
150
170
  payload = encoded.slice(2, -1);
151
- } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
171
+ } else if (encoded.startsWith(">[") || encoded.startsWith(">{") || encoded.startsWith("<[") || encoded.startsWith("<{") || encoded.startsWith("#[") || encoded.startsWith("#{")) {
152
172
  payload = encoded.slice(2, -1);
153
173
  } else if (encoded.startsWith("[") || encoded.startsWith("{") || encoded.startsWith("(")) {
154
174
  payload = encoded.slice(1, -1);
@@ -159,7 +179,7 @@ function addOptionalPrefix(encoded) {
159
179
  }
160
180
  function encodeBlockExpression(block) {
161
181
  if (block.length === 0)
162
- return "4'";
182
+ return "un'";
163
183
  if (block.length === 1)
164
184
  return encodeNode(block[0]);
165
185
  return encodeCallParts([encodeOpcode("do"), ...block.map((node) => encodeNode(node))]);
@@ -176,9 +196,13 @@ function encodeConditionalElse(elseBranch) {
176
196
  };
177
197
  return encodeNode(nested);
178
198
  }
199
+ function encodeDomainLookup(shortCode, tag) {
200
+ return `${shortCode}${tag}`;
201
+ }
179
202
  function encodeNavigation(node) {
180
203
  const domainRefs = activeEncodeOptions?.domainRefs;
181
- if (domainRefs && node.target.type === "identifier") {
204
+ const domainOpcodes = activeEncodeOptions?.domainOpcodes;
205
+ if ((domainRefs || domainOpcodes) && node.target.type === "identifier") {
182
206
  const staticPath = [node.target.name];
183
207
  for (const segment of node.segments) {
184
208
  if (segment.type !== "static")
@@ -187,14 +211,17 @@ function encodeNavigation(node) {
187
211
  }
188
212
  for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
189
213
  const dottedName = staticPath.slice(0, pathLength).join(".");
190
- const domainRef = domainRefs[dottedName];
191
- if (domainRef === undefined)
214
+ const refCode = domainRefs?.[dottedName];
215
+ const opcodeCode = domainOpcodes?.[dottedName];
216
+ const shortCode = refCode ?? opcodeCode;
217
+ if (shortCode === undefined)
192
218
  continue;
219
+ const tag = refCode !== undefined ? "'" : "%";
193
220
  const consumedStaticSegments = pathLength - 1;
194
221
  if (consumedStaticSegments === node.segments.length) {
195
- return `${encodeUint(domainRef)}'`;
222
+ return encodeDomainLookup(shortCode, tag);
196
223
  }
197
- const parts2 = [`${encodeUint(domainRef)}'`];
224
+ const parts2 = [encodeDomainLookup(shortCode, tag)];
198
225
  for (const segment of node.segments.slice(consumedStaticSegments)) {
199
226
  if (segment.type === "static")
200
227
  parts2.push(encodeBareOrLengthString(segment.key));
@@ -220,9 +247,12 @@ function encodeWhile(node) {
220
247
  }
221
248
  function encodeFor(node) {
222
249
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
223
- if (node.binding.type === "binding:expr") {
250
+ if (node.binding.type === "binding:bareIn") {
224
251
  return `>(${encodeNode(node.binding.source)}${body})`;
225
252
  }
253
+ if (node.binding.type === "binding:bareOf") {
254
+ return `<(${encodeNode(node.binding.source)}${body})`;
255
+ }
226
256
  if (node.binding.type === "binding:valueIn") {
227
257
  return `>(${encodeNode(node.binding.source)}${node.binding.value}$${body})`;
228
258
  }
@@ -233,30 +263,47 @@ function encodeFor(node) {
233
263
  }
234
264
  function encodeArrayComprehension(node) {
235
265
  const body = addOptionalPrefix(encodeNode(node.body));
236
- if (node.binding.type === "binding:expr") {
266
+ if (node.binding.type === "binding:bareIn") {
237
267
  return `>[${encodeNode(node.binding.source)}${body}]`;
238
268
  }
269
+ if (node.binding.type === "binding:bareOf") {
270
+ return `<[${encodeNode(node.binding.source)}${body}]`;
271
+ }
239
272
  if (node.binding.type === "binding:valueIn") {
240
273
  return `>[${encodeNode(node.binding.source)}${node.binding.value}$${body}]`;
241
274
  }
242
275
  if (node.binding.type === "binding:keyValueIn") {
243
276
  return `>[${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${body}]`;
244
277
  }
245
- return `>[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
278
+ return `<[${encodeNode(node.binding.source)}${node.binding.key}$${body}]`;
246
279
  }
247
280
  function encodeObjectComprehension(node) {
248
281
  const key = addOptionalPrefix(encodeNode(node.key));
249
282
  const value = addOptionalPrefix(encodeNode(node.value));
250
- if (node.binding.type === "binding:expr") {
283
+ if (node.binding.type === "binding:bareIn") {
251
284
  return `>{${encodeNode(node.binding.source)}${key}${value}}`;
252
285
  }
286
+ if (node.binding.type === "binding:bareOf") {
287
+ return `<{${encodeNode(node.binding.source)}${key}${value}}`;
288
+ }
253
289
  if (node.binding.type === "binding:valueIn") {
254
290
  return `>{${encodeNode(node.binding.source)}${node.binding.value}$${key}${value}}`;
255
291
  }
256
292
  if (node.binding.type === "binding:keyValueIn") {
257
293
  return `>{${encodeNode(node.binding.source)}${node.binding.key}$${node.binding.value}$${key}${value}}`;
258
294
  }
259
- return `>{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
295
+ return `<{${encodeNode(node.binding.source)}${node.binding.key}$${key}${value}}`;
296
+ }
297
+ function encodeWhileArrayComprehension(node) {
298
+ const cond = encodeNode(node.condition);
299
+ const body = addOptionalPrefix(encodeNode(node.body));
300
+ return `#[${cond}${body}]`;
301
+ }
302
+ function encodeWhileObjectComprehension(node) {
303
+ const cond = encodeNode(node.condition);
304
+ const key = addOptionalPrefix(encodeNode(node.key));
305
+ const value = addOptionalPrefix(encodeNode(node.value));
306
+ return `#{${cond}${key}${value}}`;
260
307
  }
261
308
  function encodeNode(node) {
262
309
  switch (node.type) {
@@ -265,7 +312,10 @@ function encodeNode(node) {
265
312
  case "identifier": {
266
313
  const domainRef = activeEncodeOptions?.domainRefs?.[node.name];
267
314
  if (domainRef !== undefined)
268
- return `${encodeUint(domainRef)}'`;
315
+ return `${domainRef}'`;
316
+ const domainOpcode = activeEncodeOptions?.domainOpcodes?.[node.name];
317
+ if (domainOpcode !== undefined)
318
+ return `${domainOpcode}%`;
269
319
  return `${node.name}$`;
270
320
  }
271
321
  case "self":
@@ -278,11 +328,11 @@ function encodeNode(node) {
278
328
  return `${encodeUint(node.depth - 1)}@`;
279
329
  }
280
330
  case "boolean":
281
- return node.value ? "1'" : "2'";
331
+ return node.value ? "tr'" : "fl'";
282
332
  case "null":
283
- return "3'";
333
+ return "nl'";
284
334
  case "undefined":
285
- return "4'";
335
+ return "un'";
286
336
  case "number":
287
337
  return encodeNumberNode(node);
288
338
  case "string":
@@ -293,12 +343,16 @@ function encodeNode(node) {
293
343
  }
294
344
  case "arrayComprehension":
295
345
  return encodeArrayComprehension(node);
346
+ case "whileArrayComprehension":
347
+ return encodeWhileArrayComprehension(node);
296
348
  case "object": {
297
349
  const body = node.entries.map(({ key, value }) => `${encodeNode(key)}${addOptionalPrefix(encodeNode(value))}`).join("");
298
350
  return `{${body}}`;
299
351
  }
300
352
  case "objectComprehension":
301
353
  return encodeObjectComprehension(node);
354
+ case "whileObjectComprehension":
355
+ return encodeWhileObjectComprehension(node);
302
356
  case "key":
303
357
  return encodeBareOrLengthString(node.name);
304
358
  case "group":
@@ -308,6 +362,10 @@ function encodeNode(node) {
308
362
  return `~${encodeNode(node.value)}`;
309
363
  if (node.op === "neg")
310
364
  return encodeCallParts([encodeOpcode("neg"), encodeNode(node.value)]);
365
+ if (node.op === "logicalNot") {
366
+ const val = encodeNode(node.value);
367
+ return `!(${val}tr')`;
368
+ }
311
369
  return encodeCallParts([encodeOpcode("not"), encodeNode(node.value)]);
312
370
  case "binary":
313
371
  if (node.op === "and") {
@@ -326,12 +384,19 @@ function encodeNode(node) {
326
384
  }).join("");
327
385
  return `|(${body})`;
328
386
  }
387
+ if (node.op === "nor") {
388
+ const left = encodeNode(node.left);
389
+ const right = addOptionalPrefix(encodeNode(node.right));
390
+ return `!(${left}${right})`;
391
+ }
329
392
  return encodeCallParts([
330
393
  encodeOpcode(BINARY_TO_OPCODE[node.op]),
331
394
  encodeNode(node.left),
332
395
  encodeNode(node.right)
333
396
  ]);
334
397
  case "assign": {
398
+ if (node.op === ":=")
399
+ return `/${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
335
400
  if (node.op === "=")
336
401
  return `=${encodeNode(node.place)}${addOptionalPrefix(encodeNode(node.value))}`;
337
402
  const opcode = ASSIGN_COMPOUND_TO_OPCODE[node.op];
@@ -342,8 +407,12 @@ function encodeNode(node) {
342
407
  }
343
408
  case "navigation":
344
409
  return encodeNavigation(node);
345
- case "call":
410
+ case "call": {
411
+ if (node.callee.type === "identifier" && KEYWORD_OPCODES.has(node.callee.name)) {
412
+ return encodeCallParts([encodeOpcode(node.callee.name), ...node.args.map((arg) => encodeNode(arg))]);
413
+ }
346
414
  return encodeCallParts([encodeNode(node.callee), ...node.args.map((arg) => encodeNode(arg))]);
415
+ }
347
416
  case "conditional": {
348
417
  const opener = node.head === "when" ? "?(" : "!(";
349
418
  const cond = encodeNode(node.condition);
@@ -351,6 +420,8 @@ function encodeNode(node) {
351
420
  const elseExpr = node.elseBranch ? addOptionalPrefix(encodeConditionalElse(node.elseBranch)) : "";
352
421
  return `${opener}${cond}${thenExpr}${elseExpr})`;
353
422
  }
423
+ case "range":
424
+ return encodeCallParts([encodeOpcode("range"), encodeNode(node.from), encodeNode(node.to)]);
354
425
  case "for":
355
426
  return encodeFor(node);
356
427
  case "while":
@@ -429,6 +500,18 @@ function isPlainObject(value) {
429
500
  function isBareKeyName(key) {
430
501
  return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
431
502
  }
503
+ function isNumericKey(key) {
504
+ if (key === "")
505
+ return false;
506
+ return String(Number(key)) === key && Number.isFinite(Number(key));
507
+ }
508
+ function stringifyKey(key) {
509
+ if (isBareKeyName(key))
510
+ return key;
511
+ if (isNumericKey(key))
512
+ return key;
513
+ return stringifyString(key);
514
+ }
432
515
  function stringifyString(value) {
433
516
  return JSON.stringify(value);
434
517
  }
@@ -440,8 +523,12 @@ function stringifyInline(value) {
440
523
  if (typeof value === "boolean")
441
524
  return value ? "true" : "false";
442
525
  if (typeof value === "number") {
443
- if (!Number.isFinite(value))
444
- throw new Error("Rex stringify() cannot encode non-finite numbers");
526
+ if (Number.isNaN(value))
527
+ return "nan";
528
+ if (value === Infinity)
529
+ return "inf";
530
+ if (value === -Infinity)
531
+ return "-inf";
445
532
  return String(value);
446
533
  }
447
534
  if (typeof value === "string")
@@ -455,7 +542,7 @@ function stringifyInline(value) {
455
542
  const entries = Object.entries(value);
456
543
  if (entries.length === 0)
457
544
  return "{}";
458
- const body = entries.map(([key, item]) => `${isBareKeyName(key) ? key : stringifyString(key)}: ${stringifyInline(item)}`).join(" ");
545
+ const body = entries.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`).join(" ");
459
546
  return `{${body}}`;
460
547
  }
461
548
  throw new Error(`Rex stringify() cannot encode value of type ${typeof value}`);
@@ -492,7 +579,7 @@ ${indent}]`;
492
579
  if (entries.length === 0)
493
580
  return "{}";
494
581
  const lines = entries.map(([key, item]) => {
495
- const keyText = isBareKeyName(key) ? key : stringifyString(key);
582
+ const keyText = stringifyKey(key);
496
583
  const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
497
584
  return `${childIndent}${keyText}: ${rendered}`;
498
585
  });
@@ -510,78 +597,55 @@ function domainRefsFromConfig(config) {
510
597
  if (!config || typeof config !== "object" || Array.isArray(config)) {
511
598
  throw new Error("Domain config must be an object");
512
599
  }
513
- const refs = {};
514
- for (const section of Object.values(config)) {
515
- if (!section || typeof section !== "object" || Array.isArray(section))
516
- continue;
517
- mapConfigEntries(section, refs);
518
- }
519
- return refs;
520
- }
521
- function decodeDomainRefKey(refText) {
522
- if (!refText)
523
- throw new Error("Domain ref key cannot be empty");
524
- if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
525
- throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
600
+ const configObj = config;
601
+ const domainRefs = {};
602
+ const domainOpcodes = {};
603
+ const dataSection = configObj.data;
604
+ if (dataSection && typeof dataSection === "object" && !Array.isArray(dataSection)) {
605
+ mapConfigEntries(dataSection, domainRefs);
526
606
  }
527
- if (refText.length > 1 && refText[0] === "0") {
528
- throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
607
+ const functionsSection = configObj.functions;
608
+ if (functionsSection && typeof functionsSection === "object" && !Array.isArray(functionsSection)) {
609
+ mapConfigEntries(functionsSection, domainOpcodes);
529
610
  }
530
- if (/^[1-9]$/.test(refText)) {
531
- throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
532
- }
533
- let value = 0;
534
- for (const char of refText) {
535
- const digit = DOMAIN_DIGIT_INDEX.get(char);
536
- if (digit === undefined)
537
- throw new Error(`Invalid domain ref key '${refText}'`);
538
- value = value * 64 + digit;
539
- if (value > Number.MAX_SAFE_INTEGER) {
540
- throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
541
- }
542
- }
543
- if (value < FIRST_NON_RESERVED_REF) {
544
- throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
545
- }
546
- return value;
611
+ return { domainRefs, domainOpcodes };
547
612
  }
548
613
  function mapConfigEntries(entries, refs) {
549
614
  const sourceKindByRoot = new Map;
550
615
  for (const root of Object.keys(refs)) {
551
616
  sourceKindByRoot.set(root, "explicit");
552
617
  }
553
- for (const [refText, rawEntry] of Object.entries(entries)) {
618
+ for (const [shortCode, rawEntry] of Object.entries(entries)) {
554
619
  const entry = rawEntry;
555
620
  if (!entry || typeof entry !== "object")
556
621
  continue;
557
622
  if (!Array.isArray(entry.names))
558
623
  continue;
559
- const refId = decodeDomainRefKey(refText);
560
624
  for (const rawName of entry.names) {
561
625
  if (typeof rawName !== "string")
562
626
  continue;
563
- const existingNameRef = refs[rawName];
564
- if (existingNameRef !== undefined && existingNameRef !== refId) {
565
- throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
627
+ const existingRef = refs[rawName];
628
+ if (existingRef !== undefined && existingRef !== shortCode) {
629
+ throw new Error(`Conflicting refs for '${rawName}': ${existingRef} vs ${shortCode}`);
566
630
  }
567
- refs[rawName] = refId;
631
+ refs[rawName] = shortCode;
568
632
  const root = rawName.split(".")[0];
569
633
  if (!root)
570
634
  continue;
571
635
  const currentKind = rawName.includes(".") ? "implicit" : "explicit";
572
636
  const existing = refs[root];
573
637
  if (existing !== undefined) {
574
- if (existing === refId)
638
+ if (existing === shortCode)
575
639
  continue;
576
640
  const existingKind = sourceKindByRoot.get(root) ?? "explicit";
577
641
  if (currentKind === "explicit") {
578
- throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
642
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${shortCode}`);
579
643
  }
580
644
  if (existingKind === "explicit")
581
645
  continue;
582
646
  continue;
583
647
  }
584
- refs[root] = refId;
648
+ refs[root] = shortCode;
585
649
  sourceKindByRoot.set(root, currentKind);
586
650
  }
587
651
  }
@@ -863,6 +927,21 @@ function dropBindingNames(env, binding) {
863
927
  clearBinding(env, binding.key);
864
928
  }
865
929
  }
930
+ function optimizeBinding(binding, sourceEnv, currentDepth) {
931
+ const source = optimizeNode(binding.source, sourceEnv, currentDepth);
932
+ switch (binding.type) {
933
+ case "binding:bareIn":
934
+ return { type: "binding:bareIn", source };
935
+ case "binding:bareOf":
936
+ return { type: "binding:bareOf", source };
937
+ case "binding:valueIn":
938
+ return { type: "binding:valueIn", value: binding.value, source };
939
+ case "binding:keyValueIn":
940
+ return { type: "binding:keyValueIn", key: binding.key, value: binding.value, source };
941
+ case "binding:keyOf":
942
+ return { type: "binding:keyOf", key: binding.key, source };
943
+ }
944
+ }
866
945
  function collectReads(node, out) {
867
946
  switch (node.type) {
868
947
  case "identifier":
@@ -885,11 +964,20 @@ function collectReads(node, out) {
885
964
  collectReads(node.binding.source, out);
886
965
  collectReads(node.body, out);
887
966
  return;
967
+ case "whileArrayComprehension":
968
+ collectReads(node.condition, out);
969
+ collectReads(node.body, out);
970
+ return;
888
971
  case "objectComprehension":
889
972
  collectReads(node.binding.source, out);
890
973
  collectReads(node.key, out);
891
974
  collectReads(node.value, out);
892
975
  return;
976
+ case "whileObjectComprehension":
977
+ collectReads(node.condition, out);
978
+ collectReads(node.key, out);
979
+ collectReads(node.value, out);
980
+ return;
893
981
  case "unary":
894
982
  collectReads(node.value, out);
895
983
  return;
@@ -926,6 +1014,10 @@ function collectReads(node, out) {
926
1014
  for (const part of node.body)
927
1015
  collectReads(part, out);
928
1016
  return;
1017
+ case "range":
1018
+ collectReads(node.from, out);
1019
+ collectReads(node.to, out);
1020
+ return;
929
1021
  case "program":
930
1022
  for (const part of node.body)
931
1023
  collectReads(part, out);
@@ -970,6 +1062,8 @@ function isPureNode(node) {
970
1062
  return node.op !== "delete" && isPureNode(node.value);
971
1063
  case "binary":
972
1064
  return isPureNode(node.left) && isPureNode(node.right);
1065
+ case "range":
1066
+ return isPureNode(node.from) && isPureNode(node.to);
973
1067
  default:
974
1068
  return false;
975
1069
  }
@@ -1034,6 +1128,8 @@ function hasIdentifierRead(node, name, asPlace = false) {
1034
1128
  return hasIdentifierRead(node.value, name, node.op === "delete");
1035
1129
  case "binary":
1036
1130
  return hasIdentifierRead(node.left, name) || hasIdentifierRead(node.right, name);
1131
+ case "range":
1132
+ return hasIdentifierRead(node.from, name) || hasIdentifierRead(node.to, name);
1037
1133
  case "assign":
1038
1134
  return hasIdentifierRead(node.place, name, true) || hasIdentifierRead(node.value, name);
1039
1135
  default:
@@ -1056,6 +1152,8 @@ function countIdentifierReads(node, name, asPlace = false) {
1056
1152
  return countIdentifierReads(node.value, name, node.op === "delete");
1057
1153
  case "binary":
1058
1154
  return countIdentifierReads(node.left, name) + countIdentifierReads(node.right, name);
1155
+ case "range":
1156
+ return countIdentifierReads(node.from, name) + countIdentifierReads(node.to, name);
1059
1157
  case "assign":
1060
1158
  return countIdentifierReads(node.place, name, true) + countIdentifierReads(node.value, name);
1061
1159
  default:
@@ -1110,6 +1208,12 @@ function replaceIdentifier(node, name, replacement, asPlace = false) {
1110
1208
  place: replaceIdentifier(node.place, name, replacement, true),
1111
1209
  value: replaceIdentifier(node.value, name, replacement)
1112
1210
  };
1211
+ case "range":
1212
+ return {
1213
+ type: "range",
1214
+ from: replaceIdentifier(node.from, name, replacement),
1215
+ to: replaceIdentifier(node.to, name, replacement)
1216
+ };
1113
1217
  default:
1114
1218
  return node;
1115
1219
  }
@@ -1150,7 +1254,16 @@ function inlineAdjacentPureAssignments(block) {
1150
1254
  return out;
1151
1255
  }
1152
1256
  function toNumberNode(value) {
1153
- return { type: "number", raw: String(value), value };
1257
+ let raw;
1258
+ if (Number.isNaN(value))
1259
+ raw = "nan";
1260
+ else if (value === Infinity)
1261
+ raw = "inf";
1262
+ else if (value === -Infinity)
1263
+ raw = "-inf";
1264
+ else
1265
+ raw = String(value);
1266
+ return { type: "number", raw, value };
1154
1267
  }
1155
1268
  function toStringNode(value) {
1156
1269
  return { type: "string", raw: JSON.stringify(value) };
@@ -1162,7 +1275,7 @@ function toLiteralNode(value) {
1162
1275
  return { type: "null" };
1163
1276
  if (typeof value === "boolean")
1164
1277
  return { type: "boolean", value };
1165
- if (typeof value === "number" && Number.isFinite(value))
1278
+ if (typeof value === "number")
1166
1279
  return toNumberNode(value);
1167
1280
  if (typeof value === "string")
1168
1281
  return toStringNode(value);
@@ -1245,6 +1358,9 @@ function foldUnary(op, value) {
1245
1358
  return ~value;
1246
1359
  return;
1247
1360
  }
1361
+ if (op === "logicalNot") {
1362
+ return value === undefined ? true : undefined;
1363
+ }
1248
1364
  return;
1249
1365
  }
1250
1366
  function foldBinary(op, left, right) {
@@ -1415,6 +1531,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1415
1531
  }
1416
1532
  return { type: "binary", op: node.op, left, right };
1417
1533
  }
1534
+ case "range":
1535
+ return {
1536
+ type: "range",
1537
+ from: optimizeNode(node.from, env, currentDepth),
1538
+ to: optimizeNode(node.to, env, currentDepth)
1539
+ };
1418
1540
  case "navigation": {
1419
1541
  const target = optimizeNode(node.target, env, currentDepth);
1420
1542
  const segments = node.segments.map((segment) => segment.type === "static" ? segment : { type: "dynamic", key: optimizeNode(segment.key, env, currentDepth) });
@@ -1525,31 +1647,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1525
1647
  }
1526
1648
  case "for": {
1527
1649
  const sourceEnv = cloneOptimizeEnv(env);
1528
- const binding = (() => {
1529
- if (node.binding.type === "binding:expr") {
1530
- return { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) };
1531
- }
1532
- if (node.binding.type === "binding:valueIn") {
1533
- return {
1534
- type: "binding:valueIn",
1535
- value: node.binding.value,
1536
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1537
- };
1538
- }
1539
- if (node.binding.type === "binding:keyValueIn") {
1540
- return {
1541
- type: "binding:keyValueIn",
1542
- key: node.binding.key,
1543
- value: node.binding.value,
1544
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1545
- };
1546
- }
1547
- return {
1548
- type: "binding:keyOf",
1549
- key: node.binding.key,
1550
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1551
- };
1552
- })();
1650
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1553
1651
  const bodyEnv = cloneOptimizeEnv(env);
1554
1652
  dropBindingNames(bodyEnv, binding);
1555
1653
  return {
@@ -1560,20 +1658,7 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1560
1658
  }
1561
1659
  case "arrayComprehension": {
1562
1660
  const sourceEnv = cloneOptimizeEnv(env);
1563
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1564
- type: "binding:valueIn",
1565
- value: node.binding.value,
1566
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1567
- } : node.binding.type === "binding:keyValueIn" ? {
1568
- type: "binding:keyValueIn",
1569
- key: node.binding.key,
1570
- value: node.binding.value,
1571
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1572
- } : {
1573
- type: "binding:keyOf",
1574
- key: node.binding.key,
1575
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1576
- };
1661
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1577
1662
  const bodyEnv = cloneOptimizeEnv(env);
1578
1663
  dropBindingNames(bodyEnv, binding);
1579
1664
  return {
@@ -1582,22 +1667,15 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1582
1667
  body: optimizeNode(node.body, bodyEnv, currentDepth + 1)
1583
1668
  };
1584
1669
  }
1670
+ case "whileArrayComprehension":
1671
+ return {
1672
+ type: "whileArrayComprehension",
1673
+ condition: optimizeNode(node.condition, env, currentDepth),
1674
+ body: optimizeNode(node.body, env, currentDepth + 1)
1675
+ };
1585
1676
  case "objectComprehension": {
1586
1677
  const sourceEnv = cloneOptimizeEnv(env);
1587
- const binding = node.binding.type === "binding:expr" ? { type: "binding:expr", source: optimizeNode(node.binding.source, sourceEnv, currentDepth) } : node.binding.type === "binding:valueIn" ? {
1588
- type: "binding:valueIn",
1589
- value: node.binding.value,
1590
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1591
- } : node.binding.type === "binding:keyValueIn" ? {
1592
- type: "binding:keyValueIn",
1593
- key: node.binding.key,
1594
- value: node.binding.value,
1595
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1596
- } : {
1597
- type: "binding:keyOf",
1598
- key: node.binding.key,
1599
- source: optimizeNode(node.binding.source, sourceEnv, currentDepth)
1600
- };
1678
+ const binding = optimizeBinding(node.binding, sourceEnv, currentDepth);
1601
1679
  const bodyEnv = cloneOptimizeEnv(env);
1602
1680
  dropBindingNames(bodyEnv, binding);
1603
1681
  return {
@@ -1607,6 +1685,13 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1607
1685
  value: optimizeNode(node.value, bodyEnv, currentDepth + 1)
1608
1686
  };
1609
1687
  }
1688
+ case "whileObjectComprehension":
1689
+ return {
1690
+ type: "whileObjectComprehension",
1691
+ condition: optimizeNode(node.condition, env, currentDepth),
1692
+ key: optimizeNode(node.key, env, currentDepth + 1),
1693
+ value: optimizeNode(node.value, env, currentDepth + 1)
1694
+ };
1610
1695
  default:
1611
1696
  return node;
1612
1697
  }
@@ -1658,6 +1743,10 @@ function collectLocalBindings(node, locals) {
1658
1743
  collectLocalBindings(node.left, locals);
1659
1744
  collectLocalBindings(node.right, locals);
1660
1745
  return;
1746
+ case "range":
1747
+ collectLocalBindings(node.from, locals);
1748
+ collectLocalBindings(node.to, locals);
1749
+ return;
1661
1750
  case "conditional":
1662
1751
  collectLocalBindings(node.condition, locals);
1663
1752
  for (const part of node.thenBlock)
@@ -1674,11 +1763,20 @@ function collectLocalBindings(node, locals) {
1674
1763
  collectLocalBindingFromBinding(node.binding, locals);
1675
1764
  collectLocalBindings(node.body, locals);
1676
1765
  return;
1766
+ case "whileArrayComprehension":
1767
+ collectLocalBindings(node.condition, locals);
1768
+ collectLocalBindings(node.body, locals);
1769
+ return;
1677
1770
  case "objectComprehension":
1678
1771
  collectLocalBindingFromBinding(node.binding, locals);
1679
1772
  collectLocalBindings(node.key, locals);
1680
1773
  collectLocalBindings(node.value, locals);
1681
1774
  return;
1775
+ case "whileObjectComprehension":
1776
+ collectLocalBindings(node.condition, locals);
1777
+ collectLocalBindings(node.key, locals);
1778
+ collectLocalBindings(node.value, locals);
1779
+ return;
1682
1780
  default:
1683
1781
  return;
1684
1782
  }
@@ -1770,6 +1868,10 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1770
1868
  collectNameFrequencies(node.left, locals, frequencies, order, nextOrder);
1771
1869
  collectNameFrequencies(node.right, locals, frequencies, order, nextOrder);
1772
1870
  return;
1871
+ case "range":
1872
+ collectNameFrequencies(node.from, locals, frequencies, order, nextOrder);
1873
+ collectNameFrequencies(node.to, locals, frequencies, order, nextOrder);
1874
+ return;
1773
1875
  case "conditional":
1774
1876
  collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1775
1877
  for (const part of node.thenBlock)
@@ -1786,11 +1888,20 @@ function collectNameFrequencies(node, locals, frequencies, order, nextOrder) {
1786
1888
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1787
1889
  collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1788
1890
  return;
1891
+ case "whileArrayComprehension":
1892
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1893
+ collectNameFrequencies(node.body, locals, frequencies, order, nextOrder);
1894
+ return;
1789
1895
  case "objectComprehension":
1790
1896
  collectNameFrequenciesBinding(node.binding, locals, frequencies, order, nextOrder);
1791
1897
  collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1792
1898
  collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1793
1899
  return;
1900
+ case "whileObjectComprehension":
1901
+ collectNameFrequencies(node.condition, locals, frequencies, order, nextOrder);
1902
+ collectNameFrequencies(node.key, locals, frequencies, order, nextOrder);
1903
+ collectNameFrequencies(node.value, locals, frequencies, order, nextOrder);
1904
+ return;
1794
1905
  default:
1795
1906
  return;
1796
1907
  }
@@ -1865,6 +1976,12 @@ function renameLocalNames(node, map) {
1865
1976
  left: renameLocalNames(node.left, map),
1866
1977
  right: renameLocalNames(node.right, map)
1867
1978
  };
1979
+ case "range":
1980
+ return {
1981
+ type: "range",
1982
+ from: renameLocalNames(node.from, map),
1983
+ to: renameLocalNames(node.to, map)
1984
+ };
1868
1985
  case "assign": {
1869
1986
  const place = node.place.type === "identifier" && map.has(node.place.name) ? { type: "identifier", name: map.get(node.place.name) } : renameLocalNames(node.place, map);
1870
1987
  return {
@@ -1894,6 +2011,12 @@ function renameLocalNames(node, map) {
1894
2011
  binding: renameLocalNamesBinding(node.binding, map),
1895
2012
  body: renameLocalNames(node.body, map)
1896
2013
  };
2014
+ case "whileArrayComprehension":
2015
+ return {
2016
+ type: "whileArrayComprehension",
2017
+ condition: renameLocalNames(node.condition, map),
2018
+ body: renameLocalNames(node.body, map)
2019
+ };
1897
2020
  case "objectComprehension":
1898
2021
  return {
1899
2022
  type: "objectComprehension",
@@ -1901,34 +2024,31 @@ function renameLocalNames(node, map) {
1901
2024
  key: renameLocalNames(node.key, map),
1902
2025
  value: renameLocalNames(node.value, map)
1903
2026
  };
2027
+ case "whileObjectComprehension":
2028
+ return {
2029
+ type: "whileObjectComprehension",
2030
+ condition: renameLocalNames(node.condition, map),
2031
+ key: renameLocalNames(node.key, map),
2032
+ value: renameLocalNames(node.value, map)
2033
+ };
1904
2034
  default:
1905
2035
  return node;
1906
2036
  }
1907
2037
  }
1908
2038
  function renameLocalNamesBinding(binding, map) {
1909
- if (binding.type === "binding:expr") {
1910
- return { type: "binding:expr", source: renameLocalNames(binding.source, map) };
2039
+ const source = renameLocalNames(binding.source, map);
2040
+ switch (binding.type) {
2041
+ case "binding:bareIn":
2042
+ return { type: "binding:bareIn", source };
2043
+ case "binding:bareOf":
2044
+ return { type: "binding:bareOf", source };
2045
+ case "binding:valueIn":
2046
+ return { type: "binding:valueIn", value: map.get(binding.value) ?? binding.value, source };
2047
+ case "binding:keyValueIn":
2048
+ return { type: "binding:keyValueIn", key: map.get(binding.key) ?? binding.key, value: map.get(binding.value) ?? binding.value, source };
2049
+ case "binding:keyOf":
2050
+ return { type: "binding:keyOf", key: map.get(binding.key) ?? binding.key, source };
1911
2051
  }
1912
- if (binding.type === "binding:valueIn") {
1913
- return {
1914
- type: "binding:valueIn",
1915
- value: map.get(binding.value) ?? binding.value,
1916
- source: renameLocalNames(binding.source, map)
1917
- };
1918
- }
1919
- if (binding.type === "binding:keyValueIn") {
1920
- return {
1921
- type: "binding:keyValueIn",
1922
- key: map.get(binding.key) ?? binding.key,
1923
- value: map.get(binding.value) ?? binding.value,
1924
- source: renameLocalNames(binding.source, map)
1925
- };
1926
- }
1927
- return {
1928
- type: "binding:keyOf",
1929
- key: map.get(binding.key) ?? binding.key,
1930
- source: renameLocalNames(binding.source, map)
1931
- };
1932
2052
  }
1933
2053
  function renameLocalNamesElse(elseBranch, map) {
1934
2054
  if (elseBranch.type === "else") {
@@ -1975,14 +2095,20 @@ function compile(source, options) {
1975
2095
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
1976
2096
  if (options?.minifyNames)
1977
2097
  lowered = minifyLocalNamesIR(lowered);
1978
- const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
2098
+ const domainMaps = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1979
2099
  return encodeIR(lowered, {
1980
- domainRefs,
2100
+ ...domainMaps,
1981
2101
  dedupeValues: options?.dedupeValues,
1982
2102
  dedupeMinBytes: options?.dedupeMinBytes
1983
2103
  });
1984
2104
  }
1985
2105
  function parseNumber(raw) {
2106
+ if (raw === "nan")
2107
+ return NaN;
2108
+ if (raw === "inf")
2109
+ return Infinity;
2110
+ if (raw === "-inf")
2111
+ return -Infinity;
1986
2112
  if (/^-?0x/i.test(raw))
1987
2113
  return parseInt(raw, 16);
1988
2114
  if (/^-?0b/i.test(raw)) {
@@ -2054,7 +2180,7 @@ function buildPostfix(base, steps) {
2054
2180
  flushSegments();
2055
2181
  return current;
2056
2182
  }
2057
- var require2, rexGrammarModule, rexGrammar, grammar, semantics, DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", OPCODE_IDS, FIRST_NON_RESERVED_REF = 5, DOMAIN_DIGIT_INDEX, BINARY_TO_OPCODE, ASSIGN_COMPOUND_TO_OPCODE, activeEncodeOptions, DIGIT_SET, DIGIT_INDEX;
2183
+ var require2, rexGrammarModule, rexGrammar, grammar, semantics, DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", OPCODE_IDS, KEYWORD_OPCODES, BINARY_TO_OPCODE, ASSIGN_COMPOUND_TO_OPCODE, DEC_PARTS, activeEncodeOptions, DIGIT_SET, DIGIT_INDEX;
2058
2184
  var init_rex = __esm(() => {
2059
2185
  require2 = createRequire(import.meta.url);
2060
2186
  rexGrammarModule = require2("./rex.ohm-bundle.cjs");
@@ -2062,30 +2188,32 @@ var init_rex = __esm(() => {
2062
2188
  grammar = rexGrammar;
2063
2189
  semantics = rexGrammar.createSemantics();
2064
2190
  OPCODE_IDS = {
2065
- do: 0,
2066
- add: 1,
2067
- sub: 2,
2068
- mul: 3,
2069
- div: 4,
2070
- eq: 5,
2071
- neq: 6,
2072
- lt: 7,
2073
- lte: 8,
2074
- gt: 9,
2075
- gte: 10,
2076
- and: 11,
2077
- or: 12,
2078
- xor: 13,
2079
- not: 14,
2080
- boolean: 15,
2081
- number: 16,
2082
- string: 17,
2083
- array: 18,
2084
- object: 19,
2085
- mod: 20,
2086
- neg: 21
2191
+ do: "",
2192
+ add: "ad",
2193
+ sub: "sb",
2194
+ mul: "ml",
2195
+ div: "dv",
2196
+ eq: "eq",
2197
+ neq: "nq",
2198
+ lt: "lt",
2199
+ lte: "le",
2200
+ gt: "gt",
2201
+ gte: "ge",
2202
+ and: "an",
2203
+ or: "or",
2204
+ xor: "xr",
2205
+ not: "nt",
2206
+ boolean: "bt",
2207
+ number: "nm",
2208
+ string: "st",
2209
+ array: "ar",
2210
+ object: "ob",
2211
+ mod: "md",
2212
+ neg: "ng",
2213
+ range: "rn",
2214
+ size: "sz"
2087
2215
  };
2088
- DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
2216
+ KEYWORD_OPCODES = new Set(["boolean", "number", "string", "array", "object", "size"]);
2089
2217
  BINARY_TO_OPCODE = {
2090
2218
  add: "add",
2091
2219
  sub: "sub",
@@ -2114,6 +2242,7 @@ var init_rex = __esm(() => {
2114
2242
  "|=": "or",
2115
2243
  "^=": "xor"
2116
2244
  };
2245
+ DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
2117
2246
  DIGIT_SET = new Set(DIGITS.split(""));
2118
2247
  DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
2119
2248
  semantics.addOperation("toIR", {
@@ -2159,6 +2288,9 @@ var init_rex = __esm(() => {
2159
2288
  ExistenceExpr_or(left, _or, right) {
2160
2289
  return { type: "binary", op: "or", left: left.toIR(), right: right.toIR() };
2161
2290
  },
2291
+ ExistenceExpr_nor(left, _nor, right) {
2292
+ return { type: "binary", op: "nor", left: left.toIR(), right: right.toIR() };
2293
+ },
2162
2294
  BitExpr_and(left, _op, right) {
2163
2295
  return { type: "binary", op: "bitAnd", left: left.toIR(), right: right.toIR() };
2164
2296
  },
@@ -2168,6 +2300,9 @@ var init_rex = __esm(() => {
2168
2300
  BitExpr_or(left, _op, right) {
2169
2301
  return { type: "binary", op: "bitOr", left: left.toIR(), right: right.toIR() };
2170
2302
  },
2303
+ RangeExpr_range(left, _op, right) {
2304
+ return { type: "range", from: left.toIR(), to: right.toIR() };
2305
+ },
2171
2306
  CompareExpr_binary(left, op, right) {
2172
2307
  const map = {
2173
2308
  "==": "eq",
@@ -2208,6 +2343,9 @@ var init_rex = __esm(() => {
2208
2343
  UnaryExpr_not(_op, value) {
2209
2344
  return { type: "unary", op: "not", value: value.toIR() };
2210
2345
  },
2346
+ UnaryExpr_logicalNot(_not, value) {
2347
+ return { type: "unary", op: "logicalNot", value: value.toIR() };
2348
+ },
2211
2349
  UnaryExpr_delete(_del, place) {
2212
2350
  return { type: "unary", op: "delete", value: place.toIR() };
2213
2351
  },
@@ -2261,14 +2399,6 @@ var init_rex = __esm(() => {
2261
2399
  ConditionalElse_else(_else, block) {
2262
2400
  return { type: "else", block: block.toIR() };
2263
2401
  },
2264
- DoExpr(_do, block, _end) {
2265
- const body = block.toIR();
2266
- if (body.length === 0)
2267
- return { type: "undefined" };
2268
- if (body.length === 1)
2269
- return body[0];
2270
- return { type: "program", body };
2271
- },
2272
2402
  WhileExpr(_while, condition, _do, block, _end) {
2273
2403
  return {
2274
2404
  type: "while",
@@ -2283,30 +2413,44 @@ var init_rex = __esm(() => {
2283
2413
  body: block.toIR()
2284
2414
  };
2285
2415
  },
2286
- BindingExpr(iterOrExpr) {
2287
- const node = iterOrExpr.toIR();
2288
- if (typeof node === "object" && node && "type" in node && String(node.type).startsWith("binding:")) {
2289
- return node;
2290
- }
2291
- return { type: "binding:expr", source: node };
2292
- },
2293
2416
  Array_empty(_open, _close) {
2294
2417
  return { type: "array", items: [] };
2295
2418
  },
2296
- Array_comprehension(_open, binding, _semi, body, _close) {
2419
+ Array_forComprehension(_open, body, _for, binding, _close) {
2297
2420
  return {
2298
2421
  type: "arrayComprehension",
2299
2422
  binding: binding.toIR(),
2300
2423
  body: body.toIR()
2301
2424
  };
2302
2425
  },
2426
+ Array_whileComprehension(_open, body, _while, condition, _close) {
2427
+ return {
2428
+ type: "whileArrayComprehension",
2429
+ condition: condition.toIR(),
2430
+ body: body.toIR()
2431
+ };
2432
+ },
2433
+ Array_inComprehension(_open, body, _in, source, _close) {
2434
+ return {
2435
+ type: "arrayComprehension",
2436
+ binding: { type: "binding:bareIn", source: source.toIR() },
2437
+ body: body.toIR()
2438
+ };
2439
+ },
2440
+ Array_ofComprehension(_open, body, _of, source, _close) {
2441
+ return {
2442
+ type: "arrayComprehension",
2443
+ binding: { type: "binding:bareOf", source: source.toIR() },
2444
+ body: body.toIR()
2445
+ };
2446
+ },
2303
2447
  Array_values(_open, items, _close) {
2304
2448
  return { type: "array", items: normalizeList(items.toIR()) };
2305
2449
  },
2306
2450
  Object_empty(_open, _close) {
2307
2451
  return { type: "object", entries: [] };
2308
2452
  },
2309
- Object_comprehension(_open, binding, _semi, key, _colon, value, _close) {
2453
+ Object_forComprehension(_open, key, _colon, value, _for, binding, _close) {
2310
2454
  return {
2311
2455
  type: "objectComprehension",
2312
2456
  binding: binding.toIR(),
@@ -2314,6 +2458,30 @@ var init_rex = __esm(() => {
2314
2458
  value: value.toIR()
2315
2459
  };
2316
2460
  },
2461
+ Object_whileComprehension(_open, key, _colon, value, _while, condition, _close) {
2462
+ return {
2463
+ type: "whileObjectComprehension",
2464
+ condition: condition.toIR(),
2465
+ key: key.toIR(),
2466
+ value: value.toIR()
2467
+ };
2468
+ },
2469
+ Object_inComprehension(_open, key, _colon, value, _in, source, _close) {
2470
+ return {
2471
+ type: "objectComprehension",
2472
+ binding: { type: "binding:bareIn", source: source.toIR() },
2473
+ key: key.toIR(),
2474
+ value: value.toIR()
2475
+ };
2476
+ },
2477
+ Object_ofComprehension(_open, key, _colon, value, _of, source, _close) {
2478
+ return {
2479
+ type: "objectComprehension",
2480
+ binding: { type: "binding:bareOf", source: source.toIR() },
2481
+ key: key.toIR(),
2482
+ value: value.toIR()
2483
+ };
2484
+ },
2317
2485
  Object_pairs(_open, pairs, _close) {
2318
2486
  return {
2319
2487
  type: "object",
@@ -2342,6 +2510,18 @@ var init_rex = __esm(() => {
2342
2510
  source: source.toIR()
2343
2511
  };
2344
2512
  },
2513
+ IterBinding_bareIn(_in, source) {
2514
+ return {
2515
+ type: "binding:bareIn",
2516
+ source: source.toIR()
2517
+ };
2518
+ },
2519
+ IterBinding_bareOf(_of, source) {
2520
+ return {
2521
+ type: "binding:bareOf",
2522
+ source: source.toIR()
2523
+ };
2524
+ },
2345
2525
  Pair(key, _colon, value) {
2346
2526
  return { key: key.toIR(), value: value.toIR() };
2347
2527
  },
@@ -2405,6 +2585,9 @@ var init_rex = __esm(() => {
2405
2585
  BooleanKw(_kw) {
2406
2586
  return { type: "identifier", name: "boolean" };
2407
2587
  },
2588
+ SizeKw(_kw) {
2589
+ return { type: "identifier", name: "size" };
2590
+ },
2408
2591
  identifier(_a, _b) {
2409
2592
  return { type: "identifier", name: this.sourceString };
2410
2593
  },
@@ -2443,7 +2626,6 @@ class CursorInterpreter {
2443
2626
  pos = 0;
2444
2627
  state;
2445
2628
  selfStack;
2446
- opcodeMarkers;
2447
2629
  pointerCache = new Map;
2448
2630
  gasLimit;
2449
2631
  gas = 0;
@@ -2458,25 +2640,22 @@ class CursorInterpreter {
2458
2640
  this.state = {
2459
2641
  vars: ctx.vars ?? {},
2460
2642
  refs: {
2461
- 0: ctx.refs?.[0],
2462
- 1: ctx.refs?.[1] ?? true,
2463
- 2: ctx.refs?.[2] ?? false,
2464
- 3: ctx.refs?.[3] ?? null,
2465
- 4: ctx.refs?.[4] ?? undefined
2643
+ tr: true,
2644
+ fl: false,
2645
+ nl: null,
2646
+ un: undefined,
2647
+ nan: NaN,
2648
+ inf: Infinity,
2649
+ nif: -Infinity,
2650
+ ...ctx.refs
2466
2651
  }
2467
2652
  };
2468
2653
  this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
2469
2654
  this.gasLimit = ctx.gasLimit ?? 0;
2470
- this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
2471
- for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
2472
- const id = Number(idText);
2473
- if (Number.isInteger(id))
2474
- this.state.refs[id] = value;
2475
- }
2476
2655
  if (ctx.opcodes) {
2477
- for (const [idText, op] of Object.entries(ctx.opcodes)) {
2656
+ for (const [key, op] of Object.entries(ctx.opcodes)) {
2478
2657
  if (op)
2479
- this.customOpcodes.set(Number(idText), op);
2658
+ this.customOpcodes.set(key, op);
2480
2659
  }
2481
2660
  }
2482
2661
  }
@@ -2537,6 +2716,22 @@ class CursorInterpreter {
2537
2716
  const end = this.pos;
2538
2717
  return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
2539
2718
  }
2719
+ advanceByBytes(start, byteCount) {
2720
+ if (byteCount <= 0)
2721
+ return start;
2722
+ let bytes = 0;
2723
+ let index = start;
2724
+ for (const char of this.text.slice(start)) {
2725
+ const charBytes = Buffer.byteLength(char);
2726
+ if (bytes + charBytes > byteCount)
2727
+ break;
2728
+ bytes += charBytes;
2729
+ index += char.length;
2730
+ if (bytes === byteCount)
2731
+ return index;
2732
+ }
2733
+ throw new Error("String container overflows input");
2734
+ }
2540
2735
  ensure(char) {
2541
2736
  if (this.text[this.pos] !== char)
2542
2737
  throw new Error(`Expected '${char}' at ${this.pos}`);
@@ -2577,36 +2772,34 @@ class CursorInterpreter {
2577
2772
  const significand = this.evalValue();
2578
2773
  if (typeof significand !== "number")
2579
2774
  throw new Error("Decimal significand must be numeric");
2580
- return significand * 10 ** power;
2775
+ return parseFloat(`${significand}e${power}`);
2581
2776
  }
2582
2777
  case ":":
2583
2778
  this.pos += 1;
2584
2779
  return prefix.raw;
2585
2780
  case "%":
2586
2781
  this.pos += 1;
2587
- return this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
2782
+ return { __opcode: prefix.raw };
2588
2783
  case "@":
2589
2784
  this.pos += 1;
2590
2785
  return this.readSelf(prefix.value);
2591
2786
  case "'":
2592
2787
  this.pos += 1;
2593
- return this.state.refs[prefix.value];
2788
+ return this.state.refs[prefix.raw];
2594
2789
  case "$":
2595
2790
  this.pos += 1;
2596
2791
  return this.state.vars[prefix.raw];
2597
2792
  case ",": {
2598
2793
  this.pos += 1;
2599
2794
  const start = this.pos;
2600
- const end = start + prefix.value;
2601
- if (end > this.text.length)
2602
- throw new Error("String container overflows input");
2795
+ const end = this.advanceByBytes(start, prefix.value);
2603
2796
  const value = this.text.slice(start, end);
2604
2797
  this.pos = end;
2605
2798
  return value;
2606
2799
  }
2607
2800
  case "^": {
2608
2801
  this.pos += 1;
2609
- const target = this.pos + prefix.value;
2802
+ const target = this.advanceByBytes(this.pos, prefix.value);
2610
2803
  if (this.pointerCache.has(target))
2611
2804
  return this.pointerCache.get(target);
2612
2805
  const save = this.pos;
@@ -2623,6 +2816,14 @@ class CursorInterpreter {
2623
2816
  this.writePlace(place, value);
2624
2817
  return value;
2625
2818
  }
2819
+ case "/": {
2820
+ this.pos += 1;
2821
+ const place = this.readPlace();
2822
+ const oldValue = this.readPlaceValue(place);
2823
+ const newValue = this.evalValue();
2824
+ this.writePlace(place, newValue);
2825
+ return oldValue;
2826
+ }
2626
2827
  case "~": {
2627
2828
  this.pos += 1;
2628
2829
  const place = this.readPlace();
@@ -2650,7 +2851,7 @@ class CursorInterpreter {
2650
2851
  case "<":
2651
2852
  return this.evalLoopLike(tag);
2652
2853
  case "#":
2653
- return this.evalWhileLoop();
2854
+ return this.evalWhileLike();
2654
2855
  default:
2655
2856
  throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
2656
2857
  }
@@ -2863,15 +3064,11 @@ class CursorInterpreter {
2863
3064
  return entries.map(([key]) => ({ key, value: key }));
2864
3065
  return entries.map(([key, value]) => ({ key, value }));
2865
3066
  }
2866
- if (typeof iterable === "number" && Number.isFinite(iterable) && iterable > 0) {
2867
- const out = [];
2868
- for (let index = 0;index < Math.floor(iterable); index += 1) {
2869
- if (keysOnly)
2870
- out.push({ key: index, value: index });
2871
- else
2872
- out.push({ key: index, value: index + 1 });
2873
- }
2874
- return out;
3067
+ if (typeof iterable === "string") {
3068
+ const entries = Array.from(iterable);
3069
+ if (keysOnly)
3070
+ return entries.map((_value, index) => ({ key: index, value: index }));
3071
+ return entries.map((value, index) => ({ key: index, value }));
2875
3072
  }
2876
3073
  return [];
2877
3074
  }
@@ -2916,21 +3113,32 @@ class CursorInterpreter {
2916
3113
  }
2917
3114
  return last;
2918
3115
  }
2919
- evalWhileLoop() {
3116
+ evalWhileLike() {
3117
+ this.pos += 1;
3118
+ const open = this.text[this.pos];
3119
+ if (!open || !"([{".includes(open))
3120
+ throw new Error(`Expected opener after '#' at ${this.pos}`);
3121
+ const close = open === "(" ? ")" : open === "[" ? "]" : "}";
2920
3122
  this.pos += 1;
2921
- this.ensure("(");
2922
3123
  const condStart = this.pos;
2923
3124
  const condValue = this.evalValue();
2924
3125
  const bodyStart = this.pos;
2925
- const bodyValueCount = 1;
3126
+ const bodyValueCount = open === "{" ? 2 : 1;
2926
3127
  let cursor = bodyStart;
2927
3128
  for (let index = 0;index < bodyValueCount; index += 1) {
2928
3129
  cursor = this.skipValueFrom(cursor);
2929
3130
  }
2930
3131
  const bodyEnd = cursor;
2931
3132
  this.pos = bodyEnd;
2932
- this.ensure(")");
3133
+ this.ensure(close);
2933
3134
  const afterClose = this.pos;
3135
+ if (open === "[")
3136
+ return this.evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
3137
+ if (open === "{")
3138
+ return this.evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
3139
+ return this.evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue);
3140
+ }
3141
+ evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue) {
2934
3142
  let last = undefined;
2935
3143
  let currentCond = condValue;
2936
3144
  while (isDefined(currentCond)) {
@@ -2951,6 +3159,58 @@ class CursorInterpreter {
2951
3159
  this.pos = afterClose;
2952
3160
  return last;
2953
3161
  }
3162
+ evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
3163
+ const out = [];
3164
+ let currentCond = condValue;
3165
+ while (isDefined(currentCond)) {
3166
+ this.tick();
3167
+ this.selfStack.push(currentCond);
3168
+ const value = this.evalBodySlice(bodyStart, bodyEnd);
3169
+ this.selfStack.pop();
3170
+ const control = this.handleLoopControl(value);
3171
+ if (control) {
3172
+ if (control.depth > 1)
3173
+ return { kind: control.kind, depth: control.depth - 1 };
3174
+ if (control.kind === "break")
3175
+ break;
3176
+ currentCond = this.evalBodySlice(condStart, bodyStart);
3177
+ continue;
3178
+ }
3179
+ if (isDefined(value))
3180
+ out.push(value);
3181
+ currentCond = this.evalBodySlice(condStart, bodyStart);
3182
+ }
3183
+ this.pos = afterClose;
3184
+ return out;
3185
+ }
3186
+ evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue) {
3187
+ const result = {};
3188
+ let currentCond = condValue;
3189
+ while (isDefined(currentCond)) {
3190
+ this.tick();
3191
+ this.selfStack.push(currentCond);
3192
+ const save = this.pos;
3193
+ this.pos = bodyStart;
3194
+ const key = this.evalValue();
3195
+ const value = this.evalValue();
3196
+ this.pos = save;
3197
+ this.selfStack.pop();
3198
+ const control = this.handleLoopControl(value);
3199
+ if (control) {
3200
+ if (control.depth > 1)
3201
+ return { kind: control.kind, depth: control.depth - 1 };
3202
+ if (control.kind === "break")
3203
+ break;
3204
+ currentCond = this.evalBodySlice(condStart, bodyStart);
3205
+ continue;
3206
+ }
3207
+ if (isDefined(value))
3208
+ result[String(key)] = value;
3209
+ currentCond = this.evalBodySlice(condStart, bodyStart);
3210
+ }
3211
+ this.pos = afterClose;
3212
+ return result;
3213
+ }
2954
3214
  evalArrayComprehension(iterable, varA, varB, bodyStart, bodyEnd, keysOnly) {
2955
3215
  const items = this.iterate(iterable, keysOnly);
2956
3216
  const out = [];
@@ -3019,25 +3279,39 @@ class CursorInterpreter {
3019
3279
  case OPCODES.do:
3020
3280
  return args.length ? args[args.length - 1] : undefined;
3021
3281
  case OPCODES.add:
3282
+ if (args[0] === undefined || args[1] === undefined)
3283
+ return;
3022
3284
  if (typeof args[0] === "string" || typeof args[1] === "string") {
3023
- return String(args[0] ?? "") + String(args[1] ?? "");
3285
+ return String(args[0]) + String(args[1]);
3024
3286
  }
3025
- return Number(args[0] ?? 0) + Number(args[1] ?? 0);
3287
+ return Number(args[0]) + Number(args[1]);
3026
3288
  case OPCODES.sub:
3027
- return Number(args[0] ?? 0) - Number(args[1] ?? 0);
3289
+ if (args[0] === undefined || args[1] === undefined)
3290
+ return;
3291
+ return Number(args[0]) - Number(args[1]);
3028
3292
  case OPCODES.mul:
3029
- return Number(args[0] ?? 0) * Number(args[1] ?? 0);
3293
+ if (args[0] === undefined || args[1] === undefined)
3294
+ return;
3295
+ return Number(args[0]) * Number(args[1]);
3030
3296
  case OPCODES.div:
3031
- return Number(args[0] ?? 0) / Number(args[1] ?? 0);
3297
+ if (args[0] === undefined || args[1] === undefined)
3298
+ return;
3299
+ return Number(args[0]) / Number(args[1]);
3032
3300
  case OPCODES.mod:
3033
- return Number(args[0] ?? 0) % Number(args[1] ?? 0);
3301
+ if (args[0] === undefined || args[1] === undefined)
3302
+ return;
3303
+ return Number(args[0]) % Number(args[1]);
3034
3304
  case OPCODES.neg:
3035
- return -Number(args[0] ?? 0);
3305
+ if (args[0] === undefined)
3306
+ return;
3307
+ return -Number(args[0]);
3036
3308
  case OPCODES.not: {
3037
3309
  const value = args[0];
3310
+ if (value === undefined)
3311
+ return;
3038
3312
  if (typeof value === "boolean")
3039
3313
  return !value;
3040
- return ~Number(value ?? 0);
3314
+ return ~Number(value);
3041
3315
  }
3042
3316
  case OPCODES.and: {
3043
3317
  const [a, b] = args;
@@ -3079,6 +3353,25 @@ class CursorInterpreter {
3079
3353
  return Array.isArray(args[0]) ? args[0] : undefined;
3080
3354
  case OPCODES.object:
3081
3355
  return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
3356
+ case OPCODES.range: {
3357
+ const from = Number(args[0]);
3358
+ const to = Number(args[1]);
3359
+ const step = to >= from ? 1 : -1;
3360
+ const out = [];
3361
+ for (let v = from;step > 0 ? v <= to : v >= to; v += step)
3362
+ out.push(v);
3363
+ return out;
3364
+ }
3365
+ case OPCODES.size: {
3366
+ const target = args[0];
3367
+ if (Array.isArray(target))
3368
+ return target.length;
3369
+ if (typeof target === "string")
3370
+ return Array.from(target).length;
3371
+ if (target && typeof target === "object")
3372
+ return Object.keys(target).length;
3373
+ return;
3374
+ }
3082
3375
  default:
3083
3376
  throw new Error(`Unknown opcode ${id}`);
3084
3377
  }
@@ -3088,10 +3381,59 @@ class CursorInterpreter {
3088
3381
  for (const key of keys) {
3089
3382
  if (current === undefined || current === null)
3090
3383
  return;
3091
- current = current[String(key)];
3384
+ current = this.readProperty(current, key);
3385
+ if (current === undefined)
3386
+ return;
3092
3387
  }
3093
3388
  return current;
3094
3389
  }
3390
+ readProperty(target, key) {
3391
+ const index = this.parseIndexKey(key);
3392
+ if (Array.isArray(target)) {
3393
+ if (index === undefined)
3394
+ return;
3395
+ return target[index];
3396
+ }
3397
+ if (typeof target === "string") {
3398
+ if (index === undefined)
3399
+ return;
3400
+ return Array.from(target)[index];
3401
+ }
3402
+ if (this.isPlainObject(target)) {
3403
+ const prop = String(key);
3404
+ if (!Object.prototype.hasOwnProperty.call(target, prop))
3405
+ return;
3406
+ return target[prop];
3407
+ }
3408
+ return;
3409
+ }
3410
+ canWriteProperty(target, key) {
3411
+ const index = this.parseIndexKey(key);
3412
+ if (Array.isArray(target)) {
3413
+ if (index === undefined)
3414
+ return;
3415
+ return { kind: "array", index };
3416
+ }
3417
+ if (this.isPlainObject(target))
3418
+ return { kind: "object" };
3419
+ return;
3420
+ }
3421
+ parseIndexKey(key) {
3422
+ if (typeof key === "number" && Number.isInteger(key) && key >= 0)
3423
+ return key;
3424
+ if (typeof key !== "string" || key.length === 0)
3425
+ return;
3426
+ if (!/^(0|[1-9]\d*)$/.test(key))
3427
+ return;
3428
+ const index = Number(key);
3429
+ return Number.isSafeInteger(index) ? index : undefined;
3430
+ }
3431
+ isPlainObject(value) {
3432
+ if (!value || typeof value !== "object")
3433
+ return false;
3434
+ const proto = Object.getPrototypeOf(value);
3435
+ return proto === Object.prototype || proto === null;
3436
+ }
3095
3437
  readPlace() {
3096
3438
  this.skipNonCode();
3097
3439
  const direct = this.readRootVarOrRefIfPresent();
@@ -3146,13 +3488,13 @@ class CursorInterpreter {
3146
3488
  }
3147
3489
  this.pos += 1;
3148
3490
  return {
3149
- root: tag === "$" ? prefix.raw : prefix.value,
3491
+ root: prefix.raw,
3150
3492
  isRef: tag === "'"
3151
3493
  };
3152
3494
  }
3153
3495
  writePlace(place, value) {
3154
3496
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3155
- const rootKey = String(place.root);
3497
+ const rootKey = place.root;
3156
3498
  if (place.keys.length === 0) {
3157
3499
  rootTable[rootKey] = value;
3158
3500
  return;
@@ -3163,17 +3505,48 @@ class CursorInterpreter {
3163
3505
  rootTable[rootKey] = target;
3164
3506
  }
3165
3507
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3166
- const key = String(place.keys[index]);
3167
- const next = target[key];
3508
+ const key = place.keys[index];
3509
+ const access2 = this.canWriteProperty(target, key);
3510
+ if (!access2)
3511
+ return;
3512
+ if (access2.kind === "array") {
3513
+ const next2 = target[access2.index];
3514
+ if (!next2 || typeof next2 !== "object")
3515
+ target[access2.index] = {};
3516
+ target = target[access2.index];
3517
+ continue;
3518
+ }
3519
+ const prop = String(key);
3520
+ const next = target[prop];
3168
3521
  if (!next || typeof next !== "object")
3169
- target[key] = {};
3170
- target = target[key];
3522
+ target[prop] = {};
3523
+ target = target[prop];
3171
3524
  }
3172
- target[String(place.keys[place.keys.length - 1])] = value;
3525
+ const lastKey = place.keys[place.keys.length - 1];
3526
+ const access = this.canWriteProperty(target, lastKey);
3527
+ if (!access)
3528
+ return;
3529
+ if (access.kind === "array") {
3530
+ target[access.index] = value;
3531
+ return;
3532
+ }
3533
+ target[String(lastKey)] = value;
3534
+ }
3535
+ readPlaceValue(place) {
3536
+ const rootTable = place.isRef ? this.state.refs : this.state.vars;
3537
+ let current = rootTable[place.root];
3538
+ for (const key of place.keys) {
3539
+ if (current === undefined || current === null)
3540
+ return;
3541
+ current = this.readProperty(current, key);
3542
+ if (current === undefined)
3543
+ return;
3544
+ }
3545
+ return current;
3173
3546
  }
3174
3547
  deletePlace(place) {
3175
3548
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
3176
- const rootKey = String(place.root);
3549
+ const rootKey = place.root;
3177
3550
  if (place.keys.length === 0) {
3178
3551
  delete rootTable[rootKey];
3179
3552
  return;
@@ -3182,11 +3555,29 @@ class CursorInterpreter {
3182
3555
  if (!target || typeof target !== "object")
3183
3556
  return;
3184
3557
  for (let index = 0;index < place.keys.length - 1; index += 1) {
3185
- target = target[String(place.keys[index])];
3558
+ const key = place.keys[index];
3559
+ const access2 = this.canWriteProperty(target, key);
3560
+ if (!access2)
3561
+ return;
3562
+ if (access2.kind === "array") {
3563
+ target = target[access2.index];
3564
+ if (!target || typeof target !== "object")
3565
+ return;
3566
+ continue;
3567
+ }
3568
+ target = target[String(key)];
3186
3569
  if (!target || typeof target !== "object")
3187
3570
  return;
3188
3571
  }
3189
- delete target[String(place.keys[place.keys.length - 1])];
3572
+ const lastKey = place.keys[place.keys.length - 1];
3573
+ const access = this.canWriteProperty(target, lastKey);
3574
+ if (!access)
3575
+ return;
3576
+ if (access.kind === "array") {
3577
+ delete target[access.index];
3578
+ return;
3579
+ }
3580
+ delete target[String(lastKey)];
3190
3581
  }
3191
3582
  skipValue() {
3192
3583
  this.pos = this.skipValueFrom(this.pos);
@@ -3202,12 +3593,12 @@ class CursorInterpreter {
3202
3593
  return startPos;
3203
3594
  }
3204
3595
  if (tag === ",") {
3205
- this.pos += 1 + prefix.value;
3596
+ this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
3206
3597
  const end2 = this.pos;
3207
3598
  this.pos = save;
3208
3599
  return end2;
3209
3600
  }
3210
- if (tag === "=") {
3601
+ if (tag === "=" || tag === "/") {
3211
3602
  this.pos += 1;
3212
3603
  this.skipValue();
3213
3604
  this.skipValue();
@@ -3242,7 +3633,8 @@ class CursorInterpreter {
3242
3633
  if (opener && "([{".includes(opener)) {
3243
3634
  const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
3244
3635
  if (prefix.value > 0) {
3245
- this.pos += 1 + prefix.value + 1;
3636
+ const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
3637
+ this.pos = bodyEnd + 1;
3246
3638
  const end3 = this.pos;
3247
3639
  this.pos = save;
3248
3640
  return end3;
@@ -3278,28 +3670,30 @@ var init_rexc_interpreter = __esm(() => {
3278
3670
  init_rex();
3279
3671
  digitMap = new Map(Array.from(DIGITS2).map((char, index) => [char, index]));
3280
3672
  OPCODES = {
3281
- do: 0,
3282
- add: 1,
3283
- sub: 2,
3284
- mul: 3,
3285
- div: 4,
3286
- eq: 5,
3287
- neq: 6,
3288
- lt: 7,
3289
- lte: 8,
3290
- gt: 9,
3291
- gte: 10,
3292
- and: 11,
3293
- or: 12,
3294
- xor: 13,
3295
- not: 14,
3296
- boolean: 15,
3297
- number: 16,
3298
- string: 17,
3299
- array: 18,
3300
- object: 19,
3301
- mod: 20,
3302
- neg: 21
3673
+ do: "",
3674
+ add: "ad",
3675
+ sub: "sb",
3676
+ mul: "ml",
3677
+ div: "dv",
3678
+ eq: "eq",
3679
+ neq: "nq",
3680
+ lt: "lt",
3681
+ lte: "le",
3682
+ gt: "gt",
3683
+ gte: "ge",
3684
+ and: "an",
3685
+ or: "or",
3686
+ xor: "xr",
3687
+ not: "nt",
3688
+ boolean: "bt",
3689
+ number: "nm",
3690
+ string: "st",
3691
+ array: "ar",
3692
+ object: "ob",
3693
+ mod: "md",
3694
+ neg: "ng",
3695
+ range: "rn",
3696
+ size: "sz"
3303
3697
  };
3304
3698
  });
3305
3699
 
@@ -3310,6 +3704,7 @@ __export(exports_rex_repl, {
3310
3704
  isIncomplete: () => isIncomplete,
3311
3705
  highlightRexc: () => highlightRexc,
3312
3706
  highlightLine: () => highlightLine,
3707
+ highlightJSON: () => highlightJSON,
3313
3708
  formatVarState: () => formatVarState
3314
3709
  });
3315
3710
  import * as readline from "node:readline";
@@ -3421,6 +3816,7 @@ function highlightRexc(text) {
3421
3816
  break;
3422
3817
  }
3423
3818
  case "=":
3819
+ case "/":
3424
3820
  case "~":
3425
3821
  out += C.red + prefix + tag + C.reset;
3426
3822
  i++;
@@ -3460,6 +3856,32 @@ function highlightRexc(text) {
3460
3856
  }
3461
3857
  return out;
3462
3858
  }
3859
+ function highlightJSON(json) {
3860
+ let result = "";
3861
+ let lastIndex = 0;
3862
+ JSON_TOKEN_RE.lastIndex = 0;
3863
+ for (const m of json.matchAll(JSON_TOKEN_RE)) {
3864
+ result += json.slice(lastIndex, m.index);
3865
+ const text = m[0];
3866
+ const g = m.groups;
3867
+ if (g.key) {
3868
+ result += C.cyan + g.key + C.reset + ":";
3869
+ } else if (g.string) {
3870
+ result += C.green + text + C.reset;
3871
+ } else if (g.number) {
3872
+ result += C.yellow + text + C.reset;
3873
+ } else if (g.bool) {
3874
+ result += C.yellow + text + C.reset;
3875
+ } else if (g.null) {
3876
+ result += C.dim + text + C.reset;
3877
+ } else {
3878
+ result += text;
3879
+ }
3880
+ lastIndex = m.index + text.length;
3881
+ }
3882
+ result += json.slice(lastIndex);
3883
+ return result;
3884
+ }
3463
3885
  function stripStringsAndComments(source) {
3464
3886
  return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, (m) => " ".repeat(m.length));
3465
3887
  }
@@ -3497,7 +3919,7 @@ function isIncomplete(buffer) {
3497
3919
  const trimmed = buffer.trimEnd();
3498
3920
  if (/[+\-*/%&|^=<>]$/.test(trimmed))
3499
3921
  return true;
3500
- if (/\b(?:and|or|do|in|of)\s*$/.test(trimmed))
3922
+ if (/\b(?:and|or|nor|do|in|of)\s*$/.test(trimmed))
3501
3923
  return true;
3502
3924
  return false;
3503
3925
  }
@@ -3707,7 +4129,7 @@ async function startRepl() {
3707
4129
  const ir = parseToIR(source);
3708
4130
  const lowered = state.optimize ? optimizeIR(ir) : ir;
3709
4131
  if (state.showIR) {
3710
- console.log(`${C.dim} IR: ${JSON.stringify(lowered)}${C.reset}`);
4132
+ console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3711
4133
  }
3712
4134
  const rexc = compile(source, { optimize: state.optimize });
3713
4135
  if (state.showRexc) {
@@ -3739,7 +4161,7 @@ async function startRepl() {
3739
4161
  });
3740
4162
  rl.prompt();
3741
4163
  }
3742
- var req, version, C, TOKEN_RE, REXC_DIGITS, KEYWORDS, GAS_LIMIT = 1e7;
4164
+ var req, version, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, GAS_LIMIT = 1e7;
3743
4165
  var init_rex_repl = __esm(() => {
3744
4166
  init_rex();
3745
4167
  init_rexc_interpreter();
@@ -3757,8 +4179,9 @@ var init_rex_repl = __esm(() => {
3757
4179
  gray: "\x1B[90m",
3758
4180
  boldBlue: "\x1B[1;34m"
3759
4181
  };
3760
- 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;
4182
+ 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;
3761
4183
  REXC_DIGITS = new Set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_");
4184
+ JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3762
4185
  KEYWORDS = [
3763
4186
  "when",
3764
4187
  "unless",
@@ -3770,6 +4193,7 @@ var init_rex_repl = __esm(() => {
3770
4193
  "of",
3771
4194
  "and",
3772
4195
  "or",
4196
+ "nor",
3773
4197
  "else",
3774
4198
  "break",
3775
4199
  "continue",
@@ -3779,6 +4203,8 @@ var init_rex_repl = __esm(() => {
3779
4203
  "false",
3780
4204
  "null",
3781
4205
  "undefined",
4206
+ "nan",
4207
+ "inf",
3782
4208
  "string",
3783
4209
  "number",
3784
4210
  "object",
@@ -3794,6 +4220,7 @@ import { dirname, resolve } from "node:path";
3794
4220
  import { readFile, writeFile } from "node:fs/promises";
3795
4221
  function parseArgs(argv) {
3796
4222
  const options = {
4223
+ sources: [],
3797
4224
  compile: false,
3798
4225
  ir: false,
3799
4226
  minifyNames: false,
@@ -3839,7 +4266,7 @@ function parseArgs(argv) {
3839
4266
  const value = argv[index + 1];
3840
4267
  if (!value)
3841
4268
  throw new Error("Missing value for --expr");
3842
- options.expr = value;
4269
+ options.sources.push({ type: "expr", value });
3843
4270
  index += 1;
3844
4271
  continue;
3845
4272
  }
@@ -3847,7 +4274,7 @@ function parseArgs(argv) {
3847
4274
  const value = argv[index + 1];
3848
4275
  if (!value)
3849
4276
  throw new Error("Missing value for --file");
3850
- options.file = value;
4277
+ options.sources.push({ type: "file", path: value });
3851
4278
  index += 1;
3852
4279
  continue;
3853
4280
  }
@@ -3860,9 +4287,7 @@ function parseArgs(argv) {
3860
4287
  continue;
3861
4288
  }
3862
4289
  if (!arg.startsWith("-")) {
3863
- if (options.file)
3864
- throw new Error("Multiple file arguments provided");
3865
- options.file = arg;
4290
+ options.sources.push({ type: "file", path: arg });
3866
4291
  continue;
3867
4292
  }
3868
4293
  throw new Error(`Unknown option: ${arg}`);
@@ -3880,11 +4305,18 @@ function usage() {
3880
4305
  " cat input.rex | rex Evaluate from stdin",
3881
4306
  " rex -c input.rex Compile to rexc bytecode",
3882
4307
  "",
4308
+ " Sources are concatenated in order, so flags and files can be mixed:",
4309
+ " rex -e 'max = 200' primes.rex Set max before running script",
4310
+ " rex primes.rex -e '42' Run script, then evaluate 42",
4311
+ "",
3883
4312
  "Input:",
3884
4313
  " <file> Evaluate/compile a Rex source file",
3885
4314
  " -e, --expr <source> Evaluate/compile an inline expression",
3886
4315
  " -f, --file <path> Evaluate/compile source from a file",
3887
4316
  "",
4317
+ " Multiple -e and -f flags (and positional files) can be combined.",
4318
+ " They are concatenated in the order they appear on the command line.",
4319
+ "",
3888
4320
  "Output mode:",
3889
4321
  " (default) Evaluate and output result as JSON",
3890
4322
  " -c, --compile Compile to rexc bytecode",
@@ -3909,12 +4341,17 @@ async function readStdin() {
3909
4341
  return Buffer.concat(chunks).toString("utf8");
3910
4342
  }
3911
4343
  async function resolveSource(options) {
3912
- if (options.expr && options.file)
3913
- throw new Error("Use only one of --expr, --file, or a positional file path");
3914
- if (options.expr)
3915
- return options.expr;
3916
- if (options.file)
3917
- return readFile(options.file, "utf8");
4344
+ if (options.sources.length > 0) {
4345
+ const parts = [];
4346
+ for (const seg of options.sources) {
4347
+ if (seg.type === "expr")
4348
+ parts.push(seg.value);
4349
+ else
4350
+ parts.push(await readFile(seg.path, "utf8"));
4351
+ }
4352
+ return parts.join(`
4353
+ `);
4354
+ }
3918
4355
  if (!process.stdin.isTTY) {
3919
4356
  const piped = await readStdin();
3920
4357
  if (piped.trim().length > 0)
@@ -3922,6 +4359,13 @@ async function resolveSource(options) {
3922
4359
  }
3923
4360
  throw new Error("No input provided. Use a file path, --expr, or pipe source via stdin.");
3924
4361
  }
4362
+ function findFirstFilePath(sources) {
4363
+ for (const seg of sources) {
4364
+ if (seg.type === "file")
4365
+ return seg.path;
4366
+ }
4367
+ return;
4368
+ }
3925
4369
  async function loadDomainConfigFromFolder(folderPath) {
3926
4370
  const configPath = resolve(folderPath, ".config.rex");
3927
4371
  try {
@@ -3933,7 +4377,8 @@ async function loadDomainConfigFromFolder(folderPath) {
3933
4377
  }
3934
4378
  }
3935
4379
  async function resolveDomainConfig(options) {
3936
- const baseFolder = options.file ? dirname(resolve(options.file)) : process.cwd();
4380
+ const filePath = findFirstFilePath(options.sources);
4381
+ const baseFolder = filePath ? dirname(resolve(filePath)) : process.cwd();
3937
4382
  return loadDomainConfigFromFolder(baseFolder);
3938
4383
  }
3939
4384
  async function main() {
@@ -3942,7 +4387,7 @@ async function main() {
3942
4387
  console.log(usage());
3943
4388
  return;
3944
4389
  }
3945
- const hasSource = options.expr || options.file || !process.stdin.isTTY;
4390
+ const hasSource = options.sources.length > 0 || !process.stdin.isTTY;
3946
4391
  if (!hasSource && !options.compile && !options.ir) {
3947
4392
  const { startRepl: startRepl2 } = await Promise.resolve().then(() => (init_rex_repl(), exports_rex_repl));
3948
4393
  await startRepl2();
@@ -3962,7 +4407,7 @@ async function main() {
3962
4407
  });
3963
4408
  } else {
3964
4409
  const { value } = evaluateSource(source);
3965
- output = JSON.stringify(value, null, 2);
4410
+ output = stringify(value);
3966
4411
  }
3967
4412
  if (options.out) {
3968
4413
  await writeFile(options.out, `${output}