diff options
author | Alex Orlenko <zxteam@protonmail.com> | 2021-11-16 00:08:13 +0000 |
---|---|---|
committer | Alex Orlenko <zxteam@protonmail.com> | 2021-11-16 12:05:34 +0000 |
commit | 0ef709672d4bcd9a5c96654424f596c658a81adb (patch) | |
tree | ef33b789455de24bf79d447d88840f8b11e2b4e3 | |
parent | 41503b4fb8bf5555ad157433ca9507c44a169825 (diff) | |
download | mlua-0ef709672d4bcd9a5c96654424f596c658a81adb.zip |
Add set_warning_function/remove_warning_function/warning functions to Lua for 5.4
This utilizes Lua 5.4 warnings system (https://www.lua.org/manual/5.4/manual.html#pdf-warn)
-rw-r--r-- | src/ffi/lua.rs | 2 | ||||
-rw-r--r-- | src/lua.rs | 67 | ||||
-rw-r--r-- | src/types.rs | 9 | ||||
-rw-r--r-- | tests/tests.rs | 43 |
4 files changed, 119 insertions, 2 deletions
diff --git a/src/ffi/lua.rs b/src/ffi/lua.rs index 41ef9c6..12ec1cc 100644 --- a/src/ffi/lua.rs +++ b/src/ffi/lua.rs @@ -574,7 +574,7 @@ pub unsafe fn lua_resume( // warning-related functions #[cfg(feature = "lua54")] extern "C" { - pub fn lua_setwarnf(L: *mut lua_State, f: lua_WarnFunction, ud: *mut c_void); + pub fn lua_setwarnf(L: *mut lua_State, f: Option<lua_WarnFunction>, ud: *mut c_void); pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int); } @@ -38,7 +38,10 @@ use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Va #[cfg(not(feature = "lua54"))] use crate::util::push_userdata; #[cfg(feature = "lua54")] -use crate::{userdata::USER_VALUE_MAXSLOT, util::push_userdata_uv}; +use { + crate::{types::WarnCallback, userdata::USER_VALUE_MAXSLOT, util::push_userdata_uv}, + std::ffi::CStr, +}; #[cfg(not(feature = "send"))] use std::rc::Rc; @@ -101,6 +104,8 @@ struct ExtraData { ref_waker_idx: c_int, hook_callback: Option<HookCallback>, + #[cfg(feature = "lua54")] + warn_callback: Option<WarnCallback>, } #[cfg_attr(any(feature = "lua51", feature = "luajit"), allow(dead_code))] @@ -523,6 +528,8 @@ impl Lua { #[cfg(feature = "async")] ref_waker_idx, hook_callback: None, + #[cfg(feature = "lua54")] + warn_callback: None, })); mlua_expect!( @@ -780,6 +787,64 @@ impl Lua { } } + /// Sets the warning function to be used by Lua to emit warnings. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + pub fn set_warning_function<F>(&self, callback: F) + where + F: 'static + MaybeSend + Fn(&Lua, &CStr, bool) -> Result<()>, + { + unsafe extern "C" fn warn_proc(ud: *mut c_void, msg: *const c_char, tocont: c_int) { + let state = ud as *mut ffi::lua_State; + let lua = match Lua::make_from_ptr(state) { + Some(lua) => lua, + None => return, + }; + let extra = lua.extra.get(); + callback_error_ext(state, extra, move |_| { + let cb = mlua_expect!( + (*lua.extra.get()).warn_callback.as_ref(), + "no warning callback set" + ); + let msg = CStr::from_ptr(msg); + cb(&lua, msg, tocont != 0) + }); + } + + let state = self.main_state.unwrap_or(self.state); + unsafe { + (*self.extra.get()).warn_callback = Some(Box::new(callback)); + ffi::lua_setwarnf(state, Some(warn_proc), state as *mut c_void); + } + } + + /// Removes warning function previously set by `set_warning_function`. + /// + /// This function has no effect if a warning function was not previously set. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + pub fn remove_warning_function(&self) { + let state = self.main_state.unwrap_or(self.state); + unsafe { + (*self.extra.get()).warn_callback = None; + ffi::lua_setwarnf(state, None, ptr::null_mut()); + } + } + + /// Emits a warning with the given message. + /// + /// A message in a call with `tocont` set to `true` should be continued in another call to this function. + /// + /// Requires `feature = "lua54"` + #[cfg(feature = "lua54")] + pub fn warning<S: Into<Vec<u8>>>(&self, msg: S, tocont: bool) -> Result<()> { + let msg = CString::new(msg).map_err(|err| Error::RuntimeError(err.to_string()))?; + unsafe { ffi::lua_warning(self.state, msg.as_ptr(), if tocont { 1 } else { 0 }) }; + Ok(()) + } + /// Gets information about the interpreter runtime stack. /// /// This function returns [`Debug`] structure that can be used to get information about the function diff --git a/src/types.rs b/src/types.rs index 2cb5af0..0f07e8c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,6 +4,9 @@ use std::os::raw::{c_int, c_void}; use std::sync::{Arc, Mutex}; use std::{fmt, mem, ptr}; +#[cfg(feature = "lua54")] +use std::ffi::CStr; + #[cfg(feature = "async")] use futures_core::future::LocalBoxFuture; @@ -53,6 +56,12 @@ pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()> #[cfg(not(feature = "send"))] pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>; +#[cfg(all(feature = "send", feature = "lua54"))] +pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()> + Send>; + +#[cfg(all(not(feature = "send"), feature = "lua54"))] +pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()>>; + #[cfg(feature = "send")] pub trait MaybeSend: Send {} #[cfg(feature = "send")] diff --git a/tests/tests.rs b/tests/tests.rs index db5340d..528f17d 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1205,3 +1205,46 @@ fn test_multi_states() -> Result<()> { Ok(()) } + +#[test] +#[cfg(feature = "lua54")] +fn test_warnings() -> Result<()> { + let lua = Lua::new(); + lua.set_app_data::<Vec<(StdString, bool)>>(Vec::new()); + + lua.set_warning_function(|lua, msg, tocont| { + let msg = msg.to_string_lossy().to_string(); + lua.app_data_mut::<Vec<(StdString, bool)>>() + .unwrap() + .push((msg, tocont)); + Ok(()) + }); + + lua.warning("native warning ...", true)?; + lua.warning("finish", false)?; + lua.load(r#"warn("lua warning", "continue")"#).exec()?; + + lua.remove_warning_function(); + lua.warning("one more warning", false)?; + + let messages = lua.app_data_ref::<Vec<(StdString, bool)>>().unwrap(); + assert_eq!( + *messages, + vec![ + ("native warning ...".to_string(), true), + ("finish".to_string(), false), + ("lua warning".to_string(), true), + ("continue".to_string(), false), + ] + ); + + // Trigger error inside warning + lua.set_warning_function(|_, _, _| Err(Error::RuntimeError("warning error".to_string()))); + assert!(matches!( + lua.load(r#"warn("test")"#).exec(), + Err(Error::CallbackError { cause, .. }) + if matches!(*cause, Error::RuntimeError(ref err) if err == "warning error") + )); + + Ok(()) +} |