use log::{Level, debug, error, info}; use console_log; use wasm_bindgen::prelude::*; pub mod forth; #[wasm_bindgen] pub struct ExportedInstructionPointer { pub word: usize, pub offset: usize, } // wasm can't wrap Vec>, so we need a custom type // - 23-aug-2025 #[wasm_bindgen] pub struct ExportedByteCode(Vec); impl ExportedByteCode { pub fn from_bc(bc: &forth::interp::ByteCode) -> Self { fn tr(op: &forth::interp::OpCode) -> String { use forth::interp::OpCode::*; let s = match op { If(t, None) => format!("If({}, none)", t), If(t, Some(f)) => format!("If({}, {})", t, f), TIf(t, None) => format!("TIf({}, none)", t), TIf(t, Some(f)) => format!("TIf({}, {})", t, f), other => format!("{:?}", other), }; s.to_string() } ExportedByteCode(bc.iter().map(tr).collect()) } } #[wasm_bindgen] impl ExportedByteCode { pub fn len(&self) -> usize { self.0.len() } pub fn at(&self, offset: usize) -> String { self.0[offset].clone() } } #[wasm_bindgen] pub struct ExportedInterp { i: Option, } #[wasm_bindgen] impl ExportedInterp { fn new() -> Self { Self { i: None } } pub fn compile(&mut self, text: &str) -> bool { let mut p = forth::parser::Parser::new(&text); if let Err(e) = p.parse() { error!("couldn't parse program text: {:?}", e); return false } debug!("wordlist: {:?}", &p.wordlist); let interp = forth::interp::Interp::new(p.wordlist); let _ = self.i.insert(interp); true } pub fn tick(&mut self) { info!("executing single instruction"); } pub fn run(&mut self) -> Result { let Some(interp) = &mut self.i else { return Err("no interpreter".to_string()) }; interp.ip.word = 0; interp.ip.offset = 0; interp.run().or_else(|err| Err(format!("runtime error: {:?}", err))) } pub fn stack(&self) -> Vec { let Some(interp) = &self.i else { return vec![] }; return interp.stack.0.clone() } pub fn wordlist(&self) -> Vec { let Some(interp) = &self.i else { return vec![] }; info!("wordlist: ‘{:?}’", interp.wordlist); interp.wordlist.iter().map(|bc| ExportedByteCode::from_bc(bc)).collect() } pub fn ip(&self) -> ExportedInstructionPointer { let Some(interp) = self.i.as_ref() else { return ExportedInstructionPointer { word: 0, offset: 0 } }; ExportedInstructionPointer { word: interp.ip.word, offset: interp.ip.offset, } } } #[wasm_bindgen] pub fn make_interp() -> ExportedInterp { ExportedInterp::new() } #[wasm_bindgen(start)] pub fn init() -> Result<(), JsValue> { console_log::init_with_level(Level::Debug).expect("couldn't init console log"); info!("wasm init"); Ok(()) }