@tishlang/tish 1.4.2 → 1.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.
Files changed (39) hide show
  1. package/bin/tish +0 -0
  2. package/crates/tish/Cargo.toml +2 -2
  3. package/crates/tish/src/cli_help.rs +504 -0
  4. package/crates/tish/src/main.rs +76 -90
  5. package/crates/tish/src/repl_completion.rs +1 -1
  6. package/crates/tish/tests/cargo_example_compile.rs +65 -0
  7. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  8. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  9. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  10. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  11. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  12. package/crates/tish/tests/integration_test.rs +48 -0
  13. package/crates/tish_build_utils/src/lib.rs +204 -1
  14. package/crates/tish_builtins/src/string.rs +248 -0
  15. package/crates/tish_bytecode/Cargo.toml +1 -0
  16. package/crates/tish_bytecode/src/compiler.rs +289 -66
  17. package/crates/tish_bytecode/src/opcode.rs +13 -3
  18. package/crates/tish_bytecode/src/peephole.rs +21 -16
  19. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  20. package/crates/tish_compile/Cargo.toml +1 -0
  21. package/crates/tish_compile/src/codegen.rs +277 -93
  22. package/crates/tish_compile/src/lib.rs +7 -4
  23. package/crates/tish_compile/src/resolve.rs +418 -40
  24. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +1 -0
  25. package/crates/tish_core/src/value.rs +1 -0
  26. package/crates/tish_eval/src/eval.rs +49 -1
  27. package/crates/tish_eval/src/lib.rs +1 -1
  28. package/crates/tish_native/src/build.rs +86 -17
  29. package/crates/tish_native/src/lib.rs +36 -16
  30. package/crates/tish_runtime/src/lib.rs +4 -0
  31. package/crates/tish_vm/src/lib.rs +1 -1
  32. package/crates/tish_vm/src/vm.rs +165 -19
  33. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  34. package/package.json +1 -1
  35. package/platform/darwin-arm64/tish +0 -0
  36. package/platform/darwin-x64/tish +0 -0
  37. package/platform/linux-arm64/tish +0 -0
  38. package/platform/linux-x64/tish +0 -0
  39. package/platform/win32-x64/tish.exe +0 -0
package/bin/tish CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "1.4.2"
3
+ version = "1.6.0"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -39,7 +39,7 @@ tishlang_wasm = { path = "../tish_wasm", version = ">=0.1" }
39
39
  tishlang_runtime = { path = "../tish_runtime", version = ">=0.1" }
40
40
  tishlang_core = { path = "../tish_core", version = ">=0.1" }
41
41
  tishlang_js_to_tish = { path = "../js_to_tish", version = ">=0.1" }
42
- clap = { version = "4.6.0", features = ["derive"] }
42
+ clap = { version = "4.6.0", features = ["derive", "color"] }
43
43
 
44
44
  [dev-dependencies]
45
45
  rayon = "1.11"
@@ -0,0 +1,504 @@
1
+ //! Long help text, terminal styling, and ASCII banner for the `tish` CLI.
2
+
3
+ use std::io::{self, IsTerminal, Write};
4
+ use std::thread;
5
+ use std::time::Duration;
6
+
7
+ use clap::builder::styling::{Color, Effects, RgbColor, Style, Styles};
8
+ use clap::{CommandFactory, Parser, Subcommand};
9
+
10
+ /// FIGlet-style block letters (UTF-8). On a TTY, a short expand + palette-color animation runs.
11
+ const TISH_BANNER_LINES: &[&str] = &[
12
+ "",
13
+ "████████╗██╗███████╗██╗ ██╗",
14
+ "╚══██╔══╝██║██╔════╝██║ ██║",
15
+ " ██║ ██║███████╗███████║",
16
+ " ██║ ██║╚════██║██╔══██║",
17
+ " ██║ ██║███████║██║ ██║",
18
+ " ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝",
19
+ ];
20
+
21
+ /// Frames used for the left-to-right expand reveal.
22
+ const BANNER_REVEAL_FRAMES: usize = 14;
23
+ /// Extra frames of rainbow cycling after the logo is fully visible.
24
+ const BANNER_CYCLE_FRAMES: usize = 4;
25
+ const BANNER_FRAME_MS: u64 = 20;
26
+
27
+ /// Orange → Yellow → Green → Teal → Blue → Purple → Pink (matching the brand palette).
28
+ const PALETTE: &[(u8, u8, u8)] = &[
29
+ (255, 159, 64), // Orange
30
+ (255, 213, 64), // Yellow
31
+ ( 52, 199, 89), // Green
32
+ ( 48, 209, 188), // Teal
33
+ ( 10, 132, 255), // Blue
34
+ (175, 82, 222), // Purple
35
+ (255, 55, 148), // Pink
36
+ ];
37
+
38
+ fn ease_out_cubic(t: f32) -> f32 {
39
+ let u = 1.0 - t.clamp(0.0, 1.0);
40
+ 1.0 - u * u * u
41
+ }
42
+
43
+ /// Linearly interpolate between two palette colors.
44
+ fn lerp_color(a: (u8, u8, u8), b: (u8, u8, u8), t: f32) -> (u8, u8, u8) {
45
+ let t = t.clamp(0.0, 1.0);
46
+ (
47
+ (a.0 as f32 + (b.0 as f32 - a.0 as f32) * t).round() as u8,
48
+ (a.1 as f32 + (b.1 as f32 - a.1 as f32) * t).round() as u8,
49
+ (a.2 as f32 + (b.2 as f32 - a.2 as f32) * t).round() as u8,
50
+ )
51
+ }
52
+
53
+ /// Smooth palette sample for a given (row, col) cell and scrolling color frame.
54
+ /// Uses column as the primary gradient axis so every row has a continuous sweep.
55
+ /// A small per-row offset adds a gentle diagonal tilt rather than flat stripes.
56
+ fn palette_color(row: usize, col: usize, color_frame: usize) -> (u8, u8, u8) {
57
+ let n = PALETTE.len();
58
+ // one full palette cycle every ~5 columns; row adds a slight diagonal
59
+ let scroll = color_frame as f32 * 0.22;
60
+ let pos = ((col as f32 / 5.0) + (row as f32 * 0.25) + scroll).rem_euclid(n as f32);
61
+ let lo = pos.floor() as usize % n;
62
+ let hi = (lo + 1) % n;
63
+ lerp_color(PALETTE[lo], PALETTE[hi], pos.fract())
64
+ }
65
+
66
+ /// Render one frame. `reveal_t` is 0..=1 (how much of each line is visible).
67
+ /// `color_frame` is the ever-incrementing counter that drives the rainbow scroll.
68
+ fn write_tish_banner_frame(out: &mut impl Write, reveal_t: f32, color_frame: usize) {
69
+ for (row, line) in TISH_BANNER_LINES.iter().enumerate() {
70
+ let chars: Vec<char> = line.chars().collect();
71
+ let len = chars.len();
72
+ let visible = ((len as f32) * reveal_t).round() as usize;
73
+ let visible = visible.min(len);
74
+
75
+ for col in 0..len {
76
+ let ch = chars[col];
77
+ if col >= visible || ch == ' ' {
78
+ let _ = write!(out, " ");
79
+ } else {
80
+ let (r, g, b) = palette_color(row, col, color_frame);
81
+ let _ = write!(out, "\x1b[1;38;2;{r};{g};{b}m{ch}\x1b[0m");
82
+ }
83
+ }
84
+ let _ = writeln!(out);
85
+ }
86
+ }
87
+
88
+ fn print_tish_banner_plain(out: &mut impl Write) {
89
+ for line in TISH_BANNER_LINES {
90
+ let _ = writeln!(out, "{line}");
91
+ }
92
+ let _ = writeln!(out);
93
+ }
94
+
95
+ fn print_tish_banner_animated(out: &mut impl Write) {
96
+ let n = TISH_BANNER_LINES.len();
97
+ let total = BANNER_REVEAL_FRAMES + BANNER_CYCLE_FRAMES;
98
+
99
+ for f in 0..total {
100
+ if f > 0 {
101
+ let _ = write!(out, "\x1b[{n}A");
102
+ }
103
+ // Phase 1: ease-out expand. Phase 2: fully visible, rainbow keeps scrolling.
104
+ let reveal_t = if f < BANNER_REVEAL_FRAMES {
105
+ ease_out_cubic((f + 1) as f32 / BANNER_REVEAL_FRAMES as f32)
106
+ } else {
107
+ 1.0
108
+ };
109
+ write_tish_banner_frame(out, reveal_t, f);
110
+ let _ = out.flush();
111
+ thread::sleep(Duration::from_millis(BANNER_FRAME_MS));
112
+ }
113
+ let _ = writeln!(out);
114
+ }
115
+
116
+ /// Print the `TISH` tile banner to stdout (animated palette on a TTY; plain text otherwise).
117
+ pub fn print_tish_banner() {
118
+ let mut out = io::stdout().lock();
119
+ if io::stdout().is_terminal() {
120
+ print_tish_banner_animated(&mut out);
121
+ } else {
122
+ print_tish_banner_plain(&mut out);
123
+ }
124
+ }
125
+
126
+ /// Build the `Command` with all colored after_help text attached.
127
+ /// Use this instead of `Cli::command()` everywhere so the help text is consistent.
128
+ pub fn build_command() -> clap::Command {
129
+ Cli::command()
130
+ .after_help(cli_after_help())
131
+ .mut_subcommand("run", |sub| sub.after_help(run_after_help()))
132
+ .mut_subcommand("repl", |sub| sub.after_help(repl_after_help()))
133
+ .mut_subcommand("build", |sub| sub.after_long_help(build_after_help()))
134
+ }
135
+
136
+ /// Write help text to `w` (plain bytes, used for line-counting only).
137
+ fn count_help_lines(cmd: &mut clap::Command, sub_name: Option<&str>) -> usize {
138
+ let mut buf = Vec::<u8>::new();
139
+ if let Some(name) = sub_name {
140
+ if cmd.find_subcommand(name).is_some() {
141
+ let _ = cmd.find_subcommand_mut(name).unwrap().write_long_help(&mut buf);
142
+ } else {
143
+ let _ = cmd.write_long_help(&mut buf);
144
+ }
145
+ } else {
146
+ let _ = cmd.write_long_help(&mut buf);
147
+ }
148
+ buf.iter().filter(|&&b| b == b'\n').count()
149
+ }
150
+
151
+ /// Print help text directly to stdout via clap's own stdout path (guaranteed colors).
152
+ fn print_help_to_stdout(cmd: &mut clap::Command, sub_name: Option<&str>) {
153
+ if let Some(name) = sub_name {
154
+ if cmd.find_subcommand(name).is_some() {
155
+ let _ = cmd.find_subcommand_mut(name).unwrap().print_long_help();
156
+ return;
157
+ }
158
+ }
159
+ let _ = cmd.print_long_help();
160
+ }
161
+
162
+ /// Detect which subcommand (if any) is being asked about from raw argv.
163
+ fn sub_name_from_argv(argv: &[String]) -> Option<String> {
164
+ match argv.get(1).map(String::as_str) {
165
+ Some("help") => argv.get(2).map(String::to_string), // tish help run
166
+ Some(s) if !s.starts_with('-') => Some(s.to_string()), // tish run --help
167
+ _ => None,
168
+ }
169
+ }
170
+
171
+ const VERSION: &str = env!("CARGO_PKG_VERSION");
172
+ /// ANSI: bold purple (175, 82, 222)
173
+ const H_PURPLE: &str = "\x1b[1;38;2;175;82;222m";
174
+ /// ANSI: medium grey — clearly less-than-white on dark backgrounds
175
+ const H_GREY: &str = "\x1b[38;2;150;150;150m";
176
+ /// ANSI: pink (255, 55, 148) — used for the website URL
177
+ const H_PINK: &str = "\x1b[38;2;255;55;148m";
178
+ const H_RESET: &str = "\x1b[0m";
179
+
180
+ /// Branded header used for subcommand help pages.
181
+ /// Prints `[purple]Tish[reset] [grey](version x)[reset]`
182
+ /// `[pink]https://tishlang.com[reset]`
183
+ /// followed by a blank line.
184
+ fn print_small_header() {
185
+ if io::stdout().is_terminal() {
186
+ println!("{H_PURPLE}Tish{H_RESET} {H_GREY}(version {VERSION}){H_RESET}");
187
+ println!("{H_PINK}https://tishlang.com{H_RESET}\n");
188
+ } else {
189
+ println!("Tish (version {VERSION})");
190
+ println!("https://tishlang.com\n");
191
+ }
192
+ }
193
+
194
+ /// Number of lines the main-help manual prefix takes (printed before clap output).
195
+ /// Layout: title, description, url, blank = 4 lines.
196
+ const MAIN_PREFIX_LINES: usize = 4;
197
+
198
+ /// Print help, prefixed with the right header and (for top-level only) the
199
+ /// animated banner. Help is written via clap's own stdout path for full colors.
200
+ pub fn print_banner_with_help(argv: &[String]) {
201
+ let sub_name = sub_name_from_argv(argv);
202
+ let sub = sub_name.as_deref();
203
+
204
+ // ── Subcommand help: compact static header, no animation ─────────────
205
+ if sub.is_some() {
206
+ print_small_header();
207
+ let mut cmd = build_command();
208
+ cmd.build();
209
+ print_help_to_stdout(&mut cmd, sub);
210
+ return;
211
+ }
212
+
213
+ // ── Top-level help ────────────────────────────────────────────────────
214
+
215
+ if !io::stdout().is_terminal() {
216
+ let mut out = io::stdout().lock();
217
+ print_tish_banner_plain(&mut out);
218
+ drop(out);
219
+ let mut cmd = build_command().color(clap::ColorChoice::Never);
220
+ cmd.build();
221
+ print_help_to_stdout(&mut cmd, sub);
222
+ return;
223
+ }
224
+
225
+ // Line-count pass (ANSI codes never add \n, so Never == Always count).
226
+ let h: usize = {
227
+ let mut cmd = build_command().color(clap::ColorChoice::Never);
228
+ cmd.build();
229
+ count_help_lines(&mut cmd, sub)
230
+ };
231
+
232
+ let n = TISH_BANNER_LINES.len();
233
+
234
+ // 1. First banner frame + manual prefix + full help – all visible immediately.
235
+ {
236
+ let mut out = io::stdout().lock();
237
+ write_tish_banner_frame(&mut out, 1.0, 0);
238
+ let _ = writeln!(out); // blank separator (row n+1)
239
+ // ── Manual prefix (MAIN_PREFIX_LINES = 4 lines) ──────────────────
240
+ let _ = writeln!(out, "{H_PURPLE}Tish{H_RESET} {H_GREY}(version {VERSION}){H_RESET}");
241
+ let _ = writeln!(out, "Minimal TS/JS-ish language");
242
+ let _ = writeln!(out, "{H_PINK}https://tishlang.com{H_RESET}");
243
+ let _ = writeln!(out); // blank before Usage
244
+ let _ = out.flush();
245
+ }
246
+ {
247
+ let mut cmd = build_command();
248
+ cmd.build();
249
+ print_help_to_stdout(&mut cmd, sub);
250
+ let _ = io::stdout().flush();
251
+ }
252
+
253
+ // 2. Jump cursor back to banner top and cycle colors.
254
+ // Total rows above cursor: n + 1 (sep) + MAIN_PREFIX_LINES + h (clap)
255
+ {
256
+ let mut out = io::stdout().lock();
257
+ let _ = write!(out, "\x1b[{}A", n + 1 + MAIN_PREFIX_LINES + h);
258
+ let _ = out.flush();
259
+
260
+ let frames = BANNER_CYCLE_FRAMES;
261
+ for f in 0..frames {
262
+ write_tish_banner_frame(&mut out, 1.0, f);
263
+ if f < frames - 1 {
264
+ let _ = write!(out, "\x1b[{}A", n);
265
+ }
266
+ let _ = out.flush();
267
+ thread::sleep(Duration::from_millis(BANNER_FRAME_MS));
268
+ }
269
+
270
+ // After last frame cursor is at row n; skip sep + prefix + clap rows.
271
+ let _ = write!(out, "\x1b[{}B", 1 + MAIN_PREFIX_LINES + h);
272
+ let _ = writeln!(out);
273
+ let _ = out.flush();
274
+ }
275
+ }
276
+
277
+ /// Whether argv will cause clap to print help (top-level or subcommand).
278
+ pub fn argv_requests_help(argv: &[String]) -> bool {
279
+ argv.iter().any(|a| a == "--help" || a == "-h")
280
+ || matches!(argv.get(1).map(String::as_str), Some("help"))
281
+ }
282
+
283
+ /// Build a bold true-color `Style` from the brand palette.
284
+ fn rgb_bold(r: u8, g: u8, b: u8) -> Style {
285
+ Style::new().fg_color(Some(Color::Rgb(RgbColor(r, g, b)))) | Effects::BOLD
286
+ }
287
+
288
+ /// Help colors using the brand palette.
289
+ /// Orange → section headers / usage. Teal → literals (commands, flags). Yellow → placeholders.
290
+ pub fn cargo_help_styles() -> Styles {
291
+ Styles::styled()
292
+ .header(rgb_bold(255, 159, 64)) // Orange – "Commands:", "Options:", "Usage:"
293
+ .usage(rgb_bold(255, 159, 64)) // Orange
294
+ .literal(rgb_bold( 48, 209, 188)) // Teal – run, repl, --help, -V …
295
+ .placeholder(rgb_bold(255, 213, 64)) // Yellow – <FILE>, <NAME>, …
296
+ .error(rgb_bold(255, 55, 148)) // Pink – error messages
297
+ .valid(rgb_bold( 52, 199, 89)) // Green – valid values
298
+ .invalid(rgb_bold(255, 55, 148)) // Pink – invalid values
299
+ }
300
+
301
+ /// Returns the colored `after_help` text for the top-level `tish --help`.
302
+ /// Colors are emitted only when stdout is a TTY.
303
+ pub fn cli_after_help() -> String {
304
+ let (oh, t, r) = if io::stdout().is_terminal() {
305
+ ("\x1b[1;38;2;255;159;64m", "\x1b[1;38;2;48;209;188m", "\x1b[0m")
306
+ } else {
307
+ ("", "", "")
308
+ };
309
+ format!(
310
+ "\
311
+ {oh}Environment variables:{r}
312
+ {t}TISH_NO_OPTIMIZE=1{r}
313
+ Disable AST and bytecode optimizations for run/build
314
+
315
+ See {t}tish run --help{r} and {t}tish build --help{r} for backend and feature options."
316
+ )
317
+ }
318
+
319
+ fn capabilities_section(oh: &str, t: &str, r: &str) -> String {
320
+ format!(
321
+ "\
322
+ {oh}Backends{r} (--backend):
323
+ {t}vm{r}
324
+ Bytecode VM (default)
325
+ {t}interp{r}
326
+ Tree-walking interpreter
327
+
328
+ {oh}Capabilities{r} (--feature, repeatable; comma-separated values are split):
329
+ {t}http{r}
330
+ Network: fetch, serve, Promise / timers (native async)
331
+ {t}fs{r}
332
+ Filesystem: readFile, writeFile, fileExists, isDir, readDir, mkdir
333
+ {t}process{r}
334
+ process.exit, cwd, exec, argv, env
335
+ {t}regex{r}
336
+ RegExp
337
+ {t}ws{r}
338
+ WebSocket client / server
339
+ {t}full{r}
340
+ All of the above (http, fs, process, regex, ws)
341
+
342
+ Omit --feature to use every capability linked into this binary."
343
+ )
344
+ }
345
+
346
+ /// Returns the colored `after_help` for `tish run --help`.
347
+ pub fn run_after_help() -> String {
348
+ let (oh, t, r) = if io::stdout().is_terminal() {
349
+ ("\x1b[1;38;2;255;159;64m", "\x1b[1;38;2;48;209;188m", "\x1b[0m")
350
+ } else {
351
+ ("", "", "")
352
+ };
353
+ capabilities_section(oh, t, r)
354
+ }
355
+
356
+ /// Returns the colored `after_help` for `tish repl --help`.
357
+ pub fn repl_after_help() -> String {
358
+ let (oh, t, r) = if io::stdout().is_terminal() {
359
+ ("\x1b[1;38;2;255;159;64m", "\x1b[1;38;2;48;209;188m", "\x1b[0m")
360
+ } else {
361
+ ("", "", "")
362
+ };
363
+ capabilities_section(oh, t, r)
364
+ }
365
+
366
+ /// Returns the colored `after_long_help` for `tish build --help`.
367
+ pub fn build_after_help() -> String {
368
+ let (oh, t, r) = if io::stdout().is_terminal() {
369
+ ("\x1b[1;38;2;255;159;64m", "\x1b[1;38;2;48;209;188m", "\x1b[0m")
370
+ } else {
371
+ ("", "", "")
372
+ };
373
+ format!(
374
+ "\
375
+ {oh}Build targets{r} (--target, default: native):
376
+ {t}native{r}
377
+ Native executable (see --native-backend)
378
+ {t}js{r}
379
+ JavaScript bundle
380
+ {t}wasm{r}
381
+ WebAssembly (.tish project; .js source supported on some paths)
382
+ {t}wasi{r}
383
+ WASI WebAssembly
384
+
385
+ {oh}Native backends{r} (--native-backend, only with --target native, default: rust):
386
+ {t}rust{r}
387
+ Emit Rust + link tishlang_runtime via cargo
388
+ {t}cranelift{r}
389
+ Embedded bytecode + Cranelift/VM runtime binary
390
+ {t}llvm{r}
391
+ Embedded bytecode + LLVM/clang link path
392
+
393
+ {oh}Capabilities{r} (--feature, repeatable; comma-separated values are split):
394
+ {t}http{r}
395
+ Network: fetch, serve, Promise / timers (native async)
396
+ {t}fs{r}
397
+ Filesystem: readFile, writeFile, fileExists, isDir, readDir, mkdir
398
+ {t}process{r}
399
+ process.exit, cwd, exec, argv, env
400
+ {t}regex{r}
401
+ RegExp
402
+ {t}ws{r}
403
+ WebSocket client / server
404
+ {t}full{r}
405
+ All of the above (http, fs, process, regex, ws)
406
+
407
+ Omit --feature to use every capability linked into this binary.
408
+ Build `tish` with matching Cargo features (e.g. cargo build -p tishlang --features full)."
409
+ )
410
+ }
411
+
412
+ #[derive(Parser)]
413
+ #[command(name = "tish")]
414
+ #[command(version = env!("CARGO_PKG_VERSION"))]
415
+ #[command(styles = cargo_help_styles())]
416
+ pub(crate) struct Cli {
417
+ #[command(subcommand)]
418
+ pub command: Option<Commands>,
419
+ }
420
+
421
+ #[derive(Parser)]
422
+ pub(crate) struct RunArgs {
423
+ /// Path to a `.tish` file, or `-` to read the program from stdin (like `node -`).
424
+ #[arg(required = true, allow_hyphen_values = true, value_name = "FILE", help_heading = "Arguments")]
425
+ pub file: String,
426
+ /// `vm` or `interp` (see `tish --help` for capabilities / `--feature`).
427
+ #[arg(long, default_value = "vm", value_name = "NAME", help_heading = "Options")]
428
+ pub backend: String,
429
+ /// Subset of capabilities (see `tish --help` for the full list).
430
+ #[arg(
431
+ long = "feature",
432
+ value_name = "NAME",
433
+ action = clap::ArgAction::Append,
434
+ help_heading = "Options"
435
+ )]
436
+ pub features: Vec<String>,
437
+ /// Disable AST and bytecode optimizations (for debugging).
438
+ #[arg(long, help_heading = "Options")]
439
+ pub no_optimize: bool,
440
+ }
441
+
442
+ #[derive(Parser)]
443
+ pub(crate) struct ReplArgs {
444
+ /// `vm` or `interp` (see `tish --help`).
445
+ #[arg(long, default_value = "vm", value_name = "NAME", help_heading = "Options")]
446
+ pub backend: String,
447
+ /// Subset of capabilities (see `tish --help` for the full list).
448
+ #[arg(
449
+ long = "feature",
450
+ value_name = "NAME",
451
+ action = clap::ArgAction::Append,
452
+ help_heading = "Options"
453
+ )]
454
+ pub features: Vec<String>,
455
+ #[arg(long, help_heading = "Options")]
456
+ pub no_optimize: bool,
457
+ }
458
+
459
+ #[derive(Parser)]
460
+ pub(crate) struct BuildArgs {
461
+ #[arg(
462
+ short,
463
+ long,
464
+ default_value = "tish_out",
465
+ value_name = "PATH",
466
+ help_heading = "Options"
467
+ )]
468
+ pub output: String,
469
+ /// `native`, `js`, `wasm`, or `wasi` (see long help below).
470
+ #[arg(long, default_value = "native", value_name = "TARGET", help_heading = "Options")]
471
+ pub target: String,
472
+ /// `rust`, `cranelift`, or `llvm` (only for `--target native`).
473
+ #[arg(long, default_value = "rust", value_name = "BACKEND", help_heading = "Options")]
474
+ pub native_backend: String,
475
+ /// Capability subset for native output (see long help below).
476
+ #[arg(
477
+ long = "feature",
478
+ value_name = "NAME",
479
+ action = clap::ArgAction::Append,
480
+ help_heading = "Options"
481
+ )]
482
+ pub features: Vec<String>,
483
+ #[arg(long, help_heading = "Options")]
484
+ pub no_optimize: bool,
485
+ /// Entry `.tish` file (or `.js` where supported).
486
+ #[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
487
+ pub file: String,
488
+ }
489
+
490
+ #[derive(Subcommand)]
491
+ pub(crate) enum Commands {
492
+ /// Run a Tish file (interpret)
493
+ Run(RunArgs),
494
+ /// Interactive REPL
495
+ Repl(ReplArgs),
496
+ /// Build native binary, wasm, wasi, or JavaScript output
497
+ Build(BuildArgs),
498
+ /// Parse and dump AST
499
+ #[command(name = "dump-ast")]
500
+ DumpAst {
501
+ #[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
502
+ file: String,
503
+ },
504
+ }