@creationix/rex 0.4.1 → 0.6.0
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/README.md +24 -0
- package/package.json +9 -6
- package/rex-cli.js +785 -987
- package/rex-cli.ts +236 -16
- package/rex-repl.js +592 -1005
- package/rex-repl.ts +389 -101
- package/rex.js +51 -845
- package/rex.ohm +7 -8
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +4 -3
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +52 -23
- package/rexc-interpreter.ts +136 -15
- package/rx-cli.js +2836 -0
- package/rx-cli.ts +298 -0
package/rex-cli.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { compile, parse, parseToIR, stringify } from "./rex.ts";
|
|
2
|
-
import { evaluateSource } from "./rexc-interpreter.ts";
|
|
1
|
+
import { compile, parse, parseToIR, stringify, grammar } from "./rex.ts";
|
|
2
|
+
import { evaluateRexc, evaluateSource } from "./rexc-interpreter.ts";
|
|
3
|
+
import { highlightLine, highlightJSON, highlightAuto, setColorEnabled } from "./rex-repl.ts";
|
|
3
4
|
import { dirname, resolve } from "node:path";
|
|
4
5
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
6
|
|
|
@@ -14,6 +15,16 @@ type CliOptions = {
|
|
|
14
15
|
dedupeValues: boolean;
|
|
15
16
|
dedupeMinBytes?: number;
|
|
16
17
|
help: boolean;
|
|
18
|
+
cat: boolean;
|
|
19
|
+
showSource: boolean;
|
|
20
|
+
showExpr: boolean;
|
|
21
|
+
showIR: boolean;
|
|
22
|
+
showRexc: boolean;
|
|
23
|
+
showVars: boolean;
|
|
24
|
+
color: boolean;
|
|
25
|
+
colorExplicit: boolean;
|
|
26
|
+
showExprExplicit: boolean;
|
|
27
|
+
format: "rex" | "json";
|
|
17
28
|
};
|
|
18
29
|
|
|
19
30
|
function parseArgs(argv: string[]): CliOptions {
|
|
@@ -24,6 +35,16 @@ function parseArgs(argv: string[]): CliOptions {
|
|
|
24
35
|
minifyNames: false,
|
|
25
36
|
dedupeValues: false,
|
|
26
37
|
help: false,
|
|
38
|
+
cat: false,
|
|
39
|
+
showSource: false,
|
|
40
|
+
showExpr: true,
|
|
41
|
+
showIR: false,
|
|
42
|
+
showRexc: false,
|
|
43
|
+
showVars: false,
|
|
44
|
+
color: process.stdout.isTTY,
|
|
45
|
+
colorExplicit: false,
|
|
46
|
+
showExprExplicit: false,
|
|
47
|
+
format: "rex",
|
|
27
48
|
};
|
|
28
49
|
for (let index = 0; index < argv.length; index += 1) {
|
|
29
50
|
const arg = argv[index];
|
|
@@ -32,6 +53,58 @@ function parseArgs(argv: string[]): CliOptions {
|
|
|
32
53
|
options.help = true;
|
|
33
54
|
continue;
|
|
34
55
|
}
|
|
56
|
+
if (arg === "--cat") {
|
|
57
|
+
options.cat = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (arg === "--show-source") {
|
|
61
|
+
options.showSource = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (arg === "--show-expr") {
|
|
65
|
+
options.showExpr = true;
|
|
66
|
+
options.showExprExplicit = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (arg === "--no-expr") {
|
|
70
|
+
options.showExpr = false;
|
|
71
|
+
options.showExprExplicit = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (arg === "--show-ir") {
|
|
75
|
+
options.showIR = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (arg === "--show-rexc") {
|
|
79
|
+
options.showRexc = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === "--show-vars") {
|
|
83
|
+
options.showVars = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (arg === "--json") {
|
|
87
|
+
options.format = "json";
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg === "--format") {
|
|
91
|
+
const value = argv[index + 1];
|
|
92
|
+
if (!value) throw new Error("Missing value for --format");
|
|
93
|
+
if (value !== "rex" && value !== "json") throw new Error("--format must be 'rex' or 'json'");
|
|
94
|
+
options.format = value;
|
|
95
|
+
index += 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (arg === "--color") {
|
|
99
|
+
options.color = true;
|
|
100
|
+
options.colorExplicit = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (arg === "--no-color") {
|
|
104
|
+
options.color = false;
|
|
105
|
+
options.colorExplicit = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
35
108
|
if (arg === "--compile" || arg === "-c") {
|
|
36
109
|
options.compile = true;
|
|
37
110
|
continue;
|
|
@@ -112,9 +185,26 @@ function usage() {
|
|
|
112
185
|
" They are concatenated in the order they appear on the command line.",
|
|
113
186
|
"",
|
|
114
187
|
"Output mode:",
|
|
115
|
-
" (default) Evaluate and output result
|
|
116
|
-
" -c, --compile
|
|
117
|
-
" --ir
|
|
188
|
+
" (default) Evaluate and output result",
|
|
189
|
+
" -c, --compile Show rexc only (same as --show-rexc --no-expr)",
|
|
190
|
+
" --ir Show IR only (same as --show-ir --no-expr)",
|
|
191
|
+
" --cat Print input with Rex highlighting",
|
|
192
|
+
" --show-source Show input source text",
|
|
193
|
+
" --show-expr Show expression result",
|
|
194
|
+
" --no-expr Hide expression result",
|
|
195
|
+
" --show-ir Show IR JSON",
|
|
196
|
+
" --show-rexc Show rexc bytecode",
|
|
197
|
+
" --show-vars Show variable state",
|
|
198
|
+
"",
|
|
199
|
+
"Output formatting:",
|
|
200
|
+
" --format <type> Output format: rex|json (default: rex)",
|
|
201
|
+
" --json Shortcut for --format json",
|
|
202
|
+
" --color Force color output",
|
|
203
|
+
" --no-color Disable color output",
|
|
204
|
+
"",
|
|
205
|
+
"TTY vs non-TTY:",
|
|
206
|
+
" TTY output prints labeled blocks when multiple outputs are selected.",
|
|
207
|
+
" Non-TTY output emits a single value (object if multiple outputs).",
|
|
118
208
|
"",
|
|
119
209
|
"Compile options:",
|
|
120
210
|
" -m, --minify-names Minify local variable names",
|
|
@@ -174,12 +264,56 @@ async function resolveDomainConfig(options: CliOptions): Promise<unknown | undef
|
|
|
174
264
|
return loadDomainConfigFromFolder(baseFolder);
|
|
175
265
|
}
|
|
176
266
|
|
|
267
|
+
function formatJson(value: unknown, indent = 2): string {
|
|
268
|
+
const normalized = normalizeJsonValue(value, false);
|
|
269
|
+
const text = JSON.stringify(normalized, null, indent);
|
|
270
|
+
return text ?? "null";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizeJsonValue(value: unknown, inArray: boolean): unknown {
|
|
274
|
+
if (value === undefined) return inArray ? null : undefined;
|
|
275
|
+
if (value === null || typeof value !== "object") return value;
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
return value.map((item) => normalizeJsonValue(item, true));
|
|
278
|
+
}
|
|
279
|
+
const out: Record<string, unknown> = {};
|
|
280
|
+
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
|
|
281
|
+
const normalized = normalizeJsonValue(val, false);
|
|
282
|
+
if (normalized !== undefined) out[key] = normalized;
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isRexSource(source: string): boolean {
|
|
288
|
+
try {
|
|
289
|
+
return grammar.match(source).succeeded();
|
|
290
|
+
} catch {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
177
295
|
async function main() {
|
|
178
296
|
const options = parseArgs(process.argv.slice(2));
|
|
179
297
|
if (options.help) {
|
|
180
298
|
console.log(usage());
|
|
181
299
|
return;
|
|
182
300
|
}
|
|
301
|
+
setColorEnabled(options.color);
|
|
302
|
+
if (options.cat) {
|
|
303
|
+
const source = await resolveSource(options);
|
|
304
|
+
const hasRexc = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rexc"));
|
|
305
|
+
const hasRex = options.sources.some((seg) => seg.type === "file" && seg.path.endsWith(".rex"));
|
|
306
|
+
const hint = hasRexc && !hasRex ? "rexc" : hasRex && !hasRexc ? "rex" : undefined;
|
|
307
|
+
const output = (process.stdout.isTTY && options.color)
|
|
308
|
+
? highlightAuto(source, hint)
|
|
309
|
+
: source;
|
|
310
|
+
if (options.out) {
|
|
311
|
+
await writeFile(options.out, `${output}\n`, "utf8");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
console.log(output);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
183
317
|
|
|
184
318
|
// No source provided on a TTY → launch interactive REPL
|
|
185
319
|
const hasSource = options.sources.length > 0 || !process.stdin.isTTY;
|
|
@@ -190,21 +324,107 @@ async function main() {
|
|
|
190
324
|
}
|
|
191
325
|
|
|
192
326
|
const source = await resolveSource(options);
|
|
327
|
+
const sourceIsRex = isRexSource(source);
|
|
193
328
|
|
|
194
|
-
|
|
329
|
+
if (options.compile) {
|
|
330
|
+
if (!sourceIsRex) throw new Error("--compile requires Rex source");
|
|
331
|
+
options.showRexc = true;
|
|
332
|
+
if (!options.showExprExplicit) options.showExpr = false;
|
|
333
|
+
}
|
|
195
334
|
if (options.ir) {
|
|
196
|
-
|
|
197
|
-
|
|
335
|
+
if (!sourceIsRex) throw new Error("--ir requires Rex source");
|
|
336
|
+
options.showIR = true;
|
|
337
|
+
if (!options.showExprExplicit) options.showExpr = false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const outputFlags = [options.showSource, options.showExpr, options.showIR, options.showRexc, options.showVars].filter(Boolean).length;
|
|
341
|
+
if (outputFlags === 0) {
|
|
342
|
+
throw new Error("No output selected. Use --show-source, --show-expr, --show-ir, --show-rexc, or --show-vars.");
|
|
343
|
+
}
|
|
344
|
+
const humanMode = process.stdout.isTTY && options.color;
|
|
345
|
+
type OutputKey = "source" | "ir" | "rexc" | "vars" | "result";
|
|
346
|
+
const outputs: Partial<Record<OutputKey, unknown>> = {};
|
|
347
|
+
if (options.showSource) outputs.source = source;
|
|
348
|
+
if (!sourceIsRex && options.showIR) {
|
|
349
|
+
throw new Error("--show-ir is only available for Rex source");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (options.showIR) {
|
|
353
|
+
outputs.ir = parseToIR(source);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (options.showRexc) {
|
|
198
357
|
const domainConfig = await resolveDomainConfig(options);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
358
|
+
outputs.rexc = sourceIsRex
|
|
359
|
+
? compile(source, {
|
|
360
|
+
minifyNames: options.minifyNames,
|
|
361
|
+
dedupeValues: options.dedupeValues,
|
|
362
|
+
dedupeMinBytes: options.dedupeMinBytes,
|
|
363
|
+
domainConfig,
|
|
364
|
+
})
|
|
365
|
+
: source;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (options.showExpr || options.showVars) {
|
|
369
|
+
const result = sourceIsRex ? evaluateSource(source) : evaluateRexc(source);
|
|
370
|
+
if (options.showExpr) outputs.result = result.value;
|
|
371
|
+
if (options.showVars) outputs.vars = result.state.vars;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const order: OutputKey[] = ["source", "ir", "rexc", "vars", "result"];
|
|
375
|
+
const selected = order.filter((key) => outputs[key] !== undefined);
|
|
376
|
+
|
|
377
|
+
function formatValue(value: unknown, kind: OutputKey): string {
|
|
378
|
+
if (kind === "source") {
|
|
379
|
+
const raw = String(value ?? "");
|
|
380
|
+
if (options.format === "json") {
|
|
381
|
+
const json = formatJson(raw, 2);
|
|
382
|
+
return humanMode && options.color ? highlightJSON(json) : json;
|
|
383
|
+
}
|
|
384
|
+
return humanMode && options.color ? highlightAuto(raw) : raw;
|
|
385
|
+
}
|
|
386
|
+
if (kind === "rexc" && humanMode) {
|
|
387
|
+
const raw = String(value ?? "");
|
|
388
|
+
return options.color ? highlightAuto(raw, "rexc") : raw;
|
|
389
|
+
}
|
|
390
|
+
if (options.format === "json") {
|
|
391
|
+
const raw = formatJson(value, 2);
|
|
392
|
+
return humanMode && options.color ? highlightJSON(raw) : raw;
|
|
393
|
+
}
|
|
394
|
+
if (kind === "rexc") {
|
|
395
|
+
const raw = stringify(String(value ?? ""));
|
|
396
|
+
return humanMode && options.color ? highlightLine(raw) : raw;
|
|
397
|
+
}
|
|
398
|
+
const raw = stringify(value);
|
|
399
|
+
return humanMode && options.color ? highlightLine(raw) : raw;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let output: string;
|
|
403
|
+
if (humanMode) {
|
|
404
|
+
const header = options.color ? "\x1b[90m" : "";
|
|
405
|
+
const reset = options.color ? "\x1b[0m" : "";
|
|
406
|
+
const lines: string[] = [];
|
|
407
|
+
for (const key of order) {
|
|
408
|
+
const value = outputs[key];
|
|
409
|
+
if (value === undefined) continue;
|
|
410
|
+
lines.push(`${header}${key}:${reset} ${formatValue(value, key)}`);
|
|
411
|
+
}
|
|
412
|
+
output = lines.join("\n");
|
|
205
413
|
} else {
|
|
206
|
-
|
|
207
|
-
|
|
414
|
+
let value: unknown;
|
|
415
|
+
if (selected.length === 1) {
|
|
416
|
+
const only = selected[0];
|
|
417
|
+
if (!only) throw new Error("No output selected.");
|
|
418
|
+
value = outputs[only];
|
|
419
|
+
} else {
|
|
420
|
+
const out: Record<string, unknown> = {};
|
|
421
|
+
for (const key of order) {
|
|
422
|
+
const v = outputs[key];
|
|
423
|
+
if (v !== undefined) out[key] = v;
|
|
424
|
+
}
|
|
425
|
+
value = out;
|
|
426
|
+
}
|
|
427
|
+
output = options.format === "json" ? formatJson(value, 2) : stringify(value);
|
|
208
428
|
}
|
|
209
429
|
|
|
210
430
|
if (options.out) {
|