Compare commits
2 Commits
8e2fd0d7d4
...
e6c10a6fa1
| Author | SHA1 | Date | |
|---|---|---|---|
| e6c10a6fa1 | |||
| e110ca9516 |
@@ -5,6 +5,24 @@ All notable changes to WAFER are documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.2.6] - 2026-08-07
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **An uncaught `ABORT` no longer prints anything.** It used to report
|
||||||
|
`ABORT (throw -1)`, but the standard defines `ABORT` as "empty the data
|
||||||
|
stack and perform the function of `QUIT`", and `QUIT` displays no
|
||||||
|
message. gforth and SwiftForth are both silent here. `CATCH` still
|
||||||
|
reports -1 as before, and `ABORT"` still prints its text — that is a
|
||||||
|
different word with a different code (-2).
|
||||||
|
- **Compile-only words used in interpretation state name the condition.**
|
||||||
|
`ABORT"`, `IF`, `THEN`, `LOOP`, `LITERAL`, `RECURSE` and the rest of
|
||||||
|
the compile-time constructs claimed to be an `unknown word`, which is
|
||||||
|
actively misleading for a word the system obviously knows. They now
|
||||||
|
report `interpreting a compile-only word: <name> (throw -14)`, the
|
||||||
|
standard condition both reference engines give. A genuine typo still
|
||||||
|
reports `unknown word`.
|
||||||
|
|
||||||
## [0.2.5] - 2026-08-06
|
## [0.2.5] - 2026-08-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
Generated
+3
-3
@@ -1589,7 +1589,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wafer"
|
name = "wafer"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1600,7 +1600,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wafer-core"
|
name = "wafer-core"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"insta",
|
"insta",
|
||||||
@@ -1615,7 +1615,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wafer-web"
|
name = "wafer-web"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ members = ["crates/*"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/ok2/wafer"
|
repository = "https://github.com/ok2/wafer"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ license.workspace = true
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wafer-core = { path = "../core", version = "0.2.5" }
|
wafer-core = { path = "../core", version = "0.2.6" }
|
||||||
wasmtime = { workspace = true }
|
wasmtime = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|||||||
@@ -184,6 +184,11 @@ enum PendingAction {
|
|||||||
/// not an exception: CATCH lets it through and the interpreter reports nothing.
|
/// not an exception: CATCH lets it through and the interpreter reports nothing.
|
||||||
const QUIT_THROW: i32 = -56;
|
const QUIT_THROW: i32 = -56;
|
||||||
|
|
||||||
|
/// Forth 2012 throw code for ABORT. CATCH sees it like any other exception,
|
||||||
|
/// but an uncaught one prints nothing: ABORT is specified as "empty the data
|
||||||
|
/// stack and perform the function of QUIT", and QUIT displays no message.
|
||||||
|
const ABORT_THROW: i32 = -1;
|
||||||
|
|
||||||
// Control-flow action codes for PendingAction::CompileControl
|
// Control-flow action codes for PendingAction::CompileControl
|
||||||
const CTRL_IF: i32 = 1;
|
const CTRL_IF: i32 = 1;
|
||||||
const CTRL_ELSE: i32 = 2;
|
const CTRL_ELSE: i32 = 2;
|
||||||
@@ -762,13 +767,15 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
self.compile_frames.clear();
|
self.compile_frames.clear();
|
||||||
self.compiling_source.clear();
|
self.compiling_source.clear();
|
||||||
self.source_capture_from = None;
|
self.source_capture_from = None;
|
||||||
// QUIT is not an error: the wipe above IS its "enter
|
// QUIT and ABORT are not errors: the wipe above IS their
|
||||||
// interpretation state", and the standard asks for the
|
// "enter interpretation state", and the standard asks for
|
||||||
// user input device back and no message at all. The rest
|
// the user input device back and no message at all (ABORT
|
||||||
|
// is defined as emptying the data stack and then doing
|
||||||
|
// QUIT; only ABORT" prints, and that is code -2). The rest
|
||||||
// of this input -- and any EVALUATE / INCLUDE frame it
|
// of this input -- and any EVALUATE / INCLUDE frame it
|
||||||
// unwound through -- is abandoned by returning here.
|
// unwound through -- is abandoned by returning here.
|
||||||
let mut tc = self.throw_code.lock().unwrap();
|
let mut tc = self.throw_code.lock().unwrap();
|
||||||
if *tc == Some(QUIT_THROW) {
|
if matches!(*tc, Some(QUIT_THROW | ABORT_THROW)) {
|
||||||
*tc = None;
|
*tc = None;
|
||||||
drop(tc);
|
drop(tc);
|
||||||
self.rt.mem_write_i32(crate::memory::SYSVAR_SOURCE_ID, 0);
|
self.rt.mem_write_i32(crate::memory::SYSVAR_SOURCE_ID, 0);
|
||||||
@@ -1289,6 +1296,14 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constructs the outer interpreter only knows how to compile. Forth
|
||||||
|
// 2012 leaves their interpretation semantics undefined and both gforth
|
||||||
|
// and SwiftForth name the standard condition, so say what is wrong
|
||||||
|
// instead of claiming the word does not exist.
|
||||||
|
if INTERPRETER_TOKENS.contains(&token.to_uppercase().as_str()) {
|
||||||
|
anyhow::bail!("interpreting a compile-only word: {token} (throw -14)");
|
||||||
|
}
|
||||||
|
|
||||||
anyhow::bail!("unknown word: {token}");
|
anyhow::bail!("unknown word: {token}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9438,6 +9453,55 @@ mod tests {
|
|||||||
assert_eq!(vm.take_output(), "0 ", "back to the user input device");
|
assert_eq!(vm.take_output(), "0 ", "back to the user input device");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
// ABORT reporting — gforth and sf64 both print nothing for an uncaught
|
||||||
|
// ABORT: it is specified as "empty the data stack and perform the
|
||||||
|
// function of QUIT", and QUIT displays no message. Only ABORT" prints.
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_abort_is_silent_and_abandons_the_rest() {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
vm.evaluate("1 2 ABORT 99 .").unwrap();
|
||||||
|
assert_eq!(vm.take_output(), "");
|
||||||
|
assert!(vm.data_stack().is_empty(), "ABORT empties the data stack");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_abort_is_still_catchable() {
|
||||||
|
// Unlike QUIT: CATCH reports -1 and restores the stack depth.
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
vm.evaluate("1 2 ' ABORT CATCH .").unwrap();
|
||||||
|
assert_eq!(vm.take_output(), "-1 ");
|
||||||
|
assert_eq!(vm.data_stack(), vec![2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_abort_quote_still_reports_its_text() {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
let err = vm.evaluate(": T -1 ABORT\" oops\" ; T").unwrap_err();
|
||||||
|
assert_eq!(err.to_string(), "oops");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compile_only_words_say_so_in_interpret_mode() {
|
||||||
|
for word in ["ABORT\"", "IF", "THEN", "LOOP", "LITERAL", "RECURSE"] {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
let err = vm.evaluate(word).unwrap_err().to_string();
|
||||||
|
assert!(
|
||||||
|
err.contains("compile-only word"),
|
||||||
|
"{word}: expected the standard condition, got {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compile_only_check_does_not_swallow_typos() {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
let err = vm.evaluate("NOSUCHWORD").unwrap_err().to_string();
|
||||||
|
assert!(err.contains("unknown word"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
// New words: SOURCE
|
// New words: SOURCE
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ workspace = true
|
|||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wafer-core = { path = "../core", version = "0.2.5", default-features = false, features = ["crypto"] }
|
wafer-core = { path = "../core", version = "0.2.6", default-features = false, features = ["crypto"] }
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
send_wrapper = { workspace = true }
|
send_wrapper = { workspace = true }
|
||||||
|
|||||||
Reference in New Issue
Block a user