Skip to main content

wasm_wave/
writer.rs

1//! WAVE writer
2
3use core::fmt::Write;
4
5use alloc::{
6    collections::{BTreeMap, btree_map::Entry},
7    string::String,
8    vec::Vec,
9};
10use thiserror::Error;
11
12use crate::{
13    lex::Keyword,
14    wasm::{WasmTypeKind, WasmValue},
15};
16
17/// A Web Assembly Value Encoding writer.
18///
19/// Writes to the wrapped `W` writer.
20pub struct Writer<W> {
21    inner: W,
22}
23
24impl<W: Write> Writer<W> {
25    /// Returns a new Writer for the given [`std::io::Write`].
26    pub fn new(w: W) -> Self {
27        Self { inner: w }
28    }
29
30    /// WAVE-encodes and writes the given [`WasmValue`] to the underlying writer.
31    ///
32    /// Returns [`WriterError::UnsupportedType`] if `val`, or any value nested
33    /// within it, has a type that WAVE cannot encode. Output written before the
34    /// error was encountered is left in the underlying writer.
35    pub fn write_value<V>(&mut self, val: &V) -> Result<(), WriterError>
36    where
37        V: WasmValue,
38    {
39        match val.kind() {
40            WasmTypeKind::Bool => self.write_str(if val.unwrap_bool() { "true" } else { "false" }),
41            WasmTypeKind::S8 => self.write_display(val.unwrap_s8()),
42            WasmTypeKind::S16 => self.write_display(val.unwrap_s16()),
43            WasmTypeKind::S32 => self.write_display(val.unwrap_s32()),
44            WasmTypeKind::S64 => self.write_display(val.unwrap_s64()),
45            WasmTypeKind::U8 => self.write_display(val.unwrap_u8()),
46            WasmTypeKind::U16 => self.write_display(val.unwrap_u16()),
47            WasmTypeKind::U32 => self.write_display(val.unwrap_u32()),
48            WasmTypeKind::U64 => self.write_display(val.unwrap_u64()),
49            WasmTypeKind::F32 => {
50                let f = val.unwrap_f32();
51                if f.is_nan() {
52                    self.write_str("nan") // Display is "NaN"
53                } else {
54                    self.write_display(f)
55                }
56            }
57            WasmTypeKind::F64 => {
58                let f = val.unwrap_f64();
59                if f.is_nan() {
60                    self.write_str("nan") // Display is "NaN"
61                } else {
62                    self.write_display(f)
63                }
64            }
65            WasmTypeKind::Char => {
66                self.write_str("'")?;
67                self.write_char(val.unwrap_char())?;
68                self.write_str("'")
69            }
70            WasmTypeKind::String => {
71                self.write_str("\"")?;
72                for ch in val.unwrap_string().chars() {
73                    self.write_char(ch)?;
74                }
75                self.write_str("\"")
76            }
77            WasmTypeKind::List => {
78                self.write_str("[")?;
79                for (idx, val) in val.unwrap_list().enumerate() {
80                    if idx != 0 {
81                        self.write_str(", ")?;
82                    }
83                    self.write_value(&*val)?;
84                }
85                self.write_str("]")
86            }
87            WasmTypeKind::FixedLengthList => {
88                self.write_str("[")?;
89                for (idx, val) in val.unwrap_list().enumerate() {
90                    if idx != 0 {
91                        self.write_str(", ")?;
92                    }
93                    self.write_value(&*val)?;
94                }
95                self.write_str("]")
96            }
97            WasmTypeKind::Record => {
98                self.write_str("{")?;
99                let mut first = true;
100                for (name, val) in val.unwrap_record() {
101                    if !matches!(val.kind(), WasmTypeKind::Option) || val.unwrap_option().is_some()
102                    {
103                        if first {
104                            first = false;
105                        } else {
106                            self.write_str(", ")?;
107                        }
108                        self.write_str(name)?;
109                        self.write_str(": ")?;
110                        self.write_value(&*val)?;
111                    }
112                }
113                if first {
114                    self.write_str(":")?;
115                }
116                self.write_str("}")
117            }
118            WasmTypeKind::Tuple => {
119                self.write_str("(")?;
120                for (idx, val) in val.unwrap_tuple().enumerate() {
121                    if idx != 0 {
122                        self.write_str(", ")?;
123                    }
124                    self.write_value(&*val)?;
125                }
126                self.write_str(")")
127            }
128            WasmTypeKind::Variant => {
129                let (name, val) = val.unwrap_variant();
130                if Keyword::decode(&name).is_some() {
131                    self.write_char('%')?;
132                }
133                self.write_str(name)?;
134                if let Some(val) = val {
135                    self.write_str("(")?;
136                    self.write_value(&*val)?;
137                    self.write_str(")")?;
138                }
139                Ok(())
140            }
141            WasmTypeKind::Enum => {
142                let case = val.unwrap_enum();
143                if Keyword::decode(&case).is_some() {
144                    self.write_char('%')?;
145                }
146                self.write_str(case)
147            }
148            WasmTypeKind::Option => match val.unwrap_option() {
149                Some(val) => {
150                    self.write_str("some(")?;
151                    self.write_value(&*val)?;
152                    self.write_str(")")
153                }
154                None => self.write_str("none"),
155            },
156            WasmTypeKind::Result => {
157                let (name, val) = match val.unwrap_result() {
158                    Ok(val) => ("ok", val),
159                    Err(val) => ("err", val),
160                };
161                self.write_str(name)?;
162                if let Some(val) = val {
163                    self.write_str("(")?;
164                    self.write_value(&*val)?;
165                    self.write_str(")")?;
166                }
167                Ok(())
168            }
169            WasmTypeKind::Flags => {
170                self.write_str("{")?;
171                for (idx, name) in val.unwrap_flags().enumerate() {
172                    if idx != 0 {
173                        self.write_str(", ")?;
174                    }
175                    self.write_str(name)?;
176                }
177                self.write_str("}")?;
178                Ok(())
179            }
180            WasmTypeKind::Map => {
181                let entries = val
182                    .unwrap_map()
183                    .map(|(key, value)| Ok((crate::to_string(&*key)?, key, value)))
184                    .collect::<Result<Vec<_>, WriterError>>()?;
185                self.write_str("{")?;
186                for (idx, (key, value)) in merge_map_entries(entries).into_iter().enumerate() {
187                    if idx != 0 {
188                        self.write_str(", ")?;
189                    }
190                    self.write_value(&*key)?;
191                    self.write_str(": ")?;
192                    self.write_value(&*value)?;
193                }
194                self.write_str("}")
195            }
196            WasmTypeKind::Unsupported => Err(WriterError::UnsupportedType),
197        }
198    }
199
200    fn write_str(&mut self, s: impl AsRef<str>) -> Result<(), WriterError> {
201        self.inner.write_str(s.as_ref())?;
202        Ok(())
203    }
204
205    fn write_display(&mut self, d: impl core::fmt::Display) -> Result<(), WriterError> {
206        write!(self.inner, "{d}")?;
207        Ok(())
208    }
209
210    fn write_char(&mut self, ch: char) -> Result<(), WriterError> {
211        if "\\\"\'\t\r\n".contains(ch) {
212            write!(self.inner, "{}", ch.escape_default())?;
213        } else if ch.is_control() {
214            write!(self.inner, "{}", ch.escape_unicode())?;
215        } else {
216            write!(self.inner, "{}", ch.escape_debug())?;
217        }
218        Ok(())
219    }
220}
221
222/// Merges map entries that have the same key, as the component model
223/// specifies for maps: the last value wins. Like in an insertion-ordered hash
224/// table, each key keeps the position where it first appears.
225///
226/// Each entry is given with its key's WAVE encoding, which is what keys are
227/// compared by, so that e.g. `"a"` and `"\u{61}"` are the same key.
228pub(crate) fn merge_map_entries<K, V>(
229    entries: impl IntoIterator<Item = (String, K, V)>,
230) -> Vec<(K, V)> {
231    let mut merged: Vec<(K, V)> = Vec::new();
232    let mut indices = BTreeMap::<String, usize>::new();
233    for (encoded_key, key, value) in entries {
234        match indices.entry(encoded_key) {
235            Entry::Occupied(entry) => merged[*entry.get()].1 = value,
236            Entry::Vacant(entry) => {
237                entry.insert(merged.len());
238                merged.push((key, value));
239            }
240        }
241    }
242    merged
243}
244
245impl<W> AsMut<W> for Writer<W> {
246    fn as_mut(&mut self) -> &mut W {
247        &mut self.inner
248    }
249}
250
251/// A Writer error.
252#[derive(Debug, Error)]
253#[non_exhaustive]
254pub enum WriterError {
255    /// An error from the underlying writer
256    #[error("write failed: {0}")]
257    Io(#[from] core::fmt::Error),
258    /// The value (or a value nested within it) has a type that WAVE cannot
259    /// encode.
260    #[error("unsupported value type")]
261    UnsupportedType,
262}
263
264#[cfg(test)]
265mod tests {
266    use alloc::{borrow::Cow, boxed::Box, string::String, vec, vec::Vec};
267    use core::fmt::Write;
268
269    use super::WriterError;
270    use crate::{
271        value::Type,
272        wasm::{DisplayValue, WasmTypeKind, WasmValue},
273    };
274
275    /// A minimal [`WasmValue`] that can represent a value WAVE can't encode.
276    #[derive(Clone)]
277    enum TestValue {
278        List(Vec<TestValue>),
279        Unsupported,
280    }
281
282    impl WasmValue for TestValue {
283        type Type = Type;
284
285        fn kind(&self) -> WasmTypeKind {
286            match self {
287                TestValue::List(_) => WasmTypeKind::List,
288                TestValue::Unsupported => WasmTypeKind::Unsupported,
289            }
290        }
291
292        fn unwrap_list(&self) -> Box<dyn Iterator<Item = Cow<'_, Self>> + '_> {
293            match self {
294                TestValue::List(vals) => Box::new(vals.iter().map(Cow::Borrowed)),
295                TestValue::Unsupported => panic!("not a list"),
296            }
297        }
298    }
299
300    #[test]
301    fn unsupported_value_is_an_error() {
302        let err = crate::to_string(&TestValue::Unsupported).unwrap_err();
303        assert!(matches!(err, WriterError::UnsupportedType), "{err:?}");
304
305        let nested = TestValue::List(vec![TestValue::List(vec![]), TestValue::Unsupported]);
306        let err = crate::to_string(&nested).unwrap_err();
307        assert!(matches!(err, WriterError::UnsupportedType), "{err:?}");
308
309        // Through `Display` the error surfaces as a `fmt::Error`.
310        let mut buf = String::new();
311        assert!(write!(buf, "{}", DisplayValue(&nested)).is_err());
312    }
313}