@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.ts CHANGED
@@ -1,11 +1,12 @@
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";
5
5
 
6
+ type SourceSegment = { type: "expr"; value: string } | { type: "file"; path: string };
7
+
6
8
  type CliOptions = {
7
- expr?: string;
8
- file?: string;
9
+ sources: SourceSegment[];
9
10
  out?: string;
10
11
  compile: boolean;
11
12
  ir: boolean;
@@ -17,6 +18,7 @@ type CliOptions = {
17
18
 
18
19
  function parseArgs(argv: string[]): CliOptions {
19
20
  const options: CliOptions = {
21
+ sources: [],
20
22
  compile: false,
21
23
  ir: false,
22
24
  minifyNames: false,
@@ -58,14 +60,14 @@ function parseArgs(argv: string[]): CliOptions {
58
60
  if (arg === "--expr" || arg === "-e") {
59
61
  const value = argv[index + 1];
60
62
  if (!value) throw new Error("Missing value for --expr");
61
- options.expr = value;
63
+ options.sources.push({ type: "expr", value });
62
64
  index += 1;
63
65
  continue;
64
66
  }
65
67
  if (arg === "--file" || arg === "-f") {
66
68
  const value = argv[index + 1];
67
69
  if (!value) throw new Error("Missing value for --file");
68
- options.file = value;
70
+ options.sources.push({ type: "file", path: value });
69
71
  index += 1;
70
72
  continue;
71
73
  }
@@ -78,8 +80,7 @@ function parseArgs(argv: string[]): CliOptions {
78
80
  }
79
81
  // Positional argument = file path
80
82
  if (!arg.startsWith("-")) {
81
- if (options.file) throw new Error("Multiple file arguments provided");
82
- options.file = arg;
83
+ options.sources.push({ type: "file", path: arg });
83
84
  continue;
84
85
  }
85
86
  throw new Error(`Unknown option: ${arg}`);
@@ -98,11 +99,18 @@ function usage() {
98
99
  " cat input.rex | rex Evaluate from stdin",
99
100
  " rex -c input.rex Compile to rexc bytecode",
100
101
  "",
102
+ " Sources are concatenated in order, so flags and files can be mixed:",
103
+ " rex -e 'max = 200' primes.rex Set max before running script",
104
+ " rex primes.rex -e '42' Run script, then evaluate 42",
105
+ "",
101
106
  "Input:",
102
107
  " <file> Evaluate/compile a Rex source file",
103
108
  " -e, --expr <source> Evaluate/compile an inline expression",
104
109
  " -f, --file <path> Evaluate/compile source from a file",
105
110
  "",
111
+ " Multiple -e and -f flags (and positional files) can be combined.",
112
+ " They are concatenated in the order they appear on the command line.",
113
+ "",
106
114
  "Output mode:",
107
115
  " (default) Evaluate and output result as JSON",
108
116
  " -c, --compile Compile to rexc bytecode",
@@ -128,9 +136,14 @@ async function readStdin(): Promise<string> {
128
136
  }
129
137
 
130
138
  async function resolveSource(options: CliOptions): Promise<string> {
131
- if (options.expr && options.file) throw new Error("Use only one of --expr, --file, or a positional file path");
132
- if (options.expr) return options.expr;
133
- if (options.file) return readFile(options.file, "utf8");
139
+ if (options.sources.length > 0) {
140
+ const parts: string[] = [];
141
+ for (const seg of options.sources) {
142
+ if (seg.type === "expr") parts.push(seg.value);
143
+ else parts.push(await readFile(seg.path, "utf8"));
144
+ }
145
+ return parts.join("\n");
146
+ }
134
147
  if (!process.stdin.isTTY) {
135
148
  const piped = await readStdin();
136
149
  if (piped.trim().length > 0) return piped;
@@ -138,6 +151,13 @@ async function resolveSource(options: CliOptions): Promise<string> {
138
151
  throw new Error("No input provided. Use a file path, --expr, or pipe source via stdin.");
139
152
  }
140
153
 
154
+ function findFirstFilePath(sources: SourceSegment[]): string | undefined {
155
+ for (const seg of sources) {
156
+ if (seg.type === "file") return seg.path;
157
+ }
158
+ return undefined;
159
+ }
160
+
141
161
  async function loadDomainConfigFromFolder(folderPath: string): Promise<unknown | undefined> {
142
162
  const configPath = resolve(folderPath, ".config.rex");
143
163
  try {
@@ -149,7 +169,8 @@ async function loadDomainConfigFromFolder(folderPath: string): Promise<unknown |
149
169
  }
150
170
 
151
171
  async function resolveDomainConfig(options: CliOptions): Promise<unknown | undefined> {
152
- const baseFolder = options.file ? dirname(resolve(options.file)) : process.cwd();
172
+ const filePath = findFirstFilePath(options.sources);
173
+ const baseFolder = filePath ? dirname(resolve(filePath)) : process.cwd();
153
174
  return loadDomainConfigFromFolder(baseFolder);
154
175
  }
155
176
 
@@ -161,7 +182,7 @@ async function main() {
161
182
  }
162
183
 
163
184
  // No source provided on a TTY → launch interactive REPL
164
- const hasSource = options.expr || options.file || !process.stdin.isTTY;
185
+ const hasSource = options.sources.length > 0 || !process.stdin.isTTY;
165
186
  if (!hasSource && !options.compile && !options.ir) {
166
187
  const { startRepl } = await import("./rex-repl.ts");
167
188
  await startRepl();
@@ -183,7 +204,7 @@ async function main() {
183
204
  });
184
205
  } else {
185
206
  const { value } = evaluateSource(source);
186
- output = JSON.stringify(value, null, 2);
207
+ output = stringify(value);
187
208
  }
188
209
 
189
210
  if (options.out) {