use crate::Result;
use crate::data::Mem;
use crate::error::{Error, KernelError};
use crate::host::{Io, MaybeClock, NullHost};
use crate::kernel::{Config, Kernel};
use crate::log::debug;
use crate::state::{Booted, Loading, Ready, State};
const WORDLISTS: &[(&str, &[u8])] = &[
("core", include_bytes!("core.fth")),
("core-ext", include_bytes!("core-ext.fth")),
("tools", include_bytes!("tools.fth")),
];
pub struct Ferth<M: Mem = [u8; 65536], H: Io = NullHost, S: State = Ready> {
kernel: Kernel<M, H, Booted>,
state: S,
}
impl<M: Mem, H: Io, S: State> Ferth<M, H, S> {
pub fn evaluate(&mut self, code: impl AsRef<[u8]>) -> Result<()> {
for line in code.as_ref().split(|&u| u == b'\n') {
self.kernel.set_source(line)?;
self.kernel.catch_interpret()?;
}
Ok(())
}
pub fn push(&mut self, x: usize) -> Result<()> {
self.kernel.push(x)
}
pub fn pop(&mut self) -> Result<usize> {
self.kernel.pop()
}
pub fn reset(&mut self) {
self.kernel.reset()
}
pub fn stack(&self) -> impl Iterator<Item = usize> + '_ {
self.kernel.stack()
}
}
impl<M: Mem, H: Io> Ferth<M, H, Loading> {
pub fn new(mem: M, host: H) -> Result<Ferth<M, H, Ready>>
where
H: MaybeClock,
{
Self::with_config(mem, host, Config::default())
}
pub fn with_config(mem: M, host: H, config: Config) -> Result<Ferth<M, H, Ready>>
where
H: MaybeClock,
{
let mut fe = Ferth {
kernel: Kernel::new(mem, host, config)?.boot()?,
state: Loading {},
};
#[allow(unused_variables)] for (name, src) in WORDLISTS {
fe.evaluate(src)?;
debug!("SYSTEM", "Loaded {} wordlist", name);
}
let mut xt = |name: &'static str| -> Result<usize> {
fe.kernel
.dict()
.find(name.as_bytes())?
.map(|(xt, _)| xt)
.ok_or(KernelError::MissingEntryPoint(name).into())
};
let state = Ready {
xt_load: xt("(load)")?,
xt_quit: xt("quit")?,
};
fe.evaluate(b"(check-bootstrap)")?;
debug!("SYSTEM", "Passed boot checks");
debug!("SYSTEM", "Ready");
Ok(Ferth {
kernel: fe.kernel,
state,
})
}
}
impl<M: Mem, H: Io> Ferth<M, H, Ready> {
pub fn load(&mut self) -> Result<()> {
let catch_xt = self
.kernel
.dict()
.find(b"catch")
.unwrap()
.map(|(xt, _)| xt)
.unwrap();
self.push(self.state.xt_load)?;
self.kernel.execute(catch_xt)?;
let code = self.pop()? as isize;
if code != 0 {
return Err(Error::Throw(code.into()));
}
Ok(())
}
pub fn quit(&mut self) -> Result<()> {
self.kernel.execute(self.state.xt_quit)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{Error, Ior};
use crate::{FALSE, TRUE};
#[test]
fn test_undefined_word() {
let mut fe = Ferth::new([0u8; 65536], NullHost).unwrap();
assert!(matches!(
fe.evaluate(b"nope"),
Err(Error::Throw(Ior::UNDEFINED_WORD))
));
}
#[test]
fn test_long_word_name_errors() {
let mut fe = Ferth::new([0u8; 65536], NullHost).unwrap();
let ok = [b": ".as_slice(), &[b'a'; 30], b" 1 ;"].concat();
assert!(fe.evaluate(&ok).is_ok());
let long = [b": ".as_slice(), &[b'a'; 40], b" 1 ;"].concat();
assert_eq!(
fe.evaluate(&long),
Err(Error::Throw(Ior::DEFINITION_NAME_TOO_LONG))
);
}
#[test]
fn test_environment() {
let mut fe = Ferth::new([0u8; 65536], NullHost).unwrap();
let single = |fe: &mut Ferth, q: &[u8], expected: usize| {
fe.evaluate(q).unwrap();
assert_eq!(fe.pop().unwrap(), TRUE);
assert_eq!(fe.pop().unwrap(), expected);
};
let double = |fe: &mut Ferth, q: &[u8], lo: usize, hi: usize| {
fe.evaluate(q).unwrap();
assert_eq!(fe.pop().unwrap(), TRUE);
assert_eq!(fe.pop().unwrap(), hi);
assert_eq!(fe.pop().unwrap(), lo);
};
single(
&mut fe,
br#"s" /COUNTED-STRING" environment?"#,
u8::MAX as usize,
);
single(
&mut fe,
br#"s" /HOLD" environment?"#,
2 * (usize::BITS as usize) + 2,
);
single(&mut fe, br#"s" /PAD" environment?"#, 84);
single(
&mut fe,
br#"s" ADDRESS-UNIT-BITS" environment?"#,
u8::BITS as usize,
);
single(&mut fe, br#"s" FLOORED" environment?"#, FALSE);
single(&mut fe, br#"s" MAX-CHAR" environment?"#, u8::MAX as usize);
double(
&mut fe,
br#"s" MAX-D" environment?"#,
usize::MAX,
isize::MAX as usize,
);
single(&mut fe, br#"s" MAX-N" environment?"#, isize::MAX as usize);
single(&mut fe, br#"s" MAX-U" environment?"#, usize::MAX);
double(
&mut fe,
br#"s" MAX-UD" environment?"#,
usize::MAX,
usize::MAX,
);
single(&mut fe, br#"s" RETURN-STACK-CELLS" environment?"#, 64);
single(&mut fe, br#"s" STACK-CELLS" environment?"#, 64);
fe.evaluate(br#"s" UNKNOWN" environment?"#).unwrap();
assert_eq!(fe.pop().unwrap(), FALSE);
}
#[test]
fn test_catch_throw() {
let mut fe = Ferth::new([0u8; 65536], NullHost).unwrap();
fe.evaluate(b": ok 42 ;").unwrap();
fe.evaluate(b"' ok catch").unwrap();
assert_eq!(fe.pop().unwrap(), 0);
assert_eq!(fe.pop().unwrap(), 42);
fe.evaluate(b": fine 7 0 throw ;").unwrap();
fe.evaluate(b"' fine catch").unwrap();
assert_eq!(fe.pop().unwrap(), 0);
assert_eq!(fe.pop().unwrap(), 7);
fe.evaluate(b": foo -1 throw ;").unwrap();
fe.evaluate(b"' foo catch").unwrap();
assert_eq!(fe.pop().unwrap(), -1isize as usize);
fe.evaluate(b": junk 1 2 3 42 throw ;").unwrap();
fe.evaluate(b"47 ' junk catch").unwrap();
assert_eq!(fe.pop().unwrap(), 42);
assert_eq!(fe.pop().unwrap(), 47);
}
#[test]
fn test_abort_irrecoverable_error() {
use crate::error::VmError;
let mut fe = Ferth::new([0u8; 65536], NullHost).unwrap();
fe.evaluate(b": overflow begin 1 again ;").unwrap();
assert!(matches!(
fe.evaluate(b"' overflow catch"),
Err(Error::Vm(VmError::StackOverflow))
));
fe.evaluate(b"1 2 +").unwrap();
assert_eq!(fe.pop().unwrap(), 3);
}
#[test]
fn test_catch_recoverable_error() {
let mut fe = Ferth::new([0u8; 65536], NullHost).unwrap();
fe.evaluate(b": divzero 0 0 0 um/mod ;").unwrap();
fe.evaluate(b"' divzero catch").unwrap();
assert_eq!(fe.pop().unwrap() as isize, Ior::DIVISION_BY_ZERO.into());
}
#[cfg(feature = "time")]
#[test]
fn test_time_and_date() {
let mut fe = Ferth::new([0u8; 65536], crate::host::StdHost).unwrap();
fe.evaluate(b"time&date").unwrap();
let year = fe.pop().unwrap();
let month = fe.pop().unwrap();
let day = fe.pop().unwrap();
let hour = fe.pop().unwrap();
let minute = fe.pop().unwrap();
let second = fe.pop().unwrap();
assert!(second <= 60); assert!(minute <= 59);
assert!(hour <= 23);
assert!((1..=31).contains(&day));
assert!((1..=12).contains(&month));
assert!(year >= 2024);
}
#[cfg(feature = "time")]
#[test]
fn test_ms() {
use std::time::{Duration, Instant};
let mut fe = Ferth::new([0u8; 65536], crate::host::StdHost).unwrap();
let start = Instant::now();
fe.evaluate(b"10 ms").unwrap();
assert!(start.elapsed() >= Duration::from_millis(10));
}
#[cfg(feature = "time")]
#[test]
fn test_utime() {
use std::thread;
use std::time::Duration;
let mut fe = Ferth::new([0u8; 65536], crate::host::StdHost).unwrap();
let read = |fe: &mut Ferth<[u8; 65536], crate::host::StdHost>| -> u128 {
fe.evaluate(b"(utime)").unwrap();
let hi = fe.pop().unwrap() as u128;
let lo = fe.pop().unwrap() as u128;
(hi << usize::BITS) | lo
};
let before = read(&mut fe);
thread::sleep(Duration::from_millis(5));
let after = read(&mut fe);
assert!(after - before >= 5_000); }
}