mod error;
mod merger;
mod parser;
pub use error::ComposeError;
pub use parser::ParsedModule;
use merger::{ExportSpec, Merger, Wiring};
use std::collections::HashMap;
pub struct StaticComposer {
modules: Vec<ParsedModule>,
wirings: Vec<Wiring>,
exports: Vec<ExportSpec>,
}
impl StaticComposer {
pub fn new() -> Self {
Self {
modules: Vec::new(),
wirings: Vec::new(),
exports: Vec::new(),
}
}
pub fn add_module(mut self, name: &str, wasm: Vec<u8>) -> Result<Self, ComposeError> {
let parsed = ParsedModule::parse(name, &wasm)?;
self.modules.push(parsed);
Ok(self)
}
pub fn wire(
mut self,
consumer: &str,
import_module: &str,
import_fn: &str,
provider: &str,
provider_export: &str,
) -> Self {
self.wirings.push(Wiring {
consumer: consumer.to_string(),
import_module: import_module.to_string(),
import_fn: import_fn.to_string(),
provider: provider.to_string(),
provider_export: provider_export.to_string(),
});
self
}
pub fn auto_wire(mut self) -> Result<Self, ComposeError> {
let mut export_map: HashMap<String, Vec<(&str, &parser::Export)>> = HashMap::new();
for module in &self.modules {
for export in &module.exports {
export_map
.entry(export.name.clone())
.or_default()
.push((&module.name, export));
}
}
for module in &self.modules {
for import in &module.imports {
let already_wired = self.wirings.iter().any(|w| {
w.consumer == module.name
&& w.import_module == import.module
&& w.import_fn == import.name
});
if already_wired {
continue;
}
if let Some(exports) = export_map.get(&import.name) {
for (provider_name, export) in exports {
if *provider_name != module.name {
if matches!(import.kind, parser::ImportKind::Function(_))
&& export.kind == parser::ExportKind::Function
{
self.wirings.push(Wiring {
consumer: module.name.clone(),
import_module: import.module.clone(),
import_fn: import.name.clone(),
provider: provider_name.to_string(),
provider_export: export.name.clone(),
});
break;
}
}
}
}
}
}
Ok(self)
}
pub fn export(mut self, name: &str, source_module: &str, source_fn: &str) -> Self {
self.exports.push(ExportSpec {
name: name.to_string(),
source_module: source_module.to_string(),
source_export: source_fn.to_string(),
});
self
}
pub fn compose(self) -> Result<Vec<u8>, ComposeError> {
if self.modules.is_empty() {
return Err(ComposeError::NoModules);
}
let merger = Merger::new(self.modules, self.wirings, self.exports);
merger.merge()
}
}
impl Default for StaticComposer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_static_composer_api() {
let _composer = StaticComposer::new();
}
#[test]
fn test_parse_simple_module() {
let wasm = wat::parse_str(
r#"
(module
(func $add (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add
)
(export "add" (func $add))
)
"#,
)
.unwrap();
let parsed = ParsedModule::parse("test", &wasm).unwrap();
assert_eq!(parsed.name, "test");
assert_eq!(parsed.functions.len(), 1);
assert_eq!(parsed.exports.len(), 1);
assert_eq!(parsed.exports[0].name, "add");
}
#[test]
fn test_compose_simple_modules() {
let module_a = wat::parse_str(
r#"
(module
(memory (export "memory") 1)
(func $double (param i32) (result i32)
local.get 0
i32.const 2
i32.mul
)
(export "double" (func $double))
)
"#,
)
.unwrap();
let module_b = wat::parse_str(
r#"
(module
(import "math" "double" (func $double (param i32) (result i32)))
(func $add_doubled (param i32 i32) (result i32)
local.get 0
call $double
local.get 1
call $double
i32.add
)
(export "add_doubled" (func $add_doubled))
)
"#,
)
.unwrap();
let composed = StaticComposer::new()
.add_module("doubler", module_a)
.unwrap()
.add_module("adder", module_b)
.unwrap()
.wire("adder", "math", "double", "doubler", "double")
.export("add_doubled", "adder", "add_doubled")
.export("memory", "doubler", "memory")
.compose()
.unwrap();
assert!(!composed.is_empty());
let parsed = ParsedModule::parse("composed", &composed).unwrap();
assert!(parsed.exports.iter().any(|e| e.name == "add_doubled"));
assert!(parsed.exports.iter().any(|e| e.name == "memory"));
assert_eq!(parsed.num_imported_functions, 0);
}
#[test]
fn test_auto_wire() {
let module_a = wat::parse_str(
r#"
(module
(func $transform (param i32) (result i32)
local.get 0
i32.const 10
i32.add
)
(export "transform" (func $transform))
)
"#,
)
.unwrap();
let module_b = wat::parse_str(
r#"
(module
(import "util" "transform" (func $transform (param i32) (result i32)))
(func $process (param i32) (result i32)
local.get 0
call $transform
)
(export "process" (func $process))
)
"#,
)
.unwrap();
let composed = StaticComposer::new()
.add_module("a", module_a)
.unwrap()
.add_module("b", module_b)
.unwrap()
.auto_wire()
.unwrap()
.export("process", "b", "process")
.compose()
.unwrap();
let parsed = ParsedModule::parse("composed", &composed).unwrap();
assert_eq!(parsed.num_imported_functions, 0);
}
}