fix(core): search order is authoritative; host words validate stack args
- Dictionary::find no longer falls back to the newest entry across all wordlists when the search order has no match (Forth 2012 16.3.3; gforth and SwiftForth agree). Cross-engine corpus program guards it. - ~40 argument-taking host words (RND-SEED, ACCEPT, RESIZE, ALLOCATE, SEARCH, SUBSTITUTE, ROLL, M*, UM/MOD, SF@/SF!/DF@/DF!, F./FE./FS./F~, 2R@, ...) popped or read stack cells with no underflow check; on an empty stack the pointer silently drifted past its base. New host_need/ host_fneed/host_fpop checked helpers; class-wide regression test drives every word on an empty stack.
This commit is contained in:
@@ -192,10 +192,9 @@ impl Dictionary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fallback: return newest entry across all wordlists
|
// In no wordlist of the search order: not findable
|
||||||
if let Some(&(_wid, word_addr, fn_index, is_immediate)) = entries.last() {
|
// (Forth 2012 §16.3.3 — the order is authoritative).
|
||||||
return Some((word_addr, WordId(fn_index), is_immediate));
|
return None;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: linked-list walk (for words not yet in the index)
|
// Fallback: linked-list walk (for words not yet in the index)
|
||||||
|
|||||||
+187
-91
@@ -521,6 +521,40 @@ fn host_pop(ctx: &mut dyn HostAccess) -> anyhow::Result<i32> {
|
|||||||
Ok(v)
|
Ok(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure the data stack holds at least `n` cells; returns the stack
|
||||||
|
/// pointer for the caller's reads. Host words must check before raw
|
||||||
|
/// pointer arithmetic — compiled-code guards do not cover them.
|
||||||
|
fn host_need(ctx: &mut dyn HostAccess, n: u32) -> anyhow::Result<u32> {
|
||||||
|
let sp = ctx.get_dsp();
|
||||||
|
match n.checked_mul(CELL_SIZE).and_then(|b| sp.checked_add(b)) {
|
||||||
|
Some(end) if end <= DATA_STACK_TOP => Ok(sp),
|
||||||
|
_ => anyhow::bail!("Stack underflow"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure the float stack holds at least `n` floats; returns the pointer.
|
||||||
|
fn host_fneed(ctx: &mut dyn HostAccess, n: u32) -> anyhow::Result<u32> {
|
||||||
|
let sp = ctx.get_fsp();
|
||||||
|
match n.checked_mul(FLOAT_SIZE).and_then(|b| sp.checked_add(b)) {
|
||||||
|
Some(end) if end <= FLOAT_STACK_TOP => Ok(sp),
|
||||||
|
_ => anyhow::bail!("Float stack underflow"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checked float-stack pop for host words.
|
||||||
|
fn host_fpop(ctx: &mut dyn HostAccess) -> anyhow::Result<f64> {
|
||||||
|
let sp = ctx.get_fsp();
|
||||||
|
if sp >= FLOAT_STACK_TOP {
|
||||||
|
anyhow::bail!("Float stack underflow");
|
||||||
|
}
|
||||||
|
let bytes: [u8; 8] = ctx
|
||||||
|
.mem_read_slice(sp, 8)
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| anyhow::anyhow!("float stack read failed"))?;
|
||||||
|
ctx.set_fsp(sp + FLOAT_SIZE);
|
||||||
|
Ok(f64::from_le_bytes(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
/// Advance past the next `\n` in `buf`, starting at `from`. Returns the
|
/// Advance past the next `\n` in `buf`, starting at `from`. Returns the
|
||||||
/// byte index of the first character on the next line (or `buf.len()` if
|
/// byte index of the first character on the next line (or `buf.len()` if
|
||||||
/// there's no more newline). Used by the `\` line-comment handler per
|
/// there's no more newline). Used by the `\` line-comment handler per
|
||||||
@@ -3325,7 +3359,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
let digest_len = algo.digest_len as i32;
|
let digest_len = algo.digest_len as i32;
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop ( c-addr u )
|
// Pop ( c-addr u )
|
||||||
let dsp = ctx.get_dsp();
|
let dsp = host_need(ctx, 2)?;
|
||||||
let u = ctx.mem_read_i32(dsp) as u32;
|
let u = ctx.mem_read_i32(dsp) as u32;
|
||||||
let c_addr = ctx.mem_read_i32(dsp + CELL_SIZE) as u32;
|
let c_addr = ctx.mem_read_i32(dsp + CELL_SIZE) as u32;
|
||||||
|
|
||||||
@@ -4077,8 +4111,9 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
fn register_roll(&mut self) -> anyhow::Result<()> {
|
fn register_roll(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop u from stack
|
// Pop u from stack
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let u = ctx.mem_read_i32(sp as u32) as u32;
|
let u = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
|
host_need(ctx, u.saturating_add(2))?;
|
||||||
let sp = sp + CELL_SIZE; // pop u
|
let sp = sp + CELL_SIZE; // pop u
|
||||||
|
|
||||||
if u == 0 {
|
if u == 0 {
|
||||||
@@ -4263,7 +4298,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
|
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop xt from data stack
|
// Pop xt from data stack
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let xt = ctx.mem_read_i32(sp as u32) as u32;
|
let xt = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
|
|
||||||
// Look up PFA for this xt
|
// Look up PFA for this xt
|
||||||
@@ -4283,7 +4318,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
/// ENVIRONMENT? -- ( c-addr u -- false | value true ) query system parameters.
|
/// ENVIRONMENT? -- ( c-addr u -- false | value true ) query system parameters.
|
||||||
fn register_environment_q(&mut self) -> anyhow::Result<()> {
|
fn register_environment_q(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
let u = ctx.mem_read_i32(sp as u32) as u32;
|
let u = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
let addr = u32::from_le_bytes(b);
|
let addr = u32::from_le_bytes(b);
|
||||||
@@ -5119,7 +5154,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
/// M* ( n1 n2 -- d ) signed multiply producing double-cell result.
|
/// M* ( n1 n2 -- d ) signed multiply producing double-cell result.
|
||||||
fn register_m_star(&mut self) -> anyhow::Result<()> {
|
fn register_m_star(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
let n2 = ctx.mem_read_i32(sp as u32) as i64;
|
let n2 = ctx.mem_read_i32(sp as u32) as i64;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
let n1 = i32::from_le_bytes(b) as i64;
|
let n1 = i32::from_le_bytes(b) as i64;
|
||||||
@@ -5140,7 +5175,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
/// UM* ( u1 u2 -- ud ) unsigned multiply producing double-cell result.
|
/// UM* ( u1 u2 -- ud ) unsigned multiply producing double-cell result.
|
||||||
fn register_um_star(&mut self) -> anyhow::Result<()> {
|
fn register_um_star(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
let u2 = ctx.mem_read_i32(sp as u32) as u32 as u64;
|
let u2 = ctx.mem_read_i32(sp as u32) as u32 as u64;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
let u1 = u32::from_le_bytes(b) as u64;
|
let u1 = u32::from_le_bytes(b) as u64;
|
||||||
@@ -5159,7 +5194,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
/// UM/MOD ( ud u -- rem quot ) unsigned double-cell divide.
|
/// UM/MOD ( ud u -- rem quot ) unsigned double-cell divide.
|
||||||
fn register_um_div_mod(&mut self) -> anyhow::Result<()> {
|
fn register_um_div_mod(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 3)?;
|
||||||
// Pop u (divisor)
|
// Pop u (divisor)
|
||||||
let divisor = ctx.mem_read_i32(sp as u32) as u32 as u64;
|
let divisor = ctx.mem_read_i32(sp as u32) as u32 as u64;
|
||||||
// Pop ud (double-cell): high at sp+4, low at sp+8
|
// Pop ud (double-cell): high at sp+4, low at sp+8
|
||||||
@@ -5247,7 +5282,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
|
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop xt from data stack
|
// Pop xt from data stack
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let xt = ctx.mem_read_i32(sp as u32) as u32;
|
let xt = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
// Drop top of stack
|
// Drop top of stack
|
||||||
let new_sp = sp + 4;
|
let new_sp = sp + 4;
|
||||||
@@ -5332,7 +5367,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
|
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// ( c-addr u -- ) — pop both cells.
|
// ( c-addr u -- ) — pop both cells.
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
let u = ctx.mem_read_i32(sp) as u32;
|
let u = ctx.mem_read_i32(sp) as u32;
|
||||||
let addr = ctx.mem_read_i32(sp + CELL_SIZE) as u32;
|
let addr = ctx.mem_read_i32(sp + CELL_SIZE) as u32;
|
||||||
ctx.set_dsp(sp + 2 * CELL_SIZE);
|
ctx.set_dsp(sp + 2 * CELL_SIZE);
|
||||||
@@ -5453,10 +5488,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
/// WORD ( char -- c-addr ) reads from the WASM input buffer and updates >IN.
|
/// WORD ( char -- c-addr ) reads from the WASM input buffer and updates >IN.
|
||||||
fn register_word_word(&mut self) -> anyhow::Result<()> {
|
fn register_word_word(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop delimiter from data stack
|
let delim = host_pop(ctx)? as u8;
|
||||||
let sp = ctx.get_dsp();
|
|
||||||
let delim = ctx.mem_read_i32(sp as u32) as u8;
|
|
||||||
ctx.set_dsp(((sp + CELL_SIZE) as i32) as u32);
|
|
||||||
|
|
||||||
// Read >IN and #TIB from WASM memory
|
// Read >IN and #TIB from WASM memory
|
||||||
let b: [u8; 4] = ctx.mem_read_i32(SYSVAR_TO_IN as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32(SYSVAR_TO_IN as u32).to_le_bytes();
|
||||||
@@ -5502,8 +5534,8 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
ctx.mem_write_u8((dst_start + i) as u32, byte);
|
ctx.mem_write_u8((dst_start + i) as u32, byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push c-addr onto data stack
|
// Push c-addr onto data stack (reuse the popped delim's slot)
|
||||||
let new_sp = sp; // We already popped delim, now push c-addr
|
let new_sp = ctx.get_dsp() - CELL_SIZE;
|
||||||
ctx.mem_write_i32(new_sp, buf_addr as i32);
|
ctx.mem_write_i32(new_sp, buf_addr as i32);
|
||||||
ctx.set_dsp(new_sp);
|
ctx.set_dsp(new_sp);
|
||||||
|
|
||||||
@@ -5856,6 +5888,9 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
fn register_2r_fetch(&mut self) -> anyhow::Result<()> {
|
fn register_2r_fetch(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let rsp_val = ctx.get_rsp();
|
let rsp_val = ctx.get_rsp();
|
||||||
|
if rsp_val + 2 * CELL_SIZE > RETURN_STACK_TOP {
|
||||||
|
anyhow::bail!("Return stack underflow");
|
||||||
|
}
|
||||||
let sp = ctx.get_dsp();
|
let sp = ctx.get_dsp();
|
||||||
// Return stack: x2 at rsp, x1 at rsp+4
|
// Return stack: x2 at rsp, x1 at rsp+4
|
||||||
let b: [u8; 4] = ctx.mem_read_i32(rsp_val as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32(rsp_val as u32).to_le_bytes();
|
||||||
@@ -5963,9 +5998,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
|
|
||||||
let state = Arc::clone(&self.rng_state);
|
let state = Arc::clone(&self.rng_state);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let seed = host_pop(ctx)? as u32 as u64;
|
||||||
let seed = ctx.mem_read_i32(sp as u32) as u32 as u64;
|
|
||||||
ctx.set_dsp(sp + CELL_SIZE);
|
|
||||||
let mut s = state.lock().unwrap();
|
let mut s = state.lock().unwrap();
|
||||||
*s = if seed == 0 {
|
*s = if seed == 0 {
|
||||||
0xDEAD_BEEF_CAFE_BABE
|
0xDEAD_BEEF_CAFE_BABE
|
||||||
@@ -5982,7 +6015,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
fn register_parse_host(&mut self) -> anyhow::Result<()> {
|
fn register_parse_host(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop delimiter from data stack
|
// Pop delimiter from data stack
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let delim = ctx.mem_read_i32(sp as u32) as u8;
|
let delim = ctx.mem_read_i32(sp as u32) as u8;
|
||||||
let sp = sp + CELL_SIZE; // pop delimiter
|
let sp = sp + CELL_SIZE; // pop delimiter
|
||||||
|
|
||||||
@@ -6100,7 +6133,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// In non-interactive mode, return 0 (no input).
|
// In non-interactive mode, return 0 (no input).
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop +n1 (max count) and c-addr from stack
|
// Pop +n1 (max count) and c-addr from stack
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
let new_sp = sp + CELL_SIZE; // pop +n1
|
let new_sp = sp + CELL_SIZE; // pop +n1
|
||||||
let new_sp = new_sp + CELL_SIZE; // pop c-addr
|
let new_sp = new_sp + CELL_SIZE; // pop c-addr
|
||||||
// Push 0 (no characters received)
|
// Push 0 (no characters received)
|
||||||
@@ -6125,7 +6158,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
fn register_memory_alloc(&mut self) -> anyhow::Result<()> {
|
fn register_memory_alloc(&mut self) -> anyhow::Result<()> {
|
||||||
// ALLOCATE ( u -- a-addr ior )
|
// ALLOCATE ( u -- a-addr ior )
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let size = ctx.mem_read_i32(sp as u32) as u32;
|
let size = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
|
|
||||||
let mem_len = ctx.mem_len() as u32;
|
let mem_len = ctx.mem_len() as u32;
|
||||||
@@ -6184,7 +6217,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// FREE ( a-addr -- ior )
|
// FREE ( a-addr -- ior )
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Simple allocator: FREE is a no-op (arena style), return ior=0
|
// Simple allocator: FREE is a no-op (arena style), return ior=0
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
// Replace a-addr with ior=0
|
// Replace a-addr with ior=0
|
||||||
ctx.mem_write_i32(sp as u32, 0i32 as i32);
|
ctx.mem_write_i32(sp as u32, 0i32 as i32);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -6193,7 +6226,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
|
|
||||||
// RESIZE ( a-addr u -- a-addr2 ior )
|
// RESIZE ( a-addr u -- a-addr2 ior )
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
let new_size = ctx.mem_read_i32(sp as u32) as u32;
|
let new_size = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
let old_addr = u32::from_le_bytes(b);
|
let old_addr = u32::from_le_bytes(b);
|
||||||
@@ -6614,8 +6647,14 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
{
|
{
|
||||||
let so = Arc::clone(&self.search_order);
|
let so = Arc::clone(&self.search_order);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let n = ctx.mem_read_i32(sp as u32);
|
let n = ctx.mem_read_i32(sp as u32);
|
||||||
|
if !(-1..=64).contains(&n) {
|
||||||
|
anyhow::bail!("SET-ORDER: bad wordlist count: {n}");
|
||||||
|
}
|
||||||
|
if n != -1 {
|
||||||
|
host_need(ctx, 1 + n as u32)?;
|
||||||
|
}
|
||||||
|
|
||||||
if n == -1 {
|
if n == -1 {
|
||||||
*so.lock().unwrap() = vec![1];
|
*so.lock().unwrap() = vec![1];
|
||||||
@@ -6718,8 +6757,9 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
fn register_n_to_r(&mut self) -> anyhow::Result<()> {
|
fn register_n_to_r(&mut self) -> anyhow::Result<()> {
|
||||||
// N>R ( xn..x1 n -- ; R: -- x1..xn n )
|
// N>R ( xn..x1 n -- ; R: -- x1..xn n )
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let n = ctx.mem_read_i32(sp as u32) as u32;
|
let n = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
|
host_need(ctx, n.saturating_add(1))?;
|
||||||
|
|
||||||
let mut rsp_val = ctx.get_rsp();
|
let mut rsp_val = ctx.get_rsp();
|
||||||
|
|
||||||
@@ -6803,7 +6843,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// UNESCAPE ( c-addr1 u1 c-addr2 -- c-addr2 u2 )
|
// UNESCAPE ( c-addr1 u1 c-addr2 -- c-addr2 u2 )
|
||||||
// Copy string escaping each % as %%
|
// Copy string escaping each % as %%
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 3)?;
|
||||||
let dest = ctx.mem_read_i32(sp as u32) as u32;
|
let dest = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
let u1 = u32::from_le_bytes(b);
|
let u1 = u32::from_le_bytes(b);
|
||||||
@@ -6841,7 +6881,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// Define substitution: name (c-addr2 u2) → replacement (c-addr1 u1)
|
// Define substitution: name (c-addr2 u2) → replacement (c-addr1 u1)
|
||||||
let subs = Arc::clone(&self.substitutions);
|
let subs = Arc::clone(&self.substitutions);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 4)?;
|
||||||
// Stack: u2(sp), c-addr2(sp+4), u1(sp+8), c-addr1(sp+12)
|
// Stack: u2(sp), c-addr2(sp+4), u1(sp+8), c-addr1(sp+12)
|
||||||
let u2 = ctx.mem_read_i32(sp as u32) as u32;
|
let u2 = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
@@ -6869,7 +6909,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// Replace %name% patterns, %% → %
|
// Replace %name% patterns, %% → %
|
||||||
let subs = Arc::clone(&self.substitutions);
|
let subs = Arc::clone(&self.substitutions);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 4)?;
|
||||||
// Stack: u2/capacity(sp), c-addr2/dest(sp+4), u1(sp+8), c-addr1(sp+12)
|
// Stack: u2/capacity(sp), c-addr2/dest(sp+4), u1(sp+8), c-addr1(sp+12)
|
||||||
let capacity = ctx.mem_read_i32(sp as u32) as u32 as usize;
|
let capacity = ctx.mem_read_i32(sp as u32) as u32 as usize;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
@@ -6957,7 +6997,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
/// M*/ ( d n1 n2 -- d ) multiply d by n1, divide by n2.
|
/// M*/ ( d n1 n2 -- d ) multiply d by n1, divide by n2.
|
||||||
fn register_m_star_slash(&mut self) -> anyhow::Result<()> {
|
fn register_m_star_slash(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 4)?;
|
||||||
// Stack: n2(sp), n1(sp+4), d-hi(sp+8), d-lo(sp+12)
|
// Stack: n2(sp), n1(sp+4), d-hi(sp+8), d-lo(sp+12)
|
||||||
let n2 = ctx.mem_read_i32(sp as u32) as i128;
|
let n2 = ctx.mem_read_i32(sp as u32) as i128;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
@@ -7093,7 +7133,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
/// SEARCH ( c-addr1 u1 c-addr2 u2 -- c-addr3 u3 flag ) search for substring.
|
/// SEARCH ( c-addr1 u1 c-addr2 u2 -- c-addr3 u3 flag ) search for substring.
|
||||||
fn register_search(&mut self) -> anyhow::Result<()> {
|
fn register_search(&mut self) -> anyhow::Result<()> {
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 4)?;
|
||||||
// Stack: u2(sp), c-addr2(sp+4), u1(sp+8), c-addr1(sp+12)
|
// Stack: u2(sp), c-addr2(sp+4), u1(sp+8), c-addr1(sp+12)
|
||||||
let u2 = ctx.mem_read_i32(sp as u32) as usize;
|
let u2 = ctx.mem_read_i32(sp as u32) as usize;
|
||||||
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
let b: [u8; 4] = ctx.mem_read_i32((sp + 4) as u32).to_le_bytes();
|
||||||
@@ -7244,7 +7284,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// FROT ( F: r1 r2 r3 -- r2 r3 r1 )
|
// FROT ( F: r1 r2 r3 -- r2 r3 r1 )
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_fsp();
|
let sp = host_fneed(ctx, 3)?;
|
||||||
let c: [u8; 8] = ctx.mem_read_slice(sp, 8).try_into().unwrap();
|
let c: [u8; 8] = ctx.mem_read_slice(sp, 8).try_into().unwrap();
|
||||||
let b: [u8; 8] = ctx.mem_read_slice(sp + 8, 8).try_into().unwrap();
|
let b: [u8; 8] = ctx.mem_read_slice(sp + 8, 8).try_into().unwrap();
|
||||||
let a: [u8; 8] = ctx.mem_read_slice(sp + 16, 8).try_into().unwrap();
|
let a: [u8; 8] = ctx.mem_read_slice(sp + 16, 8).try_into().unwrap();
|
||||||
@@ -7311,14 +7351,9 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// If r3 < 0: true if |r1-r2| < |r3|*(|r1|+|r2|)
|
// If r3 < 0: true if |r1-r2| < |r3|*(|r1|+|r2|)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_fsp();
|
let r3 = host_fpop(ctx)?;
|
||||||
let r3_bytes: [u8; 8] = ctx.mem_read_slice(sp, 8).try_into().unwrap();
|
let r2 = host_fpop(ctx)?;
|
||||||
let r2_bytes: [u8; 8] = ctx.mem_read_slice(sp + 8, 8).try_into().unwrap();
|
let r1 = host_fpop(ctx)?;
|
||||||
let r1_bytes: [u8; 8] = ctx.mem_read_slice(sp + 16, 8).try_into().unwrap();
|
|
||||||
let r3 = f64::from_le_bytes(r3_bytes);
|
|
||||||
let r2 = f64::from_le_bytes(r2_bytes);
|
|
||||||
let r1 = f64::from_le_bytes(r1_bytes);
|
|
||||||
ctx.set_fsp(((sp + 24) as i32) as u32);
|
|
||||||
|
|
||||||
let result = if r3 > 0.0 {
|
let result = if r3 > 0.0 {
|
||||||
(r1 - r2).abs() < r3
|
(r1 - r2).abs() < r3
|
||||||
@@ -7366,7 +7401,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// FALIGNED ( addr -- f-addr ) align to float boundary (8 bytes)
|
// FALIGNED ( addr -- f-addr ) align to float boundary (8 bytes)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let addr = ctx.mem_read_i32(sp as u32) as u32;
|
let addr = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
let aligned = (addr + 7) & !7;
|
let aligned = (addr + 7) & !7;
|
||||||
ctx.mem_write_i32(sp as u32, aligned as i32);
|
ctx.mem_write_i32(sp as u32, aligned as i32);
|
||||||
@@ -7411,7 +7446,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// D>F ( d -- ) ( F: -- r ) convert double-cell integer to float
|
// D>F ( d -- ) ( F: -- r ) convert double-cell integer to float
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
// Double-cell: hi on top, lo below
|
// Double-cell: hi on top, lo below
|
||||||
let hi_bytes: [u8; 4] = ctx.mem_read_slice(sp, 4).try_into().unwrap();
|
let hi_bytes: [u8; 4] = ctx.mem_read_slice(sp, 4).try_into().unwrap();
|
||||||
let lo_bytes: [u8; 4] = ctx.mem_read_slice(sp + 4, 4).try_into().unwrap();
|
let lo_bytes: [u8; 4] = ctx.mem_read_slice(sp + 4, 4).try_into().unwrap();
|
||||||
@@ -7434,11 +7469,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// F>D ( -- d ) ( F: r -- ) convert float to double-cell integer
|
// F>D ( -- d ) ( F: r -- ) convert float to double-cell integer
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Pop from float stack
|
let f = host_fpop(ctx)?;
|
||||||
let fsp_val = ctx.get_fsp();
|
|
||||||
let bytes: [u8; 8] = ctx.mem_read_slice(fsp_val, 8).try_into().unwrap();
|
|
||||||
let f = f64::from_le_bytes(bytes);
|
|
||||||
ctx.set_fsp(fsp_val + FLOAT_SIZE);
|
|
||||||
// Convert to i64
|
// Convert to i64
|
||||||
let d = f as i64;
|
let d = f as i64;
|
||||||
let lo = d as i32;
|
let lo = d as i32;
|
||||||
@@ -7524,10 +7555,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
let output = Arc::clone(&self.output);
|
let output = Arc::clone(&self.output);
|
||||||
let precision = Arc::clone(&self.float_precision);
|
let precision = Arc::clone(&self.float_precision);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_fsp();
|
let val = host_fpop(ctx)?;
|
||||||
let bytes: [u8; 8] = ctx.mem_read_slice(sp as u32, 8).try_into().unwrap();
|
|
||||||
let val = f64::from_le_bytes(bytes);
|
|
||||||
ctx.set_fsp(((sp + 8) as i32) as u32);
|
|
||||||
let prec = *precision.lock().unwrap();
|
let prec = *precision.lock().unwrap();
|
||||||
let s = format!("{val:.prec$} ");
|
let s = format!("{val:.prec$} ");
|
||||||
output.lock().unwrap().push_str(&s);
|
output.lock().unwrap().push_str(&s);
|
||||||
@@ -7541,10 +7569,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
let output = Arc::clone(&self.output);
|
let output = Arc::clone(&self.output);
|
||||||
let precision = Arc::clone(&self.float_precision);
|
let precision = Arc::clone(&self.float_precision);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_fsp();
|
let val = host_fpop(ctx)?;
|
||||||
let bytes: [u8; 8] = ctx.mem_read_slice(sp as u32, 8).try_into().unwrap();
|
|
||||||
let val = f64::from_le_bytes(bytes);
|
|
||||||
ctx.set_fsp(((sp + 8) as i32) as u32);
|
|
||||||
let prec = *precision.lock().unwrap();
|
let prec = *precision.lock().unwrap();
|
||||||
let s = format_engineering(val, prec);
|
let s = format_engineering(val, prec);
|
||||||
output.lock().unwrap().push_str(&s);
|
output.lock().unwrap().push_str(&s);
|
||||||
@@ -7558,10 +7583,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
let output = Arc::clone(&self.output);
|
let output = Arc::clone(&self.output);
|
||||||
let precision = Arc::clone(&self.float_precision);
|
let precision = Arc::clone(&self.float_precision);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_fsp();
|
let val = host_fpop(ctx)?;
|
||||||
let bytes: [u8; 8] = ctx.mem_read_slice(sp as u32, 8).try_into().unwrap();
|
|
||||||
let val = f64::from_le_bytes(bytes);
|
|
||||||
ctx.set_fsp(((sp + 8) as i32) as u32);
|
|
||||||
let prec = *precision.lock().unwrap();
|
let prec = *precision.lock().unwrap();
|
||||||
let s = format!("{val:.prec$E} ");
|
let s = format!("{val:.prec$E} ");
|
||||||
output.lock().unwrap().push_str(&s);
|
output.lock().unwrap().push_str(&s);
|
||||||
@@ -7588,9 +7610,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
{
|
{
|
||||||
let precision = Arc::clone(&self.float_precision);
|
let precision = Arc::clone(&self.float_precision);
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let n = host_pop(ctx)? as usize;
|
||||||
let n = ctx.mem_read_i32(sp as u32) as usize;
|
|
||||||
ctx.set_dsp(((sp + CELL_SIZE) as i32) as u32);
|
|
||||||
*precision.lock().unwrap() = n;
|
*precision.lock().unwrap() = n;
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -7600,17 +7620,12 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// REPRESENT ( c-addr u -- n flag1 flag2 ) ( F: r -- )
|
// REPRESENT ( c-addr u -- n flag1 flag2 ) ( F: r -- )
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
// Read all values from memory first
|
let sp = host_need(ctx, 2)?;
|
||||||
let sp = ctx.get_dsp();
|
|
||||||
let fsp_val = ctx.get_fsp();
|
|
||||||
let u = ctx.mem_read_i32(sp) as usize;
|
let u = ctx.mem_read_i32(sp) as usize;
|
||||||
let c_addr = ctx.mem_read_i32(sp + 4) as u32;
|
let c_addr = ctx.mem_read_i32(sp + 4) as u32;
|
||||||
let f_bytes: [u8; 8] = ctx.mem_read_slice(fsp_val, 8).try_into().unwrap();
|
let val = host_fpop(ctx)?;
|
||||||
let val = f64::from_le_bytes(f_bytes);
|
// Pop the 2 data cells
|
||||||
|
|
||||||
// Update stack pointers: pop 2 data cells, pop 1 float
|
|
||||||
ctx.set_dsp(sp + 8);
|
ctx.set_dsp(sp + 8);
|
||||||
ctx.set_fsp(fsp_val + FLOAT_SIZE);
|
|
||||||
|
|
||||||
let (digits, exp, is_negative, is_valid) = represent_float(val, u);
|
let (digits, exp, is_negative, is_valid) = represent_float(val, u);
|
||||||
|
|
||||||
@@ -7638,7 +7653,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// >FLOAT ( c-addr u -- flag ) ( F: -- r | ) parse string as float
|
// >FLOAT ( c-addr u -- flag ) ( F: -- r | ) parse string as float
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 2)?;
|
||||||
let u = ctx.mem_read_i32(sp) as usize;
|
let u = ctx.mem_read_i32(sp) as usize;
|
||||||
let c_addr = ctx.mem_read_i32(sp + 4) as u32;
|
let c_addr = ctx.mem_read_i32(sp + 4) as u32;
|
||||||
let s_bytes = ctx.mem_read_slice(c_addr, u);
|
let s_bytes = ctx.mem_read_slice(c_addr, u);
|
||||||
@@ -7679,14 +7694,9 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// SF! ( sf-addr -- ) ( F: r -- ) store as single-precision float (f32)
|
// SF! ( sf-addr -- ) ( F: r -- ) store as single-precision float (f32)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let addr = host_pop(ctx)? as u32;
|
||||||
let fsp_val = ctx.get_fsp();
|
let val = host_fpop(ctx)?;
|
||||||
let addr = ctx.mem_read_i32(sp) as u32;
|
|
||||||
let f_bytes: [u8; 8] = ctx.mem_read_slice(fsp_val, 8).try_into().unwrap();
|
|
||||||
let val = f64::from_le_bytes(f_bytes);
|
|
||||||
let f32_bytes = (val as f32).to_le_bytes();
|
let f32_bytes = (val as f32).to_le_bytes();
|
||||||
ctx.set_dsp(sp + CELL_SIZE);
|
|
||||||
ctx.set_fsp(fsp_val + FLOAT_SIZE);
|
|
||||||
ctx.mem_write_slice(addr, &f32_bytes);
|
ctx.mem_write_slice(addr, &f32_bytes);
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -7696,12 +7706,10 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// SF@ ( sf-addr -- ) ( F: -- r ) fetch single-precision float (f32)
|
// SF@ ( sf-addr -- ) ( F: -- r ) fetch single-precision float (f32)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let addr = host_pop(ctx)? as u32;
|
||||||
let fsp_val = ctx.get_fsp();
|
let fsp_val = ctx.get_fsp();
|
||||||
let addr = ctx.mem_read_i32(sp) as u32;
|
|
||||||
let f32_bytes: [u8; 4] = ctx.mem_read_slice(addr, 4).try_into().unwrap();
|
let f32_bytes: [u8; 4] = ctx.mem_read_slice(addr, 4).try_into().unwrap();
|
||||||
let val = f32::from_le_bytes(f32_bytes) as f64;
|
let val = f32::from_le_bytes(f32_bytes) as f64;
|
||||||
ctx.set_dsp(sp + CELL_SIZE);
|
|
||||||
let new_fsp = fsp_val - FLOAT_SIZE;
|
let new_fsp = fsp_val - FLOAT_SIZE;
|
||||||
ctx.set_fsp(new_fsp);
|
ctx.set_fsp(new_fsp);
|
||||||
ctx.mem_write_slice(new_fsp, &val.to_le_bytes());
|
ctx.mem_write_slice(new_fsp, &val.to_le_bytes());
|
||||||
@@ -7713,12 +7721,8 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// DF! ( df-addr -- ) ( F: r -- ) same as F! (our floats are already f64)
|
// DF! ( df-addr -- ) ( F: r -- ) same as F! (our floats are already f64)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let addr = host_pop(ctx)? as u32;
|
||||||
let fsp_val = ctx.get_fsp();
|
let float_bytes = host_fpop(ctx)?.to_le_bytes();
|
||||||
let addr = ctx.mem_read_i32(sp) as u32;
|
|
||||||
let float_bytes: [u8; 8] = ctx.mem_read_slice(fsp_val, 8).try_into().unwrap();
|
|
||||||
ctx.set_dsp(sp + CELL_SIZE);
|
|
||||||
ctx.set_fsp(fsp_val + FLOAT_SIZE);
|
|
||||||
ctx.mem_write_slice(addr, &float_bytes);
|
ctx.mem_write_slice(addr, &float_bytes);
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
@@ -7728,12 +7732,10 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// DF@ ( df-addr -- ) ( F: -- r ) same as F@ (our floats are already f64)
|
// DF@ ( df-addr -- ) ( F: -- r ) same as F@ (our floats are already f64)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let addr = host_pop(ctx)? as u32;
|
||||||
let fsp_val = ctx.get_fsp();
|
let fsp_val = ctx.get_fsp();
|
||||||
let addr = ctx.mem_read_i32(sp) as u32;
|
|
||||||
let float_bytes: [u8; 8] = ctx.mem_read_slice(addr, 8).try_into().unwrap();
|
let float_bytes: [u8; 8] = ctx.mem_read_slice(addr, 8).try_into().unwrap();
|
||||||
let val = f64::from_le_bytes(float_bytes);
|
let val = f64::from_le_bytes(float_bytes);
|
||||||
ctx.set_dsp(sp + CELL_SIZE);
|
|
||||||
let new_fsp = fsp_val - FLOAT_SIZE;
|
let new_fsp = fsp_val - FLOAT_SIZE;
|
||||||
ctx.set_fsp(new_fsp);
|
ctx.set_fsp(new_fsp);
|
||||||
ctx.mem_write_slice(new_fsp, &val.to_le_bytes());
|
ctx.mem_write_slice(new_fsp, &val.to_le_bytes());
|
||||||
@@ -7745,7 +7747,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// SFALIGNED, DFALIGNED (alignment words for single/double floats)
|
// SFALIGNED, DFALIGNED (alignment words for single/double floats)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let addr = ctx.mem_read_i32(sp as u32) as u32;
|
let addr = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
let aligned = (addr + 3) & !3; // 4-byte alignment for single float
|
let aligned = (addr + 3) & !3; // 4-byte alignment for single float
|
||||||
ctx.mem_write_i32(sp as u32, aligned as i32);
|
ctx.mem_write_i32(sp as u32, aligned as i32);
|
||||||
@@ -7757,7 +7759,7 @@ impl<R: Runtime> ForthVM<R> {
|
|||||||
// DFALIGNED is the same as FALIGNED (8-byte alignment)
|
// DFALIGNED is the same as FALIGNED (8-byte alignment)
|
||||||
{
|
{
|
||||||
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
let func: HostFn = Box::new(move |ctx: &mut dyn HostAccess| {
|
||||||
let sp = ctx.get_dsp();
|
let sp = host_need(ctx, 1)?;
|
||||||
let addr = ctx.mem_read_i32(sp as u32) as u32;
|
let addr = ctx.mem_read_i32(sp as u32) as u32;
|
||||||
let aligned = (addr + 7) & !7;
|
let aligned = (addr + 7) & !7;
|
||||||
ctx.mem_write_i32(sp as u32, aligned as i32);
|
ctx.mem_write_i32(sp as u32, aligned as i32);
|
||||||
@@ -9549,6 +9551,100 @@ mod tests {
|
|||||||
assert!(!output.contains("__CTRL__"));
|
assert!(!output.contains("__CTRL__"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rnd_seed_underflow_is_clean_error() {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
let err = vm.evaluate("RND-SEED").unwrap_err();
|
||||||
|
assert!(err.to_string().contains("underflow"), "{err}");
|
||||||
|
// The stack pointer must not have drifted above the base.
|
||||||
|
vm.evaluate("RANDOM .S").unwrap();
|
||||||
|
assert!(vm.take_output().starts_with("<1>"), "dsp drifted");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_host_words_underflow_cleanly() {
|
||||||
|
// Every argument-taking host word must fail cleanly on an empty
|
||||||
|
// stack and leave both stack pointers at their bases (host words
|
||||||
|
// are outside the compiled-code guards).
|
||||||
|
let words = [
|
||||||
|
"RND-SEED",
|
||||||
|
"WORD",
|
||||||
|
"SET-ORDER",
|
||||||
|
"SET-PRECISION",
|
||||||
|
"REPRESENT",
|
||||||
|
">FLOAT",
|
||||||
|
"SF!",
|
||||||
|
"SF@",
|
||||||
|
"DF!",
|
||||||
|
"DF@",
|
||||||
|
"D>F",
|
||||||
|
"F.",
|
||||||
|
"FE.",
|
||||||
|
"FS.",
|
||||||
|
"F~",
|
||||||
|
"ROLL",
|
||||||
|
">BODY",
|
||||||
|
"ENVIRONMENT?",
|
||||||
|
"M*",
|
||||||
|
"UM*",
|
||||||
|
"UM/MOD",
|
||||||
|
"COMPILE,",
|
||||||
|
"ACCEPT",
|
||||||
|
"ALLOCATE",
|
||||||
|
"FREE",
|
||||||
|
"RESIZE",
|
||||||
|
"N>R",
|
||||||
|
"UNESCAPE",
|
||||||
|
"REPLACES",
|
||||||
|
"SUBSTITUTE",
|
||||||
|
"M*/",
|
||||||
|
"SEARCH",
|
||||||
|
"FALIGNED",
|
||||||
|
"SFALIGNED",
|
||||||
|
"DFALIGNED",
|
||||||
|
"FROT",
|
||||||
|
"F>D",
|
||||||
|
"2R@",
|
||||||
|
// WORD and PARSE are intercepted by the outer interpreter in
|
||||||
|
// interpret mode; exercise their host variants compiled.
|
||||||
|
": T_ WORD ; T_",
|
||||||
|
": T_ PARSE ; T_",
|
||||||
|
#[cfg(feature = "crypto")]
|
||||||
|
"SHA256",
|
||||||
|
];
|
||||||
|
for w in words {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
let r = vm.evaluate(w);
|
||||||
|
assert!(r.is_err(), "{w}: silent underflow accepted");
|
||||||
|
vm.evaluate("DEPTH FDEPTH + .").unwrap();
|
||||||
|
assert_eq!(vm.take_output(), "0 ", "{w}: stack pointer drifted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Search order is authoritative (matches gforth + SwiftForth) --
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_search_order_hides_unlisted_wordlists() {
|
||||||
|
let mut vm = ForthVM::<NativeRuntime>::new().unwrap();
|
||||||
|
vm.evaluate(
|
||||||
|
"WORDLIST CONSTANT MY-WL MY-WL SET-CURRENT : SECRET 42 ; FORTH-WORDLIST SET-CURRENT",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// MY-WL was never in the search order: SECRET must not resolve.
|
||||||
|
let err = vm.evaluate("SECRET").unwrap_err();
|
||||||
|
assert!(err.to_string().contains("unknown word"), "{err}");
|
||||||
|
// Push MY-WL onto the order: now it resolves.
|
||||||
|
vm.evaluate("GET-ORDER MY-WL SWAP 1+ SET-ORDER SECRET .")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(vm.take_output(), "42 ");
|
||||||
|
// Back to the default order: hidden again.
|
||||||
|
vm.evaluate("-1 SET-ORDER").unwrap();
|
||||||
|
assert!(vm.evaluate("SECRET").is_err());
|
||||||
|
// FORTH words stay findable throughout.
|
||||||
|
vm.evaluate("1 2 + .").unwrap();
|
||||||
|
assert_eq!(vm.take_output(), "3 ");
|
||||||
|
}
|
||||||
|
|
||||||
// -- Error reporting (WS-008) --
|
// -- Error reporting (WS-008) --
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -453,6 +453,21 @@ fn programs() -> Vec<Program> {
|
|||||||
expected: "99 \n",
|
expected: "99 \n",
|
||||||
category: Category::Definitions,
|
category: Category::Definitions,
|
||||||
},
|
},
|
||||||
|
Program {
|
||||||
|
name: "search-order-hides",
|
||||||
|
code: "WORDLIST CONSTANT MY-WL\n\
|
||||||
|
MY-WL SET-CURRENT\n\
|
||||||
|
: SECRET 42 ;\n\
|
||||||
|
FORTH-WORDLIST SET-CURRENT\n\
|
||||||
|
[UNDEFINED] SECRET . CR\n\
|
||||||
|
GET-ORDER MY-WL SWAP 1+ SET-ORDER\n\
|
||||||
|
[DEFINED] SECRET . CR\n\
|
||||||
|
SECRET . CR\n\
|
||||||
|
-1 SET-ORDER\n\
|
||||||
|
[UNDEFINED] SECRET . CR",
|
||||||
|
expected: "-1 \n-1 \n42 \n-1 \n",
|
||||||
|
category: Category::Definitions,
|
||||||
|
},
|
||||||
// -- Strings --
|
// -- Strings --
|
||||||
Program {
|
Program {
|
||||||
name: "s-quote-type",
|
name: "s-quote-type",
|
||||||
|
|||||||
Reference in New Issue
Block a user