From 463a3a6f56a5ca06cda890fd6fe7f16485ef70a0 Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 26 Apr 2026 19:44:34 +0200 Subject: feat: new tinywasm cli Signed-off-by: Henry --- crates/cli/src/load.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 crates/cli/src/load.rs (limited to 'crates/cli/src/load.rs') diff --git a/crates/cli/src/load.rs b/crates/cli/src/load.rs new file mode 100644 index 0000000..d16469b --- /dev/null +++ b/crates/cli/src/load.rs @@ -0,0 +1,103 @@ +use std::ffi::OsStr; +use std::io::{Read, Write}; +use std::path::Path; + +use eyre::{Context, Result, bail}; +use tinywasm::Module; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum InputFormat { + Wasm, + Wat, + Twasm, +} + +pub struct LoadedModule { + pub module: Module, + pub format: InputFormat, +} + +pub fn load_module(input: &str) -> Result { + let bytes = read_input_bytes(input)?; + load_module_from_bytes(input, &bytes) +} + +pub fn load_compilable_module(input: &str) -> Result { + let loaded = load_module(input)?; + if loaded.format == InputFormat::Twasm { + bail!("input is already a twasm archive; use `run`, `dump`, or `inspect` instead") + } + Ok(loaded.module) +} + +pub fn default_twasm_output_path(input: &str) -> Result { + if input == "-" { + bail!("--output is required when compiling from stdin") + } + + let path = Path::new(input); + let stem = path.file_stem().and_then(OsStr::to_str).unwrap_or("module"); + let output = path.with_file_name(format!("{stem}.twasm")); + Ok(output.to_string_lossy().into_owned()) +} + +pub fn write_output_bytes(output: &str, bytes: &[u8], force: bool) -> Result<()> { + if output == "-" { + std::io::stdout().write_all(bytes)?; + std::io::stdout().flush()?; + return Ok(()); + } + + let path = Path::new(output); + if path.exists() && !force { + bail!("output file already exists: {output}; pass --force to overwrite") + } + + std::fs::write(path, bytes).with_context(|| format!("failed to write output file `{output}`"))?; + Ok(()) +} + +fn load_module_from_bytes(input: &str, bytes: &[u8]) -> Result { + if bytes.starts_with(b"TWAS") { + let module = Module::try_from_twasm(bytes).with_context(|| format!("failed to read twasm input `{input}`"))?; + return Ok(LoadedModule { module, format: InputFormat::Twasm }); + } + + #[cfg(feature = "wat")] + if input != "-" && has_extension(input, "wat") { + let wasm = wat::parse_bytes(bytes).with_context(|| format!("failed to parse WAT input `{input}`"))?; + let module = + tinywasm::parse_bytes(&wasm).with_context(|| format!("failed to parse Wasm generated from `{input}`"))?; + return Ok(LoadedModule { module, format: InputFormat::Wat }); + } + + #[cfg(not(feature = "wat"))] + if input != "-" && has_extension(input, "wat") { + bail!("wat support is not enabled in this build") + } + + #[cfg(feature = "wat")] + if input == "-" + && let Ok(wasm) = wat::parse_bytes(bytes) + { + let module = tinywasm::parse_bytes(&wasm).context("failed to parse Wasm generated from stdin WAT input")?; + return Ok(LoadedModule { module, format: InputFormat::Wat }); + } + + let module = tinywasm::parse_bytes(bytes).with_context(|| format!("failed to parse Wasm input `{input}`"))?; + Ok(LoadedModule { module, format: InputFormat::Wasm }) +} + +fn read_input_bytes(input: &str) -> Result> { + if input == "-" { + let mut bytes = Vec::new(); + std::io::stdin().read_to_end(&mut bytes).context("failed to read stdin")?; + return Ok(bytes); + } + + std::fs::read(input).with_context(|| format!("failed to read input `{input}`")) +} + +fn has_extension(path: &str, extension: &str) -> bool { + Path::new(path).extension().and_then(OsStr::to_str) == Some(extension) +} -- cgit v1.3.1