use core::fmt::Write;
use alloc::{
collections::{BTreeMap, btree_map::Entry},
string::String,
vec::Vec,
};
use thiserror::Error;
use crate::{
lex::Keyword,
wasm::{WasmTypeKind, WasmValue},
};
pub struct Writer<W> {
inner: W,
}
impl<W: Write> Writer<W> {
pub fn new(w: W) -> Self {
Self { inner: w }
}
pub fn write_value<V>(&mut self, val: &V) -> Result<(), WriterError>
where
V: WasmValue,
{
match val.kind() {
WasmTypeKind::Bool => self.write_str(if val.unwrap_bool() { "true" } else { "false" }),
WasmTypeKind::S8 => self.write_display(val.unwrap_s8()),
WasmTypeKind::S16 => self.write_display(val.unwrap_s16()),
WasmTypeKind::S32 => self.write_display(val.unwrap_s32()),
WasmTypeKind::S64 => self.write_display(val.unwrap_s64()),
WasmTypeKind::U8 => self.write_display(val.unwrap_u8()),
WasmTypeKind::U16 => self.write_display(val.unwrap_u16()),
WasmTypeKind::U32 => self.write_display(val.unwrap_u32()),
WasmTypeKind::U64 => self.write_display(val.unwrap_u64()),
WasmTypeKind::F32 => {
let f = val.unwrap_f32();
if f.is_nan() {
self.write_str("nan") } else {
self.write_display(f)
}
}
WasmTypeKind::F64 => {
let f = val.unwrap_f64();
if f.is_nan() {
self.write_str("nan") } else {
self.write_display(f)
}
}
WasmTypeKind::Char => {
self.write_str("'")?;
self.write_char(val.unwrap_char())?;
self.write_str("'")
}
WasmTypeKind::String => {
self.write_str("\"")?;
for ch in val.unwrap_string().chars() {
self.write_char(ch)?;
}
self.write_str("\"")
}
WasmTypeKind::List => {
self.write_str("[")?;
for (idx, val) in val.unwrap_list().enumerate() {
if idx != 0 {
self.write_str(", ")?;
}
self.write_value(&*val)?;
}
self.write_str("]")
}
WasmTypeKind::FixedLengthList => {
self.write_str("[")?;
for (idx, val) in val.unwrap_list().enumerate() {
if idx != 0 {
self.write_str(", ")?;
}
self.write_value(&*val)?;
}
self.write_str("]")
}
WasmTypeKind::Record => {
self.write_str("{")?;
let mut first = true;
for (name, val) in val.unwrap_record() {
if !matches!(val.kind(), WasmTypeKind::Option) || val.unwrap_option().is_some()
{
if first {
first = false;
} else {
self.write_str(", ")?;
}
self.write_str(name)?;
self.write_str(": ")?;
self.write_value(&*val)?;
}
}
if first {
self.write_str(":")?;
}
self.write_str("}")
}
WasmTypeKind::Tuple => {
self.write_str("(")?;
for (idx, val) in val.unwrap_tuple().enumerate() {
if idx != 0 {
self.write_str(", ")?;
}
self.write_value(&*val)?;
}
self.write_str(")")
}
WasmTypeKind::Variant => {
let (name, val) = val.unwrap_variant();
if Keyword::decode(&name).is_some() {
self.write_char('%')?;
}
self.write_str(name)?;
if let Some(val) = val {
self.write_str("(")?;
self.write_value(&*val)?;
self.write_str(")")?;
}
Ok(())
}
WasmTypeKind::Enum => {
let case = val.unwrap_enum();
if Keyword::decode(&case).is_some() {
self.write_char('%')?;
}
self.write_str(case)
}
WasmTypeKind::Option => match val.unwrap_option() {
Some(val) => {
self.write_str("some(")?;
self.write_value(&*val)?;
self.write_str(")")
}
None => self.write_str("none"),
},
WasmTypeKind::Result => {
let (name, val) = match val.unwrap_result() {
Ok(val) => ("ok", val),
Err(val) => ("err", val),
};
self.write_str(name)?;
if let Some(val) = val {
self.write_str("(")?;
self.write_value(&*val)?;
self.write_str(")")?;
}
Ok(())
}
WasmTypeKind::Flags => {
self.write_str("{")?;
for (idx, name) in val.unwrap_flags().enumerate() {
if idx != 0 {
self.write_str(", ")?;
}
self.write_str(name)?;
}
self.write_str("}")?;
Ok(())
}
WasmTypeKind::Map => {
let entries = val
.unwrap_map()
.map(|(key, value)| Ok((crate::to_string(&*key)?, key, value)))
.collect::<Result<Vec<_>, WriterError>>()?;
self.write_str("{")?;
for (idx, (key, value)) in merge_map_entries(entries).into_iter().enumerate() {
if idx != 0 {
self.write_str(", ")?;
}
self.write_value(&*key)?;
self.write_str(": ")?;
self.write_value(&*value)?;
}
self.write_str("}")
}
WasmTypeKind::Unsupported => Err(WriterError::UnsupportedType),
}
}
fn write_str(&mut self, s: impl AsRef<str>) -> Result<(), WriterError> {
self.inner.write_str(s.as_ref())?;
Ok(())
}
fn write_display(&mut self, d: impl core::fmt::Display) -> Result<(), WriterError> {
write!(self.inner, "{d}")?;
Ok(())
}
fn write_char(&mut self, ch: char) -> Result<(), WriterError> {
if "\\\"\'\t\r\n".contains(ch) {
write!(self.inner, "{}", ch.escape_default())?;
} else if ch.is_control() {
write!(self.inner, "{}", ch.escape_unicode())?;
} else {
write!(self.inner, "{}", ch.escape_debug())?;
}
Ok(())
}
}
pub(crate) fn merge_map_entries<K, V>(
entries: impl IntoIterator<Item = (String, K, V)>,
) -> Vec<(K, V)> {
let mut merged: Vec<(K, V)> = Vec::new();
let mut indices = BTreeMap::<String, usize>::new();
for (encoded_key, key, value) in entries {
match indices.entry(encoded_key) {
Entry::Occupied(entry) => merged[*entry.get()].1 = value,
Entry::Vacant(entry) => {
entry.insert(merged.len());
merged.push((key, value));
}
}
}
merged
}
impl<W> AsMut<W> for Writer<W> {
fn as_mut(&mut self) -> &mut W {
&mut self.inner
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum WriterError {
#[error("write failed: {0}")]
Io(#[from] core::fmt::Error),
#[error("unsupported value type")]
UnsupportedType,
}
#[cfg(test)]
mod tests {
use alloc::{borrow::Cow, boxed::Box, string::String, vec, vec::Vec};
use core::fmt::Write;
use super::WriterError;
use crate::{
value::Type,
wasm::{DisplayValue, WasmTypeKind, WasmValue},
};
#[derive(Clone)]
enum TestValue {
List(Vec<TestValue>),
Unsupported,
}
impl WasmValue for TestValue {
type Type = Type;
fn kind(&self) -> WasmTypeKind {
match self {
TestValue::List(_) => WasmTypeKind::List,
TestValue::Unsupported => WasmTypeKind::Unsupported,
}
}
fn unwrap_list(&self) -> Box<dyn Iterator<Item = Cow<'_, Self>> + '_> {
match self {
TestValue::List(vals) => Box::new(vals.iter().map(Cow::Borrowed)),
TestValue::Unsupported => panic!("not a list"),
}
}
}
#[test]
fn unsupported_value_is_an_error() {
let err = crate::to_string(&TestValue::Unsupported).unwrap_err();
assert!(matches!(err, WriterError::UnsupportedType), "{err:?}");
let nested = TestValue::List(vec![TestValue::List(vec![]), TestValue::Unsupported]);
let err = crate::to_string(&nested).unwrap_err();
assert!(matches!(err, WriterError::UnsupportedType), "{err:?}");
let mut buf = String::new();
assert!(write!(buf, "{}", DisplayValue(&nested)).is_err());
}
}