From 2c439f8097c9d8f444830635394367ad8c16edbe Mon Sep 17 00:00:00 2001 From: kyren Date: Sun, 25 Jun 2017 01:47:55 -0400 Subject: [PATCH] Not sure I like everything about this approach yet --- src/conversion.rs | 59 +++++++++++++++++++++++---------- src/lua.rs | 83 +++++++++++++++++++++++++++++++++++------------ src/tests.rs | 8 ++--- src/util.rs | 12 +++---- 4 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 98a68d4..c7a5a45 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -63,24 +63,6 @@ impl<'lua> FromLua<'lua> for LuaFunction<'lua> { } } -impl<'lua> ToLua<'lua> for LuaUserData<'lua> { - fn to_lua(self, _: &'lua Lua) -> LuaResult> { - Ok(LuaValue::UserData(self)) - } -} - -impl<'lua> FromLua<'lua> for LuaUserData<'lua> { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult> { - match value { - LuaValue::UserData(ud) => Ok(ud), - _ => Err( - LuaConversionError::FromLua("cannot convert lua value to userdata".to_owned()) - .into(), - ), - } - } -} - impl<'lua> ToLua<'lua> for LuaThread<'lua> { fn to_lua(self, _: &'lua Lua) -> LuaResult> { Ok(LuaValue::Thread(self)) @@ -99,6 +81,24 @@ impl<'lua> FromLua<'lua> for LuaThread<'lua> { } } +impl<'lua> ToLua<'lua> for LuaUserData<'lua> { + fn to_lua(self, _: &'lua Lua) -> LuaResult> { + Ok(LuaValue::UserData(self)) + } +} + +impl<'lua> FromLua<'lua> for LuaUserData<'lua> { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult> { + match value { + LuaValue::UserData(ud) => Ok(ud), + _ => Err( + LuaConversionError::FromLua("cannot convert lua value to userdata".to_owned()) + .into(), + ), + } + } +} + impl<'lua, T: LuaUserDataType> ToLua<'lua> for T { fn to_lua(self, lua: &'lua Lua) -> LuaResult> { lua.create_userdata(self).map(LuaValue::UserData) @@ -117,6 +117,29 @@ impl<'lua, T: LuaUserDataType + Copy> FromLua<'lua> for T { } } +impl<'lua> ToLua<'lua> for LuaErrorUserData<'lua> { + fn to_lua(self, _: &'lua Lua) -> LuaResult> { + Ok(LuaValue::Error(self)) + } +} + +impl<'lua> FromLua<'lua> for LuaErrorUserData<'lua> { + fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult> { + match value { + LuaValue::Error(err) => Ok(err), + _ => Err( + LuaConversionError::FromLua("cannot convert lua value to error".to_owned()).into(), + ), + } + } +} + +impl<'lua> ToLua<'lua> for LuaError { + fn to_lua(self, lua: &'lua Lua) -> LuaResult> { + Ok(LuaValue::Error(lua.create_error(self)?)) + } +} + impl<'lua> ToLua<'lua> for bool { fn to_lua(self, _: &'lua Lua) -> LuaResult> { Ok(LuaValue::Boolean(self)) diff --git a/src/lua.rs b/src/lua.rs index aa28aa8..537a096 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -38,10 +38,14 @@ pub enum LuaValue<'lua> { Table(LuaTable<'lua>), /// Reference to a Lua function (or closure). Function(LuaFunction<'lua>), - /// Reference to a "full" userdata object. - UserData(LuaUserData<'lua>), /// Reference to a Lua thread (or coroutine). Thread(LuaThread<'lua>), + /// Reference to a userdata object that holds a custom type which implements + /// `LuaUserDataType`. Special builtin userdata types will be represented as + /// other `LuaValue` variants. + UserData(LuaUserData<'lua>), + /// `LuaError` is a special builtin userdata type. + Error(LuaErrorUserData<'lua>), } pub use self::LuaValue::Nil as LuaNil; @@ -122,15 +126,6 @@ pub trait FromLuaMulti<'a>: Sized { fn from_lua_multi(values: LuaMultiValue<'a>, lua: &'a Lua) -> LuaResult; } -impl<'lua> ToLua<'lua> for LuaError { - fn to_lua(self, lua: &'lua Lua) -> LuaResult> { - unsafe { - push_wrapped_error(lua.state, self); - Ok(lua.pop_value(lua.state)) - } - } -} - type LuaCallback = Box< for<'lua> FnMut(&'lua Lua, LuaMultiValue<'lua>) -> LuaResult>, @@ -889,16 +884,20 @@ 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. +/// Handle to an internal Lua userdata for a type that implements +/// LuaUserDataType. Internally, instances are stored in a `RefCell`, to best +/// match the mutable semantics of the Lua language. #[derive(Clone, Debug)] pub struct LuaUserData<'lua>(LuaRef<'lua>); impl<'lua> LuaUserData<'lua> { /// Checks whether `T` is the type of this userdata. - pub fn is(&self) -> bool { - self.inspect(|_: &RefCell| Ok(())).is_ok() + pub fn is(&self) -> LuaResult { + match self.inspect(|_: &RefCell| Ok(())) { + Ok(_) => Ok(true), + Err(LuaError::UserDataError(LuaUserDataError::TypeMismatch)) => Ok(false), + Err(err) => Err(err), + } } /// Borrow this userdata out of the internal RefCell that is held in lua. @@ -910,7 +909,8 @@ impl<'lua> LuaUserData<'lua> { }) } - /// Borrow mutably this userdata out of the internal RefCell that is held in lua. + /// 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().map_err( @@ -955,6 +955,27 @@ impl<'lua> LuaUserData<'lua> { } } +/// Handle to a `LuaError` that is held internally in Lua +#[derive(Clone, Debug)] +pub struct LuaErrorUserData<'lua>(LuaRef<'lua>); + +impl<'lua> LuaErrorUserData<'lua> { + /// Gets a reference to the internally held `LuaError`. + pub fn get(&self) -> LuaResult<&LuaError> { + unsafe { + let lua = self.0.lua; + stack_guard(lua.state, 0, move || { + check_stack(lua.state, 1)?; + lua.push_ref(lua.state, &self.0); + + let userdata = ffi::lua_touserdata(lua.state, -1); + let err = &*(userdata as *const WrappedError); + Ok(&err.0) + }) + } + } +} + /// Top level Lua struct which holds the Lua state itself. pub struct Lua { state: *mut ffi::lua_State, @@ -1236,6 +1257,14 @@ impl Lua { } } + /// Create a userdata object from a LuaError + pub fn create_error(&self, err: LuaError) -> LuaResult { + unsafe { + push_wrapped_error(self.state, err); + Ok(LuaErrorUserData(self.pop_ref(self.state))) + } + } + /// Returns a handle to the global environment. pub fn globals(&self) -> LuaResult { unsafe { @@ -1436,12 +1465,16 @@ impl Lua { self.push_ref(state, &f.0); } + LuaValue::Thread(t) => { + self.push_ref(state, &t.0); + } + LuaValue::UserData(ud) => { self.push_ref(state, &ud.0); } - LuaValue::Thread(t) => { - self.push_ref(state, &t.0); + LuaValue::Error(e) => { + self.push_ref(state, &e.0); } } } @@ -1483,7 +1516,17 @@ impl Lua { ffi::LUA_TFUNCTION => LuaValue::Function(LuaFunction(self.pop_ref(state))), - ffi::LUA_TUSERDATA => LuaValue::UserData(LuaUserData(self.pop_ref(state))), + ffi::LUA_TUSERDATA => { + // It should not be possible to interact with userdata types + // other than custom LuaUserDataType types OR a WrappedError. + // WrappedPanic should never be able to be caught in lua, so it + // should never be here. + if is_wrapped_error(state, -1) { + LuaValue::Error(LuaErrorUserData(self.pop_ref(state))) + } else { + LuaValue::UserData(LuaUserData(self.pop_ref(state))) + } + } ffi::LUA_TTHREAD => LuaValue::Thread(LuaThread(self.pop_ref(state))), diff --git a/src/tests.rs b/src/tests.rs index 42ed823..49cfd2c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -244,10 +244,10 @@ fn test_user_data() { let userdata1 = lua.create_userdata(UserData1(1)).unwrap(); let userdata2 = lua.create_userdata(UserData2(Box::new(2))).unwrap(); - assert!(userdata1.is::()); - assert!(!userdata1.is::()); - assert!(userdata2.is::()); - assert!(!userdata2.is::()); + assert!(userdata1.is::().unwrap()); + assert!(!userdata1.is::().unwrap()); + assert!(userdata2.is::().unwrap()); + assert!(!userdata2.is::().unwrap()); assert_eq!(userdata1.borrow::().unwrap().0, 1); assert_eq!(*userdata2.borrow::().unwrap().0, 2); diff --git a/src/util.rs b/src/util.rs index 32161c0..207b74d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -121,8 +121,8 @@ pub unsafe fn handle_error(state: *mut ffi::lua_State, err: c_int) -> LuaResult< if err == ffi::LUA_OK || err == ffi::LUA_YIELD { Ok(()) } else { - if is_wrapped_error(state, -1) { - Err(pop_wrapped_error(state).unwrap()) + if let Some(err) = pop_wrapped_error(state) { + Err(err) } else if is_wrapped_panic(state, -1) { let userdata = ffi::lua_touserdata(state, -1); @@ -200,7 +200,7 @@ pub unsafe extern "C" fn destructor(state: *mut ffi::lua_State) -> c_int { // In the context of a lua callback, this will call the given function and if the given function // returns an error, *or if the given function panics*, this will result in a call to lua_error (a -// longjmp). The error or panic is wrapped in such a way that when calling pop_error back on +// longjmp). The error or panic is wrapped in such a way that when calling handle_error back on // the rust side, it will resume the panic. pub unsafe fn callback_error(state: *mut ffi::lua_State, f: F) -> R where @@ -228,8 +228,7 @@ pub unsafe fn pcall_with_traceback( nresults: c_int, ) -> c_int { unsafe extern "C" fn message_handler(state: *mut ffi::lua_State) -> c_int { - if is_wrapped_error(state, 1) { - let error = pop_wrapped_error(state).unwrap(); + if let Some(error) = pop_wrapped_error(state) { ffi::luaL_traceback(state, state, ptr::null(), 0); let traceback = CStr::from_ptr(ffi::lua_tolstring(state, -1, ptr::null_mut())) .to_str() @@ -262,8 +261,7 @@ pub unsafe fn resume_with_traceback( ) -> c_int { let res = ffi::lua_resume(state, from, nargs); if res != ffi::LUA_OK && res != ffi::LUA_YIELD { - if is_wrapped_error(state, 1) { - let error = pop_wrapped_error(state).unwrap(); + if let Some(error) = pop_wrapped_error(state) { ffi::luaL_traceback(from, state, ptr::null(), 0); let traceback = CStr::from_ptr(ffi::lua_tolstring(from, -1, ptr::null_mut())) .to_str()