@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.
- package/Cargo.toml +51 -0
- package/LICENSE +13 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +611 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +938 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2157 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +281 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- 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"] }
|