@tishlang/tish-format 1.0.12 → 2.0.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.
Files changed (189) hide show
  1. package/Cargo.toml +51 -0
  2. package/LICENSE +13 -0
  3. package/bin/tish-format +0 -0
  4. package/crates/js_to_tish/Cargo.toml +11 -0
  5. package/crates/js_to_tish/README.md +18 -0
  6. package/crates/js_to_tish/src/error.rs +55 -0
  7. package/crates/js_to_tish/src/lib.rs +11 -0
  8. package/crates/js_to_tish/src/span_util.rs +35 -0
  9. package/crates/js_to_tish/src/transform/expr.rs +611 -0
  10. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  11. package/crates/js_to_tish/src/transform.rs +60 -0
  12. package/crates/tish/Cargo.toml +62 -0
  13. package/crates/tish/build.rs +21 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +576 -0
  16. package/crates/tish/src/main.rs +853 -0
  17. package/crates/tish/src/repl_completion.rs +199 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/error_source_location.rs +36 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  24. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  25. package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
  26. package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
  27. package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
  28. package/crates/tish/tests/integration_test.rs +1406 -0
  29. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  30. package/crates/tish/tests/shortcircuit.rs +65 -0
  31. package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
  32. package/crates/tish/tests/tty_capability.rs +43 -0
  33. package/crates/tish_ast/Cargo.toml +9 -0
  34. package/crates/tish_ast/src/ast.rs +649 -0
  35. package/crates/tish_ast/src/lib.rs +5 -0
  36. package/crates/tish_build_utils/Cargo.toml +11 -0
  37. package/crates/tish_build_utils/src/lib.rs +577 -0
  38. package/crates/tish_builtins/Cargo.toml +22 -0
  39. package/crates/tish_builtins/src/array.rs +803 -0
  40. package/crates/tish_builtins/src/collections.rs +481 -0
  41. package/crates/tish_builtins/src/construct.rs +199 -0
  42. package/crates/tish_builtins/src/date.rs +538 -0
  43. package/crates/tish_builtins/src/globals.rs +293 -0
  44. package/crates/tish_builtins/src/helpers.rs +35 -0
  45. package/crates/tish_builtins/src/iterator.rs +129 -0
  46. package/crates/tish_builtins/src/lib.rs +21 -0
  47. package/crates/tish_builtins/src/math.rs +89 -0
  48. package/crates/tish_builtins/src/number.rs +96 -0
  49. package/crates/tish_builtins/src/object.rs +36 -0
  50. package/crates/tish_builtins/src/string.rs +646 -0
  51. package/crates/tish_builtins/src/symbol.rs +83 -0
  52. package/crates/tish_builtins/src/typedarrays.rs +298 -0
  53. package/crates/tish_bytecode/Cargo.toml +17 -0
  54. package/crates/tish_bytecode/src/chunk.rs +164 -0
  55. package/crates/tish_bytecode/src/compiler.rs +2604 -0
  56. package/crates/tish_bytecode/src/encoding.rs +102 -0
  57. package/crates/tish_bytecode/src/lib.rs +20 -0
  58. package/crates/tish_bytecode/src/opcode.rs +185 -0
  59. package/crates/tish_bytecode/src/peephole.rs +189 -0
  60. package/crates/tish_bytecode/src/serialize.rs +193 -0
  61. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  62. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  63. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  64. package/crates/tish_compile/Cargo.toml +27 -0
  65. package/crates/tish_compile/src/check.rs +774 -0
  66. package/crates/tish_compile/src/codegen.rs +7317 -0
  67. package/crates/tish_compile/src/infer.rs +1681 -0
  68. package/crates/tish_compile/src/lib.rs +206 -0
  69. package/crates/tish_compile/src/resolve.rs +1951 -0
  70. package/crates/tish_compile/src/types.rs +605 -0
  71. package/crates/tish_compile_js/Cargo.toml +18 -0
  72. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  73. package/crates/tish_compile_js/src/codegen.rs +938 -0
  74. package/crates/tish_compile_js/src/error.rs +20 -0
  75. package/crates/tish_compile_js/src/lib.rs +26 -0
  76. package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
  77. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  78. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  79. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  80. package/crates/tish_core/Cargo.toml +32 -0
  81. package/crates/tish_core/src/console_style.rs +170 -0
  82. package/crates/tish_core/src/json.rs +430 -0
  83. package/crates/tish_core/src/lib.rs +20 -0
  84. package/crates/tish_core/src/macros.rs +36 -0
  85. package/crates/tish_core/src/shape.rs +85 -0
  86. package/crates/tish_core/src/uri.rs +118 -0
  87. package/crates/tish_core/src/value.rs +1350 -0
  88. package/crates/tish_core/src/vmref.rs +183 -0
  89. package/crates/tish_cranelift/Cargo.toml +19 -0
  90. package/crates/tish_cranelift/src/lib.rs +43 -0
  91. package/crates/tish_cranelift/src/link.rs +130 -0
  92. package/crates/tish_cranelift/src/lower.rs +85 -0
  93. package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
  94. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  95. package/crates/tish_eval/Cargo.toml +51 -0
  96. package/crates/tish_eval/src/eval.rs +4265 -0
  97. package/crates/tish_eval/src/http.rs +191 -0
  98. package/crates/tish_eval/src/lib.rs +99 -0
  99. package/crates/tish_eval/src/natives.rs +551 -0
  100. package/crates/tish_eval/src/promise.rs +179 -0
  101. package/crates/tish_eval/src/regex.rs +299 -0
  102. package/crates/tish_eval/src/timers.rs +120 -0
  103. package/crates/tish_eval/src/value.rs +336 -0
  104. package/crates/tish_eval/src/value_convert.rs +117 -0
  105. package/crates/tish_ffi/Cargo.toml +26 -0
  106. package/crates/tish_ffi/src/lib.rs +518 -0
  107. package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
  108. package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
  109. package/crates/tish_ffi/tests/loader.rs +65 -0
  110. package/crates/tish_fmt/Cargo.toml +16 -0
  111. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  112. package/crates/tish_fmt/src/lib.rs +2157 -0
  113. package/crates/tish_jsx_web/Cargo.toml +9 -0
  114. package/crates/tish_jsx_web/README.md +5 -0
  115. package/crates/tish_jsx_web/src/lib.rs +2 -0
  116. package/crates/tish_lexer/Cargo.toml +9 -0
  117. package/crates/tish_lexer/src/lib.rs +1104 -0
  118. package/crates/tish_lexer/src/token.rs +170 -0
  119. package/crates/tish_lint/Cargo.toml +18 -0
  120. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  121. package/crates/tish_lint/src/lib.rs +281 -0
  122. package/crates/tish_llvm/Cargo.toml +13 -0
  123. package/crates/tish_llvm/src/lib.rs +115 -0
  124. package/crates/tish_lsp/Cargo.toml +25 -0
  125. package/crates/tish_lsp/README.md +26 -0
  126. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  127. package/crates/tish_lsp/src/import_goto.rs +564 -0
  128. package/crates/tish_lsp/src/main.rs +1459 -0
  129. package/crates/tish_native/Cargo.toml +16 -0
  130. package/crates/tish_native/src/build.rs +481 -0
  131. package/crates/tish_native/src/config.rs +48 -0
  132. package/crates/tish_native/src/lib.rs +416 -0
  133. package/crates/tish_opt/Cargo.toml +13 -0
  134. package/crates/tish_opt/src/lib.rs +1046 -0
  135. package/crates/tish_parser/Cargo.toml +11 -0
  136. package/crates/tish_parser/src/lib.rs +386 -0
  137. package/crates/tish_parser/src/parser.rs +2726 -0
  138. package/crates/tish_pg/Cargo.toml +34 -0
  139. package/crates/tish_pg/README.md +38 -0
  140. package/crates/tish_pg/src/error.rs +52 -0
  141. package/crates/tish_pg/src/lib.rs +955 -0
  142. package/crates/tish_resolve/Cargo.toml +13 -0
  143. package/crates/tish_resolve/src/lib.rs +3601 -0
  144. package/crates/tish_resolve/src/pos.rs +141 -0
  145. package/crates/tish_runtime/Cargo.toml +100 -0
  146. package/crates/tish_runtime/src/http.rs +1347 -0
  147. package/crates/tish_runtime/src/http_fetch.rs +492 -0
  148. package/crates/tish_runtime/src/http_hyper.rs +441 -0
  149. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  150. package/crates/tish_runtime/src/lib.rs +1447 -0
  151. package/crates/tish_runtime/src/native_promise.rs +15 -0
  152. package/crates/tish_runtime/src/promise.rs +558 -0
  153. package/crates/tish_runtime/src/promise_io.rs +38 -0
  154. package/crates/tish_runtime/src/timers.rs +172 -0
  155. package/crates/tish_runtime/src/tty.rs +226 -0
  156. package/crates/tish_runtime/src/ws.rs +778 -0
  157. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  158. package/crates/tish_ui/Cargo.toml +17 -0
  159. package/crates/tish_ui/src/jsx.rs +692 -0
  160. package/crates/tish_ui/src/lib.rs +20 -0
  161. package/crates/tish_ui/src/runtime/hooks.rs +573 -0
  162. package/crates/tish_ui/src/runtime/mod.rs +183 -0
  163. package/crates/tish_vm/Cargo.toml +60 -0
  164. package/crates/tish_vm/src/jit.rs +1050 -0
  165. package/crates/tish_vm/src/lib.rs +41 -0
  166. package/crates/tish_vm/src/vm.rs +3536 -0
  167. package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
  168. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  169. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  170. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  171. package/crates/tish_wasm/Cargo.toml +15 -0
  172. package/crates/tish_wasm/src/lib.rs +428 -0
  173. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  174. package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
  175. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  176. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  177. package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
  178. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  179. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  180. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  181. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  182. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  183. package/justfile +276 -0
  184. package/package.json +2 -2
  185. package/platform/darwin-arm64/tish-fmt +0 -0
  186. package/platform/darwin-x64/tish-fmt +0 -0
  187. package/platform/linux-arm64/tish-fmt +0 -0
  188. package/platform/linux-x64/tish-fmt +0 -0
  189. package/platform/win32-x64/tish-fmt.exe +0 -0
@@ -0,0 +1,576 @@
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, &ch) in chars.iter().enumerate() {
76
+ if col >= visible || ch == ' ' {
77
+ let _ = write!(out, " ");
78
+ } else {
79
+ let (r, g, b) = palette_color(row, col, color_frame);
80
+ let _ = write!(out, "\x1b[1;38;2;{r};{g};{b}m{ch}\x1b[0m");
81
+ }
82
+ }
83
+ let _ = writeln!(out);
84
+ }
85
+ }
86
+
87
+ fn print_tish_banner_plain(out: &mut impl Write) {
88
+ for line in TISH_BANNER_LINES {
89
+ let _ = writeln!(out, "{line}");
90
+ }
91
+ let _ = writeln!(out);
92
+ }
93
+
94
+ fn print_tish_banner_animated(out: &mut impl Write) {
95
+ let n = TISH_BANNER_LINES.len();
96
+ let total = BANNER_REVEAL_FRAMES + BANNER_CYCLE_FRAMES;
97
+
98
+ for f in 0..total {
99
+ if f > 0 {
100
+ let _ = write!(out, "\x1b[{n}A");
101
+ }
102
+ // Phase 1: ease-out expand. Phase 2: fully visible, rainbow keeps scrolling.
103
+ let reveal_t = if f < BANNER_REVEAL_FRAMES {
104
+ ease_out_cubic((f + 1) as f32 / BANNER_REVEAL_FRAMES as f32)
105
+ } else {
106
+ 1.0
107
+ };
108
+ write_tish_banner_frame(out, reveal_t, f);
109
+ let _ = out.flush();
110
+ thread::sleep(Duration::from_millis(BANNER_FRAME_MS));
111
+ }
112
+ let _ = writeln!(out);
113
+ }
114
+
115
+ /// Print the `TISH` tile banner to stdout (animated palette on a TTY; plain text otherwise).
116
+ pub fn print_tish_banner() {
117
+ let mut out = io::stdout().lock();
118
+ if io::stdout().is_terminal() {
119
+ print_tish_banner_animated(&mut out);
120
+ } else {
121
+ print_tish_banner_plain(&mut out);
122
+ }
123
+ }
124
+
125
+ /// Build the `Command` with all colored after_help text attached.
126
+ /// Use this instead of `Cli::command()` everywhere so the help text is consistent.
127
+ pub fn build_command() -> clap::Command {
128
+ Cli::command()
129
+ .after_help(cli_after_help())
130
+ .mut_subcommand("run", |sub| sub.after_help(run_after_help()))
131
+ .mut_subcommand("repl", |sub| sub.after_help(repl_after_help()))
132
+ .mut_subcommand("build", |sub| sub.after_long_help(build_after_help()))
133
+ }
134
+
135
+ /// Write help text to `w` (plain bytes, used for line-counting only).
136
+ fn count_help_lines(cmd: &mut clap::Command, sub_name: Option<&str>) -> usize {
137
+ let mut buf = Vec::<u8>::new();
138
+ if let Some(name) = sub_name {
139
+ if cmd.find_subcommand(name).is_some() {
140
+ let _ = cmd
141
+ .find_subcommand_mut(name)
142
+ .unwrap()
143
+ .write_long_help(&mut buf);
144
+ } else {
145
+ let _ = cmd.write_long_help(&mut buf);
146
+ }
147
+ } else {
148
+ let _ = cmd.write_long_help(&mut buf);
149
+ }
150
+ buf.iter().filter(|&&b| b == b'\n').count()
151
+ }
152
+
153
+ /// Print help text directly to stdout via clap's own stdout path (guaranteed colors).
154
+ fn print_help_to_stdout(cmd: &mut clap::Command, sub_name: Option<&str>) {
155
+ if let Some(name) = sub_name {
156
+ if cmd.find_subcommand(name).is_some() {
157
+ let _ = cmd.find_subcommand_mut(name).unwrap().print_long_help();
158
+ return;
159
+ }
160
+ }
161
+ let _ = cmd.print_long_help();
162
+ }
163
+
164
+ /// Detect which subcommand (if any) is being asked about from raw argv.
165
+ fn sub_name_from_argv(argv: &[String]) -> Option<String> {
166
+ match argv.get(1).map(String::as_str) {
167
+ Some("help") => argv.get(2).map(String::to_string), // tish help run
168
+ Some(s) if !s.starts_with('-') => Some(s.to_string()), // tish run --help
169
+ _ => None,
170
+ }
171
+ }
172
+
173
+ const VERSION: &str = env!("CARGO_PKG_VERSION");
174
+ /// ANSI: bold purple (175, 82, 222)
175
+ const H_PURPLE: &str = "\x1b[1;38;2;175;82;222m";
176
+ /// ANSI: medium grey — clearly less-than-white on dark backgrounds
177
+ const H_GREY: &str = "\x1b[38;2;150;150;150m";
178
+ /// ANSI: pink (255, 55, 148) — used for the website URL
179
+ const H_PINK: &str = "\x1b[38;2;255;55;148m";
180
+ const H_RESET: &str = "\x1b[0m";
181
+
182
+ /// Branded header used for subcommand help pages.
183
+ /// Prints `[purple]Tish[reset] [grey](version x)[reset]`
184
+ /// `[pink]https://tishlang.com[reset]`
185
+ /// followed by a blank line.
186
+ fn print_small_header() {
187
+ if io::stdout().is_terminal() {
188
+ println!("{H_PURPLE}Tish{H_RESET} {H_GREY}(version {VERSION}){H_RESET}");
189
+ println!("{H_PINK}https://tishlang.com{H_RESET}\n");
190
+ } else {
191
+ println!("Tish (version {VERSION})");
192
+ println!("https://tishlang.com\n");
193
+ }
194
+ }
195
+
196
+ /// Number of lines the main-help manual prefix takes (printed before clap output).
197
+ /// Layout: title, description, url, blank = 4 lines.
198
+ const MAIN_PREFIX_LINES: usize = 4;
199
+
200
+ /// Print help, prefixed with the right header and (for top-level only) the
201
+ /// animated banner. Help is written via clap's own stdout path for full colors.
202
+ pub fn print_banner_with_help(argv: &[String]) {
203
+ let sub_name = sub_name_from_argv(argv);
204
+ let sub = sub_name.as_deref();
205
+
206
+ // ── Subcommand help: compact static header, no animation ─────────────
207
+ if sub.is_some() {
208
+ print_small_header();
209
+ let mut cmd = build_command();
210
+ cmd.build();
211
+ print_help_to_stdout(&mut cmd, sub);
212
+ return;
213
+ }
214
+
215
+ // ── Top-level help ────────────────────────────────────────────────────
216
+
217
+ if !io::stdout().is_terminal() {
218
+ let mut out = io::stdout().lock();
219
+ print_tish_banner_plain(&mut out);
220
+ drop(out);
221
+ let mut cmd = build_command().color(clap::ColorChoice::Never);
222
+ cmd.build();
223
+ print_help_to_stdout(&mut cmd, sub);
224
+ return;
225
+ }
226
+
227
+ // Line-count pass (ANSI codes never add \n, so Never == Always count).
228
+ let h: usize = {
229
+ let mut cmd = build_command().color(clap::ColorChoice::Never);
230
+ cmd.build();
231
+ count_help_lines(&mut cmd, sub)
232
+ };
233
+
234
+ let n = TISH_BANNER_LINES.len();
235
+
236
+ // 1. First banner frame + manual prefix + full help – all visible immediately.
237
+ {
238
+ let mut out = io::stdout().lock();
239
+ write_tish_banner_frame(&mut out, 1.0, 0);
240
+ let _ = writeln!(out); // blank separator (row n+1)
241
+ // ── Manual prefix (MAIN_PREFIX_LINES = 4 lines) ──────────────────
242
+ let _ = writeln!(
243
+ out,
244
+ "{H_PURPLE}Tish{H_RESET} {H_GREY}(version {VERSION}){H_RESET}"
245
+ );
246
+ let _ = writeln!(out, "Minimal TS/JS-ish language");
247
+ let _ = writeln!(out, "{H_PINK}https://tishlang.com{H_RESET}");
248
+ let _ = writeln!(out); // blank before Usage
249
+ let _ = out.flush();
250
+ }
251
+ {
252
+ let mut cmd = build_command();
253
+ cmd.build();
254
+ print_help_to_stdout(&mut cmd, sub);
255
+ let _ = io::stdout().flush();
256
+ }
257
+
258
+ // 2. Jump cursor back to banner top and cycle colors.
259
+ // Total rows above cursor: n + 1 (sep) + MAIN_PREFIX_LINES + h (clap)
260
+ {
261
+ let mut out = io::stdout().lock();
262
+ let _ = write!(out, "\x1b[{}A", n + 1 + MAIN_PREFIX_LINES + h);
263
+ let _ = out.flush();
264
+
265
+ let frames = BANNER_CYCLE_FRAMES;
266
+ for f in 0..frames {
267
+ write_tish_banner_frame(&mut out, 1.0, f);
268
+ if f < frames - 1 {
269
+ let _ = write!(out, "\x1b[{}A", n);
270
+ }
271
+ let _ = out.flush();
272
+ thread::sleep(Duration::from_millis(BANNER_FRAME_MS));
273
+ }
274
+
275
+ // After last frame cursor is at row n; skip sep + prefix + clap rows.
276
+ let _ = write!(out, "\x1b[{}B", 1 + MAIN_PREFIX_LINES + h);
277
+ let _ = writeln!(out);
278
+ let _ = out.flush();
279
+ }
280
+ }
281
+
282
+ /// Whether argv will cause clap to print help (top-level or subcommand).
283
+ pub fn argv_requests_help(argv: &[String]) -> bool {
284
+ argv.iter().any(|a| a == "--help" || a == "-h")
285
+ || matches!(argv.get(1).map(String::as_str), Some("help"))
286
+ }
287
+
288
+ /// Build a bold true-color `Style` from the brand palette.
289
+ fn rgb_bold(r: u8, g: u8, b: u8) -> Style {
290
+ Style::new().fg_color(Some(Color::Rgb(RgbColor(r, g, b)))) | Effects::BOLD
291
+ }
292
+
293
+ /// Help colors using the brand palette.
294
+ /// Orange → section headers / usage. Teal → literals (commands, flags). Yellow → placeholders.
295
+ pub fn cargo_help_styles() -> Styles {
296
+ Styles::styled()
297
+ .header(rgb_bold(255, 159, 64)) // Orange – "Commands:", "Options:", "Usage:"
298
+ .usage(rgb_bold(255, 159, 64)) // Orange
299
+ .literal(rgb_bold(48, 209, 188)) // Teal – run, repl, --help, -V …
300
+ .placeholder(rgb_bold(255, 213, 64)) // Yellow – <FILE>, <NAME>, …
301
+ .error(rgb_bold(255, 55, 148)) // Pink – error messages
302
+ .valid(rgb_bold(52, 199, 89)) // Green – valid values
303
+ .invalid(rgb_bold(255, 55, 148)) // Pink – invalid values
304
+ }
305
+
306
+ /// Returns the colored `after_help` text for the top-level `tish --help`.
307
+ /// Colors are emitted only when stdout is a TTY.
308
+ pub fn cli_after_help() -> String {
309
+ let (oh, t, r) = if io::stdout().is_terminal() {
310
+ (
311
+ "\x1b[1;38;2;255;159;64m",
312
+ "\x1b[1;38;2;48;209;188m",
313
+ "\x1b[0m",
314
+ )
315
+ } else {
316
+ ("", "", "")
317
+ };
318
+ format!(
319
+ "\
320
+ {oh}Environment variables:{r}
321
+ {t}TISH_NO_OPTIMIZE=1{r}
322
+ Disable AST and bytecode optimizations for run/build
323
+ {t}TISH_IGNORE_INDENT=1{r}
324
+ Ignore indentation syntax: parse blocks by braces only (debug nested-block transpilation)
325
+
326
+ See {t}tish run --help{r} and {t}tish build --help{r} for backend and feature options."
327
+ )
328
+ }
329
+
330
+ fn capabilities_section(oh: &str, t: &str, r: &str) -> String {
331
+ format!(
332
+ "\
333
+ {oh}Backends{r} (--backend):
334
+ {t}vm{r}
335
+ Bytecode VM (default)
336
+ {t}interp{r}
337
+ Tree-walking interpreter
338
+
339
+ {oh}Capabilities{r} (--feature, repeatable; comma-separated values are split):
340
+ {t}http{r}
341
+ Network: fetch, fetchAll, serve, Promise (and `await`); enabling http also enables timers
342
+ {t}timers{r}
343
+ setTimeout, setInterval, clearTimeout, clearInterval (global + `import from \"timers\"` / tish:timers)
344
+ {t}fs{r}
345
+ Filesystem: readFile, writeFile, fileExists, isDir, readDir, mkdir
346
+ {t}process{r}
347
+ process.exit, cwd, exec, argv, env
348
+ {t}regex{r}
349
+ RegExp
350
+ {t}ws{r}
351
+ WebSocket client / server
352
+ {t}tty{r}
353
+ Interactive terminal: raw mode, key/resize events, size, alt screen (`import from \"tish:tty\"`)
354
+ {t}full{r}
355
+ All of the above (http, timers, fs, process, regex, ws, tty)
356
+
357
+ Omit --feature to allow every capability compiled into this `tish` binary; pass flags to restrict what scripts may use. The CLI is normally built with all of them (Cargo default on `tishlang`)."
358
+ )
359
+ }
360
+
361
+ /// Returns the colored `after_help` for `tish run --help`.
362
+ pub fn run_after_help() -> String {
363
+ let (oh, t, r) = if io::stdout().is_terminal() {
364
+ (
365
+ "\x1b[1;38;2;255;159;64m",
366
+ "\x1b[1;38;2;48;209;188m",
367
+ "\x1b[0m",
368
+ )
369
+ } else {
370
+ ("", "", "")
371
+ };
372
+ capabilities_section(oh, t, r)
373
+ }
374
+
375
+ /// Returns the colored `after_help` for `tish repl --help`.
376
+ pub fn repl_after_help() -> String {
377
+ let (oh, t, r) = if io::stdout().is_terminal() {
378
+ (
379
+ "\x1b[1;38;2;255;159;64m",
380
+ "\x1b[1;38;2;48;209;188m",
381
+ "\x1b[0m",
382
+ )
383
+ } else {
384
+ ("", "", "")
385
+ };
386
+ capabilities_section(oh, t, r)
387
+ }
388
+
389
+ /// Returns the colored `after_long_help` for `tish build --help`.
390
+ pub fn build_after_help() -> String {
391
+ let (oh, t, r) = if io::stdout().is_terminal() {
392
+ (
393
+ "\x1b[1;38;2;255;159;64m",
394
+ "\x1b[1;38;2;48;209;188m",
395
+ "\x1b[0m",
396
+ )
397
+ } else {
398
+ ("", "", "")
399
+ };
400
+ format!(
401
+ "\
402
+ {oh}Build targets{r} (--target, default: native):
403
+ {t}native{r}
404
+ Native executable (see --native-backend)
405
+ {t}js{r}
406
+ JavaScript bundle
407
+ {t}wasm{r}
408
+ WebAssembly (.tish project; .js source supported on some paths)
409
+ {t}wasi{r}
410
+ WASI WebAssembly
411
+ {t}bytecode{r}
412
+ Raw serialized bytecode chunk (no VM binary/JS/HTML); for hosts that already ship the runtime
413
+
414
+ {oh}Native backends{r} (--native-backend, only with --target native, default: rust):
415
+ {t}rust{r}
416
+ Emit Rust + link tishlang_runtime via cargo
417
+ {t}cranelift{r}
418
+ Embedded bytecode + Cranelift/VM runtime binary
419
+ {t}llvm{r}
420
+ Embedded bytecode + LLVM/clang link path
421
+
422
+ {oh}Capabilities{r} (--feature, repeatable; comma-separated values are split):
423
+ {t}http{r}
424
+ Network: fetch, fetchAll, serve, Promise (and `await`); enabling http also enables timers
425
+ {t}timers{r}
426
+ setTimeout, setInterval, clearTimeout, clearInterval (global + `import from \"timers\"` / tish:timers)
427
+ {t}fs{r}
428
+ Filesystem: readFile, writeFile, fileExists, isDir, readDir, mkdir
429
+ {t}process{r}
430
+ process.exit, cwd, exec, argv, env
431
+ {t}regex{r}
432
+ RegExp
433
+ {t}ws{r}
434
+ WebSocket client / server
435
+ {t}tty{r}
436
+ Interactive terminal: raw mode, key/resize events, size, alt screen (`import from \"tish:tty\"`)
437
+ {t}full{r}
438
+ All of the above (http, timers, fs, process, regex, ws, tty)
439
+
440
+ For `--target native`, these choose what is linked into the **output** executable (omit = same set as this `tish` binary was built with). Minimal native outputs still use a full `tish` CLI unless you built it with `cargo build -p tishlang --no-default-features`."
441
+ )
442
+ }
443
+
444
+ #[derive(Parser)]
445
+ #[command(name = "tish")]
446
+ #[command(version = env!("CARGO_PKG_VERSION"))]
447
+ #[command(styles = cargo_help_styles())]
448
+ pub(crate) struct Cli {
449
+ #[command(subcommand)]
450
+ pub command: Option<Commands>,
451
+ }
452
+
453
+ #[derive(Parser)]
454
+ pub(crate) struct RunArgs {
455
+ /// Path to a `.tish` file, or `-` to read the program from stdin (like `node -`).
456
+ #[arg(
457
+ required = true,
458
+ allow_hyphen_values = true,
459
+ value_name = "FILE",
460
+ help_heading = "Arguments"
461
+ )]
462
+ pub file: String,
463
+ /// `vm` or `interp` (see `tish --help` for capabilities / `--feature`).
464
+ #[arg(
465
+ long,
466
+ default_value = "vm",
467
+ value_name = "NAME",
468
+ help_heading = "Options"
469
+ )]
470
+ pub backend: String,
471
+ /// Restrict which platform APIs the script may use (omit = all capabilities compiled into this `tish`).
472
+ #[arg(
473
+ long = "feature",
474
+ value_name = "NAME",
475
+ action = clap::ArgAction::Append,
476
+ help_heading = "Options"
477
+ )]
478
+ pub features: Vec<String>,
479
+ /// Disable AST and bytecode optimizations (for debugging).
480
+ #[arg(long, help_heading = "Options")]
481
+ pub no_optimize: bool,
482
+ }
483
+
484
+ #[derive(Parser)]
485
+ pub(crate) struct ReplArgs {
486
+ /// `vm` or `interp` (see `tish --help`).
487
+ #[arg(
488
+ long,
489
+ default_value = "vm",
490
+ value_name = "NAME",
491
+ help_heading = "Options"
492
+ )]
493
+ pub backend: String,
494
+ /// Restrict which platform APIs the REPL may use (omit = all capabilities compiled into this `tish`).
495
+ #[arg(
496
+ long = "feature",
497
+ value_name = "NAME",
498
+ action = clap::ArgAction::Append,
499
+ help_heading = "Options"
500
+ )]
501
+ pub features: Vec<String>,
502
+ #[arg(long, help_heading = "Options")]
503
+ pub no_optimize: bool,
504
+ }
505
+
506
+ #[derive(Parser)]
507
+ pub(crate) struct BuildArgs {
508
+ #[arg(
509
+ short,
510
+ long,
511
+ default_value = "tish_out",
512
+ value_name = "PATH",
513
+ help_heading = "Options"
514
+ )]
515
+ pub output: String,
516
+ /// `native`, `js`, `wasm`, `wasi`, or `bytecode` (see long help below).
517
+ #[arg(
518
+ long,
519
+ default_value = "native",
520
+ value_name = "TARGET",
521
+ help_heading = "Options"
522
+ )]
523
+ pub target: String,
524
+ /// `rust`, `cranelift`, or `llvm` (only for `--target native`).
525
+ #[arg(
526
+ long,
527
+ default_value = "rust",
528
+ value_name = "BACKEND",
529
+ help_heading = "Options"
530
+ )]
531
+ pub native_backend: String,
532
+ /// For `--target native`: which capabilities to link into the produced binary (omit = same as this `tish`; see long help).
533
+ #[arg(
534
+ long = "feature",
535
+ value_name = "NAME",
536
+ action = clap::ArgAction::Append,
537
+ help_heading = "Options"
538
+ )]
539
+ pub features: Vec<String>,
540
+ /// Cross-compile to an Apple iOS triple (e.g. `aarch64-apple-ios-sim`). Implies `--crate-type staticlib`.
541
+ #[arg(long, value_name = "TRIPLE", help_heading = "Options")]
542
+ pub ios_triple: Option<String>,
543
+ /// Output artifact for `--target native` (default: `bin`; use `staticlib` for embedded iOS).
544
+ #[arg(long, value_name = "TYPE", default_value = "bin", help_heading = "Options")]
545
+ pub crate_type: String,
546
+ #[arg(long, help_heading = "Options")]
547
+ pub no_optimize: bool,
548
+ /// For `--target js` project builds: emit `OUTPUT.js.map` and `//# sourceMappingURL=…` so JS/TS tools can jump to original `.tish` (implies `--no-optimize` for that build).
549
+ #[arg(long, help_heading = "Options")]
550
+ pub source_map: bool,
551
+ /// Run the gradual type checker: `warn` prints `line:col` type diagnostics; `error` also fails the build on them. (Equivalent to setting `TISH_CHECK`.)
552
+ #[arg(long, value_name = "MODE", help_heading = "Options")]
553
+ pub check: Option<String>,
554
+ /// Entry `.tish` file (or `.js` where supported).
555
+ #[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
556
+ pub file: String,
557
+ }
558
+
559
+ #[derive(Subcommand)]
560
+ pub(crate) enum Commands {
561
+ /// Run a Tish file (interpret)
562
+ Run(RunArgs),
563
+ /// Interactive REPL
564
+ Repl(ReplArgs),
565
+ /// Build native binary, wasm, wasi, or JavaScript output
566
+ Build(BuildArgs),
567
+ /// Parse and dump AST
568
+ #[command(name = "dump-ast")]
569
+ DumpAst {
570
+ #[arg(required = true, value_name = "FILE", help_heading = "Arguments")]
571
+ file: String,
572
+ /// Ignore indentation syntax: parse blocks by braces only (same as TISH_IGNORE_INDENT=1).
573
+ #[arg(long, help_heading = "Options")]
574
+ ignore_indent: bool,
575
+ },
576
+ }