yarnspinner_core/
yarn_value.rs

1//! Implements a subset of dotnet's [`Convert`](https://learn.microsoft.com/en-us/dotnet/api/system.convert?view=net-8.0) type.
2use crate::prelude::*;
3use core::error::Error;
4use core::fmt::{Display, Formatter};
5
6/// Represents a Yarn value. The chosen variant corresponds to the last assignment of the value,
7/// with the type being inferred from the type checker.
8///
9/// The type implements meaningful conversions between types through [`TryFrom`] and [`From`].
10/// A failure to convert one variant to another will result in an [`YarnValueCastError`].
11///
12/// ## Implementation Notes
13///
14/// Corresponds to C#'s [`Convert`](https://docs.microsoft.com/en-us/dotnet/api/system.convert?view=net-5.0) class.
15#[derive(Debug, Clone, PartialEq)]
16#[cfg_attr(feature = "bevy", derive(Reflect))]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18#[cfg_attr(feature = "bevy", reflect(Debug, PartialEq))]
19#[cfg_attr(
20    all(feature = "bevy", feature = "serde"),
21    reflect(Serialize, Deserialize)
22)]
23pub enum YarnValue {
24    /// Any kind of Rust number, i.e. one of `f32`, `f64`, `i8`, `i16`, `i32`, `i64`, `i128`, `u8`, `u16`, `u32`, `u64`, `u128`, `usize`, `isize`.
25    /// They are internally stored as `f32` through simple type casts.
26    Number(f32),
27    /// An owned Rust string.
28    String(String),
29    /// A Rust boolean.
30    Boolean(bool),
31}
32
33/// The return value of a [`YarnFn`]. See [`YarnFn`] for more information on the kinds of signatures that can be registered.
34///
35/// Needed to ensure that the return type of a registered function is
36/// able to be turned into a [`YarnValue`], but not a [`YarnValue`] itself.
37pub trait IntoYarnValueFromNonYarnValue {
38    #[doc(hidden)]
39    fn into_yarn_value(self) -> YarnValue;
40}
41
42impl YarnValue {
43    /// Checks if two [`YarnValue`]s are equal, with a given epsilon for two [`YarnValue::Number`]s.
44    /// Note that all equality operations are type-safe, i.e. comparing a [`YarnValue::Number`] to a [`YarnValue::String`] will always return `false`.
45    pub fn eq(&self, other: &Self, epsilon: f32) -> bool {
46        match (self, other) {
47            (Self::Number(a), Self::Number(b)) => (a - b).abs() < epsilon,
48            (a, b) => a == b,
49        }
50    }
51}
52
53impl<T> From<&T> for YarnValue
54where
55    T: Copy,
56    YarnValue: From<T>,
57{
58    fn from(value: &T) -> Self {
59        Self::from(*value)
60    }
61}
62
63macro_rules! impl_floating_point {
64        ($($from_type:ty,)*) => {
65        $(
66            impl From<$from_type> for YarnValue {
67                fn from(value: $from_type) -> Self {
68                    Self::Number(value as f32)
69                }
70            }
71
72            impl TryFrom<YarnValue> for $from_type {
73                type Error = YarnValueCastError;
74
75                fn try_from(value: YarnValue) -> Result<Self, Self::Error> {
76                    Self::try_from(&value)
77                }
78            }
79
80            impl TryFrom<&YarnValue> for $from_type {
81                type Error = YarnValueCastError;
82
83                fn try_from(value: &YarnValue) -> Result<Self, Self::Error> {
84                    match value {
85                        YarnValue::Number(value) => Ok(*value as $from_type),
86                        YarnValue::String(value) => value.parse().map_err(Into::into),
87                        YarnValue::Boolean(value) => Ok(if *value { 1.0 as $from_type } else { 0.0 }),
88                    }
89                }
90            }
91
92
93            impl IntoYarnValueFromNonYarnValue for $from_type {
94                fn into_yarn_value(self) -> YarnValue {
95                    self.into()
96                }
97            }
98        )*
99    };
100}
101
102impl_floating_point![f32, f64,];
103
104macro_rules! impl_whole_number {
105    ($($from_type:ty,)*) => {
106        $(
107            impl From<$from_type> for YarnValue {
108                fn from(value: $from_type) -> Self {
109                    Self::Number(value as f32)
110                }
111            }
112
113            impl TryFrom<YarnValue> for $from_type {
114                type Error = YarnValueCastError;
115
116                fn try_from(value: YarnValue) -> Result<Self, Self::Error> {
117                    Self::try_from(&value)
118                }
119            }
120
121            impl TryFrom<&YarnValue> for $from_type {
122                type Error = YarnValueCastError;
123
124                fn try_from(value: &YarnValue) -> Result<Self, Self::Error> {
125                    f32::try_from(value).map(|value| value as $from_type)
126                }
127            }
128
129            impl IntoYarnValueFromNonYarnValue for $from_type {
130                fn into_yarn_value(self) -> YarnValue {
131                    self.into()
132                }
133            }
134        )*
135    };
136}
137
138impl_whole_number![
139    i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize,
140];
141
142impl From<YarnValue> for String {
143    fn from(value: YarnValue) -> Self {
144        match value {
145            YarnValue::Number(value) => value.to_string(),
146            YarnValue::String(value) => value,
147            YarnValue::Boolean(value) => value.to_string(),
148        }
149    }
150}
151
152impl From<&YarnValue> for String {
153    fn from(value: &YarnValue) -> Self {
154        Self::from(value.clone())
155    }
156}
157
158impl From<String> for YarnValue {
159    fn from(value: String) -> Self {
160        Self::String(value)
161    }
162}
163
164impl From<&str> for YarnValue {
165    fn from(value: &str) -> Self {
166        Self::String(value.to_string())
167    }
168}
169
170impl IntoYarnValueFromNonYarnValue for String {
171    fn into_yarn_value(self) -> YarnValue {
172        self.into()
173    }
174}
175
176impl TryFrom<YarnValue> for bool {
177    type Error = YarnValueCastError;
178
179    fn try_from(value: YarnValue) -> Result<Self, Self::Error> {
180        Self::try_from(&value)
181    }
182}
183
184impl TryFrom<&YarnValue> for bool {
185    type Error = YarnValueCastError;
186
187    fn try_from(value: &YarnValue) -> Result<Self, Self::Error> {
188        match value {
189            YarnValue::Number(value) => Ok(*value != 0.0),
190            YarnValue::String(value) => value.parse().map_err(Into::into),
191            YarnValue::Boolean(value) => Ok(*value),
192        }
193    }
194}
195
196impl From<bool> for YarnValue {
197    fn from(value: bool) -> Self {
198        Self::Boolean(value)
199    }
200}
201
202impl IntoYarnValueFromNonYarnValue for bool {
203    fn into_yarn_value(self) -> YarnValue {
204        self.into()
205    }
206}
207
208/// Represents a failure to convert one variant of [`YarnValue`] to a base type.
209#[derive(Debug)]
210#[allow(missing_docs)]
211pub enum YarnValueCastError {
212    ParseFloatError(core::num::ParseFloatError),
213    ParseIntError(core::num::ParseIntError),
214    ParseBoolError(core::str::ParseBoolError),
215}
216
217impl Error for YarnValueCastError {
218    fn source(&self) -> Option<&(dyn Error + 'static)> {
219        match self {
220            YarnValueCastError::ParseFloatError(e) => Some(e),
221            YarnValueCastError::ParseIntError(e) => Some(e),
222            YarnValueCastError::ParseBoolError(e) => Some(e),
223        }
224    }
225}
226
227impl Display for YarnValueCastError {
228    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
229        match self {
230            YarnValueCastError::ParseFloatError(e) => Display::fmt(e, f),
231            YarnValueCastError::ParseIntError(e) => Display::fmt(e, f),
232            YarnValueCastError::ParseBoolError(e) => Display::fmt(e, f),
233        }
234    }
235}
236
237impl From<core::num::ParseFloatError> for YarnValueCastError {
238    fn from(value: core::num::ParseFloatError) -> Self {
239        Self::ParseFloatError(value)
240    }
241}
242
243impl From<core::num::ParseIntError> for YarnValueCastError {
244    fn from(value: core::num::ParseIntError) -> Self {
245        Self::ParseIntError(value)
246    }
247}
248
249impl From<core::str::ParseBoolError> for YarnValueCastError {
250    fn from(value: core::str::ParseBoolError) -> Self {
251        Self::ParseBoolError(value)
252    }
253}
254
255impl Display for YarnValue {
256    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
257        match self {
258            Self::Number(value) => write!(f, "{value}"),
259            Self::String(value) => write!(f, "{value}"),
260            Self::Boolean(value) => write!(f, "{value}"),
261        }
262    }
263}