fix(web): surface Forth messages from host-word throws
A host word signals failure by throwing across the JS boundary, and the
browser runtime reported that exception with its Debug form, so an
empty-stack RESIZE surfaced as
call_func(134) failed: JsValue(Error: Stack underflow ...)
with the engine's JS stack trace glued on. The thrown message IS the
Forth message, so take it verbatim: 'Stack underflow', the same text the
native CLI prints. Exceptions without a message keep the call context --
those are genuine runtime faults, not Forth throws.
CATCH is unaffected: it reads the throw code from its own channel rather
than parsing messages. Verified against a fresh VM in Node (initSync +
WaferRepl): host-word underflow, compiled-guard underflow, THROW,
unknown word and ' RESIZE CATCH . all match the native CLI.
Ships as v0.2.4.
This commit is contained in:
@@ -9,7 +9,7 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
wafer-core = { path = "../core", version = "0.2.3" }
|
||||
wafer-core = { path = "../core", version = "0.2.4" }
|
||||
wasmtime = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
@@ -12,7 +12,7 @@ workspace = true
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
wafer-core = { path = "../core", version = "0.2.3", default-features = false, features = ["crypto"] }
|
||||
wafer-core = { path = "../core", version = "0.2.4", default-features = false, features = ["crypto"] }
|
||||
wasm-bindgen = "0.2"
|
||||
js-sys = "0.3"
|
||||
send_wrapper = { workspace = true }
|
||||
|
||||
@@ -38,6 +38,23 @@ impl WebHostAccess {
|
||||
}
|
||||
}
|
||||
|
||||
/// An exception on its way back out of compiled code. Host words rethrow the
|
||||
/// Forth message (`Stack underflow`, an `ABORT"` text, a `THROW` description),
|
||||
/// so surface exactly that and nothing else — the JS `Error` carries the whole
|
||||
/// engine stack in its message, which is noise to a Forth programmer. Anything
|
||||
/// without a message is a genuine runtime fault and keeps the call context.
|
||||
fn call_error(fn_index: u32, e: &JsValue) -> anyhow::Error {
|
||||
match Reflect::get(e, &"message".into())
|
||||
.ok()
|
||||
.and_then(|m| m.as_string())
|
||||
.and_then(|m| m.lines().next().map(str::trim).map(str::to_string))
|
||||
.filter(|m| !m.is_empty())
|
||||
{
|
||||
Some(msg) => anyhow::anyhow!("{msg}"),
|
||||
None => anyhow::anyhow!("call_func({fn_index}) failed: {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
impl HostAccess for WebHostAccess {
|
||||
fn mem_read_i32(&mut self, addr: u32) -> i32 {
|
||||
let view = js_sys::Int32Array::new(&self.buffer());
|
||||
@@ -134,7 +151,7 @@ impl HostAccess for WebHostAccess {
|
||||
.dyn_into()
|
||||
.map_err(|_| anyhow::anyhow!("table entry {fn_index} is not a function"))?;
|
||||
func.call0(&JsValue::NULL)
|
||||
.map_err(|e| anyhow::anyhow!("call_func({fn_index}) failed: {e:?}"))?;
|
||||
.map_err(|e| call_error(fn_index, &e))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -406,7 +423,7 @@ impl Runtime for WebRuntime {
|
||||
.dyn_into()
|
||||
.map_err(|_| anyhow::anyhow!("table entry {fn_index} is not callable"))?;
|
||||
func.call0(&JsValue::NULL)
|
||||
.map_err(|e| anyhow::anyhow!("call_func({fn_index}) failed: {e:?}"))?;
|
||||
.map_err(|e| call_error(fn_index, &e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user