darklua_core/process/evaluator/
lua_value.rs

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