summaryrefslogtreecommitdiff
path: root/crates/cli/src/cmd
diff options
context:
space:
mode:
authorHenry <mail@henrygressmann.de>2026-04-26 19:44:34 +0200
committerHenry <mail@henrygressmann.de>2026-04-26 19:44:34 +0200
commit463a3a6f56a5ca06cda890fd6fe7f16485ef70a0 (patch)
treebfa657a2de16c04cd36f4b6c61e77b370fcd46fc /crates/cli/src/cmd
parent27004a8738c7a3c32ac4fb6966f32647b4c96db7 (diff)
feat: new tinywasm cli
Signed-off-by: Henry <mail@henrygressmann.de>
Diffstat (limited to 'crates/cli/src/cmd')
-rw-r--r--crates/cli/src/cmd/compile.rs15
-rw-r--r--crates/cli/src/cmd/completion.rs12
-rw-r--r--crates/cli/src/cmd/dump.rs71
-rw-r--r--crates/cli/src/cmd/inspect.rs35
-rw-r--r--crates/cli/src/cmd/mod.rs7
-rw-r--r--crates/cli/src/cmd/run.rs47
-rw-r--r--crates/cli/src/cmd/wast.rs12
7 files changed, 199 insertions, 0 deletions
diff --git a/crates/cli/src/cmd/compile.rs b/crates/cli/src/cmd/compile.rs
new file mode 100644
index 0000000..93e38d5
--- /dev/null
+++ b/crates/cli/src/cmd/compile.rs
@@ -0,0 +1,15 @@
+use eyre::Result;
+
+use crate::cli::CompileArgs;
+use crate::load::{default_twasm_output_path, load_compilable_module, write_output_bytes};
+
+pub fn run(args: CompileArgs) -> Result<()> {
+ let module = load_compilable_module(&args.input)?;
+ let twasm = module.serialize_twasm()?;
+ let output = match args.output {
+ Some(output) => output,
+ None => default_twasm_output_path(&args.input)?,
+ };
+
+ write_output_bytes(&output, &twasm, args.force)
+}
diff --git a/crates/cli/src/cmd/completion.rs b/crates/cli/src/cmd/completion.rs
new file mode 100644
index 0000000..5815f57
--- /dev/null
+++ b/crates/cli/src/cmd/completion.rs
@@ -0,0 +1,12 @@
+use std::io;
+
+use clap::CommandFactory;
+use eyre::Result;
+
+use crate::cli::{Cli, CompletionArgs};
+
+pub fn run(args: CompletionArgs) -> Result<()> {
+ let mut cmd = Cli::command();
+ clap_complete::generate(args.shell, &mut cmd, "tinywasm", &mut io::stdout());
+ Ok(())
+}
diff --git a/crates/cli/src/cmd/dump.rs b/crates/cli/src/cmd/dump.rs
new file mode 100644
index 0000000..78fb28b
--- /dev/null
+++ b/crates/cli/src/cmd/dump.rs
@@ -0,0 +1,71 @@
+use anstream::println;
+use eyre::Result;
+use owo_colors::OwoColorize;
+use tinywasm::types::{ExternalKind, ImportKind};
+
+use crate::cli::ModuleInputArgs;
+use crate::load::load_module;
+
+pub fn run(args: ModuleInputArgs) -> Result<()> {
+ let loaded = load_module(&args.module)?;
+ let module = loaded.module;
+
+ let imported_func_count =
+ module.imports.iter().filter(|import| matches!(import.kind, ImportKind::Function(_))).count() as u32;
+
+ for (func_idx, func) in module.funcs.iter().enumerate() {
+ let global_idx = imported_func_count + func_idx as u32;
+
+ let exports = module
+ .exports
+ .iter()
+ .filter(|export| export.kind == ExternalKind::Func && export.index == global_idx)
+ .map(|export| export.name.as_ref())
+ .collect::<Vec<_>>();
+
+ let header = format!("func[{func_idx}]").blue().bold().to_string();
+ if exports.is_empty() {
+ println!("{header}");
+ } else {
+ println!("{header} {}", format!("exports={}", format!("{exports:?}").cyan()).bright_black());
+ }
+
+ for (ip, instr) in func.instructions.iter().enumerate() {
+ let instr = print_instr(instr);
+ println!(" {}: {}", print_ip(ip), instr);
+ }
+ println!();
+ }
+
+ Ok(())
+}
+
+fn print_ip(ip: usize) -> String {
+ let s = format!("{ip:04}");
+ let first_non_zero = s.find(|c| c != '0').unwrap_or(s.len() - 1);
+
+ format!(
+ "{}{}",
+ &s[..first_non_zero].to_string().bright_black().dimmed(),
+ &s[first_non_zero..].to_string().bright_black()
+ )
+}
+
+fn print_instr(instr: &tinywasm::types::Instruction) -> String {
+ let instr = format!("{instr:?}");
+ let Some(split) = instr.find(['(', ' ', '{']) else {
+ return instr.bold().to_string();
+ };
+
+ let (name, rest) = instr.split_at(split);
+
+ let rest = rest
+ .replace('(', &"(".bright_black().to_string())
+ .replace(')', &")".bright_black().to_string())
+ .replace('{', &"{".bright_black().to_string())
+ .replace('}', &"}".bright_black().to_string())
+ .replace(',', &",".bright_black().to_string())
+ .replace(':', &":".bright_black().to_string());
+
+ format!("{}{}", name.bold(), rest)
+}
diff --git a/crates/cli/src/cmd/inspect.rs b/crates/cli/src/cmd/inspect.rs
new file mode 100644
index 0000000..ea2cec7
--- /dev/null
+++ b/crates/cli/src/cmd/inspect.rs
@@ -0,0 +1,35 @@
+use eyre::Result;
+
+use crate::cli::ModuleInputArgs;
+use crate::load::load_module;
+use crate::output::{format_export_type, format_import_type};
+use anstream::println;
+use owo_colors::OwoColorize;
+
+pub fn run(args: ModuleInputArgs) -> Result<()> {
+ let loaded = load_module(&args.module)?;
+ let module = loaded.module;
+
+ println!("{}", "Imports".bold());
+ let mut import_count = 0usize;
+ for import in module.imports() {
+ import_count += 1;
+ println!(" {}.{}: {}", import.module.blue(), import.name.cyan(), format_import_type(import.ty).yellow());
+ }
+ if import_count == 0 {
+ println!(" {}", "(none)".yellow());
+ }
+
+ println!();
+ println!("{}", "Exports".bold());
+ let mut export_count = 0usize;
+ for export in module.exports() {
+ export_count += 1;
+ println!(" {}: {}", export.name.green(), format_export_type(export.ty).yellow());
+ }
+ if export_count == 0 {
+ println!(" {}", "(none)".yellow());
+ }
+
+ Ok(())
+}
diff --git a/crates/cli/src/cmd/mod.rs b/crates/cli/src/cmd/mod.rs
new file mode 100644
index 0000000..0861af2
--- /dev/null
+++ b/crates/cli/src/cmd/mod.rs
@@ -0,0 +1,7 @@
+pub mod compile;
+pub mod completion;
+pub mod dump;
+pub mod inspect;
+pub mod run;
+#[cfg(feature = "wast")]
+pub mod wast;
diff --git a/crates/cli/src/cmd/run.rs b/crates/cli/src/cmd/run.rs
new file mode 100644
index 0000000..b2f5e03
--- /dev/null
+++ b/crates/cli/src/cmd/run.rs
@@ -0,0 +1,47 @@
+use eyre::{Result, bail};
+use tinywasm::types::ExportType;
+use tinywasm::{ModuleInstance, Store};
+
+use crate::cli::RunArgs;
+use crate::load::load_module;
+use crate::output::print_results;
+use crate::value_parse::parse_invocation_args;
+
+pub fn run(args: RunArgs) -> Result<()> {
+ let module_path = args.module.as_deref().ok_or_else(|| eyre::eyre!("missing module path"))?;
+ let loaded = load_module(module_path)?;
+ let mut store = Store::new(args.engine.build_engine()?);
+ let instance = ModuleInstance::instantiate_no_start(&mut store, &loaded.module, None)?;
+
+ match args.invoke.as_deref() {
+ Some(export) => {
+ if loaded.module.start_func.is_some() {
+ let _ = instance.start(&mut store)?;
+ }
+
+ let func_ty = loaded
+ .module
+ .exports()
+ .find_map(|item| match (item.name == export, item.ty) {
+ (true, ExportType::Func(ty)) => Some(ty),
+ _ => None,
+ })
+ .ok_or_else(|| eyre::eyre!("export is not a function: {export}"))?;
+ let func = instance.func_untyped(&store, export)?;
+ let params = parse_invocation_args(func_ty, &args.args)?;
+ let results = func.call(&mut store, &params)?;
+ print_results(&results);
+ Ok(())
+ }
+ None => {
+ if instance.start_func(&store)?.is_none() {
+ bail!(
+ "module has no start function or `_start` export; use `tinywasm inspect {module_path}` or `tinywasm run --invoke <export> {module_path}`"
+ )
+ }
+
+ let _ = instance.start(&mut store)?;
+ Ok(())
+ }
+ }
+}
diff --git a/crates/cli/src/cmd/wast.rs b/crates/cli/src/cmd/wast.rs
new file mode 100644
index 0000000..ef9c263
--- /dev/null
+++ b/crates/cli/src/cmd/wast.rs
@@ -0,0 +1,12 @@
+use std::path::PathBuf;
+
+use eyre::Result;
+
+use crate::cli::WastArgs;
+use crate::wast_runner::WastRunner;
+
+pub fn run(args: WastArgs) -> Result<()> {
+ let paths = args.paths.into_iter().map(PathBuf::from).collect::<Vec<_>>();
+ let mut runner = WastRunner::new();
+ runner.run_paths(&paths)
+}