diff --git a/crates/benilla-app/src/ui_script/addons.rs b/crates/benilla-app/src/ui_script/addons.rs index e5aef85d..cdbf2117 100644 --- a/crates/benilla-app/src/ui_script/addons.rs +++ b/crates/benilla-app/src/ui_script/addons.rs @@ -158,11 +158,11 @@ impl Addon { // order, as `AddOn_Load 0x51f240` hands every listed file to `0x6edb90`. match script.run_chunk_named(&bytes, &self.chunk_name(file, &path)) { Ok(()) => info!("ui_script: {}/{file} ran", self.name), - Err(e) => { - let e = format!("{}/{file}: {e}", self.name); + Err(err) => { + let e = format!("{}/{file}: {err}", self.name); error!("ui_script: {e}"); // A script error: it reaches the player through the Lua error handler. - script.report_script_error(&e); + script.report_script_error(&e, &benilla_ui::script::lua_message(&err)); failures.push(e); } } @@ -210,11 +210,11 @@ impl Addon { script.report_load_failure(&e); failures.push(e); } - for e in &report.errors { + for (e, heard) in report.raised() { error!("ui_script({}/{file}): {e}", self.name); // A script error, as in the Lua arm; `_ERRORMESSAGE`'s IsVisible guard shows only // a burst's first, as in the reference. - script.report_script_error(&format!("{}/{file}: {e}", self.name)); + script.report_script_error(&format!("{}/{file}: {e}", self.name), heard); failures.push(format!("{}/{file}: {e}", self.name)); } info!( @@ -2891,4 +2891,74 @@ mod tests { ); let _ = std::fs::remove_dir_all(home.parent().unwrap()); } + + /// The error handler hears what 1.12's `lua_pcall` error function hands it (`0x703b40`, from + /// `FS_DoBuffer 0x704ae0` for a file's top level and its syntax error, `0x704f10` for a + /// handler): the bare Lua message, with no frame, `OnLoad: `, addon or file word and no + /// traceback. The decorated text stays on the failures list and the host log. + #[test] + fn the_error_handler_hears_the_bare_message_of_every_load_failure() { + let tmp = std::env::temp_dir().join(format!("benilla-addon-bare-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&tmp); + let addon = dir_addon( + &tmp, + "Bare", + "## Interface: 11200\ntop.lua\nsyntax.lua\nframe.xml\n", + ); + std::fs::write(tmp.join("Bare/top.lua"), "local t = nil\nt.x = 1\n").unwrap(); + std::fs::write(tmp.join("Bare/syntax.lua"), "x = = 1\n").unwrap(); + std::fs::write( + tmp.join("Bare/frame.xml"), + r#"this:NoSuchMethod() +"#, + ) + .unwrap(); + let mut script = UiScript::new().unwrap(); + script.set_screen_size(1024.0, 768.0); + script + .run("Heard = {} seterrorhandler(function(m) table.insert(Heard, m) end)") + .unwrap(); + let failures = addon.load(&script, &mut Status::default()); + assert_eq!(failures.len(), 4, "{failures:#?}"); + script.dispatch_script_errors_to_handler(); + let heard: Vec = script.eval("return Heard").unwrap(); + assert_eq!(heard.len(), 4, "{heard:#?}"); + for m in &heard { + assert!( + !m.contains("runtime error") + && !m.contains("syntax error") + && !m.contains("stack traceback") + && !m.contains("OnLoad: ") + && !m.contains("Bare/") + && !m.contains('\n'), + "{m:?}" + ); + } + assert!( + heard[0].ends_with("top.lua:2: attempt to index local `t' (a nil value)"), + "{:?}", + heard[0] + ); + assert!( + heard[0].starts_with("Interface\\AddOns\\Bare\\top.lua"), + "{:?}", + heard[0] + ); + assert!( + heard[1].contains("syntax.lua:1:") && heard[1].contains("unexpected symbol"), + "{:?}", + heard[1] + ); + assert!( + heard[2].ends_with("attempt to call method `NoSuchMethod' (a nil value)"), + "{:?}", + heard[2] + ); + assert!( + heard[3].ends_with("attempt to index local `u' (a nil value)"), + "{:?}", + heard[3] + ); + let _ = std::fs::remove_dir_all(&tmp); + } } diff --git a/crates/benilla-ui/src/loader/backdrop.rs b/crates/benilla-ui/src/loader/backdrop.rs index 9b22381a..b7d18fad 100644 --- a/crates/benilla-ui/src/loader/backdrop.rs +++ b/crates/benilla-ui/src/loader/backdrop.rs @@ -15,7 +15,7 @@ impl Loader<'_> { let table = match self.build_backdrop_table(bd) { Ok(t) => t, Err(e) => { - self.report.errors.push(format!("{dbg}: : {e}")); + self.report.fault(format!("{dbg}: : {e}")); return; } }; diff --git a/crates/benilla-ui/src/loader/geometry.rs b/crates/benilla-ui/src/loader/geometry.rs index 84d3a756..94925791 100644 --- a/crates/benilla-ui/src/loader/geometry.rs +++ b/crates/benilla-ui/src/loader/geometry.rs @@ -146,9 +146,7 @@ impl Loader<'_> { let region: Table = match wrapper.call_method("CreateTitleRegion", ()) { Ok(r) => r, Err(e) => { - self.report - .errors - .push(format!("{dbg}: CreateTitleRegion: {e}")); + self.report.fault(format!("{dbg}: CreateTitleRegion: {e}")); return; } }; diff --git a/crates/benilla-ui/src/loader/mod.rs b/crates/benilla-ui/src/loader/mod.rs index 238d4db6..2be04727 100644 --- a/crates/benilla-ui/src/loader/mod.rs +++ b/crates/benilla-ui/src/loader/mod.rs @@ -22,6 +22,33 @@ mod regions; mod scripts; mod widgets; +impl LoadReport { + /// A load fault that is no script raise: the handler hears the row's own text. + pub(super) fn fault(&mut self, text: String) { + self.handler_errors.push(text.clone()); + self.errors.push(text); + } + + /// A script raise: the row is `text`, the handler hears the bare Lua message of `e`. + pub(super) fn raise(&mut self, text: String, e: &mlua::Error) { + self.handler_errors.push(crate::script::lua_message(e)); + self.errors.push(text); + } + + /// Each of [`Self::errors`] with what the Lua error handler is called with for it. + pub fn raised(&self) -> impl Iterator { + debug_assert_eq!( + self.errors.len(), + self.handler_errors.len(), + "every error row is written through `fault` or `raise`" + ); + self.errors + .iter() + .map(String::as_str) + .zip(self.handler_errors.iter().map(String::as_str)) + } +} + /// The outcome of a [`load`]; nothing aborts a load, as the reference logs and continues. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct LoadReport { @@ -31,6 +58,11 @@ pub struct LoadReport { /// Failures the reference raises on: a handler that fails to compile, a method call that /// errors, a malformed included document. pub errors: Vec, + /// What the Lua error handler is called with for each of [`Self::errors`], row for row + /// ([`Self::raised`]): the bare Lua message of a raise, as `lua_pcall` leaves it for the + /// reference's error function (`0x703b40`), and the row's own text for a fault that is no + /// script raise. + handler_errors: Vec, /// `` and `