use std::fmt; use std::ops::{Deref, DerefMut}; use std::iter::FromIterator; use std::cell::{RefCell, Ref, RefMut}; use std::ptr; use std::mem; use std::ffi::{CStr, CString}; use std::any::TypeId; use std::marker::PhantomData; use std::collections::{HashMap, VecDeque}; use std::collections::hash_map::Entry as HashMapEntry; use std::os::raw::{c_char, c_int, c_void}; use ffi; use error::*; use util::*; pub type LuaInteger = ffi::lua_Integer; pub type LuaNumber = ffi::lua_Number; /// A rust-side handle to an internal Lua value. #[derive(Debug, Clone)] pub enum LuaValue<'lua> { Nil, Boolean(bool), Integer(LuaInteger), Number(LuaNumber), String(LuaString<'lua>), Table(LuaTable<'lua>), Function(LuaFunction<'lua>), UserData(LuaUserData<'lua>), Thread(LuaThread<'lua>), } pub use self::LuaValue::Nil as LuaNil; /// Trait for types convertible to LuaValue pub trait ToLua<'a> { fn to_lua(self, lua: &'a Lua) -> LuaResult>; } /// Trait for types convertible from LuaValue pub trait FromLua<'a>: Sized { fn from_lua(lua_value: LuaValue<'a>, lua: &'a Lua) -> LuaResult; } /// Multiple lua values used for both argument passing and also for multiple return values. #[derive(Debug, Clone)] pub struct LuaMultiValue<'lua>(VecDeque>); impl<'lua> LuaMultiValue<'lua> { pub fn new() -> LuaMultiValue<'lua> { LuaMultiValue(VecDeque::new()) } } impl<'lua> FromIterator> for LuaMultiValue<'lua> { fn from_iter>>(iter: I) -> Self { LuaMultiValue(VecDeque::from_iter(iter)) } } impl<'lua> IntoIterator for LuaMultiValue<'lua> { type Item = LuaValue<'lua>; type IntoIter = > as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl<'lua> Deref for LuaMultiValue<'lua> { type Target = VecDeque>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'lua> DerefMut for LuaMultiValue<'lua> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } pub trait ToLuaMulti<'a> { fn to_lua_multi(self, lua: &'a Lua) -> LuaResult>; } pub trait FromLuaMulti<'a>: Sized { fn from_lua_multi(values: LuaMultiValue<'a>, lua: &'a Lua) -> LuaResult; } type LuaCallback = Box FnMut(&'lua Lua, LuaMultiValue<'lua>) -> LuaResult>>; struct LuaRef<'lua> { lua: &'lua Lua, registry_id: c_int, } impl<'lua> fmt::Debug for LuaRef<'lua> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "LuaRef({})", self.registry_id) } } impl<'lua> Clone for LuaRef<'lua> { fn clone(&self) -> Self { self.lua.push_ref(self); self.lua.pop_ref() } } impl<'lua> Drop for LuaRef<'lua> { fn drop(&mut self) { unsafe { ffi::luaL_unref(self.lua.state, ffi::LUA_REGISTRYINDEX, self.registry_id); } } } /// Handle to an an internal lua string #[derive(Clone, Debug)] pub struct LuaString<'lua>(LuaRef<'lua>); impl<'lua> LuaString<'lua> { pub fn get(&self) -> LuaResult<&str> { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { check_stack(lua.state, 1)?; lua.push_ref(&self.0); assert_eq!(ffi::lua_type(lua.state, -1), ffi::LUA_TSTRING); let s = CStr::from_ptr(ffi::lua_tostring(lua.state, -1)).to_str()?; ffi::lua_pop(lua.state, 1); Ok(s) }) } } } /// Handle to an an internal lua table #[derive(Clone, Debug)] pub struct LuaTable<'lua>(LuaRef<'lua>); impl<'lua> LuaTable<'lua> { pub fn set, V: ToLua<'lua>>(&self, key: K, value: V) -> LuaResult<()> { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { check_stack(lua.state, 3)?; lua.push_ref(&self.0); lua.push_value(key.to_lua(lua)?)?; lua.push_value(value.to_lua(lua)?)?; ffi::lua_settable(lua.state, -3); ffi::lua_pop(lua.state, 1); Ok(()) }) } } pub fn get, V: FromLua<'lua>>(&self, key: K) -> LuaResult { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { check_stack(lua.state, 2)?; lua.push_ref(&self.0); lua.push_value(key.to_lua(lua)?)?; ffi::lua_gettable(lua.state, -2); let res = V::from_lua(lua.pop_value()?, lua)?; ffi::lua_pop(lua.state, 1); Ok(res) }) } } /// Equivalent to the result of the lua '#' operator. pub fn length(&self) -> LuaResult { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { check_stack(lua.state, 1)?; lua.push_ref(&self.0); ffi::lua_len(lua.state, -1); let len = ffi::lua_tointeger(lua.state, -1); ffi::lua_pop(lua.state, 2); Ok(len) }) } } pub fn pairs, V: FromLua<'lua>>(&self) -> LuaResult> { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { let mut pairs = Vec::new(); check_stack(lua.state, 4)?; lua.push_ref(&self.0); ffi::lua_pushnil(lua.state); while ffi::lua_next(lua.state, -2) != 0 { ffi::lua_pushvalue(lua.state, -2); let key = K::from_lua(lua.pop_value()?, lua)?; let value = V::from_lua(lua.pop_value()?, lua)?; pairs.push((key, value)); } ffi::lua_pop(lua.state, 1); Ok(pairs) }) } } /// Strictly interpret the table as an array, and fail if it is not a proper lua array. pub fn array_values>(&self) -> LuaResult> { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { let mut values = Vec::new(); check_stack(lua.state, 4)?; lua.push_ref(&self.0); ffi::lua_len(lua.state, -1); let len = ffi::lua_tointeger(lua.state, -1); ffi::lua_pop(lua.state, 1); ffi::lua_pushnil(lua.state); while ffi::lua_next(lua.state, -2) != 0 { let mut isnum = 0; let i = ffi::lua_tointegerx(lua.state, -2, &mut isnum); if isnum == 0 { return Err("Not all table keys are integers".into()); } else if i > len { return Err("integer key in table is greater than length".into()); } else if i <= 0 { return Err("integer key in table is less than 1".into()); } // Skip missing keys while values.len() < (i - 1) as usize { values.push(V::from_lua(LuaNil, lua)?); } values.push(V::from_lua(lua.pop_value()?, lua)?); } ffi::lua_pop(lua.state, 1); Ok(values) }) } } } /// Handle to an an internal lua function #[derive(Clone, Debug)] pub struct LuaFunction<'lua>(LuaRef<'lua>); impl<'lua> LuaFunction<'lua> { pub fn call, R: FromLuaMulti<'lua>>(&self, args: A) -> LuaResult { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { let args = args.to_lua_multi(lua)?; let nargs = args.len() as c_int; check_stack(lua.state, nargs + 1)?; let stack_start = ffi::lua_gettop(lua.state); lua.push_ref(&self.0); for arg in args { lua.push_value(arg)?; } handle_error(lua.state, pcall_with_traceback(lua.state, nargs, ffi::LUA_MULTRET))?; let nresults = ffi::lua_gettop(lua.state) - stack_start; let mut results = LuaMultiValue::new(); for _ in 0..nresults { results.push_front(lua.pop_value()?); } R::from_lua_multi(results, lua) }) } } pub fn bind>(&self, args: A) -> LuaResult> { unsafe extern "C" fn bind_call_impl(state: *mut ffi::lua_State) -> c_int { let nargs = ffi::lua_gettop(state); let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(2)) as c_int; check_stack(state, nbinds + 1).expect("not enough space to handle bound arguments"); ffi::lua_pushvalue(state, ffi::lua_upvalueindex(1)); ffi::lua_insert(state, 1); // TODO: This is quadratic for i in 0..nbinds { ffi::lua_pushvalue(state, ffi::lua_upvalueindex(i + 3)); ffi::lua_insert(state, i + 2); } ffi::lua_call(state, nargs + nbinds, ffi::LUA_MULTRET); ffi::lua_gettop(state) } let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { let args = args.to_lua_multi(lua)?; let nargs = args.len() as c_int; check_stack(lua.state, nargs + 2)?; lua.push_ref(&self.0); ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer); for arg in args { lua.push_value(arg)?; } ffi::lua_pushcclosure(lua.state, bind_call_impl, nargs + 2); Ok(LuaFunction(lua.pop_ref())) }) } } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum LuaThreadStatus { Dead, Active, Error, } /// Handle to an an internal lua coroutine #[derive(Clone, Debug)] pub struct LuaThread<'lua>(LuaRef<'lua>); impl<'lua> LuaThread<'lua> { /// If this thread has yielded a value, will return Some, otherwise the thread is finished and /// this will return None. pub fn resume, R: FromLuaMulti<'lua>>(&self, args: A) -> LuaResult> { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { check_stack(lua.state, 1)?; lua.push_ref(&self.0); let thread_state = ffi::lua_tothread(lua.state, -1); ffi::lua_pop(lua.state, 1); let args = args.to_lua_multi(lua)?; let nargs = args.len() as c_int; check_stack(thread_state, nargs)?; for arg in args { push_value(thread_state, arg)?; } handle_error(lua.state, resume_with_traceback(thread_state, lua.state, nargs))?; let nresults = ffi::lua_gettop(thread_state); let mut results = LuaMultiValue::new(); for _ in 0..nresults { results.push_front(pop_value(thread_state, lua)?); } R::from_lua_multi(results, lua).map(|r| Some(r)) }) } } pub fn status(&self) -> LuaResult { let lua = self.0.lua; unsafe { stack_guard(lua.state, 0, || { check_stack(lua.state, 1)?; lua.push_ref(&self.0); let thread_state = ffi::lua_tothread(lua.state, -1); ffi::lua_pop(lua.state, 1); let status = ffi::lua_status(thread_state); if status != ffi::LUA_OK && status != ffi::LUA_YIELD { Ok(LuaThreadStatus::Error) } else if status == ffi::LUA_YIELD || ffi::lua_gettop(thread_state) > 0 { Ok(LuaThreadStatus::Active) } else { Ok(LuaThreadStatus::Dead) } }) } } } /// These are the metamethods that can be overridden using this API #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum LuaMetaMethod { Add, Sub, Mul, Div, Mod, Pow, Unm, Concat, Len, Eq, Lt, Le, Index, NewIndex, Call, } /// Methods added will be added to the __index table on the metatable for the userdata, so they can /// be called as userdata:method(args) as expected. If there are any regular methods, and an /// "Index" metamethod is given, it will be called as a *fallback* if the index doesn't match an /// existing regular method. pub struct LuaUserDataMethods { methods: HashMap, meta_methods: HashMap, _type: PhantomData, } impl LuaUserDataMethods { /// Add a regular method as a function which accepts a &T parameter pub fn add_method(&mut self, name: &str, method: M) where M: 'static + for<'a, 'lua> FnMut(&'lua Lua, &'a T, LuaMultiValue<'lua>) -> LuaResult> { self.methods .insert(name.to_owned(), Self::box_method(method)); } /// Add a regular method as a function which accepts a &mut T parameter pub fn add_method_mut(&mut self, name: &str, method: M) where M: 'static + for<'a, 'lua> FnMut(&'lua Lua, &'a mut T, LuaMultiValue<'lua>) -> LuaResult> { self.methods .insert(name.to_owned(), Self::box_method_mut(method)); } /// Add a regular method as a function which accepts generic arguments, the first argument will /// always be a LuaUserData of real type T pub fn add_function(&mut self, name: &str, function: F) where F: 'static + for<'a, 'lua> FnMut(&'lua Lua, LuaMultiValue<'lua>) -> LuaResult> { self.methods.insert(name.to_owned(), Box::new(function)); } /// Add a metamethod as a function which accepts a &T parameter pub fn add_meta_method(&mut self, meta: LuaMetaMethod, method: M) where M: 'static + for<'a, 'lua> FnMut(&'lua Lua, &'a T, LuaMultiValue<'lua>) -> LuaResult> { self.meta_methods.insert(meta, Self::box_method(method)); } /// Add a metamethod as a function which accepts a &mut T parameter pub fn add_meta_method_mut(&mut self, meta: LuaMetaMethod, method: M) where M: 'static + for<'a, 'lua> FnMut(&'lua Lua, &'a mut T, LuaMultiValue<'lua>) -> LuaResult> { self.meta_methods.insert(meta, Self::box_method_mut(method)); } /// Add a metamethod as a function which accepts generic arguments, the first argument will /// always be a LuaUserData of real type T pub fn add_meta_function(&mut self, meta: LuaMetaMethod, function: F) where F: 'static + for<'a, 'lua> FnMut(&'lua Lua, LuaMultiValue<'lua>) -> LuaResult> { self.meta_methods.insert(meta, Box::new(function)); } fn box_method(mut method: M) -> LuaCallback where M: 'static + for<'a, 'lua> FnMut(&'lua Lua, &'a T, LuaMultiValue<'lua>) -> LuaResult> { Box::new(move |lua, mut args| if let Some(front) = args.pop_front() { let userdata = LuaUserData::from_lua(front, lua)?; let userdata = userdata.borrow::()?; method(lua, &userdata, args) } else { Err("No userdata supplied as first argument to method".into()) }) } fn box_method_mut(mut method: M) -> LuaCallback where M: 'static + for<'a, 'lua> FnMut(&'lua Lua, &'a mut T, LuaMultiValue<'lua>) -> LuaResult> { Box::new(move |lua, mut args| if let Some(front) = args.pop_front() { let userdata = LuaUserData::from_lua(front, lua)?; let mut userdata = userdata.borrow_mut::()?; method(lua, &mut userdata, args) } else { Err("No userdata supplied as first argument to method".into()) }) } } /// Trait for types that can be converted to `LuaUserData` pub trait LuaUserDataType: 'static + Sized { fn add_methods(_methods: &mut LuaUserDataMethods) {} } /// Handle to an internal instance of custom userdata. All userdata in this API is based around /// RefCell, to best match the mutable semantics of the lua language. #[derive(Clone, Debug)] pub struct LuaUserData<'lua>(LuaRef<'lua>); impl<'lua> LuaUserData<'lua> { pub fn is(&self) -> bool { self.inspect(|_: &RefCell| Ok(())).is_ok() } /// Borrow this userdata out of the internal RefCell that is held in lua. pub fn borrow(&self) -> LuaResult> { self.inspect(|cell| Ok(cell.try_borrow()?)) } /// Borrow mutably this userdata out of the internal RefCell that is held in lua. pub fn borrow_mut(&self) -> LuaResult> { self.inspect(|cell| Ok(cell.try_borrow_mut()?)) } fn inspect<'a, T, R, F>(&'a self, func: F) -> LuaResult where T: LuaUserDataType, F: FnOnce(&'a RefCell) -> LuaResult { unsafe { let lua = self.0.lua; stack_guard(lua.state, 0, move || { check_stack(lua.state, 3)?; lua.push_ref(&self.0); let userdata = ffi::lua_touserdata(lua.state, -1); if userdata.is_null() { return Err("value not userdata".into()); } if ffi::lua_getmetatable(lua.state, -1) == 0 { return Err("value has no metatable".into()); } ffi::lua_rawgeti(lua.state, ffi::LUA_REGISTRYINDEX, lua.userdata_metatable::()? as ffi::lua_Integer); if ffi::lua_rawequal(lua.state, -1, -2) == 0 { return Err("wrong metatable type for lua userdata".into()); } let res = func(&*(userdata as *const RefCell)); ffi::lua_pop(lua.state, 3); res }) } } } /// Top level Lua struct which holds the lua state itself. pub struct Lua { state: *mut ffi::lua_State, ephemeral: bool, } impl Drop for Lua { fn drop(&mut self) { unsafe { if !self.ephemeral { ffi::lua_close(self.state); } } } } impl Lua { pub fn new() -> Lua { unsafe { let state = ffi::luaL_newstate(); ffi::luaL_openlibs(state); stack_guard(state, 0, || { ffi::lua_pushlightuserdata(state, &LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void); let registered_userdata = ffi::lua_newuserdata(state, mem::size_of::>>()) as *mut RefCell>; ptr::write(registered_userdata, RefCell::new(HashMap::new())); ffi::lua_newtable(state); push_string(state, "__gc"); ffi::lua_pushcfunction(state, destructor::>>); ffi::lua_settable(state, -3); ffi::lua_setmetatable(state, -2); ffi::lua_settable(state, ffi::LUA_REGISTRYINDEX); Ok(()) }) .unwrap(); stack_guard(state, 0, || { ffi::lua_pushlightuserdata(state, &FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void); ffi::lua_newtable(state); push_string(state, "__gc"); ffi::lua_pushcfunction(state, destructor::); ffi::lua_settable(state, -3); push_string(state, "__metatable"); ffi::lua_pushboolean(state, 0); ffi::lua_settable(state, -3); ffi::lua_settable(state, ffi::LUA_REGISTRYINDEX); Ok(()) }) .unwrap(); stack_guard(state, 0, || { ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); push_string(state, "pcall"); ffi::lua_pushcfunction(state, safe_pcall); ffi::lua_settable(state, -3); push_string(state, "xpcall"); ffi::lua_pushcfunction(state, safe_xpcall); ffi::lua_settable(state, -3); ffi::lua_pop(state, 1); Ok(()) }) .unwrap(); Lua { state, ephemeral: false, } } } pub fn load(&self, source: &str, name: Option<&str>) -> LuaResult<()> { unsafe { stack_guard(self.state, 0, || { handle_error(self.state, if let Some(name) = name { let name = CString::new(name.to_owned())?; ffi::luaL_loadbuffer(self.state, source.as_ptr() as *const c_char, source.len(), name.as_ptr()) } else { ffi::luaL_loadbuffer(self.state, source.as_ptr() as *const c_char, source.len(), ptr::null()) })?; handle_error(self.state, pcall_with_traceback(self.state, 0, 0)) }) } } pub fn eval<'lua, R: FromLuaMulti<'lua>>(&'lua self, source: &str) -> LuaResult { unsafe { stack_guard(self.state, 0, || { let stack_start = ffi::lua_gettop(self.state); // First, try interpreting the lua as an expression by adding "return", then // as a statement. This is the same thing the actual lua repl does. let return_source = "return ".to_owned() + source; let mut res = ffi::luaL_loadbuffer(self.state, return_source.as_ptr() as *const c_char, return_source.len(), ptr::null()); if res == ffi::LUA_ERRSYNTAX { ffi::lua_pop(self.state, 1); res = ffi::luaL_loadbuffer(self.state, source.as_ptr() as *const c_char, source.len(), ptr::null()); } handle_error(self.state, res)?; handle_error(self.state, pcall_with_traceback(self.state, 0, ffi::LUA_MULTRET))?; let nresults = ffi::lua_gettop(self.state) - stack_start; let mut results = LuaMultiValue::new(); for _ in 0..nresults { results.push_front(self.pop_value()?); } R::from_lua_multi(results, self) }) } } pub fn create_string(&self, s: &str) -> LuaResult { unsafe { stack_guard(self.state, 0, || { check_stack(self.state, 1)?; ffi::lua_pushlstring(self.state, s.as_ptr() as *const c_char, s.len()); Ok(LuaString(self.pop_ref())) }) } } pub fn create_empty_table(&self) -> LuaResult { unsafe { stack_guard(self.state, 0, || { check_stack(self.state, 1)?; ffi::lua_newtable(self.state); Ok(LuaTable(self.pop_ref())) }) } } pub fn create_table<'lua, K, V, I>(&'lua self, cont: I) -> LuaResult where K: ToLua<'lua>, V: ToLua<'lua>, I: IntoIterator { let table = self.create_empty_table()?; for (k, v) in cont { table.set(k, v)?; } Ok(table) } pub fn create_array_table<'lua, T, I>(&'lua self, cont: I) -> LuaResult where T: ToLua<'lua>, I: IntoIterator { let table = self.create_empty_table()?; let mut index = 1; for elem in cont { table.set(index, elem)?; index += 1; } Ok(table) } pub fn create_function(&self, func: F) -> LuaResult where F: 'static + for<'a> FnMut(&'a Lua, LuaMultiValue<'a>) -> LuaResult> { self.create_callback_function(Box::new(func)) } pub fn create_thread<'lua>(&'lua self, func: LuaFunction<'lua>) -> LuaResult> { unsafe { stack_guard(self.state, 0, move || { check_stack(self.state, 1)?; let thread_state = ffi::lua_newthread(self.state); push_ref(thread_state, &func.0); Ok(LuaThread(self.pop_ref())) }) } } pub fn create_userdata(&self, data: T) -> LuaResult where T: LuaUserDataType { unsafe { stack_guard(self.state, 0, move || { check_stack(self.state, 2)?; let data = RefCell::new(data); let data_userdata = ffi::lua_newuserdata(self.state, mem::size_of::>()) as *mut RefCell; ptr::write(data_userdata, data); ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, self.userdata_metatable::()? as ffi::lua_Integer); ffi::lua_setmetatable(self.state, -2); Ok(LuaUserData(self.pop_ref())) }) } } pub fn set<'lua, K, V>(&'lua self, key: K, value: V) -> LuaResult<()> where K: ToLua<'lua>, V: ToLua<'lua> { unsafe { stack_guard(self.state, 0, || { check_stack(self.state, 3)?; ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); self.push_value(key.to_lua(self)?)?; self.push_value(value.to_lua(self)?)?; ffi::lua_settable(self.state, -3); ffi::lua_pop(self.state, 1); Ok(()) }) } } pub fn get<'lua, K, V>(&'lua self, key: K) -> LuaResult where K: ToLua<'lua>, V: FromLua<'lua> { unsafe { stack_guard(self.state, 0, || { check_stack(self.state, 2)?; ffi::lua_rawgeti(self.state, ffi::LUA_REGISTRYINDEX, ffi::LUA_RIDX_GLOBALS); self.push_value(key.to_lua(self)?)?; ffi::lua_gettable(self.state, -2); let ret = self.pop_value()?; ffi::lua_pop(self.state, 1); V::from_lua(ret, self) }) } } pub fn coerce_string<'lua>(&'lua self, v: LuaValue<'lua>) -> LuaResult> { match v { LuaValue::String(s) => Ok(s), v => unsafe { stack_guard(self.state, 0, || { check_stack(self.state, 1)?; self.push_value(v)?; if ffi::lua_tostring(self.state, -1).is_null() { Err("cannot convert lua value to string".into()) } else { Ok(LuaString(self.pop_ref())) } }) }, } } pub fn coerce_integer(&self, v: LuaValue) -> LuaResult { match v { LuaValue::Integer(i) => Ok(i), LuaValue::Number(n) => Ok(n as LuaInteger), v => unsafe { stack_guard(self.state, 0, || { check_stack(self.state, 1)?; self.push_value(v)?; let mut isint = 0; let i = ffi::lua_tointegerx(self.state, -1, &mut isint); if isint == 0 { Err("cannot convert lua value to integer".into()) } else { ffi::lua_pop(self.state, 1); Ok(i) } }) }, } } pub fn coerce_number(&self, v: LuaValue) -> LuaResult { match v { LuaValue::Integer(i) => Ok(i as LuaNumber), LuaValue::Number(n) => Ok(n), v => unsafe { stack_guard(self.state, 0, || { check_stack(self.state, 1)?; self.push_value(v)?; let mut isnum = 0; let n = ffi::lua_tonumberx(self.state, -1, &mut isnum); if isnum == 0 { Err("cannot convert lua value to number".into()) } else { ffi::lua_pop(self.state, 1); Ok(n) } }) }, } } pub fn to<'lua, T: ToLua<'lua>>(&'lua self, t: T) -> LuaResult> { t.to_lua(self) } pub fn from<'lua, T: FromLua<'lua>>(&'lua self, value: LuaValue<'lua>) -> LuaResult { T::from_lua(value, self) } pub fn pack<'lua, T: ToLuaMulti<'lua>>(&'lua self, t: T) -> LuaResult> { t.to_lua_multi(self) } pub fn unpack<'lua, T: FromLuaMulti<'lua>>(&'lua self, value: LuaMultiValue<'lua>) -> LuaResult { T::from_lua_multi(value, self) } fn create_callback_function(&self, func: LuaCallback) -> LuaResult { unsafe extern "C" fn callback_call_impl(state: *mut ffi::lua_State) -> c_int { callback_error(state, || { let lua = Lua { state: state, ephemeral: true, }; let func = &mut *(ffi::lua_touserdata(state, ffi::lua_upvalueindex(1)) as *mut LuaCallback); let nargs = ffi::lua_gettop(state); let mut args = LuaMultiValue::new(); for _ in 0..nargs { args.push_front(lua.pop_value()?); } let results = func(&lua, args)?; let nresults = results.len() as c_int; for r in results { lua.push_value(r)?; } Ok(nresults) }) } unsafe { stack_guard(self.state, 0, move || { check_stack(self.state, 2)?; let func_userdata = ffi::lua_newuserdata(self.state, mem::size_of::()) as *mut LuaCallback; ptr::write(func_userdata, func); ffi::lua_pushlightuserdata(self.state, &FUNCTION_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void); ffi::lua_gettable(self.state, ffi::LUA_REGISTRYINDEX); ffi::lua_setmetatable(self.state, -2); ffi::lua_pushcclosure(self.state, callback_call_impl, 1); Ok(LuaFunction(self.pop_ref())) }) } } unsafe fn userdata_metatable(&self) -> LuaResult { // Used if both an __index metamethod is set and regular methods, checks methods table // first, then __index metamethod. unsafe extern "C" fn meta_index_impl(state: *mut ffi::lua_State) -> c_int { ffi::lua_pushvalue(state, -1); ffi::lua_gettable(state, ffi::lua_upvalueindex(1)); if ffi::lua_isnil(state, -1) == 0 { ffi::lua_insert(state, -3); ffi::lua_pop(state, 2); 1 } else { ffi::lua_pop(state, 1); ffi::lua_pushvalue(state, ffi::lua_upvalueindex(2)); ffi::lua_insert(state, -3); ffi::lua_call(state, 2, 1); 1 } } stack_guard(self.state, 0, move || { check_stack(self.state, 3)?; ffi::lua_pushlightuserdata(self.state, &LUA_USERDATA_REGISTRY_KEY as *const u8 as *mut c_void); ffi::lua_gettable(self.state, ffi::LUA_REGISTRYINDEX); let registered_userdata = ffi::lua_touserdata(self.state, -1) as *mut RefCell>; let mut map = (*registered_userdata).borrow_mut(); ffi::lua_pop(self.state, 1); match map.entry(TypeId::of::()) { HashMapEntry::Occupied(entry) => Ok(*entry.get()), HashMapEntry::Vacant(entry) => { ffi::lua_newtable(self.state); let mut methods = LuaUserDataMethods { methods: HashMap::new(), meta_methods: HashMap::new(), _type: PhantomData, }; T::add_methods(&mut methods); let has_methods = !methods.methods.is_empty(); if has_methods { push_string(self.state, "__index"); ffi::lua_newtable(self.state); check_stack(self.state, methods.methods.len() as c_int * 2)?; for (k, m) in methods.methods { push_string(self.state, &k); self.push_value(LuaValue::Function(self.create_callback_function(m)?))?; ffi::lua_settable(self.state, -3); } ffi::lua_settable(self.state, -3); } check_stack(self.state, methods.meta_methods.len() as c_int * 2)?; for (k, m) in methods.meta_methods { if k == LuaMetaMethod::Index && has_methods { push_string(self.state, "__index"); ffi::lua_pushvalue(self.state, -1); ffi::lua_gettable(self.state, -3); self.push_value(LuaValue::Function(self.create_callback_function(m)?))?; ffi::lua_pushcclosure(self.state, meta_index_impl, 2); ffi::lua_settable(self.state, -3); } else { let name = match k { LuaMetaMethod::Add => "__add", LuaMetaMethod::Sub => "__sub", LuaMetaMethod::Mul => "__mul", LuaMetaMethod::Div => "__div", LuaMetaMethod::Mod => "__mod", LuaMetaMethod::Pow => "__pow", LuaMetaMethod::Unm => "__unm", LuaMetaMethod::Concat => "__concat", LuaMetaMethod::Len => "__len", LuaMetaMethod::Eq => "__eq", LuaMetaMethod::Lt => "__lt", LuaMetaMethod::Le => "__le", LuaMetaMethod::Index => "__index", LuaMetaMethod::NewIndex => "__newIndex", LuaMetaMethod::Call => "__call", }; push_string(self.state, name); self.push_value(LuaValue::Function(self.create_callback_function(m)?))?; ffi::lua_settable(self.state, -3); } } push_string(self.state, "__gc"); ffi::lua_pushcfunction(self.state, destructor::>); ffi::lua_settable(self.state, -3); push_string(self.state, "__metatable"); ffi::lua_pushboolean(self.state, 0); ffi::lua_settable(self.state, -3); let id = ffi::luaL_ref(self.state, ffi::LUA_REGISTRYINDEX); entry.insert(id); Ok(id) } } }) } fn push_value(&self, value: LuaValue) -> LuaResult<()> { unsafe { push_value(self.state, value) } } fn push_ref(&self, lref: &LuaRef) { unsafe { push_ref(self.state, lref) } } fn pop_value(&self) -> LuaResult { unsafe { pop_value(self.state, self) } } fn pop_ref(&self) -> LuaRef { unsafe { pop_ref(self.state, self) } } } static LUA_USERDATA_REGISTRY_KEY: u8 = 0; static FUNCTION_METATABLE_REGISTRY_KEY: u8 = 0; // If the return code is not LUA_OK, pops the error off of the stack and returns Err. If the error // was actually a rust panic, clears the current lua stack and panics. unsafe fn handle_error(state: *mut ffi::lua_State, ret: c_int) -> LuaResult<()> { if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD { Err(pop_error(state)) } else { Ok(()) } } unsafe fn push_value(state: *mut ffi::lua_State, value: LuaValue) -> LuaResult<()> { stack_guard(state, 1, move || { match value { LuaValue::Nil => { ffi::lua_pushnil(state); } LuaValue::Boolean(b) => { ffi::lua_pushboolean(state, if b { 1 } else { 0 }); } LuaValue::Integer(i) => { ffi::lua_pushinteger(state, i); } LuaValue::Number(n) => { ffi::lua_pushnumber(state, n); } LuaValue::String(s) => { push_ref(state, &s.0); } LuaValue::Table(t) => { push_ref(state, &t.0); } LuaValue::Function(f) => { push_ref(state, &f.0); } LuaValue::UserData(ud) => { push_ref(state, &ud.0); } LuaValue::Thread(t) => { push_ref(state, &t.0); } } Ok(()) }) } unsafe fn push_ref(state: *mut ffi::lua_State, lref: &LuaRef) { ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, lref.registry_id as ffi::lua_Integer); } unsafe fn pop_value(state: *mut ffi::lua_State, lua: &Lua) -> LuaResult { stack_guard(state, -1, || match ffi::lua_type(state, -1) { ffi::LUA_TNIL => { ffi::lua_pop(state, 1); Ok(LuaNil) } ffi::LUA_TBOOLEAN => { let b = LuaValue::Boolean(ffi::lua_toboolean(state, -1) != 0); ffi::lua_pop(state, 1); Ok(b) } ffi::LUA_TNUMBER => { if ffi::lua_isinteger(state, -1) != 0 { let i = LuaValue::Integer(ffi::lua_tointeger(state, -1)); ffi::lua_pop(state, 1); Ok(i) } else { let n = LuaValue::Number(ffi::lua_tonumber(state, -1)); ffi::lua_pop(state, 1); Ok(n) } } ffi::LUA_TSTRING => Ok(LuaValue::String(LuaString(pop_ref(state, lua)))), ffi::LUA_TTABLE => Ok(LuaValue::Table(LuaTable(pop_ref(state, lua)))), ffi::LUA_TFUNCTION => Ok(LuaValue::Function(LuaFunction(pop_ref(state, lua)))), ffi::LUA_TUSERDATA => Ok(LuaValue::UserData(LuaUserData(pop_ref(state, lua)))), ffi::LUA_TTHREAD => Ok(LuaValue::Thread(LuaThread(pop_ref(state, lua)))), _ => Err("Unsupported type in pop_value".into()), }) } unsafe fn pop_ref(state: *mut ffi::lua_State, lua: &Lua) -> LuaRef { let registry_id = ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX); LuaRef { lua: lua, registry_id: registry_id, } }