1use 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
17pub struct Writer<W> {
21 inner: W,
22}
23
24impl<W: Write> Writer<W> {
25 pub fn new(w: W) -> Self {
27 Self { inner: w }
28 }
29
30 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") } 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") } 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
222pub(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#[derive(Debug, Error)]
253#[non_exhaustive]
254pub enum WriterError {
255 #[error("write failed: {0}")]
257 Io(#[from] core::fmt::Error),
258 #[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 #[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 let mut buf = String::new();
311 assert!(write!(buf, "{}", DisplayValue(&nested)).is_err());
312 }
313}