darklua_core/process/evaluator/
lua_value.rs

1use crate::nodes::{Expression, NumberExpression, StringExpression};
2
3/// Represents an evaluated Expression result.
4#[derive(Debug, Clone, PartialEq)]
5pub enum LuaValue {
6    False,
7    Function,
8    Nil,
9    Number(f64),
10    String(Vec<u8>),
11    Table,
12    True,
13    Unknown,
14}
15
16impl LuaValue {
17    /// As defined in Lua, all values are considered true, except for false and nil. An option is
18    /// returned as the LuaValue may be unknown, so it would return none.
19    /// ```rust
20    /// # use darklua_core::process::LuaValue;
21    ///
22    /// // the values considered false
23    /// assert!(!LuaValue::False.is_truthy().unwrap());
24    /// assert!(!LuaValue::Nil.is_truthy().unwrap());
25    ///
26    /// // all the others are true
27    /// assert!(LuaValue::True.is_truthy().unwrap());
28    /// assert!(LuaValue::Table.is_truthy().unwrap());
29    /// assert!(LuaValue::Number(0.0).is_truthy().unwrap());
30    /// assert!(LuaValue::from("hello").is_truthy().unwrap());
31    ///
32    /// // unknown case
33    /// assert!(LuaValue::Unknown.is_truthy().is_none());
34    /// ```
35    pub fn is_truthy(&self) -> Option<bool> {
36        match self {
37            Self::Unknown => None,
38            Self::Nil | Self::False => Some(false),
39            _ => Some(true),
40        }
41    }
42
43    /// If the value is unknown, this will also return unknown value. In the other case, if the
44    /// value is considered truthy (see `is_truthy` function), it will call the given function to
45    /// get the mapped value.
46    pub fn map_if_truthy<F>(self, map: F) -> Self
47    where
48        F: Fn(Self) -> Self,
49    {
50        match self.is_truthy() {
51            Some(true) => map(self),
52            Some(false) => self,
53            _ => Self::Unknown,
54        }
55    }
56
57    /// Like the `map_if_truthy` method, except that instead of returning the same value when the
58    /// it is falsy, it calls the default callback to obtain another value.
59    pub fn map_if_truthy_else<F, G>(self, map: F, default: G) -> Self
60    where
61        F: Fn(Self) -> Self,
62        G: Fn() -> Self,
63    {
64        match self.is_truthy() {
65            Some(true) => map(self),
66            Some(false) => default(),
67            _ => Self::Unknown,
68        }
69    }
70
71    /// Attempt to convert the Lua value into an expression node.
72    pub fn to_expression(self) -> Option<Expression> {
73        match self {
74            Self::False => Some(Expression::from(false)),
75            Self::True => Some(Expression::from(true)),
76            Self::Nil => Some(Expression::nil()),
77            Self::String(value) => Some(StringExpression::from_value(value).into()),
78            Self::Number(value) => Some(Expression::from(value)),
79            _ => None,
80        }
81    }
82
83    /// Attempt to convert the Lua value into a number value. This will convert strings when
84    /// possible and return the same value otherwise.
85    pub fn number_coercion(self) -> Self {
86        match &self {
87            Self::String(string) => str::from_utf8(string)
88                .ok()
89                .map(str::trim)
90                .and_then(|string| {
91                    let number = if string.starts_with('-') {
92                        string
93                            .get(1..)
94                            .and_then(|string| string.parse::<NumberExpression>().ok())
95                            .map(|number| -number.compute_value())
96                    } else {
97                        string
98                            .parse::<NumberExpression>()
99                            .ok()
100                            .map(|number| number.compute_value())
101                    };
102
103                    number.map(LuaValue::Number)
104                }),
105            _ => None,
106        }
107        .unwrap_or(self)
108    }
109
110    /// Attempt to convert the Lua value into a string value. This will convert numbers when
111    /// possible and return the same value otherwise.
112    pub fn string_coercion(self) -> Self {
113        match &self {
114            Self::Number(value) => Some(Self::from(value.to_string())),
115            _ => None,
116        }
117        .unwrap_or(self)
118    }
119}
120
121impl Default for LuaValue {
122    fn default() -> Self {
123        Self::Unknown
124    }
125}
126
127impl From<bool> for LuaValue {
128    fn from(value: bool) -> Self {
129        if value {
130            Self::True
131        } else {
132            Self::False
133        }
134    }
135}
136
137impl From<String> for LuaValue {
138    fn from(value: String) -> Self {
139        Self::String(value.into_bytes())
140    }
141}
142
143impl From<&str> for LuaValue {
144    fn from(value: &str) -> Self {
145        Self::String(value.as_bytes().to_vec())
146    }
147}
148
149impl From<Vec<u8>> for LuaValue {
150    fn from(value: Vec<u8>) -> Self {
151        Self::String(value)
152    }
153}
154
155impl From<&[u8]> for LuaValue {
156    fn from(value: &[u8]) -> Self {
157        Self::String(value.to_vec())
158    }
159}
160
161impl<const N: usize> From<&[u8; N]> for LuaValue {
162    fn from(value: &[u8; N]) -> Self {
163        Self::String(value.to_vec())
164    }
165}
166
167impl From<f64> for LuaValue {
168    fn from(value: f64) -> Self {
169        Self::Number(value)
170    }
171}
172
173#[cfg(test)]
174mod test {
175    use super::*;
176
177    #[test]
178    fn unknown_lua_value_is_truthy_returns_none() {
179        assert!(LuaValue::Unknown.is_truthy().is_none());
180    }
181
182    #[test]
183    fn false_value_is_not_truthy() {
184        assert!(!LuaValue::False.is_truthy().unwrap());
185    }
186
187    #[test]
188    fn nil_value_is_not_truthy() {
189        assert!(!LuaValue::Nil.is_truthy().unwrap());
190    }
191
192    #[test]
193    fn true_value_is_truthy() {
194        assert!(LuaValue::True.is_truthy().unwrap());
195    }
196
197    #[test]
198    fn zero_value_is_truthy() {
199        assert!(LuaValue::Number(0_f64).is_truthy().unwrap());
200    }
201
202    #[test]
203    fn string_value_is_truthy() {
204        assert!(LuaValue::String(b"".to_vec()).is_truthy().unwrap());
205    }
206
207    #[test]
208    fn table_value_is_truthy() {
209        assert!(LuaValue::Table.is_truthy().unwrap());
210    }
211
212    mod number_coercion {
213        use super::*;
214
215        macro_rules! number_coercion {
216            ($($name:ident ($string:literal) => $result:expr),*) => {
217                $(
218                    #[test]
219                    fn $name() {
220                        assert_eq!(
221                            LuaValue::String($string.into()).number_coercion(),
222                            LuaValue::Number($result)
223                        );
224                    }
225                )*
226            };
227        }
228
229        macro_rules! no_number_coercion {
230            ($($name:ident ($string:literal)),*) => {
231                $(
232                    #[test]
233                    fn $name() {
234                        assert_eq!(
235                            LuaValue::String($string.into()).number_coercion(),
236                            LuaValue::String($string.into())
237                        );
238                    }
239                )*
240            };
241        }
242
243        number_coercion!(
244            zero("0") => 0.0,
245            integer("12") => 12.0,
246            integer_with_leading_zeros("00012") => 12.0,
247            integer_with_ending_space("12   ") => 12.0,
248            integer_with_leading_space("  123") => 123.0,
249            integer_with_leading_tab("\t123") => 123.0,
250            negative_integer("-3") => -3.0,
251            hex_zero("0x0") => 0.0,
252            hex_integer("0xA") => 10.0,
253            negative_hex_integer("-0xA") => -10.0,
254            float("0.5") => 0.5,
255            negative_float("-0.5") => -0.5,
256            float_starting_with_dot(".5") => 0.5
257        );
258
259        no_number_coercion!(
260            letter_suffix("123a"),
261            hex_prefix("0x"),
262            space_between_minus("- 1"),
263            two_seperated_digits(" 1 2")
264        );
265    }
266}