@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 +3 -2
- package/rex-cli.js +124 -25
- package/rex-cli.ts +2 -2
- package/rex-repl.js +122 -23
- package/rex-repl.ts +36 -3
- package/rex.js +64 -12
- package/rex.ohm +23 -3
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +5 -0
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +52 -9
- package/rexc-interpreter.ts +18 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@creationix/rex",
|
|
3
|
-
"version": "0.3.
|
|
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 (
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
return
|
|
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
|
|
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 (
|
|
444
|
-
|
|
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]) => `${
|
|
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 =
|
|
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
|
-
|
|
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"
|
|
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 =
|
|
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]
|
|
3080
|
+
return String(args[0]) + String(args[1]);
|
|
3024
3081
|
}
|
|
3025
|
-
return Number(args[0]
|
|
3082
|
+
return Number(args[0]) + Number(args[1]);
|
|
3026
3083
|
case OPCODES.sub:
|
|
3027
|
-
|
|
3084
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3085
|
+
return;
|
|
3086
|
+
return Number(args[0]) - Number(args[1]);
|
|
3028
3087
|
case OPCODES.mul:
|
|
3029
|
-
|
|
3088
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3089
|
+
return;
|
|
3090
|
+
return Number(args[0]) * Number(args[1]);
|
|
3030
3091
|
case OPCODES.div:
|
|
3031
|
-
|
|
3092
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3093
|
+
return;
|
|
3094
|
+
return Number(args[0]) / Number(args[1]);
|
|
3032
3095
|
case OPCODES.mod:
|
|
3033
|
-
|
|
3096
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3097
|
+
return;
|
|
3098
|
+
return Number(args[0]) % Number(args[1]);
|
|
3034
3099
|
case OPCODES.neg:
|
|
3035
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
return
|
|
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
|
|
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 (
|
|
454
|
-
|
|
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]) => `${
|
|
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 =
|
|
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
|
-
|
|
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"
|
|
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]
|
|
3052
|
+
return String(args[0]) + String(args[1]);
|
|
2996
3053
|
}
|
|
2997
|
-
return Number(args[0]
|
|
3054
|
+
return Number(args[0]) + Number(args[1]);
|
|
2998
3055
|
case OPCODES.sub:
|
|
2999
|
-
|
|
3056
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3057
|
+
return;
|
|
3058
|
+
return Number(args[0]) - Number(args[1]);
|
|
3000
3059
|
case OPCODES.mul:
|
|
3001
|
-
|
|
3060
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3061
|
+
return;
|
|
3062
|
+
return Number(args[0]) * Number(args[1]);
|
|
3002
3063
|
case OPCODES.div:
|
|
3003
|
-
|
|
3064
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3065
|
+
return;
|
|
3066
|
+
return Number(args[0]) / Number(args[1]);
|
|
3004
3067
|
case OPCODES.mod:
|
|
3005
|
-
|
|
3068
|
+
if (args[0] === undefined || args[1] === undefined)
|
|
3069
|
+
return;
|
|
3070
|
+
return Number(args[0]) % Number(args[1]);
|
|
3006
3071
|
case OPCODES.neg:
|
|
3007
|
-
|
|
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
|
|
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
|
|
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
|
|
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 });
|