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)
This commit is contained in:
Alex Orlenko 2021-11-16 00:08:13 +00:00
parent 41503b4fb8
commit 0ef709672d
No known key found for this signature in database
GPG key ID: 4C150C250863B96D
4 changed files with 119 additions and 2 deletions

View file

@ -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);
}

View file

@ -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

View file

@ -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")]

View file

@ -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(())
}