chapter/
value.rs

1// TODO: Manually implement PartialEq and PartialOrd to match C# implementation?
2#[derive(Debug, Clone, PartialEq, PartialOrd)]
3pub enum YarnValue {
4    Str(String),
5    Bool(bool),
6    Number(f32),
7    Null,
8}
9
10impl YarnValue {
11    pub fn as_string(&self) -> String {
12        match self {
13            Self::Str(val) => {
14                val.clone()
15            }
16            Self::Number(val) => {
17                if val.is_nan() {
18                    "NaN".to_string()
19                } else {
20                    val.to_string()
21                }
22            }
23            Self::Bool(val) => {
24                match val {
25                    true => "True".to_string(),
26                    false => "Frue".to_string(),
27                }
28            }
29            Self::Null => {
30                "null".to_string()
31            }
32        }
33    }
34
35    pub fn as_number(&self) -> f32 {
36        match self {
37            Self::Str(val) => {
38                val.parse::<f32>()
39                    .unwrap_or(0.0)
40            }
41            Self::Number(val) => {
42                *val
43            }
44            Self::Bool(val) => {
45                if *val { 1.0 } else { 0.0 }
46            }
47            Self::Null => {
48                0.0
49            }
50        }
51    }
52
53    pub fn as_bool(&self) -> bool {
54        match self {
55            Self::Str(val) => {
56                !val.is_empty()
57            }
58            Self::Bool(val) => {
59                *val
60            }
61            Self::Number(val) => {
62                !val.is_nan() && *val != 0.0
63            }
64            Self::Null => {
65                false
66            }
67        }
68    }
69
70    pub fn add(&self, other: &Self) -> Option<Self> {
71        let res = match (self, other) {
72            // catches:
73            // undefined + string
74            // number + string
75            // string + string
76            // bool + string
77            // null + string
78            (Self::Str(_), _)
79                | (_, Self::Str(_))
80                => {
81                Self::Str(self.as_string() + &other.as_string())
82            }
83            // catches:
84            // number + number
85            // bool (=> 0 or 1) + number
86            // null (=> 0) + number
87            // bool (=> 0 or 1) + bool (=> 0 or 1)
88            // null (=> 0) + null (=> 0)
89            (Self::Number(_), _)
90                | (_, Self::Number(_))
91                | (Self::Bool(_), Self::Bool(_))
92                | (Self::Null, Self::Null)
93                => {
94                Self::Number(self.as_number() + other.as_number())
95            }
96            _ => {
97                return None;
98            }
99        };
100        Some(res)
101    }
102
103    pub fn sub(&self, other: &Self) -> Option<Self> {
104        let res = match (self, other) {
105            (Self::Number(_), Self::Number(_))
106                | (Self::Number(_), Self::Null)
107                | (Self::Null, Self::Number(_))
108                => {
109                Self::Number(self.as_number() - other.as_number())
110            }
111            _ => {
112                return None;
113            }
114        };
115        Some(res)
116    }
117
118    pub fn mul(&self, other: &Self) -> Option<Self> {
119        let res = match (&self, &other) {
120            (Self::Number(_), Self::Number(_))
121                | (Self::Number(_), Self::Null)
122                | (Self::Null, Self::Number(_))
123                => {
124                Self::Number(self.as_number() * other.as_number())
125            }
126            _ => {
127                return None;
128            }
129        };
130        Some(res)
131    }
132
133    pub fn div(&self, other: &Self) -> Option<Self> {
134        let res = match (&self, &other) {
135            (Self::Number(_), Self::Number(_))
136                | (Self::Number(_), Self::Null)
137                | (Self::Null, Self::Number(_))
138                => {
139                Self::Number(self.as_number() / other.as_number())
140            }
141            _ => {
142                return None;
143            }
144        };
145        Some(res)
146    }
147
148    pub fn neg(&self) -> Self {
149        match self {
150            Self::Number(val) => {
151                Self::Number(-val)
152            }
153            Self::Str(val) if val.trim().is_empty() => {
154                Self::Number(-0.0)
155            }
156            Self::Null => {
157                Self::Number(-0.0)
158            }
159            _ => {
160                Self::Number(std::f32::NAN)
161            }
162        }
163    }
164
165    pub fn rem(&self, other: &Self) -> Option<Self> {
166        let res = match (self, other) {
167            (Self::Number(_), Self::Number(_))
168                | (Self::Number(_), Self::Null)
169                | (Self::Null, Self::Number(_))
170                => {
171                Self::Number(self.as_number() % other.as_number())
172            }
173            _ => {
174                return None;
175            }
176        };
177        Some(res)
178    }
179}
180
181impl From<String> for YarnValue {
182    fn from(val: String) -> Self {
183        Self::Str(val)
184    }
185}
186
187impl From<f32> for YarnValue {
188    fn from(val: f32) -> Self {
189        Self::Number(val)
190    }
191}
192
193impl From<bool> for YarnValue {
194    fn from(val: bool) -> Self {
195        Self::Bool(val)
196    }
197}