1use std::ffi::OsStr;
2use std::io::{Read, Write};
3use std::path::Path;
4
5use eyre::{Context, Result, bail};
6use tinywasm::Module;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum InputFormat {
10 Wasm,
11 Wat,
12 Twasm,
13}
14
15pub struct LoadedModule {
16 pub module: Module,
17 pub format: InputFormat,
18}
19
20pub fn load_module(input: &str) -> Result<LoadedModule> {
21 let bytes = read_input_bytes(input)?;
22 load_module_from_bytes(input, &bytes)
23}
24
25pub fn load_compilable_module(input: &str) -> Result<Module> {
26 let loaded = load_module(input)?;
27 if loaded.format == InputFormat::Twasm {
28 bail!("input is already a twasm archive; use `run`, `dump`, or `inspect` instead")
29 }
30 Ok(loaded.module)
31}
32
33pub fn default_twasm_output_path(input: &str) -> Result<String> {
34 if input == "-" {
35 bail!("--output is required when compiling from stdin")
36 }
37
38 let path = Path::new(input);
39 let stem = path.file_stem().and_then(OsStr::to_str).unwrap_or("module");
40 let output = path.with_file_name(format!("{stem}.twasm"));
41 Ok(output.to_string_lossy().into_owned())
42}
43
44pub fn write_output_bytes(output: &str, bytes: &[u8], force: bool) -> Result<()> {
45 if output == "-" {
46 std::io::stdout().write_all(bytes)?;
47 std::io::stdout().flush()?;
48 return Ok(());
49 }
50
51 let path = Path::new(output);
52 if path.exists() && !force {
53 bail!("output file already exists: {output}; pass --force to overwrite")
54 }
55
56 std::fs::write(path, bytes).with_context(|| format!("failed to write output file `{output}`"))?;
57 Ok(())
58}
59
60fn load_module_from_bytes(input: &str, bytes: &[u8]) -> Result<LoadedModule> {
61 if bytes.starts_with(b"TWAS") {
62 let module = Module::try_from_twasm(bytes).with_context(|| format!("failed to read twasm input `{input}`"))?;
63 return Ok(LoadedModule { module, format: InputFormat::Twasm });
64 }
65
66 #[cfg(feature = "wat")]
67 if input != "-" && has_extension(input, "wat") {
68 let wasm = wat::parse_bytes(bytes).with_context(|| format!("failed to parse WAT input `{input}`"))?;
69 let module =
70 tinywasm::parse_bytes(&wasm).with_context(|| format!("failed to parse Wasm generated from `{input}`"))?;
71 return Ok(LoadedModule { module, format: InputFormat::Wat });
72 }
73
74 #[cfg(not(feature = "wat"))]
75 if input != "-" && has_extension(input, "wat") {
76 bail!("wat support is not enabled in this build")
77 }
78
79 #[cfg(feature = "wat")]
80 if input == "-"
81 && let Ok(wasm) = wat::parse_bytes(bytes)
82 {
83 let module = tinywasm::parse_bytes(&wasm).context("failed to parse Wasm generated from stdin WAT input")?;
84 return Ok(LoadedModule { module, format: InputFormat::Wat });
85 }
86
87 let module = tinywasm::parse_bytes(bytes).with_context(|| format!("failed to parse Wasm input `{input}`"))?;
88 Ok(LoadedModule { module, format: InputFormat::Wasm })
89}
90
91fn read_input_bytes(input: &str) -> Result<Vec<u8>> {
92 if input == "-" {
93 let mut bytes = Vec::new();
94 std::io::stdin().read_to_end(&mut bytes).context("failed to read stdin")?;
95 return Ok(bytes);
96 }
97
98 std::fs::read(input).with_context(|| format!("failed to read input `{input}`"))
99}
100
101fn has_extension(path: &str, extension: &str) -> bool {
102 Path::new(path).extension().and_then(OsStr::to_str) == Some(extension)
103}