@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,428 @@
1
+ //! WebAssembly backend for Tish.
2
+ //!
3
+ //! Compiles Tish to bytecode, then produces a .wasm VM binary + loader.
4
+ //! The VM runs in the browser; your program runs as serialized bytecode.
5
+
6
+ use std::collections::BTreeSet;
7
+ use std::path::Path;
8
+ use std::process::Command;
9
+
10
+ use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
11
+
12
+ use tishlang_ast::Program;
13
+ use tishlang_bytecode::{serialize, Chunk};
14
+ use tishlang_compile::{
15
+ detect_cycles, extract_native_import_features, has_external_native_imports, merge_modules,
16
+ resolve_project,
17
+ };
18
+
19
+ /// Error from WASM compilation.
20
+ #[derive(Debug)]
21
+ pub struct WasmError {
22
+ pub message: String,
23
+ }
24
+
25
+ impl std::fmt::Display for WasmError {
26
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27
+ write!(f, "{}", self.message)
28
+ }
29
+ }
30
+
31
+ impl std::error::Error for WasmError {}
32
+
33
+ /// Map CLI / import capability names to `tishlang_wasm_runtime` Cargo features for wasm32-wasip1.
34
+ /// The full `http` stack (tokio/socket2/…) does not build on WASI here; `http` maps to `promise`
35
+ /// so `Promise` / `await` work. `ws` is skipped for the same reason.
36
+ fn insert_wasi_runtime_cap(out: &mut BTreeSet<String>, cap: &str) {
37
+ match cap {
38
+ "http" => {
39
+ out.insert("promise".to_string());
40
+ }
41
+ "ws" => {}
42
+ "fs" | "process" | "promise" | "timers" | "regex" => {
43
+ out.insert(cap.to_string());
44
+ }
45
+ _ => {}
46
+ }
47
+ }
48
+
49
+ /// Resolve project, merge modules, and compile to bytecode chunk.
50
+ /// Returns (Chunk, Program) so WASI can extract features for the runtime.
51
+ fn resolve_and_compile_to_chunk(
52
+ entry_path: &Path,
53
+ project_root: Option<&Path>,
54
+ optimize: bool,
55
+ ) -> Result<(Chunk, Program), WasmError> {
56
+ let modules = resolve_project(entry_path, project_root).map_err(|e| WasmError {
57
+ message: e.to_string(),
58
+ })?;
59
+ detect_cycles(&modules).map_err(|e| WasmError {
60
+ message: e.to_string(),
61
+ })?;
62
+ let program = {
63
+ let prog = merge_modules(modules)
64
+ .map(|m| m.program)
65
+ .map_err(|e| WasmError {
66
+ message: e.to_string(),
67
+ })?;
68
+ if optimize {
69
+ tishlang_opt::optimize(&prog)
70
+ } else {
71
+ prog
72
+ }
73
+ };
74
+ let chunk = if optimize {
75
+ tishlang_bytecode::compile(&program).map_err(|e| WasmError {
76
+ message: e.to_string(),
77
+ })?
78
+ } else {
79
+ tishlang_bytecode::compile_unoptimized(&program).map_err(|e| WasmError {
80
+ message: e.to_string(),
81
+ })?
82
+ };
83
+ Ok((chunk, program))
84
+ }
85
+
86
+ /// Compile a single Program (e.g. from tishlang_js_to_tish) for WebAssembly.
87
+ pub fn compile_program_to_wasm(
88
+ program: &Program,
89
+ output_path: &Path,
90
+ optimize: bool,
91
+ ) -> Result<(), WasmError> {
92
+ let program = if optimize {
93
+ tishlang_opt::optimize(program)
94
+ } else {
95
+ program.clone()
96
+ };
97
+ let chunk = if optimize {
98
+ tishlang_bytecode::compile(&program).map_err(|e| WasmError {
99
+ message: e.to_string(),
100
+ })?
101
+ } else {
102
+ tishlang_bytecode::compile_unoptimized(&program).map_err(|e| WasmError {
103
+ message: e.to_string(),
104
+ })?
105
+ };
106
+ emit_wasm_from_chunk(&chunk, output_path)
107
+ }
108
+
109
+ fn emit_wasm_from_chunk(chunk: &Chunk, output_path: &Path) -> Result<(), WasmError> {
110
+ let chunk_bytes = serialize(chunk);
111
+ let chunk_b64 = BASE64.encode(&chunk_bytes);
112
+ let stem = output_path
113
+ .file_stem()
114
+ .and_then(|s| s.to_str())
115
+ .unwrap_or("main");
116
+ let out_dir = output_path
117
+ .parent()
118
+ .filter(|p| !p.as_os_str().is_empty())
119
+ .unwrap_or(Path::new("."));
120
+ let out_dir_abs = out_dir
121
+ .canonicalize()
122
+ .or_else(|_| std::env::current_dir().map(|cwd| cwd.join(out_dir)))
123
+ .map_err(|e| WasmError {
124
+ message: format!("Cannot resolve output dir: {}", e),
125
+ })?;
126
+ std::fs::create_dir_all(&out_dir_abs).map_err(|e| WasmError {
127
+ message: format!("Cannot create output directory: {}", e),
128
+ })?;
129
+ let workspace_root =
130
+ tishlang_build_utils::find_workspace_root().map_err(|e| WasmError { message: e })?;
131
+ let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
132
+ let build_status = Command::new(&cargo)
133
+ .current_dir(&workspace_root)
134
+ .args([
135
+ "build",
136
+ "-p",
137
+ "tishlang_wasm_runtime",
138
+ "--target",
139
+ "wasm32-unknown-unknown",
140
+ "--release",
141
+ "--features",
142
+ "browser",
143
+ ])
144
+ .status()
145
+ .map_err(|e| WasmError {
146
+ message: format!("Failed to run cargo: {}", e),
147
+ })?;
148
+ if !build_status.success() {
149
+ return Err(WasmError {
150
+ message: "Failed to build wasm runtime. Run: rustup target add wasm32-unknown-unknown"
151
+ .to_string(),
152
+ });
153
+ }
154
+ let wasm_artifact =
155
+ workspace_root.join("target/wasm32-unknown-unknown/release/tishlang_wasm_runtime.wasm");
156
+ if !wasm_artifact.exists() {
157
+ return Err(WasmError {
158
+ message: format!("Wasm artifact not found: {}", wasm_artifact.display()),
159
+ });
160
+ }
161
+ let wasm_bindgen = std::env::var("WASM_BINDGEN").unwrap_or_else(|_| "wasm-bindgen".to_string());
162
+ let out_name = stem.to_string();
163
+ let bindgen_status = Command::new(&wasm_bindgen)
164
+ .args([
165
+ "--target",
166
+ "web",
167
+ "--out-dir",
168
+ out_dir_abs.to_str().unwrap(),
169
+ "--out-name",
170
+ &out_name,
171
+ wasm_artifact.to_str().unwrap(),
172
+ ])
173
+ .status()
174
+ .map_err(|e| WasmError {
175
+ message: format!(
176
+ "Failed to run wasm-bindgen: {}. Install with: cargo install wasm-bindgen-cli",
177
+ e
178
+ ),
179
+ })?;
180
+ if !bindgen_status.success() {
181
+ return Err(WasmError {
182
+ message: "wasm-bindgen failed".to_string(),
183
+ });
184
+ }
185
+ let js_name = format!("{}.js", stem);
186
+ let html = format!(
187
+ r#"<!DOCTYPE html>
188
+ <html>
189
+ <head><meta charset="utf-8"><title>{}</title></head>
190
+ <body>
191
+ <script type="module">
192
+ const CHUNK_B64 = "{}";
193
+ const chunk = Uint8Array.from(atob(CHUNK_B64), c => c.charCodeAt(0));
194
+ import init, {{ run }} from './{}';
195
+ await init();
196
+ run(chunk);
197
+ </script>
198
+ </body>
199
+ </html>
200
+ "#,
201
+ stem, chunk_b64, js_name
202
+ );
203
+ let html_path = out_dir_abs.join(format!("{}.html", stem));
204
+ std::fs::write(&html_path, html).map_err(|e| WasmError {
205
+ message: format!("Cannot write {}: {}", html_path.display(), e),
206
+ })?;
207
+ println!(
208
+ "Built: {}_bg.wasm, {}.js, {}",
209
+ stem,
210
+ stem,
211
+ html_path.display()
212
+ );
213
+ Ok(())
214
+ }
215
+
216
+ /// Compile a Tish project for WebAssembly.
217
+ ///
218
+ /// Produces:
219
+ /// - `{output}.wasm` — VM binary (runs your program)
220
+ /// - `{output}.js` — wasm-bindgen glue
221
+ /// - `{output}.html` — loader (open in browser)
222
+ ///
223
+ /// Requires: `rustup target add wasm32-unknown-unknown`, `wasm-bindgen-cli`
224
+ pub fn compile_to_wasm(
225
+ entry_path: &Path,
226
+ project_root: Option<&Path>,
227
+ output_path: &Path,
228
+ optimize: bool,
229
+ ) -> Result<(), WasmError> {
230
+ let (chunk, _) = resolve_and_compile_to_chunk(entry_path, project_root, optimize)?;
231
+ emit_wasm_from_chunk(&chunk, output_path)
232
+ }
233
+
234
+ /// Compile a Tish project to a raw serialized bytecode chunk.
235
+ ///
236
+ /// Writes a single `{output}` file of the exact bytes that the wasm/WASI runtime entry points
237
+ /// (`start` / `run`) deserialize directly — the same chunk `--target wasm` embeds as base64 in
238
+ /// its generated HTML loader, but written raw with no VM binary, JS glue, or HTML wrapper. Lets a
239
+ /// host that already ships the VM runtime (e.g. a bundler) consume the bytecode without the
240
+ /// throwaway standalone build.
241
+ pub fn compile_to_bytecode(
242
+ entry_path: &Path,
243
+ project_root: Option<&Path>,
244
+ output_path: &Path,
245
+ optimize: bool,
246
+ ) -> Result<(), WasmError> {
247
+ let (chunk, _) = resolve_and_compile_to_chunk(entry_path, project_root, optimize)?;
248
+ let bytes = serialize(&chunk);
249
+ if let Some(parent) = output_path.parent().filter(|p| !p.as_os_str().is_empty()) {
250
+ std::fs::create_dir_all(parent).map_err(|e| WasmError {
251
+ message: format!("Cannot create output directory: {}", e),
252
+ })?;
253
+ }
254
+ std::fs::write(output_path, &bytes).map_err(|e| WasmError {
255
+ message: format!("Cannot write {}: {}", output_path.display(), e),
256
+ })?;
257
+ println!("Built: {} ({} bytes)", output_path.display(), bytes.len());
258
+ Ok(())
259
+ }
260
+
261
+ /// Compile a Tish project for Wasmtime/WASI.
262
+ ///
263
+ /// Produces a single `{output}.wasm` with embedded bytecode. Run with:
264
+ /// `wasmtime {output}.wasm`
265
+ ///
266
+ /// Requires: `rustup target add wasm32-wasip1`
267
+ ///
268
+ /// `capabilities` is the same capability list as `tish build --target native` (e.g. from
269
+ /// `native_build_features_from_cli`): merged with `import`-inferred features so globals like
270
+ /// `Promise` / `fetch` work without a top-level `import … from 'http'`.
271
+ pub fn compile_to_wasi(
272
+ entry_path: &Path,
273
+ project_root: Option<&Path>,
274
+ output_path: &Path,
275
+ optimize: bool,
276
+ capabilities: &[String],
277
+ ) -> Result<(), WasmError> {
278
+ let (chunk, program) = resolve_and_compile_to_chunk(entry_path, project_root, optimize)?;
279
+ if has_external_native_imports(&program) {
280
+ return Err(WasmError {
281
+ message: "WASI backend does not support external native imports (tish:egui, @scope/pkg). Built-in tish:fs, tish:http, tish:process, tish:timers are supported.".to_string(),
282
+ });
283
+ }
284
+ let mut wasi_feature_set: BTreeSet<String> = BTreeSet::new();
285
+ for f in extract_native_import_features(&program) {
286
+ insert_wasi_runtime_cap(&mut wasi_feature_set, f.as_str());
287
+ }
288
+ for f in capabilities {
289
+ insert_wasi_runtime_cap(&mut wasi_feature_set, f.as_str());
290
+ }
291
+ // Many scripts use global setTimeout without `import` from timers.
292
+ wasi_feature_set.insert("timers".to_string());
293
+
294
+ let chunk_bytes = serialize(&chunk);
295
+
296
+ let stem = output_path
297
+ .file_stem()
298
+ .and_then(|s| s.to_str())
299
+ .unwrap_or("main");
300
+ let out_dir = output_path
301
+ .parent()
302
+ .filter(|p| !p.as_os_str().is_empty())
303
+ .unwrap_or(Path::new("."));
304
+ let out_dir_abs = out_dir
305
+ .canonicalize()
306
+ .or_else(|_| std::env::current_dir().map(|cwd| cwd.join(out_dir)))
307
+ .map_err(|e| WasmError {
308
+ message: format!("Cannot resolve output dir: {}", e),
309
+ })?;
310
+
311
+ std::fs::create_dir_all(&out_dir_abs).map_err(|e| WasmError {
312
+ message: format!("Cannot create output directory: {}", e),
313
+ })?;
314
+
315
+ let workspace_root =
316
+ tishlang_build_utils::find_workspace_root().map_err(|e| WasmError { message: e })?;
317
+
318
+ // Create generated project: wasi_build/{stem}/
319
+ let build_dir = out_dir_abs.join("wasi_build").join(stem);
320
+ std::fs::create_dir_all(build_dir.join("src")).map_err(|e| WasmError {
321
+ message: format!("Cannot create build dir: {}", e),
322
+ })?;
323
+
324
+ // Write chunk.bin
325
+ std::fs::write(build_dir.join("chunk.bin"), &chunk_bytes).map_err(|e| WasmError {
326
+ message: format!("Cannot write chunk: {}", e),
327
+ })?;
328
+
329
+ // Cargo.toml - path to tishlang_wasm_runtime (crate in crates/tish_wasm_runtime)
330
+ let runtime_path = workspace_root.join("crates").join("tish_wasm_runtime");
331
+ let runtime_path_str = runtime_path
332
+ .canonicalize()
333
+ .unwrap_or(runtime_path)
334
+ .to_string_lossy()
335
+ .replace('\\', "/");
336
+
337
+ let features_str = format!(
338
+ ", features = [{}]",
339
+ wasi_feature_set
340
+ .iter()
341
+ .map(|f| format!("{:?}", f))
342
+ .collect::<Vec<_>>()
343
+ .join(", ")
344
+ );
345
+ let cargo_toml = format!(
346
+ r#"[package]
347
+ name = "tish_wasi_{stem}"
348
+ version = "0.1.0"
349
+ edition = "2021"
350
+
351
+ [workspace]
352
+
353
+ [[bin]]
354
+ name = "tish_wasi_{stem}"
355
+ path = "src/main.rs"
356
+
357
+ [dependencies]
358
+ tishlang_wasm_runtime = {{ path = "{runtime_path_str}"{features_str} }}
359
+ "#,
360
+ stem = stem,
361
+ runtime_path_str = runtime_path_str,
362
+ features_str = features_str
363
+ );
364
+ std::fs::write(build_dir.join("Cargo.toml"), cargo_toml).map_err(|e| WasmError {
365
+ message: format!("Cannot write Cargo.toml: {}", e),
366
+ })?;
367
+
368
+ // main.rs
369
+ let main_rs = r#"
370
+ fn main() {
371
+ let chunk = include_bytes!("../chunk.bin");
372
+ if let Err(e) = tishlang_wasm_runtime::run_wasi(chunk) {
373
+ eprintln!("Runtime error: {}", e);
374
+ std::process::exit(1);
375
+ }
376
+ }
377
+ "#;
378
+ std::fs::write(build_dir.join("src").join("main.rs"), main_rs).map_err(|e| WasmError {
379
+ message: format!("Cannot write main.rs: {}", e),
380
+ })?;
381
+
382
+ // Build into a SHARED target dir (one per host), not per-program. The wasi runtime + embedded
383
+ // VM then compile ONCE and are reused by every wasi build; only each program's tiny main is
384
+ // rebuilt. Without this each program left its own multi-GB `target/` and a full-suite sweep
385
+ // would fill the disk (same issue fixed for cranelift; see full-backend-parity-plan.md A3).
386
+ // cargo's target lock serializes concurrent builds safely.
387
+ let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
388
+ let bin_name = format!("tish_wasi_{}", stem);
389
+ let target_dir = std::env::temp_dir().join("tishlang_wasi_target");
390
+ let build_status = Command::new(&cargo)
391
+ .current_dir(&build_dir)
392
+ .env("CARGO_TARGET_DIR", &target_dir)
393
+ .args(["build", "--target", "wasm32-wasip1", "--release"])
394
+ .status()
395
+ .map_err(|e| WasmError {
396
+ message: format!("Failed to run cargo: {}", e),
397
+ })?;
398
+
399
+ if !build_status.success() {
400
+ return Err(WasmError {
401
+ message: "Failed to build WASI binary. Run: rustup target add wasm32-wasip1"
402
+ .to_string(),
403
+ });
404
+ }
405
+
406
+ let wasm_artifact = target_dir
407
+ .join("wasm32-wasip1")
408
+ .join("release")
409
+ .join(format!("{}.wasm", bin_name));
410
+
411
+ if !wasm_artifact.exists() {
412
+ return Err(WasmError {
413
+ message: format!("WASI artifact not found: {}", wasm_artifact.display()),
414
+ });
415
+ }
416
+
417
+ let final_wasm = out_dir_abs.join(format!("{}.wasm", stem));
418
+ std::fs::copy(&wasm_artifact, &final_wasm).map_err(|e| WasmError {
419
+ message: format!("Cannot copy wasm: {}", e),
420
+ })?;
421
+
422
+ println!(
423
+ "Built: {} (run with: wasmtime {})",
424
+ final_wasm.display(),
425
+ final_wasm.display()
426
+ );
427
+ Ok(())
428
+ }
@@ -0,0 +1,37 @@
1
+ [package]
2
+ name = "tishlang_wasm_runtime"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Tish VM compiled to WebAssembly (browser + Wasmtime/WASI)"
6
+ license-file = { workspace = true }
7
+ repository = { workspace = true }
8
+
9
+ [lib]
10
+ crate-type = ["cdylib", "rlib"]
11
+
12
+ [features]
13
+ # For wasm32-unknown-unknown (browser): wasm-bindgen, console output
14
+ browser = ["dep:wasm-bindgen", "tishlang_vm/wasm"]
15
+ # Browser WebGPU / JS-interop FFI + requestAnimationFrame render loop (the
16
+ # `start(chunk, env)` entry). Reflection-based bridge over js-sys; no web-sys
17
+ # WebGPU bindings needed since the WebGPU command API is synchronous.
18
+ gpu = ["browser", "dep:js-sys"]
19
+ # Built-in modules for WASI (wasm32-wasip1): align with `tishlang_cranelift_runtime` / CLI caps
20
+ fs = ["tishlang_vm/fs"]
21
+ process = ["tishlang_vm/process"]
22
+ http = ["tishlang_vm/http"]
23
+ promise = ["tishlang_vm/promise"]
24
+ timers = ["tishlang_vm/timers"]
25
+ regex = ["tishlang_vm/regex"]
26
+ ws = ["tishlang_vm/ws"]
27
+
28
+ [dependencies]
29
+ tishlang_bytecode = { path = "../tish_bytecode", version = ">=0.1" }
30
+ tishlang_vm = { path = "../tish_vm", version = ">=0.1" }
31
+ tishlang_core = { path = "../tish_core", version = ">=0.1" }
32
+ wasm-bindgen = { version = "0.2", optional = true }
33
+ js-sys = { version = "0.3", optional = true }
34
+
35
+ # rand_core → getrandom 0.4 needs wasm_js on wasm32-unknown-unknown (browser VM build).
36
+ [target.'cfg(target_arch = "wasm32")'.dependencies]
37
+ getrandom = { version = "0.4", features = ["wasm_js"] }