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(String),
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::String("hello".to_owned()).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) => {
88                let string = string.trim();
89
90                let number = if string.starts_with('-') {
91                    string
92                        .get(1..)
93                        .and_then(|string| string.parse::<NumberExpression>().ok())
94                        .map(|number| number.compute_value() * -1.0)
95                } else {
96                    string
97                        .parse::<NumberExpression>()
98                        .ok()
99                        .map(|number| number.compute_value())
100                };
101
102                number.map(LuaValue::Number)
103            }
104            _ => None,
105        }
106        .unwrap_or(self)
107    }
108
109    /// Attempt to convert the Lua value into a string value. This will convert numbers when
110    /// possible and return the same value otherwise.
111    pub fn string_coercion(self) -> Self {
112        match &self {
113            Self::Number(value) => Some(Self::String(format!("{}", value))),
114            _ => None,
115        }
116        .unwrap_or(self)
117    }
118}
119
120impl Default for LuaValue {
121    fn default() -> Self {
122        Self::Unknown
123    }
124}
125
126impl From<bool> for LuaValue {
127    fn from(value: bool) -> Self {
128        if value {
129            Self::True
130        } else {
131            Self::False
132        }
133    }
134}
135
136impl From<String> for LuaValue {
137    fn from(value: String) -> Self {
138        Self::String(value)
139    }
140}
141
142impl From<&str> for LuaValue {
143    fn from(value: &str) -> Self {
144        Self::String(value.to_owned())
145    }
146}
147
148impl From<f64> for LuaValue {
149    fn from(value: f64) -> Self {
150        Self::Number(value)
151    }
152}
153
154#[cfg(test)]
155mod test {
156    use super::*;
157
158    #[test]
159    fn unknown_lua_value_is_truthy_returns_none() {
160        assert!(LuaValue::Unknown.is_truthy().is_none());
161    }
162
163    #[test]
164    fn false_value_is_not_truthy() {
165        assert!(!LuaValue::False.is_truthy().unwrap());
166    }
167
168    #[test]
169    fn nil_value_is_not_truthy() {
170        assert!(!LuaValue::Nil.is_truthy().unwrap());
171    }
172
173    #[test]
174    fn true_value_is_truthy() {
175        assert!(LuaValue::True.is_truthy().unwrap());
176    }
177
178    #[test]
179    fn zero_value_is_truthy() {
180        assert!(LuaValue::Number(0_f64).is_truthy().unwrap());
181    }
182
183    #[test]
184    fn string_value_is_truthy() {
185        assert!(LuaValue::String("".to_owned()).is_truthy().unwrap());
186    }
187
188    #[test]
189    fn table_value_is_truthy() {
190        assert!(LuaValue::Table.is_truthy().unwrap());
191    }
192
193    mod number_coercion {
194        use super::*;
195
196        macro_rules! number_coercion {
197            ($($name:ident ($string:literal) => $result:expr),*) => {
198                $(
199                    #[test]
200                    fn $name() {
201                        assert_eq!(
202                            LuaValue::String($string.into()).number_coercion(),
203                            LuaValue::Number($result)
204                        );
205                    }
206                )*
207            };
208        }
209
210        macro_rules! no_number_coercion {
211            ($($name:ident ($string:literal)),*) => {
212                $(
213                    #[test]
214                    fn $name() {
215                        assert_eq!(
216                            LuaValue::String($string.into()).number_coercion(),
217                            LuaValue::String($string.into())
218                        );
219                    }
220                )*
221            };
222        }
223
224        number_coercion!(
225            zero("0") => 0.0,
226            integer("12") => 12.0,
227            integer_with_leading_zeros("00012") => 12.0,
228            integer_with_ending_space("12   ") => 12.0,
229            integer_with_leading_space("  123") => 123.0,
230            integer_with_leading_tab("\t123") => 123.0,
231            negative_integer("-3") => -3.0,
232            hex_zero("0x0") => 0.0,
233            hex_integer("0xA") => 10.0,
234            negative_hex_integer("-0xA") => -10.0,
235            float("0.5") => 0.5,
236            negative_float("-0.5") => -0.5,
237            float_starting_with_dot(".5") => 0.5
238        );
239
240        no_number_coercion!(
241            letter_suffix("123a"),
242            hex_prefix("0x"),
243            space_between_minus("- 1"),
244            two_seperated_digits(" 1 2")
245        );
246    }
247}