use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Interface {
pub name: Option<String>,
pub imports: HashMap<(String, String), Import>,
pub exports: HashMap<String, Export>,
}
impl Interface {
pub fn merge(&self, other: Interface) -> Result<Interface, String> {
let mut base = self.clone();
for (key, val) in other.imports.into_iter() {
if base.imports.contains_key(&key) {
if val != base.imports[&key] {
return Err(format!("Conflict detected: the import \"{}\" \"{}\" was found but the definitions were different: {:?} {:?}", &key.0, &key.1, base.imports[&key], val));
}
} else {
let res = base.imports.insert(key, val);
debug_assert!(res.is_none());
}
}
for (key, val) in other.exports.into_iter() {
if base.exports.contains_key(&key) {
if val != base.exports[&key] {
return Err(format!("Conflict detected: the key {} was found in exports but the definitions were different: {:?} {:?}", key, base.exports[&key], val));
}
} else {
let res = base.exports.insert(key, val);
debug_assert!(res.is_none());
}
}
Ok(base)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Import {
Func {
namespace: String,
name: String,
params: Vec<WasmType>,
result: Vec<WasmType>,
},
Global {
namespace: String,
name: String,
var_type: WasmType,
},
}
impl Import {
pub fn format_key(ns: &str, name: &str) -> (String, String) {
(ns.to_string(), name.to_string())
}
pub fn get_key(&self) -> (String, String) {
match self {
Import::Func {
namespace, name, ..
} => Self::format_key(&namespace, &name),
Import::Global {
namespace, name, ..
} => Self::format_key(&namespace, &name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Export {
Func {
name: String,
params: Vec<WasmType>,
result: Vec<WasmType>,
},
Global {
name: String,
var_type: WasmType,
},
}
impl Export {
pub fn format_key(name: &str) -> String {
name.to_string()
}
pub fn get_key(&self) -> String {
match self {
Export::Func { name, .. } => Self::format_key(&name),
Export::Global { name, .. } => Self::format_key(&name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WasmType {
I32,
I64,
F32,
F64,
}
impl std::fmt::Display for WasmType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
WasmType::I32 => "i32",
WasmType::I64 => "i64",
WasmType::F32 => "f32",
WasmType::F64 => "f64",
}
)
}
}
#[cfg(test)]
mod test {
use crate::parser;
#[test]
fn merging_works() {
let interface1_src =
r#"(interface (func (import "env" "plus_one") (param i32) (result i32)))"#;
let interface2_src =
r#"(interface (func (import "env" "plus_one") (param i64) (result i64)))"#;
let interface3_src =
r#"(interface (func (import "env" "times_two") (param i64) (result i64)))"#;
let interface4_src =
r#"(interface (func (import "env" "times_two") (param i64 i64) (result i64)))"#;
let interface5_src = r#"(interface (func (export "empty_bank_account") (param) (result)))"#;
let interface6_src =
r#"(interface (func (export "empty_bank_account") (param) (result i64)))"#;
let interface1 = parser::parse_interface(interface1_src).unwrap();
let interface2 = parser::parse_interface(interface2_src).unwrap();
let interface3 = parser::parse_interface(interface3_src).unwrap();
let interface4 = parser::parse_interface(interface4_src).unwrap();
let interface5 = parser::parse_interface(interface5_src).unwrap();
let interface6 = parser::parse_interface(interface6_src).unwrap();
assert!(interface1.merge(interface2.clone()).is_err());
assert!(interface2.merge(interface1.clone()).is_err());
assert!(interface1.merge(interface3.clone()).is_ok());
assert!(interface2.merge(interface3.clone()).is_ok());
assert!(interface3.merge(interface2.clone()).is_ok());
assert!(
interface1.merge(interface1.clone()).is_ok(),
"exact matches are accepted"
);
assert!(interface3.merge(interface4.clone()).is_err());
assert!(interface5.merge(interface5.clone()).is_ok());
assert!(interface5.merge(interface6.clone()).is_err());
}
}