boot: fix S interpret-mode — copy string out of TIB

`S name` in interpret mode used to leave (c-addr u) pointing into the
input buffer, so the next REFILL clobbered the bytes. Typing `s test`
then `type` on a fresh line printed "pest" because the new input
overwrote the first chars of the old TIB content.

Move `S` from boot.fth to the Rust outer interpreter alongside `S"` /
`C"`: both interpret and compile modes now copy the token to HERE-space
(stable across REFILL). Compile-mode output is still bit-identical to
writing `S" name"` inline.

Adds `test_s_interpret_survives_refill` regression.
This commit is contained in:
2026-04-15 19:49:51 +02:00
parent ec950551fd
commit 9905399edb
2 changed files with 50 additions and 15 deletions
+46
View File
@@ -671,6 +671,23 @@ impl<R: Runtime> ForthVM<R> {
}
return Ok(());
}
if token_upper == "S" {
// State-smart string literal for the next whitespace-delimited token.
// Interpret mode: copy token bytes to HERE-space (stable across REFILL),
// push ( c-addr u ). Compile-mode branch lives in compile_token.
if let Some(name) = self.next_token() {
self.refresh_user_here();
let addr = self.user_here;
let bytes = name.as_bytes();
let len = bytes.len() as u32;
self.rt.mem_write_slice(addr, bytes);
self.user_here += len;
self.sync_here_cell();
self.push_data_stack(addr as i32)?;
self.push_data_stack(len as i32)?;
}
return Ok(());
}
if token_upper == "(" {
// Comment -- skip until )
self.parse_until(')');
@@ -820,6 +837,23 @@ impl<R: Runtime> ForthVM<R> {
}
return Ok(());
}
if token_upper == "S" {
// Compile-mode twin of the interpret-mode S handler: parse next
// whitespace-delimited token, copy into HERE, compile ( c-addr u )
// literals. Bit-identical to writing S" name" inline.
if let Some(name) = self.next_token() {
self.refresh_user_here();
let addr = self.user_here;
let bytes = name.as_bytes();
let len = bytes.len() as u32;
self.rt.mem_write_slice(addr, bytes);
self.user_here += len;
self.sync_here_cell();
self.push_ir(IrOp::PushI32(addr as i32));
self.push_ir(IrOp::PushI32(len as i32));
}
return Ok(());
}
if token_upper == "(" {
self.parse_until(')');
return Ok(());
@@ -7580,6 +7614,18 @@ mod tests {
assert_eq!(vm.take_output(), "kelvar");
}
#[test]
fn test_s_interpret_survives_refill() {
// Regression: `S name` in interpret mode used to return an address
// pointing into TIB, so the next REFILL clobbered the string.
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
vm.evaluate("S test").unwrap();
vm.evaluate(".S").unwrap();
vm.take_output();
vm.evaluate("TYPE").unwrap();
assert_eq!(vm.take_output(), "test");
}
// ===================================================================
// New words: COUNT
// ===================================================================