wasm_interface/
interface.rs

1//! The definition of a WASM interface
2
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, PartialEq, Eq, Default)]
6pub struct Interface {
7    /// The name the interface gave itself
8    pub name: Option<String>,
9    /// Things that the module can import
10    pub imports: HashMap<(String, String), Import>,
11    /// Things that the module must export
12    pub exports: HashMap<String, Export>,
13}
14
15impl Interface {
16    pub fn merge(&self, other: Interface) -> Result<Interface, String> {
17        let mut base = self.clone();
18
19        for (key, val) in other.imports.into_iter() {
20            if base.imports.contains_key(&key) {
21                if val != base.imports[&key] {
22                    return Err(format!("Conflict detected: the import \"{}\" \"{}\" was found but the definitions were different: {:?} {:?}", &key.0, &key.1, base.imports[&key], val));
23                }
24            } else {
25                let res = base.imports.insert(key, val);
26                debug_assert!(res.is_none());
27            }
28        }
29
30        for (key, val) in other.exports.into_iter() {
31            if base.exports.contains_key(&key) {
32                if val != base.exports[&key] {
33                    return Err(format!("Conflict detected: the key {} was found in exports but the definitions were different: {:?} {:?}", key, base.exports[&key], val));
34                }
35            } else {
36                let res = base.exports.insert(key, val);
37                debug_assert!(res.is_none());
38            }
39        }
40        Ok(base)
41    }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum Import {
46    Func {
47        namespace: String,
48        name: String,
49        params: Vec<WasmType>,
50        result: Vec<WasmType>,
51    },
52    Global {
53        namespace: String,
54        name: String,
55        var_type: WasmType,
56    },
57}
58
59impl Import {
60    pub fn format_key(ns: &str, name: &str) -> (String, String) {
61        (ns.to_string(), name.to_string())
62    }
63
64    /// Get the key used to look this import up in the Interface's import hashmap
65    pub fn get_key(&self) -> (String, String) {
66        match self {
67            Import::Func {
68                namespace, name, ..
69            } => Self::format_key(&namespace, &name),
70            Import::Global {
71                namespace, name, ..
72            } => Self::format_key(&namespace, &name),
73        }
74    }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum Export {
79    Func {
80        name: String,
81        params: Vec<WasmType>,
82        result: Vec<WasmType>,
83    },
84    Global {
85        name: String,
86        var_type: WasmType,
87    },
88}
89
90impl Export {
91    pub fn format_key(name: &str) -> String {
92        name.to_string()
93    }
94
95    /// Get the key used to look this export up in the Interface's export hashmap
96    pub fn get_key(&self) -> String {
97        match self {
98            Export::Func { name, .. } => Self::format_key(&name),
99            Export::Global { name, .. } => Self::format_key(&name),
100        }
101    }
102}
103
104/// Primitive wasm type
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum WasmType {
107    I32,
108    I64,
109    F32,
110    F64,
111}
112
113impl std::fmt::Display for WasmType {
114    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
115        write!(
116            f,
117            "{}",
118            match self {
119                WasmType::I32 => "i32",
120                WasmType::I64 => "i64",
121                WasmType::F32 => "f32",
122                WasmType::F64 => "f64",
123            }
124        )
125    }
126}
127
128#[cfg(test)]
129mod test {
130    use crate::parser;
131
132    #[test]
133    fn merging_works() {
134        let interface1_src =
135            r#"(interface (func (import "env" "plus_one") (param i32) (result i32)))"#;
136        let interface2_src =
137            r#"(interface (func (import "env" "plus_one") (param i64) (result i64)))"#;
138        let interface3_src =
139            r#"(interface (func (import "env" "times_two") (param i64) (result i64)))"#;
140        let interface4_src =
141            r#"(interface (func (import "env" "times_two") (param i64 i64) (result i64)))"#;
142        let interface5_src = r#"(interface (func (export "empty_bank_account") (param) (result)))"#;
143        let interface6_src =
144            r#"(interface (func (export "empty_bank_account") (param) (result i64)))"#;
145
146        let interface1 = parser::parse_interface(interface1_src).unwrap();
147        let interface2 = parser::parse_interface(interface2_src).unwrap();
148        let interface3 = parser::parse_interface(interface3_src).unwrap();
149        let interface4 = parser::parse_interface(interface4_src).unwrap();
150        let interface5 = parser::parse_interface(interface5_src).unwrap();
151        let interface6 = parser::parse_interface(interface6_src).unwrap();
152
153        assert!(interface1.merge(interface2.clone()).is_err());
154        assert!(interface2.merge(interface1.clone()).is_err());
155        assert!(interface1.merge(interface3.clone()).is_ok());
156        assert!(interface2.merge(interface3.clone()).is_ok());
157        assert!(interface3.merge(interface2.clone()).is_ok());
158        assert!(
159            interface1.merge(interface1.clone()).is_ok(),
160            "exact matches are accepted"
161        );
162        assert!(interface3.merge(interface4.clone()).is_err());
163        assert!(interface5.merge(interface5.clone()).is_ok());
164        assert!(interface5.merge(interface6.clone()).is_err());
165    }
166}