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}