lua_macros/
lib.rs

1//! This crate contains useful macros for `lua` crate.
2//!
3//! ## Clean up the stack for scope
4//!
5//! Use `auto_cleanup` to revert top of stack to size before scope:
6//!
7//! ```rust
8//! # #[macro_use]
9//! # extern crate lua_macros;
10//! # use lua_macros::lua::State;
11//!
12//! fn main() {
13//!     let mut state = State::new();
14//!
15//!     state.push(1);
16//!
17//!     auto_cleanup!(state, {
18//!         state.push(2);
19//!         state.push(3);
20//!         state.push(4);
21//!         state.push(5);
22//!     });
23//!
24//!     assert_eq!(state.get_top(), 1);
25//! }
26//! ```
27//!
28//! ## Convert arguuments from lua
29//!
30//! Library has macro `convert_arguments` to convert arguments for
31//! any types which implemented `FromLua` trait:
32//!
33//! ```rust
34//! # #[macro_use]
35//! # extern crate lua_macros;
36//! # use lua_macros::lua::{State, Integer, Number};
37//! # use lua_macros::lua::ffi::lua_State;
38//! # use lua_macros::lua::libc::c_int;
39//!
40//! pub unsafe extern "C" fn fun_function(ls: *mut lua_State) -> c_int {
41//!     let mut state = State::from_ptr(ls);
42//!     let (_int, _float, _, _str) = convert_arguments!(state, Integer, Number, _, String)
43//!         .map_err(|pos| {
44//!             let msg = match pos {
45//!                 1 => "integer | integer expected as first argument",
46//!                 2 => "number | float expected as second argument",
47//!                 3 => "any | third argument expected",
48//!                 4 => "string | string expected as fourth argument",
49//!                 _ => "unknown argument for `do` funciton",
50//!             };
51//!             state.arg_error(pos, msg);
52//!         }).unwrap();
53//!     state.push_string("That's OK!");
54//!     1
55//! }
56//!
57//! fn main() {
58//!     let mut state = State::new();
59//!     state.push_fn(Some(fun_function));
60//!     state.set_global("fun");
61//!
62//!     assert!(state.do_string("return fun()").is_err());
63//!     assert!(state.to_str(-1).unwrap().contains("bad argument #1"));
64//!
65//!     assert!(state.do_string("return fun(1)").is_err());
66//!     assert!(state.to_str(-1).unwrap().contains("bad argument #2"));
67//!
68//!     assert!(state.do_string("return fun(1, 2.3)").is_err());
69//!     assert!(state.to_str(-1).unwrap().contains("bad argument #3"));
70//!
71//!     assert!(state.do_string("return fun(1, 2.3, function() end)").is_err());
72//!     assert!(state.to_str(-1).unwrap().contains("bad argument #4"));
73//!
74//!     assert!(!state.do_string("return fun(1, 2.3, {}, \"string\")").is_err());
75//!     assert!(state.to_str(-1).unwrap().contains("OK"));
76//!
77//!     assert!(state.do_string("return fun({}, 2.3, {}, \"string\")").is_err());
78//!     assert!(state.to_str(-1).unwrap().contains("bad argument #1"));
79//!
80//!     assert!(state.do_string("return fun(1, 2.3, {}, \"string\", \"extra\")").is_err());
81//!     assert!(state.to_str(-1).unwrap().contains("bad argument #5"));
82//! }
83//! ```
84//!
85//! ## Read `HashMap` from Lua's table
86//!
87//! Macro `lua_table_type` creates wrapper type to unpack tables:
88//!
89//! ```rust
90//! # #[macro_use]
91//! # extern crate lua_macros;
92//! # use lua_macros::lua::{State, Integer};
93//!
94//! lua_table_type!(UserTable<String, Integer>);
95//!
96//! fn main() {
97//!     let mut state = State::new();
98//!     state.do_string("return {one = 1, two = 2, three = 3}");
99//!     let UserTable(mut table) = state.to_type(-1).unwrap();
100//!     assert_eq!(table.remove("one"), Some(1));
101//!     assert_eq!(table.remove("two"), Some(2));
102//!     assert_eq!(table.remove("three"), Some(3));
103//! }
104//! ```
105//!
106//! ## Read `Vec` from Lua's array
107//!
108//! Macro `lua_array_type` creates wrapper type to unpack arrays:
109//!
110//! ```rust
111//! # #[macro_use]
112//! # extern crate lua_macros;
113//! # use lua_macros::lua::{State, Integer};
114//!
115//! lua_array_type!(UserArray<Integer>);
116//!
117//! fn main() {
118//!     let mut state = State::new();
119//!     state.do_string("return {1, 2, 3}");
120//!     let UserArray(array) = state.to_type(-1).unwrap();
121//!     assert_eq!(array[0], 1);
122//! }
123//! ```
124//!
125//! ## Adds own userdata
126//!
127//! Macro `lua_userdata` implements userdata features for type:
128//!
129//! ```rust
130//! # #[macro_use]
131//! # extern crate lua_macros;
132//! # use lua_macros::lua::{State, Integer};
133//!
134//! # #[derive(Clone, Debug, PartialEq)]
135//! enum UserEnum {
136//!   One,
137//!   Two,
138//!   Three,
139//! }
140//!
141//! lua_userdata!(UserEnum);
142//!
143//! fn main() {
144//!     let mut state = State::new();
145//!     UserEnum::attach(&mut state);
146//!
147//!     let ud = UserEnum::One;
148//!     state.push(ud);
149//!     state.push_nil();
150//!
151//!     let restored = state.to_type::<UserEnum>(-2).unwrap();
152//!     let wrong = state.to_type::<UserEnum>(-1);
153//!
154//!     assert_eq!(restored, UserEnum::One);
155//!     assert!(wrong.is_none());
156//! }
157//! ```
158
159
160pub extern crate lua;
161
162/// Clean up stack for the scope.
163#[macro_export]
164macro_rules! auto_cleanup {
165    ($state:ident, $b:block) => {{
166        let top = $state.get_top();
167        let result = $b;
168        $state.set_top(top);
169        result
170    }};
171}
172
173/// Convert arguments using `FromLua` trait.
174#[macro_export]
175macro_rules! convert_arguments {
176    (@strict $strict:expr, $state:ident, $($from:tt),+) => {{
177        use $crate::lua::Index;
178        let names = [$(stringify!($from),)+];
179        let quantity = names.len() as Index;
180        let top = $state.get_top();
181        auto_cleanup!($state, {
182            let mut collect = || {
183                let base = $state.get_top() - quantity;
184                if base < 0 {
185                    return Err(top + 1); // +1 because next arg expected
186                }
187                if $strict && base > 0 {
188                    return Err(top);
189                }
190                let mut position = 0;
191                let result = ($({
192                    position += 1;
193                    convert_arguments!(@unpack $from, $state, base, position)
194                },)+);
195                Ok(result)
196            };
197            collect()
198        })
199    }};
200    (@unpack _, $state:ident, $base:expr, $position:expr) => {()};
201    (@unpack $from:ty, $state:ident, $base:expr, $position:expr) => {{
202        let opt = $state.to_type::<$from>($base + $position);
203        match opt {
204            Some(v) => v,
205            None => {
206                return Err($position);
207            },
208        }
209    }};
210    ($state:ident, $($from:tt),+) =>
211        (convert_arguments!(@strict true, $state, $($from),+));
212}
213
214/// Makes wrapper to read table to hash map.
215///
216/// This macro add wrapper struct, because impossible to implement `FromLua` to `HashMap`
217/// because they are from other crates.
218#[macro_export]
219macro_rules! lua_table_type {
220    ($name:ident < $key:ty , $val:ty >) => {
221        pub struct $name(pub ::std::collections::HashMap<$key, $val>);
222
223        impl $crate::lua::FromLua for $name {
224            fn from_lua(state: &mut $crate::lua::State, index: $crate::lua::Index) -> Option<Self> {
225                if !state.is_table(index) {
226                    return None;
227                }
228                let mut map = ::std::collections::HashMap::new();
229                let index = state.abs_index(index);
230                state.push_nil();
231                while state.next(index) {
232                    // Non-strict, because this macro pushes to stack additional values
233                    if let Ok((name, value)) = convert_arguments!(@strict false, state, $key, $val) {
234                        map.insert(name, value);
235                        state.pop(1); // Pop `key` only
236                    } else {
237                        state.pop(2); // Pop `key` and `value`, because `next` call returned `true`
238                        return None;
239                    }
240                }
241                Some($name(map))
242            }
243        }
244    };
245}
246
247/// Makes wrapper to read table to array.
248#[macro_export]
249macro_rules! lua_array_type {
250    ($name:ident < $val:ty >) => {
251        pub struct $name(pub ::std::vec::Vec<$val>);
252
253        impl $crate::lua::FromLua for $name {
254            fn from_lua(state: &mut $crate::lua::State, index: $crate::lua::Index) -> Option<Self> {
255                if !state.is_table(index) {
256                    return None;
257                }
258                let mut vec = ::std::vec::Vec::new();
259                let index = state.abs_index(index);
260                for idx in 1.. {
261                    state.geti(index, idx);
262                    if state.is_nil(-1) {
263                        state.pop(1);
264                        break;
265                    }
266                    if let Ok((value,)) = convert_arguments!(@strict false, state, $val) {
267                        vec.push(value);
268                        state.pop(1);
269                    } else {
270                        state.pop(1);
271                        return None;
272                    }
273                }
274                Some($name(vec))
275            }
276        }
277
278        impl $crate::lua::ToLua for $name {
279            fn to_lua(&self, state: &mut $crate::lua::State) {
280                let $name(ref vec) = *self;
281                state.new_table();
282                let mut idx = 0;
283                for item in vec {
284                    idx += 1; // Starts from 1 too
285                    item.to_lua(state);
286                    state.raw_seti(-2, idx);
287                }
288            }
289        }
290    };
291}
292
293/// Add userdata's methods to user's type.
294#[macro_export]
295macro_rules! lua_userdata {
296    ($ud:ident $(, $field:expr => $func:ident )*) => {
297        impl $ud {
298            pub fn meta_name() -> &'static str {
299                concat!(stringify!($ud), ".Rust")
300            }
301
302            pub fn attach(state: &mut State) {
303                let created = state.new_metatable($ud::meta_name());
304                $(
305                state.push_fn(Some($func));
306                state.set_field(-2, $field);
307                )*
308                state.push_fn(Some($ud::drop_it));
309                state.set_field(-2, "__gc");
310                state.pop(1); // pop metatable
311                if !created {
312                    panic!("Metatable '{}' already exists.", $ud::meta_name());
313                }
314            }
315
316            // TODO This is not tested, because right __gc signature not described in Lua doc
317            unsafe extern "C" fn drop_it(state: *mut $crate::lua::ffi::lua_State) -> i32 {
318                let mut state = $crate::lua::State::from_ptr(state);
319                if let Some(ptr) = state.to_userdata_typed::<$ud>(-1) {
320                    ::std::ptr::drop_in_place(ptr);
321                }
322                0
323            }
324        }
325
326        impl $crate::lua::FromLua for $ud {
327            fn from_lua(state: &mut $crate::lua::State, index: $crate::lua::Index) -> Option<Self> {
328                unsafe {
329                    state.test_userdata_typed::<$ud>(index, $ud::meta_name())
330                        .map(|p| p.clone())
331                }
332            }
333        }
334
335        impl $crate::lua::ToLua for $ud {
336            fn to_lua(&self, state: &mut $crate::lua::State) {
337                unsafe {
338                    let pointer = state.new_userdata_typed();
339                    let uninit = ::std::ptr::replace(pointer, self.clone());
340                    ::std::mem::forget(uninit);
341                }
342                state.set_metatable_from_registry($ud::meta_name());
343            }
344        }
345    };
346}