use crate::analysis::AnalysedType;
use crate::{IntoValueAndType, Value, ValueAndType};
use std::borrow::Cow;
use std::collections::HashSet;
use wasm_wave::wasm::{WasmType, WasmTypeKind, WasmValue, WasmValueError};
use wasm_wave::{from_str, to_string};
pub fn parse_value_and_type(
analysed_type: &AnalysedType,
input: &str,
) -> Result<ValueAndType, String> {
let parsed: ValueAndType = from_str(analysed_type, input).map_err(|err| err.to_string())?;
Ok(parsed)
}
pub fn print_value_and_type(value: &ValueAndType) -> Result<String, String> {
if value.typ.contains_handle() {
Err("Cannot print handle type".to_string())
} else {
to_string(value).map_err(|err| err.to_string())
}
}
impl WasmValue for ValueAndType {
type Type = AnalysedType;
fn kind(&self) -> WasmTypeKind {
self.typ.kind()
}
fn make_bool(val: bool) -> Self {
val.into_value_and_type()
}
fn make_s8(val: i8) -> Self {
val.into_value_and_type()
}
fn make_s16(val: i16) -> Self {
val.into_value_and_type()
}
fn make_s32(val: i32) -> Self {
val.into_value_and_type()
}
fn make_s64(val: i64) -> Self {
val.into_value_and_type()
}
fn make_u8(val: u8) -> Self {
val.into_value_and_type()
}
fn make_u16(val: u16) -> Self {
val.into_value_and_type()
}
fn make_u32(val: u32) -> Self {
val.into_value_and_type()
}
fn make_u64(val: u64) -> Self {
val.into_value_and_type()
}
fn make_f32(val: f32) -> Self {
val.into_value_and_type()
}
fn make_f64(val: f64) -> Self {
val.into_value_and_type()
}
fn make_char(val: char) -> Self {
val.into_value_and_type()
}
fn make_string(val: Cow<str>) -> Self {
val.to_string().into_value_and_type()
}
fn make_list(
ty: &Self::Type,
vals: impl IntoIterator<Item = Self>,
) -> Result<Self, WasmValueError> {
Ok(ValueAndType {
value: Value::List(vals.into_iter().map(|vnt| vnt.value).collect()),
typ: ty.clone(),
})
}
fn make_record<'a>(
ty: &Self::Type,
fields: impl IntoIterator<Item = (&'a str, Self)>,
) -> Result<Self, WasmValueError> {
Ok(ValueAndType {
value: Value::Record(fields.into_iter().map(|(_, vnt)| vnt.value).collect()),
typ: ty.clone(),
})
}
fn make_tuple(
ty: &Self::Type,
vals: impl IntoIterator<Item = Self>,
) -> Result<Self, WasmValueError> {
Ok(ValueAndType {
value: Value::Tuple(vals.into_iter().map(|vnt| vnt.value).collect()),
typ: ty.clone(),
})
}
fn make_variant(
ty: &Self::Type,
case: &str,
val: Option<Self>,
) -> Result<Self, WasmValueError> {
if let AnalysedType::Variant(typ) = ty {
let case_idx = typ
.cases
.iter()
.position(|pair| pair.name == case)
.ok_or_else(|| WasmValueError::UnknownCase(case.to_string()))?
as u32;
Ok(ValueAndType {
value: Value::Variant {
case_idx,
case_value: val.map(|vnt| Box::new(vnt.value)),
},
typ: ty.clone(),
})
} else {
Err(WasmValueError::WrongTypeKind {
kind: WasmTypeKind::Variant,
ty: ty.kind().to_string(),
})
}
}
fn make_enum(ty: &Self::Type, case: &str) -> Result<Self, WasmValueError> {
if let AnalysedType::Enum(typ) = ty {
let case_idx = typ
.cases
.iter()
.position(|c| c == case)
.ok_or_else(|| WasmValueError::UnknownCase(case.to_string()))?
as u32;
Ok(ValueAndType {
value: Value::Enum(case_idx),
typ: ty.clone(),
})
} else {
Err(WasmValueError::WrongTypeKind {
kind: WasmTypeKind::Enum,
ty: ty.kind().to_string(),
})
}
}
fn make_option(ty: &Self::Type, val: Option<Self>) -> Result<Self, WasmValueError> {
Ok(ValueAndType {
value: Value::Option(val.map(|vnt| Box::new(vnt.value))),
typ: ty.clone(),
})
}
fn make_result(
ty: &Self::Type,
val: Result<Option<Self>, Option<Self>>,
) -> Result<Self, WasmValueError> {
Ok(ValueAndType {
value: Value::Result(
val.map(|maybe_ok| maybe_ok.map(|vnt| Box::new(vnt.value)))
.map_err(|maybe_err| maybe_err.map(|vnt| Box::new(vnt.value))),
),
typ: ty.clone(),
})
}
fn make_flags<'a>(
ty: &Self::Type,
names: impl IntoIterator<Item = &'a str>,
) -> Result<Self, WasmValueError> {
if let AnalysedType::Flags(typ) = ty {
let mut bitmap = Vec::new();
let names: HashSet<&'a str> = HashSet::from_iter(names);
for name in &typ.names {
bitmap.push(names.contains(name.as_str()));
}
Ok(ValueAndType {
value: Value::Flags(bitmap),
typ: ty.clone(),
})
} else {
Err(WasmValueError::WrongTypeKind {
kind: WasmTypeKind::Flags,
ty: ty.kind().to_string(),
})
}
}
fn unwrap_bool(&self) -> bool {
match self.value {
Value::Bool(val) => val,
_ => panic!("Expected bool, found {self:?}"),
}
}
fn unwrap_s8(&self) -> i8 {
match self.value {
Value::S8(val) => val,
_ => panic!("Expected s8, found {self:?}"),
}
}
fn unwrap_s16(&self) -> i16 {
match self.value {
Value::S16(val) => val,
_ => panic!("Expected s16, found {self:?}"),
}
}
fn unwrap_s32(&self) -> i32 {
match self.value {
Value::S32(val) => val,
_ => panic!("Expected s32, found {self:?}"),
}
}
fn unwrap_s64(&self) -> i64 {
match self.value {
Value::S64(val) => val,
_ => panic!("Expected s64, found {self:?}"),
}
}
fn unwrap_u8(&self) -> u8 {
match self.value {
Value::U8(val) => val,
_ => panic!("Expected u8, found {self:?}"),
}
}
fn unwrap_u16(&self) -> u16 {
match self.value {
Value::U16(val) => val,
_ => panic!("Expected u16, found {self:?}"),
}
}
fn unwrap_u32(&self) -> u32 {
match self.value {
Value::U32(val) => val,
_ => panic!("Expected u32, found {self:?}"),
}
}
fn unwrap_u64(&self) -> u64 {
match self.value {
Value::U64(val) => val,
_ => panic!("Expected u64, found {self:?}"),
}
}
fn unwrap_f32(&self) -> f32 {
match self.value {
Value::F32(val) => val,
_ => panic!("Expected f32, found {self:?}"),
}
}
fn unwrap_f64(&self) -> f64 {
match self.value {
Value::F64(val) => val,
_ => panic!("Expected f64, found {self:?}"),
}
}
fn unwrap_char(&self) -> char {
match self.value {
Value::Char(val) => val,
_ => panic!("Expected char, found {self:?}"),
}
}
fn unwrap_string(&self) -> Cow<'_, str> {
match &self.value {
Value::String(val) => Cow::Borrowed(val),
_ => panic!("Expected string, found {self:?}"),
}
}
fn unwrap_list(&self) -> Box<dyn Iterator<Item = Cow<'_, Self>> + '_> {
match (&self.value, &self.typ) {
(Value::List(vals), AnalysedType::List(typ)) => Box::new(vals.iter().map(|v| {
Cow::Owned(ValueAndType {
value: v.clone(),
typ: (*typ.inner).clone(),
})
})),
_ => panic!("Expected list, found {self:?}"),
}
}
fn unwrap_record(&self) -> Box<dyn Iterator<Item = (Cow<'_, str>, Cow<'_, Self>)> + '_> {
match (&self.value, &self.typ) {
(Value::Record(vals), AnalysedType::Record(typ)) => {
Box::new(vals.iter().zip(typ.fields.iter()).map(|(v, f)| {
(
Cow::Borrowed(f.name.as_str()),
Cow::Owned(ValueAndType {
value: v.clone(),
typ: f.typ.clone(),
}),
)
}))
}
_ => panic!("Expected record, found {self:?}"),
}
}
fn unwrap_tuple(&self) -> Box<dyn Iterator<Item = Cow<'_, Self>> + '_> {
match (&self.value, &self.typ) {
(Value::Tuple(vals), AnalysedType::Tuple(typ)) => {
Box::new(vals.iter().zip(typ.items.iter()).map(|(v, t)| {
Cow::Owned(ValueAndType {
value: v.clone(),
typ: t.clone(),
})
}))
}
_ => panic!("Expected tuple, found {self:?}"),
}
}
fn unwrap_variant(&self) -> (Cow<'_, str>, Option<Cow<'_, Self>>) {
match (&self.value, &self.typ) {
(
Value::Variant {
case_idx,
case_value,
},
AnalysedType::Variant(typ),
) => {
let case_name = &typ.cases[*case_idx as usize].name;
let case_value = case_value.as_ref().map(|v| {
let typ = &typ.cases[*case_idx as usize].typ;
Cow::Owned(ValueAndType {
value: *v.clone(),
typ: typ
.as_ref()
.unwrap_or_else(|| {
panic!("No type information for non-unit variant case {case_name}")
})
.clone(),
})
});
(Cow::Borrowed(case_name), case_value)
}
_ => panic!("Expected variant, found {self:?}"),
}
}
fn unwrap_enum(&self) -> Cow<'_, str> {
match (&self.value, &self.typ) {
(Value::Enum(case_idx), AnalysedType::Enum(typ)) => {
Cow::Borrowed(&typ.cases[*case_idx as usize])
}
_ => panic!("Expected enum, found {self:?}"),
}
}
fn unwrap_option(&self) -> Option<Cow<'_, Self>> {
match (&self.value, &self.typ) {
(Value::Option(Some(val)), AnalysedType::Option(typ)) => {
Some(Cow::Owned(ValueAndType {
value: *val.clone(),
typ: (*typ.inner).clone(),
}))
}
(Value::Option(None), AnalysedType::Option(_)) => None,
_ => panic!("Expected option, found {self:?}"),
}
}
fn unwrap_result(&self) -> Result<Option<Cow<'_, Self>>, Option<Cow<'_, Self>>> {
match (&self.value, &self.typ) {
(Value::Result(Ok(Some(val))), AnalysedType::Result(typ)) => {
Ok(Some(Cow::Owned(ValueAndType {
value: *val.clone(),
typ: *typ
.ok
.as_ref()
.expect("No type information for non-unit ok value")
.clone(),
})))
}
(Value::Result(Ok(None)), AnalysedType::Result(_)) => Ok(None),
(Value::Result(Err(Some(val))), AnalysedType::Result(typ)) => {
Err(Some(Cow::Owned(ValueAndType {
value: *val.clone(),
typ: *typ
.err
.as_ref()
.expect("No type information for non-unit error value")
.clone(),
})))
}
(Value::Result(Err(None)), AnalysedType::Result(_)) => Err(None),
_ => panic!("Expected result, found {self:?}"),
}
}
fn unwrap_flags(&self) -> Box<dyn Iterator<Item = Cow<'_, str>> + '_> {
match (&self.value, &self.typ) {
(Value::Flags(bitmap), AnalysedType::Flags(typ)) => Box::new(
bitmap
.iter()
.zip(typ.names.iter())
.filter_map(|(is_set, name)| {
if *is_set {
Some(Cow::Borrowed(name.as_str()))
} else {
None
}
}),
),
_ => panic!("Expected flags, found {self:?}"),
}
}
}
#[cfg(test)]
mod tests {
use test_r::test;
use crate::analysis::AnalysedType;
use crate::analysis::analysed_type::{
bool, case, chr, r#enum, f32, f64, field, flags, list, option, record, result_err,
result_ok, s8, s16, s32, s64, str, tuple, u8, u16, u32, u64, unit_case, variant,
};
use crate::text::{parse_value_and_type, print_value_and_type};
use crate::{Value, ValueAndType};
fn round_trip(value: Value, typ: AnalysedType) {
let typed_value = ValueAndType::new(value.clone(), typ.clone());
let s = print_value_and_type(&typed_value).unwrap();
let round_trip_value: ValueAndType = parse_value_and_type(&typ, &s).unwrap();
let result: Value = Value::from(round_trip_value);
assert_eq!(value, result);
}
#[test]
fn round_trip_u8() {
round_trip(Value::U8(42), u8());
}
#[test]
fn round_trip_u16() {
round_trip(Value::U16(1234), u16());
}
#[test]
fn round_trip_u32() {
round_trip(Value::U32(123456), u32());
}
#[test]
fn round_trip_u64() {
round_trip(Value::U64(1234567890123456), u64());
}
#[test]
fn round_trip_s8() {
round_trip(Value::S8(-42), s8());
}
#[test]
fn round_trip_s16() {
round_trip(Value::S16(-1234), s16());
}
#[test]
fn round_trip_s32() {
round_trip(Value::S32(-123456), s32());
}
#[test]
fn round_trip_s64() {
round_trip(Value::S64(-1234567890123456), s64());
}
#[test]
fn round_trip_f32() {
round_trip(Value::F32(1234.5678), f32());
}
#[test]
fn round_trip_f64() {
round_trip(Value::F64(1_234_567_890_123_456.8), f64());
}
#[test]
fn round_trip_bool() {
round_trip(Value::Bool(true), bool());
}
#[test]
fn round_trip_char() {
round_trip(Value::Char('a'), chr());
}
#[test]
fn round_trip_string() {
round_trip(Value::String("hello".to_string()), str());
}
#[test]
fn round_trip_list_1() {
round_trip(
Value::List(vec![Value::U8(1), Value::U8(2), Value::U8(3)]),
list(u8()),
);
}
#[test]
fn round_trip_list_2() {
round_trip(
Value::List(vec![Value::List(vec![
Value::String("hello".to_string()),
Value::String("world".to_string()),
])]),
list(list(str())),
);
}
#[test]
fn round_trip_record() {
round_trip(
Value::Record(vec![
Value::U8(1),
Value::String("hello".to_string()),
Value::Bool(true),
]),
record(vec![
field("a", u8()),
field("b", str()),
field("c", bool()),
]),
);
}
#[test]
fn round_trip_tuple() {
round_trip(
Value::Tuple(vec![
Value::U8(1),
Value::String("hello".to_string()),
Value::Bool(true),
]),
tuple(vec![u8(), str(), bool()]),
);
}
#[test]
fn round_trip_variant() {
round_trip(
Value::Variant {
case_idx: 1,
case_value: Some(Box::new(Value::String("hello".to_string()))),
},
variant(vec![unit_case("A"), case("B", str())]),
);
}
#[test]
fn round_trip_enum() {
round_trip(Value::Enum(1), r#enum(&["A", "B"]));
}
#[test]
fn round_trip_option() {
round_trip(Value::Option(Some(Box::new(Value::U8(1)))), option(u8()));
}
#[test]
fn round_trip_result_ok() {
round_trip(
Value::Result(Ok(Some(Box::new(Value::U8(1))))),
result_ok(u8()),
);
}
#[test]
fn round_trip_result_err() {
round_trip(
Value::Result(Err(Some(Box::new(Value::U8(1))))),
result_err(u8()),
);
}
#[test]
fn round_trip_flags() {
round_trip(
Value::Flags(vec![true, false, true]),
flags(&["A", "B", "C"]),
);
}
}