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    /// Returns the length of a Lua value (equivalent to the `#` operator).
121    pub fn length(&self) -> LuaValue {
122        match self {
123            Self::String(value) => LuaValue::Number(value.len() as f64),
124            _ => LuaValue::Unknown,
125        }
126    }
127}
128
129impl Default for LuaValue {
130    fn default() -> Self {
131        Self::Unknown
132    }
133}
134
135impl From<bool> for LuaValue {
136    fn from(value: bool) -> Self {
137        if value {
138            Self::True
139        } else {
140            Self::False
141        }
142    }
143}
144
145impl From<String> for LuaValue {
146    fn from(value: String) -> Self {
147        Self::String(value.into_bytes())
148    }
149}
150
151impl From<&str> for LuaValue {
152    fn from(value: &str) -> Self {
153        Self::String(value.as_bytes().to_vec())
154    }
155}
156
157impl From<Vec<u8>> for LuaValue {
158    fn from(value: Vec<u8>) -> Self {
159        Self::String(value)
160    }
161}
162
163impl From<&[u8]> for LuaValue {
164    fn from(value: &[u8]) -> Self {
165        Self::String(value.to_vec())
166    }
167}
168
169impl<const N: usize> From<&[u8; N]> for LuaValue {
170    fn from(value: &[u8; N]) -> Self {
171        Self::String(value.to_vec())
172    }
173}
174
175impl From<f64> for LuaValue {
176    fn from(value: f64) -> Self {
177        Self::Number(value)
178    }
179}
180
181#[cfg(test)]
182mod test {
183    use super::*;
184
185    #[test]
186    fn unknown_lua_value_is_truthy_returns_none() {
187        assert!(LuaValue::Unknown.is_truthy().is_none());
188    }
189
190    #[test]
191    fn false_value_is_not_truthy() {
192        assert!(!LuaValue::False.is_truthy().unwrap());
193    }
194
195    #[test]
196    fn nil_value_is_not_truthy() {
197        assert!(!LuaValue::Nil.is_truthy().unwrap());
198    }
199
200    #[test]
201    fn true_value_is_truthy() {
202        assert!(LuaValue::True.is_truthy().unwrap());
203    }
204
205    #[test]
206    fn zero_value_is_truthy() {
207        assert!(LuaValue::Number(0_f64).is_truthy().unwrap());
208    }
209
210    #[test]
211    fn string_value_is_truthy() {
212        assert!(LuaValue::String(b"".to_vec()).is_truthy().unwrap());
213    }
214
215    #[test]
216    fn table_value_is_truthy() {
217        assert!(LuaValue::Table.is_truthy().unwrap());
218    }
219
220    mod number_coercion {
221        use super::*;
222
223        macro_rules! number_coercion {
224            ($($name:ident ($string:literal) => $result:expr),*) => {
225                $(
226                    #[test]
227                    fn $name() {
228                        assert_eq!(
229                            LuaValue::String($string.into()).number_coercion(),
230                            LuaValue::Number($result)
231                        );
232                    }
233                )*
234            };
235        }
236
237        macro_rules! no_number_coercion {
238            ($($name:ident ($string:literal)),*) => {
239                $(
240                    #[test]
241                    fn $name() {
242                        assert_eq!(
243                            LuaValue::String($string.into()).number_coercion(),
244                            LuaValue::String($string.into())
245                        );
246                    }
247                )*
248            };
249        }
250
251        number_coercion!(
252            zero("0") => 0.0,
253            integer("12") => 12.0,
254            integer_with_leading_zeros("00012") => 12.0,
255            integer_with_ending_space("12   ") => 12.0,
256            integer_with_leading_space("  123") => 123.0,
257            integer_with_leading_tab("\t123") => 123.0,
258            negative_integer("-3") => -3.0,
259            hex_zero("0x0") => 0.0,
260            hex_integer("0xA") => 10.0,
261            negative_hex_integer("-0xA") => -10.0,
262            float("0.5") => 0.5,
263            negative_float("-0.5") => -0.5,
264            float_starting_with_dot(".5") => 0.5
265        );
266
267        no_number_coercion!(
268            letter_suffix("123a"),
269            hex_prefix("0x"),
270            space_between_minus("- 1"),
271            two_seperated_digits(" 1 2")
272        );
273    }
274}