faf_replay_parser/
lua.rs

1//! Handling of lua types
2
3use ordered_hash_map::OrderedHashMap;
4
5use std::ffi::{CStr, CString, IntoStringError};
6use std::fmt::{Debug, Display};
7use std::hash::{Hash, Hasher};
8use std::string::FromUtf8Error;
9
10/// Byte marking that the serialized object is a Float
11pub const LUA_FLOAT_MARKER: u8 = 0;
12/// Byte marking that the serialized object is a String
13pub const LUA_STRING_MARKER: u8 = 1;
14/// Byte marking that the serialized object is Nil
15pub const LUA_NIL_MARKER: u8 = 2;
16/// Byte marking that the serialized object is a Boolean
17pub const LUA_BOOL_MARKER: u8 = 3;
18/// Byte marking that the serialized object is a Table
19pub const LUA_TABLE_MARKER: u8 = 4;
20/// Byte marking the end of a serialized Table
21pub const LUA_END_MARKER: u8 = 5;
22
23pub type LuaTable = OrderedHashMap<LuaObject, LuaObject>;
24
25/// Provides a simple syntax for constructing lua objects.
26///
27/// *This was inspired by the very similar `json!` macro from `serde_json`.*
28///
29/// # Examples
30/// ```rust
31/// use faf_replay_parser::{lua, lua::LuaObject};
32///
33/// // Basic types
34/// let obj1: LuaObject = lua!(10.0);
35/// let obj2: LuaObject = lua!(nil);
36/// let obj3: LuaObject = lua!(true);
37/// // By default, strings are converted to `LuaObject::Unicode`
38/// let obj4: LuaObject = lua!("foo");
39/// // `LuaObject::String`s need to be explicitly prefixed
40/// let obj5: LuaObject = lua!(cstr "foo");
41///
42/// // Tables use lua syntax
43/// let table1: LuaObject = lua! {
44///     foo = "bar",
45///     // Expression evaluation is supported
46///     bar = 2.0 + 3.0,
47/// };
48/// let table2: LuaObject = lua! {
49///     ["foo"] = "bar",
50///     [10.] = true,
51///     // Trailing comma optional
52///     [false] = 100.
53/// };
54/// ```
55#[macro_export]
56macro_rules! lua {
57    () => (
58        $crate::lua::LuaObject::Nil
59    );
60    (nil) => (
61        $crate::lua::LuaObject::Nil
62    );
63    ($item:expr) => (
64        $crate::lua::LuaObject::from($item)
65    );
66    (cstr $item:literal) => (
67        $crate::lua::LuaObject::from(std::ffi::CString::new($item).unwrap())
68    );
69    ($($key:ident = $val:expr),* $(,)?) => (
70        $crate::lua::LuaObject::Table(
71            <ordered_hash_map::OrderedHashMap<$crate::lua::LuaObject, $crate::lua::LuaObject> as std::iter::FromIterator<($crate::lua::LuaObject, $crate::lua::LuaObject)>>::from_iter([
72                $(
73                    (lua!(stringify!($key)), lua!($val))
74                ),*
75            ])
76        )
77    );
78    ($([$key:expr] = $val:expr),* $(,)?) => (
79        $crate::lua::LuaObject::Table(
80            <ordered_hash_map::OrderedHashMap<$crate::lua::LuaObject, $crate::lua::LuaObject> as std::iter::FromIterator<($crate::lua::LuaObject, $crate::lua::LuaObject)>>::from_iter([
81                $(
82                    (lua!($key), lua!($val))
83                ),*
84            ])
85        )
86    );
87}
88
89// TODO: Enumerate types for better error messages
90#[derive(Clone, Debug)]
91pub struct LuaTypeError {}
92
93#[derive(Clone, Debug)]
94pub enum LuaObject {
95    Float(f32),
96    String(CString),
97    Unicode(String),
98    Nil,
99    Bool(bool),
100    Table(LuaTable),
101}
102
103impl From<FromUtf8Error> for LuaTypeError {
104    fn from(_err: FromUtf8Error) -> LuaTypeError {
105        LuaTypeError {}
106    }
107}
108
109impl From<IntoStringError> for LuaTypeError {
110    fn from(_err: IntoStringError) -> LuaTypeError {
111        LuaTypeError {}
112    }
113}
114
115impl From<f32> for LuaObject {
116    fn from(data: f32) -> LuaObject {
117        LuaObject::Float(data)
118    }
119}
120
121impl From<CString> for LuaObject {
122    fn from(data: CString) -> LuaObject {
123        LuaObject::String(data)
124    }
125}
126
127impl From<String> for LuaObject {
128    fn from(data: String) -> LuaObject {
129        LuaObject::Unicode(data)
130    }
131}
132
133impl From<Box<CStr>> for LuaObject {
134    fn from(data: Box<CStr>) -> LuaObject {
135        LuaObject::String(data.into_c_string())
136    }
137}
138
139impl From<&str> for LuaObject {
140    fn from(data: &str) -> LuaObject {
141        LuaObject::Unicode(data.to_string())
142    }
143}
144
145impl From<bool> for LuaObject {
146    fn from(data: bool) -> LuaObject {
147        LuaObject::Bool(data)
148    }
149}
150
151impl Eq for LuaObject {}
152impl PartialEq for LuaObject {
153    fn eq(&self, other: &LuaObject) -> bool {
154        use LuaObject::*;
155
156        match (self, other) {
157            (Float(f1), Float(f2)) => f1.eq(f2),
158            // String comparisons
159            (String(s1), String(s2)) => s1.eq(s2),
160            (Unicode(s1), Unicode(s2)) => s1.eq(s2),
161            (String(s1), Unicode(s2)) => s1.as_bytes().eq(s2.as_bytes()),
162            (Unicode(s1), String(s2)) => s1.as_bytes().eq(s2.as_bytes()),
163            // end string comparisons
164            (Nil, Nil) => true,
165            (Bool(b1), Bool(b2)) => b1.eq(b2),
166            (Table(t1), Table(t2)) => t1.eq(t2),
167            _ => false,
168        }
169    }
170}
171
172impl Hash for LuaObject {
173    fn hash<H: Hasher>(&self, state: &mut H) {
174        use LuaObject::*;
175
176        match self {
177            Float(f) => unsafe {
178                let ptr = if f.is_nan() { &std::f32::NAN } else { f } as *const f32;
179                state.write(&*(ptr as *const [u8; 4]))
180            },
181            String(s) => s.hash(state),
182            Unicode(s) => s.hash(state),
183            Nil => ().hash(state),
184            Bool(b) => b.hash(state),
185            Table(_) => panic!("Unhashable type 'table'"),
186        }
187    }
188}
189
190impl Display for LuaObject {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        use LuaObject::*;
193
194        match self {
195            Float(f_) => Display::fmt(f_, f),
196            String(s) => write!(f, "{}", s.to_string_lossy()),
197            Unicode(s) => Display::fmt(s, f),
198            Nil => f.write_str("Nil"),
199            Bool(b) => Display::fmt(b, f),
200            Table(ref t) => f
201                .debug_map()
202                .entries(
203                    t.iter()
204                        .map(|(k, v)| (DebugTableInner(k), DebugTableInner(v))),
205                )
206                .finish(),
207        }
208    }
209}
210
211/// Return a debug representation for displaying the value in a lua table. Tables are displayed
212/// with the standard libraries `debug_map` formatter so the type we return needs to implement
213/// debug, even though it will be used in a `Display` implementation.
214struct DebugTableInner<'a>(&'a LuaObject);
215impl std::fmt::Debug for DebugTableInner<'_> {
216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        use LuaObject::*;
218
219        match self.0 {
220            String(ref i) => Debug::fmt(i, f),
221            Unicode(ref i) => Debug::fmt(i, f),
222            Bool(ref i) => Debug::fmt(i, f),
223            _ => Display::fmt(self.0, f),
224        }
225    }
226}
227
228impl LuaObject {
229    /// Evaluate the contents of this object.
230    /// For String and Table, returns false if the object is empty.
231    /// For Number returns false on `0`.
232    /// For Bool returns the bool.
233    /// For Nil returns false.
234    pub fn evaluate_as_bool(&self) -> bool {
235        use LuaObject::*;
236        match self {
237            Float(f) => *f == 0.0,
238            String(s) => !s.as_bytes().is_empty(),
239            Unicode(s) => !s.is_empty(),
240            Nil => false,
241            Bool(b) => *b,
242            Table(t) => !t.is_empty(),
243        }
244    }
245
246    pub fn as_float(&self) -> Result<f32, LuaTypeError> {
247        match self {
248            LuaObject::Float(f) => Ok(*f),
249            _ => Err(LuaTypeError {}),
250        }
251    }
252
253    pub fn into_string(self) -> Result<String, LuaTypeError> {
254        match self {
255            LuaObject::String(s) => Ok(s.into_string()?),
256            LuaObject::Unicode(s) => Ok(s),
257            _ => Err(LuaTypeError {}),
258        }
259    }
260
261    pub fn to_string(&self) -> Result<String, LuaTypeError> {
262        match self {
263            LuaObject::String(s) => Ok(String::from_utf8(s.as_bytes().to_vec())?),
264            LuaObject::Unicode(s) => Ok(s.clone()),
265            _ => Err(LuaTypeError {}),
266        }
267    }
268
269    pub fn to_string_lossy(&self) -> Result<String, LuaTypeError> {
270        match self {
271            LuaObject::String(s) => Ok(String::from_utf8_lossy(s.as_bytes()).to_string()),
272            LuaObject::Unicode(s) => Ok(s.clone()),
273            _ => Err(LuaTypeError {}),
274        }
275    }
276
277    pub fn as_nil(&self) -> Result<(), LuaTypeError> {
278        match self {
279            LuaObject::Nil => Ok(()),
280            _ => Err(LuaTypeError {}),
281        }
282    }
283
284    pub fn as_bool(&self) -> Result<bool, LuaTypeError> {
285        match self {
286            LuaObject::Bool(b) => Ok(*b),
287            _ => Err(LuaTypeError {}),
288        }
289    }
290
291    pub fn into_hashmap(self) -> Result<LuaTable, LuaTypeError> {
292        match self {
293            LuaObject::Table(t) => Ok(t),
294            _ => Err(LuaTypeError {}),
295        }
296    }
297
298    pub fn as_hashmap(&self) -> Result<&LuaTable, LuaTypeError> {
299        match self {
300            LuaObject::Table(ref t) => Ok(t),
301            _ => Err(LuaTypeError {}),
302        }
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::*;
309    use pretty_assertions::assert_eq;
310
311    use std::iter::FromIterator;
312
313    #[test]
314    fn test_macro_simple() {
315        assert_eq!(lua!(10.), LuaObject::Float(10.));
316        assert_eq!(
317            lua!(cstr "foo"),
318            LuaObject::String(CString::new("foo").unwrap())
319        );
320        assert_eq!(lua!("foo"), LuaObject::Unicode("foo".to_string()));
321        assert_eq!(lua!(), LuaObject::Nil);
322        assert_eq!(lua!(nil), LuaObject::Nil);
323        assert_eq!(lua!(false), LuaObject::Bool(false));
324        assert_eq!(lua!(10.), LuaObject::Float(10.));
325    }
326
327    #[test]
328    fn test_macro_table_sugar() {
329        let table = lua! {
330            foo = 1.0,
331            bar = 2.0 + 3.0
332        };
333
334        let expected = LuaObject::Table(OrderedHashMap::from_iter([
335            (LuaObject::Unicode("foo".to_string()), LuaObject::Float(1.0)),
336            (LuaObject::Unicode("bar".to_string()), LuaObject::Float(5.0)),
337        ]));
338
339        assert_eq!(table, expected);
340    }
341
342    #[test]
343    fn test_macro_table_verbose() {
344        let table = lua! {
345            ["foo"] = "bar",
346            [10.] = true,
347            [false] = 100.
348        };
349
350        let expected = LuaObject::Table(OrderedHashMap::from_iter([
351            (
352                LuaObject::Unicode("foo".to_string()),
353                LuaObject::Unicode("bar".to_string()),
354            ),
355            (LuaObject::Float(10.), LuaObject::Bool(true)),
356            (LuaObject::Bool(false), LuaObject::Float(100.)),
357        ]));
358
359        assert_eq!(table, expected);
360    }
361
362    #[test]
363    fn test_display() {
364        assert_eq!(format!("{}", LuaObject::Float(100.)), "100");
365        assert_eq!(
366            format!(
367                "{}",
368                LuaObject::String(CString::new("Hello World").unwrap())
369            ),
370            "Hello World"
371        );
372        assert_eq!(
373            format!("{}", LuaObject::Unicode("Hello World".into())),
374            "Hello World"
375        );
376        assert_eq!(format!("{}", LuaObject::Nil), "Nil");
377        assert_eq!(format!("{}", LuaObject::Bool(false)), "false");
378        assert_eq!(format!("{}", LuaObject::Bool(true)), "true");
379        assert_eq!(format!("{}", LuaObject::Table(LuaTable::new())), "{}");
380        assert_eq!(format!("{}", LuaObject::Table(LuaTable::new())), "{}");
381        // Complex nested table
382        let mut table = LuaTable::new();
383        table.insert(LuaObject::Nil, LuaObject::Float(100.));
384        table.insert(LuaObject::Unicode("Foo Bar".into()), LuaObject::Bool(false));
385        table.insert(
386            LuaObject::String(CString::new("Hello World").unwrap()),
387            LuaObject::Unicode("A new day".into()),
388        );
389        let mut inner_table = LuaTable::new();
390        inner_table.insert(LuaObject::Float(1.), LuaObject::Bool(true));
391        table.insert(LuaObject::Float(2.), LuaObject::Table(inner_table));
392        assert_eq!(
393            format!("{}", LuaObject::Table(table)),
394            "{Nil: 100, \"Foo Bar\": false, \"Hello World\": \"A new day\", 2: {1: true}}"
395        );
396    }
397}