@creationix/rex 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@creationix/rex",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Compiler and parser for the Rex language",
5
5
  "keywords": [
6
6
  "rex",
@@ -40,7 +40,8 @@
40
40
  "compile": "bun run rex-cli.ts",
41
41
  "verify:docs": "bun run verify-doc-examples.ts",
42
42
  "pack:dry-run": "npm pack --dry-run",
43
- "prepublishOnly": "bun run build:grammar && bun test && bun run build:js && npm run pack:dry-run"
43
+ "prepublishOnly": "bun run build:grammar && bun test && bun run build:js && npm run pack:dry-run",
44
+ "release": "npm publish"
44
45
  },
45
46
  "publishConfig": {
46
47
  "access": "public"
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 "5'";
119
+ if (numberValue === Infinity)
120
+ return "6'";
121
+ if (numberValue === -Infinity)
122
+ return "7'";
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,7 +148,7 @@ 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
154
  return `${encodeUint(OPCODE_IDS[opcode])}%`;
@@ -429,6 +449,18 @@ function isPlainObject(value) {
429
449
  function isBareKeyName(key) {
430
450
  return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
431
451
  }
452
+ function isNumericKey(key) {
453
+ if (key === "")
454
+ return false;
455
+ return String(Number(key)) === key && Number.isFinite(Number(key));
456
+ }
457
+ function stringifyKey(key) {
458
+ if (isBareKeyName(key))
459
+ return key;
460
+ if (isNumericKey(key))
461
+ return key;
462
+ return stringifyString(key);
463
+ }
432
464
  function stringifyString(value) {
433
465
  return JSON.stringify(value);
434
466
  }
@@ -440,8 +472,12 @@ function stringifyInline(value) {
440
472
  if (typeof value === "boolean")
441
473
  return value ? "true" : "false";
442
474
  if (typeof value === "number") {
443
- if (!Number.isFinite(value))
444
- throw new Error("Rex stringify() cannot encode non-finite numbers");
475
+ if (Number.isNaN(value))
476
+ return "nan";
477
+ if (value === Infinity)
478
+ return "inf";
479
+ if (value === -Infinity)
480
+ return "-inf";
445
481
  return String(value);
446
482
  }
447
483
  if (typeof value === "string")
@@ -455,7 +491,7 @@ function stringifyInline(value) {
455
491
  const entries = Object.entries(value);
456
492
  if (entries.length === 0)
457
493
  return "{}";
458
- const body = entries.map(([key, item]) => `${isBareKeyName(key) ? key : stringifyString(key)}: ${stringifyInline(item)}`).join(" ");
494
+ const body = entries.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`).join(" ");
459
495
  return `{${body}}`;
460
496
  }
461
497
  throw new Error(`Rex stringify() cannot encode value of type ${typeof value}`);
@@ -492,7 +528,7 @@ ${indent}]`;
492
528
  if (entries.length === 0)
493
529
  return "{}";
494
530
  const lines = entries.map(([key, item]) => {
495
- const keyText = isBareKeyName(key) ? key : stringifyString(key);
531
+ const keyText = stringifyKey(key);
496
532
  const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
497
533
  return `${childIndent}${keyText}: ${rendered}`;
498
534
  });
@@ -1150,7 +1186,16 @@ function inlineAdjacentPureAssignments(block) {
1150
1186
  return out;
1151
1187
  }
1152
1188
  function toNumberNode(value) {
1153
- return { type: "number", raw: String(value), value };
1189
+ let raw;
1190
+ if (Number.isNaN(value))
1191
+ raw = "nan";
1192
+ else if (value === Infinity)
1193
+ raw = "inf";
1194
+ else if (value === -Infinity)
1195
+ raw = "-inf";
1196
+ else
1197
+ raw = String(value);
1198
+ return { type: "number", raw, value };
1154
1199
  }
1155
1200
  function toStringNode(value) {
1156
1201
  return { type: "string", raw: JSON.stringify(value) };
@@ -1162,7 +1207,7 @@ function toLiteralNode(value) {
1162
1207
  return { type: "null" };
1163
1208
  if (typeof value === "boolean")
1164
1209
  return { type: "boolean", value };
1165
- if (typeof value === "number" && Number.isFinite(value))
1210
+ if (typeof value === "number")
1166
1211
  return toNumberNode(value);
1167
1212
  if (typeof value === "string")
1168
1213
  return toStringNode(value);
@@ -1983,6 +2028,12 @@ function compile(source, options) {
1983
2028
  });
1984
2029
  }
1985
2030
  function parseNumber(raw) {
2031
+ if (raw === "nan")
2032
+ return NaN;
2033
+ if (raw === "inf")
2034
+ return Infinity;
2035
+ if (raw === "-inf")
2036
+ return -Infinity;
1986
2037
  if (/^-?0x/i.test(raw))
1987
2038
  return parseInt(raw, 16);
1988
2039
  if (/^-?0b/i.test(raw)) {
@@ -2054,7 +2105,7 @@ function buildPostfix(base, steps) {
2054
2105
  flushSegments();
2055
2106
  return current;
2056
2107
  }
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;
2108
+ var require2, rexGrammarModule, rexGrammar, grammar, semantics, DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", OPCODE_IDS, FIRST_NON_RESERVED_REF = 8, DOMAIN_DIGIT_INDEX, BINARY_TO_OPCODE, ASSIGN_COMPOUND_TO_OPCODE, DEC_PARTS, activeEncodeOptions, DIGIT_SET, DIGIT_INDEX;
2058
2109
  var init_rex = __esm(() => {
2059
2110
  require2 = createRequire(import.meta.url);
2060
2111
  rexGrammarModule = require2("./rex.ohm-bundle.cjs");
@@ -2114,6 +2165,7 @@ var init_rex = __esm(() => {
2114
2165
  "|=": "or",
2115
2166
  "^=": "xor"
2116
2167
  };
2168
+ DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
2117
2169
  DIGIT_SET = new Set(DIGITS.split(""));
2118
2170
  DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
2119
2171
  semantics.addOperation("toIR", {
@@ -2462,7 +2514,10 @@ class CursorInterpreter {
2462
2514
  1: ctx.refs?.[1] ?? true,
2463
2515
  2: ctx.refs?.[2] ?? false,
2464
2516
  3: ctx.refs?.[3] ?? null,
2465
- 4: ctx.refs?.[4] ?? undefined
2517
+ 4: ctx.refs?.[4] ?? undefined,
2518
+ 5: ctx.refs?.[5] ?? NaN,
2519
+ 6: ctx.refs?.[6] ?? Infinity,
2520
+ 7: ctx.refs?.[7] ?? -Infinity
2466
2521
  }
2467
2522
  };
2468
2523
  this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
@@ -3019,25 +3074,39 @@ class CursorInterpreter {
3019
3074
  case OPCODES.do:
3020
3075
  return args.length ? args[args.length - 1] : undefined;
3021
3076
  case OPCODES.add:
3077
+ if (args[0] === undefined || args[1] === undefined)
3078
+ return;
3022
3079
  if (typeof args[0] === "string" || typeof args[1] === "string") {
3023
- return String(args[0] ?? "") + String(args[1] ?? "");
3080
+ return String(args[0]) + String(args[1]);
3024
3081
  }
3025
- return Number(args[0] ?? 0) + Number(args[1] ?? 0);
3082
+ return Number(args[0]) + Number(args[1]);
3026
3083
  case OPCODES.sub:
3027
- return Number(args[0] ?? 0) - Number(args[1] ?? 0);
3084
+ if (args[0] === undefined || args[1] === undefined)
3085
+ return;
3086
+ return Number(args[0]) - Number(args[1]);
3028
3087
  case OPCODES.mul:
3029
- return Number(args[0] ?? 0) * Number(args[1] ?? 0);
3088
+ if (args[0] === undefined || args[1] === undefined)
3089
+ return;
3090
+ return Number(args[0]) * Number(args[1]);
3030
3091
  case OPCODES.div:
3031
- return Number(args[0] ?? 0) / Number(args[1] ?? 0);
3092
+ if (args[0] === undefined || args[1] === undefined)
3093
+ return;
3094
+ return Number(args[0]) / Number(args[1]);
3032
3095
  case OPCODES.mod:
3033
- return Number(args[0] ?? 0) % Number(args[1] ?? 0);
3096
+ if (args[0] === undefined || args[1] === undefined)
3097
+ return;
3098
+ return Number(args[0]) % Number(args[1]);
3034
3099
  case OPCODES.neg:
3035
- return -Number(args[0] ?? 0);
3100
+ if (args[0] === undefined)
3101
+ return;
3102
+ return -Number(args[0]);
3036
3103
  case OPCODES.not: {
3037
3104
  const value = args[0];
3105
+ if (value === undefined)
3106
+ return;
3038
3107
  if (typeof value === "boolean")
3039
3108
  return !value;
3040
- return ~Number(value ?? 0);
3109
+ return ~Number(value);
3041
3110
  }
3042
3111
  case OPCODES.and: {
3043
3112
  const [a, b] = args;
@@ -3310,6 +3379,7 @@ __export(exports_rex_repl, {
3310
3379
  isIncomplete: () => isIncomplete,
3311
3380
  highlightRexc: () => highlightRexc,
3312
3381
  highlightLine: () => highlightLine,
3382
+ highlightJSON: () => highlightJSON,
3313
3383
  formatVarState: () => formatVarState
3314
3384
  });
3315
3385
  import * as readline from "node:readline";
@@ -3460,6 +3530,32 @@ function highlightRexc(text) {
3460
3530
  }
3461
3531
  return out;
3462
3532
  }
3533
+ function highlightJSON(json) {
3534
+ let result = "";
3535
+ let lastIndex = 0;
3536
+ JSON_TOKEN_RE.lastIndex = 0;
3537
+ for (const m of json.matchAll(JSON_TOKEN_RE)) {
3538
+ result += json.slice(lastIndex, m.index);
3539
+ const text = m[0];
3540
+ const g = m.groups;
3541
+ if (g.key) {
3542
+ result += C.cyan + g.key + C.reset + ":";
3543
+ } else if (g.string) {
3544
+ result += C.green + text + C.reset;
3545
+ } else if (g.number) {
3546
+ result += C.yellow + text + C.reset;
3547
+ } else if (g.bool) {
3548
+ result += C.yellow + text + C.reset;
3549
+ } else if (g.null) {
3550
+ result += C.dim + text + C.reset;
3551
+ } else {
3552
+ result += text;
3553
+ }
3554
+ lastIndex = m.index + text.length;
3555
+ }
3556
+ result += json.slice(lastIndex);
3557
+ return result;
3558
+ }
3463
3559
  function stripStringsAndComments(source) {
3464
3560
  return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, (m) => " ".repeat(m.length));
3465
3561
  }
@@ -3707,7 +3803,7 @@ async function startRepl() {
3707
3803
  const ir = parseToIR(source);
3708
3804
  const lowered = state.optimize ? optimizeIR(ir) : ir;
3709
3805
  if (state.showIR) {
3710
- console.log(`${C.dim} IR: ${JSON.stringify(lowered)}${C.reset}`);
3806
+ console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3711
3807
  }
3712
3808
  const rexc = compile(source, { optimize: state.optimize });
3713
3809
  if (state.showRexc) {
@@ -3739,7 +3835,7 @@ async function startRepl() {
3739
3835
  });
3740
3836
  rl.prompt();
3741
3837
  }
3742
- var req, version, C, TOKEN_RE, REXC_DIGITS, KEYWORDS, GAS_LIMIT = 1e7;
3838
+ var req, version, C, TOKEN_RE, REXC_DIGITS, JSON_TOKEN_RE, KEYWORDS, GAS_LIMIT = 1e7;
3743
3839
  var init_rex_repl = __esm(() => {
3744
3840
  init_rex();
3745
3841
  init_rexc_interpreter();
@@ -3757,8 +3853,9 @@ var init_rex_repl = __esm(() => {
3757
3853
  gray: "\x1B[90m",
3758
3854
  boldBlue: "\x1B[1;34m"
3759
3855
  };
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;
3856
+ TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
3761
3857
  REXC_DIGITS = new Set("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_");
3858
+ JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3762
3859
  KEYWORDS = [
3763
3860
  "when",
3764
3861
  "unless",
@@ -3779,6 +3876,8 @@ var init_rex_repl = __esm(() => {
3779
3876
  "false",
3780
3877
  "null",
3781
3878
  "undefined",
3879
+ "nan",
3880
+ "inf",
3782
3881
  "string",
3783
3882
  "number",
3784
3883
  "object",
@@ -3962,7 +4061,7 @@ async function main() {
3962
4061
  });
3963
4062
  } else {
3964
4063
  const { value } = evaluateSource(source);
3965
- output = JSON.stringify(value, null, 2);
4064
+ output = stringify(value);
3966
4065
  }
3967
4066
  if (options.out) {
3968
4067
  await writeFile(options.out, `${output}
package/rex-cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { compile, parse, parseToIR } from "./rex.ts";
1
+ import { compile, parse, parseToIR, stringify } from "./rex.ts";
2
2
  import { evaluateSource } from "./rexc-interpreter.ts";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { readFile, writeFile } from "node:fs/promises";
@@ -183,7 +183,7 @@ async function main() {
183
183
  });
184
184
  } else {
185
185
  const { value } = evaluateSource(source);
186
- output = JSON.stringify(value, null, 2);
186
+ output = stringify(value);
187
187
  }
188
188
 
189
189
  if (options.out) {
package/rex-repl.js CHANGED
@@ -37,7 +37,7 @@ var OPCODE_IDS = {
37
37
  mod: 20,
38
38
  neg: 21
39
39
  };
40
- var FIRST_NON_RESERVED_REF = 5;
40
+ var FIRST_NON_RESERVED_REF = 8;
41
41
  var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
42
42
  var BINARY_TO_OPCODE = {
43
43
  add: "add",
@@ -151,12 +151,33 @@ function encodeBareOrLengthString(value) {
151
151
  return `${value}:`;
152
152
  return `${encodeUint(byteLength(value))},${value}`;
153
153
  }
154
+ var DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
155
+ function splitDecimal(num) {
156
+ const match = num.toExponential().match(DEC_PARTS);
157
+ if (!match)
158
+ throw new Error(`Failed to split decimal for ${num}`);
159
+ const [, b1, b2 = "", e1] = match;
160
+ const base = Number.parseInt(b1 + b2, 10);
161
+ const exp = Number.parseInt(e1, 10) - b2.length;
162
+ return { base, exp };
163
+ }
164
+ function encodeDecimal(significand, power) {
165
+ return `${encodeZigzag(power)}*${encodeInt(significand)}`;
166
+ }
154
167
  function encodeNumberNode(node) {
155
168
  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);
169
+ if (Number.isNaN(numberValue))
170
+ return "5'";
171
+ if (numberValue === Infinity)
172
+ return "6'";
173
+ if (numberValue === -Infinity)
174
+ return "7'";
175
+ if (Number.isInteger(numberValue)) {
176
+ const { base, exp } = splitDecimal(numberValue);
177
+ if (exp >= 0 && exp <= 4)
178
+ return encodeInt(numberValue);
179
+ return encodeDecimal(base, exp);
180
+ }
160
181
  const raw = node.raw.toLowerCase();
161
182
  const sign = raw.startsWith("-") ? -1 : 1;
162
183
  const unsigned = sign < 0 ? raw.slice(1) : raw;
@@ -179,7 +200,7 @@ function encodeNumberNode(node) {
179
200
  significand /= 10;
180
201
  power += 1;
181
202
  }
182
- return `${encodeZigzag(power)}*${encodeInt(significand)}`;
203
+ return encodeDecimal(significand, power);
183
204
  }
184
205
  function encodeOpcode(opcode) {
185
206
  return `${encodeUint(OPCODE_IDS[opcode])}%`;
@@ -439,6 +460,18 @@ function isPlainObject(value) {
439
460
  function isBareKeyName(key) {
440
461
  return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
441
462
  }
463
+ function isNumericKey(key) {
464
+ if (key === "")
465
+ return false;
466
+ return String(Number(key)) === key && Number.isFinite(Number(key));
467
+ }
468
+ function stringifyKey(key) {
469
+ if (isBareKeyName(key))
470
+ return key;
471
+ if (isNumericKey(key))
472
+ return key;
473
+ return stringifyString(key);
474
+ }
442
475
  function stringifyString(value) {
443
476
  return JSON.stringify(value);
444
477
  }
@@ -450,8 +483,12 @@ function stringifyInline(value) {
450
483
  if (typeof value === "boolean")
451
484
  return value ? "true" : "false";
452
485
  if (typeof value === "number") {
453
- if (!Number.isFinite(value))
454
- throw new Error("Rex stringify() cannot encode non-finite numbers");
486
+ if (Number.isNaN(value))
487
+ return "nan";
488
+ if (value === Infinity)
489
+ return "inf";
490
+ if (value === -Infinity)
491
+ return "-inf";
455
492
  return String(value);
456
493
  }
457
494
  if (typeof value === "string")
@@ -465,7 +502,7 @@ function stringifyInline(value) {
465
502
  const entries = Object.entries(value);
466
503
  if (entries.length === 0)
467
504
  return "{}";
468
- const body = entries.map(([key, item]) => `${isBareKeyName(key) ? key : stringifyString(key)}: ${stringifyInline(item)}`).join(" ");
505
+ const body = entries.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`).join(" ");
469
506
  return `{${body}}`;
470
507
  }
471
508
  throw new Error(`Rex stringify() cannot encode value of type ${typeof value}`);
@@ -502,7 +539,7 @@ ${indent}]`;
502
539
  if (entries.length === 0)
503
540
  return "{}";
504
541
  const lines = entries.map(([key, item]) => {
505
- const keyText = isBareKeyName(key) ? key : stringifyString(key);
542
+ const keyText = stringifyKey(key);
506
543
  const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
507
544
  return `${childIndent}${keyText}: ${rendered}`;
508
545
  });
@@ -1159,7 +1196,16 @@ function inlineAdjacentPureAssignments(block) {
1159
1196
  return out;
1160
1197
  }
1161
1198
  function toNumberNode(value) {
1162
- return { type: "number", raw: String(value), value };
1199
+ let raw;
1200
+ if (Number.isNaN(value))
1201
+ raw = "nan";
1202
+ else if (value === Infinity)
1203
+ raw = "inf";
1204
+ else if (value === -Infinity)
1205
+ raw = "-inf";
1206
+ else
1207
+ raw = String(value);
1208
+ return { type: "number", raw, value };
1163
1209
  }
1164
1210
  function toStringNode(value) {
1165
1211
  return { type: "string", raw: JSON.stringify(value) };
@@ -1171,7 +1217,7 @@ function toLiteralNode(value) {
1171
1217
  return { type: "null" };
1172
1218
  if (typeof value === "boolean")
1173
1219
  return { type: "boolean", value };
1174
- if (typeof value === "number" && Number.isFinite(value))
1220
+ if (typeof value === "number")
1175
1221
  return toNumberNode(value);
1176
1222
  if (typeof value === "string")
1177
1223
  return toStringNode(value);
@@ -1992,6 +2038,12 @@ function compile(source, options) {
1992
2038
  });
1993
2039
  }
1994
2040
  function parseNumber(raw) {
2041
+ if (raw === "nan")
2042
+ return NaN;
2043
+ if (raw === "inf")
2044
+ return Infinity;
2045
+ if (raw === "-inf")
2046
+ return -Infinity;
1995
2047
  if (/^-?0x/i.test(raw))
1996
2048
  return parseInt(raw, 16);
1997
2049
  if (/^-?0b/i.test(raw)) {
@@ -2434,7 +2486,10 @@ class CursorInterpreter {
2434
2486
  1: ctx.refs?.[1] ?? true,
2435
2487
  2: ctx.refs?.[2] ?? false,
2436
2488
  3: ctx.refs?.[3] ?? null,
2437
- 4: ctx.refs?.[4] ?? undefined
2489
+ 4: ctx.refs?.[4] ?? undefined,
2490
+ 5: ctx.refs?.[5] ?? NaN,
2491
+ 6: ctx.refs?.[6] ?? Infinity,
2492
+ 7: ctx.refs?.[7] ?? -Infinity
2438
2493
  }
2439
2494
  };
2440
2495
  this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
@@ -2991,25 +3046,39 @@ class CursorInterpreter {
2991
3046
  case OPCODES.do:
2992
3047
  return args.length ? args[args.length - 1] : undefined;
2993
3048
  case OPCODES.add:
3049
+ if (args[0] === undefined || args[1] === undefined)
3050
+ return;
2994
3051
  if (typeof args[0] === "string" || typeof args[1] === "string") {
2995
- return String(args[0] ?? "") + String(args[1] ?? "");
3052
+ return String(args[0]) + String(args[1]);
2996
3053
  }
2997
- return Number(args[0] ?? 0) + Number(args[1] ?? 0);
3054
+ return Number(args[0]) + Number(args[1]);
2998
3055
  case OPCODES.sub:
2999
- return Number(args[0] ?? 0) - Number(args[1] ?? 0);
3056
+ if (args[0] === undefined || args[1] === undefined)
3057
+ return;
3058
+ return Number(args[0]) - Number(args[1]);
3000
3059
  case OPCODES.mul:
3001
- return Number(args[0] ?? 0) * Number(args[1] ?? 0);
3060
+ if (args[0] === undefined || args[1] === undefined)
3061
+ return;
3062
+ return Number(args[0]) * Number(args[1]);
3002
3063
  case OPCODES.div:
3003
- return Number(args[0] ?? 0) / Number(args[1] ?? 0);
3064
+ if (args[0] === undefined || args[1] === undefined)
3065
+ return;
3066
+ return Number(args[0]) / Number(args[1]);
3004
3067
  case OPCODES.mod:
3005
- return Number(args[0] ?? 0) % Number(args[1] ?? 0);
3068
+ if (args[0] === undefined || args[1] === undefined)
3069
+ return;
3070
+ return Number(args[0]) % Number(args[1]);
3006
3071
  case OPCODES.neg:
3007
- return -Number(args[0] ?? 0);
3072
+ if (args[0] === undefined)
3073
+ return;
3074
+ return -Number(args[0]);
3008
3075
  case OPCODES.not: {
3009
3076
  const value = args[0];
3077
+ if (value === undefined)
3078
+ return;
3010
3079
  if (typeof value === "boolean")
3011
3080
  return !value;
3012
- return ~Number(value ?? 0);
3081
+ return ~Number(value);
3013
3082
  }
3014
3083
  case OPCODES.and: {
3015
3084
  const [a, b] = args;
@@ -3258,7 +3327,7 @@ var C = {
3258
3327
  gray: "\x1B[90m",
3259
3328
  boldBlue: "\x1B[1;34m"
3260
3329
  };
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;
3330
+ var TOKEN_RE = /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
3262
3331
  function highlightLine(line) {
3263
3332
  let result = "";
3264
3333
  let lastIndex = 0;
@@ -3406,6 +3475,33 @@ function highlightRexc(text) {
3406
3475
  }
3407
3476
  return out;
3408
3477
  }
3478
+ var JSON_TOKEN_RE = /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
3479
+ function highlightJSON(json) {
3480
+ let result = "";
3481
+ let lastIndex = 0;
3482
+ JSON_TOKEN_RE.lastIndex = 0;
3483
+ for (const m of json.matchAll(JSON_TOKEN_RE)) {
3484
+ result += json.slice(lastIndex, m.index);
3485
+ const text = m[0];
3486
+ const g = m.groups;
3487
+ if (g.key) {
3488
+ result += C.cyan + g.key + C.reset + ":";
3489
+ } else if (g.string) {
3490
+ result += C.green + text + C.reset;
3491
+ } else if (g.number) {
3492
+ result += C.yellow + text + C.reset;
3493
+ } else if (g.bool) {
3494
+ result += C.yellow + text + C.reset;
3495
+ } else if (g.null) {
3496
+ result += C.dim + text + C.reset;
3497
+ } else {
3498
+ result += text;
3499
+ }
3500
+ lastIndex = m.index + text.length;
3501
+ }
3502
+ result += json.slice(lastIndex);
3503
+ return result;
3504
+ }
3409
3505
  function stripStringsAndComments(source) {
3410
3506
  return source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, (m) => " ".repeat(m.length));
3411
3507
  }
@@ -3504,6 +3600,8 @@ var KEYWORDS = [
3504
3600
  "false",
3505
3601
  "null",
3506
3602
  "undefined",
3603
+ "nan",
3604
+ "inf",
3507
3605
  "string",
3508
3606
  "number",
3509
3607
  "object",
@@ -3680,7 +3778,7 @@ async function startRepl() {
3680
3778
  const ir = parseToIR(source);
3681
3779
  const lowered = state.optimize ? optimizeIR(ir) : ir;
3682
3780
  if (state.showIR) {
3683
- console.log(`${C.dim} IR: ${JSON.stringify(lowered)}${C.reset}`);
3781
+ console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
3684
3782
  }
3685
3783
  const rexc = compile(source, { optimize: state.optimize });
3686
3784
  if (state.showRexc) {
@@ -3717,5 +3815,6 @@ export {
3717
3815
  isIncomplete,
3718
3816
  highlightRexc,
3719
3817
  highlightLine,
3818
+ highlightJSON,
3720
3819
  formatVarState
3721
3820
  };
package/rex-repl.ts CHANGED
@@ -24,7 +24,7 @@ const C = {
24
24
  // ── Syntax highlighting ───────────────────────────────────────
25
25
 
26
26
  const TOKEN_RE =
27
- /(?<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;
27
+ /(?<blockComment>\/\*[\s\S]*?(?:\*\/|$))|(?<lineComment>\/\/[^\n]*)|(?<dstring>"(?:[^"\\]|\\.)*"?)|(?<sstring>'(?:[^'\\]|\\.)*'?)|(?<keyword>\b(?:when|unless|while|for|do|end|in|of|and|or|else|break|continue|delete|self)(?![a-zA-Z0-9_-]))|(?<literal>\b(?:true|false|null|undefined|nan)(?![a-zA-Z0-9_-])|-?\binf\b)|(?<typePred>\b(?:string|number|object|array|boolean)(?![a-zA-Z0-9_-]))|(?<num>\b(?:0x[0-9a-fA-F]+|0b[01]+|(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b)/g;
28
28
 
29
29
  export function highlightLine(line: string): string {
30
30
  let result = "";
@@ -180,6 +180,39 @@ export function highlightRexc(text: string): string {
180
180
  return out;
181
181
  }
182
182
 
183
+ // ── JSON IR highlighting ─────────────────────────────────────
184
+
185
+ const JSON_TOKEN_RE =
186
+ /(?<key>"(?:[^"\\]|\\.)*")\s*:|(?<string>"(?:[^"\\]|\\.)*")|(?<number>-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)\b|(?<bool>true|false)|(?<null>null)|(?<brace>[{}[\]])|(?<punct>[:,])/g;
187
+
188
+ export function highlightJSON(json: string): string {
189
+ let result = "";
190
+ let lastIndex = 0;
191
+ JSON_TOKEN_RE.lastIndex = 0;
192
+
193
+ for (const m of json.matchAll(JSON_TOKEN_RE)) {
194
+ result += json.slice(lastIndex, m.index);
195
+ const text = m[0];
196
+ const g = m.groups!;
197
+ if (g.key) {
198
+ result += C.cyan + g.key + C.reset + ":";
199
+ } else if (g.string) {
200
+ result += C.green + text + C.reset;
201
+ } else if (g.number) {
202
+ result += C.yellow + text + C.reset;
203
+ } else if (g.bool) {
204
+ result += C.yellow + text + C.reset;
205
+ } else if (g.null) {
206
+ result += C.dim + text + C.reset;
207
+ } else {
208
+ result += text;
209
+ }
210
+ lastIndex = m.index! + text.length;
211
+ }
212
+ result += json.slice(lastIndex);
213
+ return result;
214
+ }
215
+
183
216
  // ── Multi-line detection ──────────────────────────────────────
184
217
 
185
218
  /** Strip string literals and comments, replacing them with spaces. */
@@ -277,7 +310,7 @@ export function formatVarState(vars: Record<string, unknown>): string {
277
310
  const KEYWORDS = [
278
311
  "when", "unless", "while", "for", "do", "end", "in", "of",
279
312
  "and", "or", "else", "break", "continue", "delete",
280
- "self", "true", "false", "null", "undefined",
313
+ "self", "true", "false", "null", "undefined", "nan", "inf",
281
314
  "string", "number", "object", "array", "boolean",
282
315
  ];
283
316
 
@@ -504,7 +537,7 @@ export async function startRepl(): Promise<void> {
504
537
  const lowered = state.optimize ? optimizeIR(ir) : ir;
505
538
 
506
539
  if (state.showIR) {
507
- console.log(`${C.dim} IR: ${JSON.stringify(lowered)}${C.reset}`);
540
+ console.log(`${C.dim} IR:${C.reset} ${highlightJSON(JSON.stringify(lowered))}`);
508
541
  }
509
542
 
510
543
  const rexc = compile(source, { optimize: state.optimize });