use log::{Level, error, info}; use wasm_bindgen::prelude::*; use web_sys::js_sys; pub mod forth; pub mod robo; enum Error { NoVM, } impl From for JsValue { fn from(err: Error) -> Self { match err { Error::NoVM => "no vm".into(), } } } impl From for JsValue { fn from(err: forth::vm::RuntimeError) -> Self { format!("runtime error: {err}").into() } } fn tr_op(op: &forth::vm::OpCode) -> String { use forth::vm::OpCode::*; let s = match op { If(t, None) => format!("If({t}, none)"), If(t, Some(f)) => format!("If({t}, {f})"), TIf(t, None) => format!("TIf({t}, none)"), TIf(t, Some(f)) => format!("TIf({t}, {f})"), other => format!("{other:?}"), }; s.to_string() } fn map_set>(m: &js_sys::Map, k: &str, v: T) { let jk: js_sys::JsString = k.to_string().into(); let jv = v.into(); m.set(&jk, &jv); } impl From<&forth::vm::InstructionPointer> for js_sys::Map { fn from(ip: &forth::vm::InstructionPointer) -> Self { let res = Self::new(); let w: js_sys::JsString = "word".to_string().into(); let wv = (ip.word as i32).into(); res.set(&w, &wv); let o: js_sys::JsString = "offset".to_string().into(); let ov = (ip.offset as i32).into(); res.set(&o, &ov); res } } impl From<&forth::compiler::Annotation> for js_sys::Map { fn from(anno: &forth::compiler::Annotation) -> Self { let res = Self::new(); let w: js_sys::JsString = "start".to_string().into(); let wv = (anno.loc.0 as i32).into(); res.set(&w, &wv); let o: js_sys::JsString = "end".to_string().into(); let ov = (anno.loc.1 as i32).into(); res.set(&o, &ov); res } } #[wasm_bindgen] pub struct ExportedVM { vm: Option, annos: Vec>, } #[wasm_bindgen] impl ExportedVM { fn new() -> Self { Self { vm: None, annos: vec![], } } pub fn compile(&mut self, text: &str) -> bool { let mut comp = forth::compiler::Compiler::new(text); if let Err(e) = comp.compile() { error!("couldn't compile program text: {e:?}"); return false; } let vm = forth::vm::VM::new(comp.wordlist); let _ = self.vm.insert(vm); self.annos = comp.annotations; true } pub fn tick(&mut self) -> Result { let vm = (&mut self.vm).as_mut().ok_or(Error::NoVM)?; Ok(vm.tick()?) } pub fn run(&mut self) -> Result { self.reset_ip(); let vm = (&mut self.vm).as_mut().ok_or(Error::NoVM)?; Ok(vm.run()?) } pub fn reset_ip(&mut self) { let Some(vm) = &mut self.vm else { return; }; vm.ip.word = 0; vm.ip.offset = 0; } pub fn trans(&mut self) -> Result { let vm = (&mut self.vm).as_mut().ok_or(Error::NoVM)?; let res = js_sys::Map::new(); // wordlist let wl = vm.wordlist.iter().map( |dfn| { let ops = dfn.iter() .map(|op| { Into::::into(tr_op(op)) }); js_sys::Array::from_iter(ops) } ); map_set(&res, "wordlist", js_sys::Array::from_iter(wl)); // annotations let annos = self.annos.iter().map( |dfn| { let ops = dfn.iter() .map(|anno| { let a: js_sys::Map = anno.into(); js_sys::Object::from_entries(&a).expect("can't make object from anno map") }); js_sys::Array::from_iter(ops) } ); map_set(&res, "annos", js_sys::Array::from_iter(annos)); // instruction pointer let ip: js_sys::Map = (&vm.ip).into(); map_set(&res, "ip", js_sys::Object::from_entries(&ip)?); // stack let s = vm.stack.0.iter().map(|v| Into::::into(*v as i32)); map_set(&res, "stack", js_sys::Array::from_iter(s)); // callstack let cs = vm.callstack.0.iter().map(|v| { let ip: js_sys::Map = v.into(); js_sys::Object::from_entries(&ip).expect("can't make object from ip map") }); map_set(&res, "callstack", js_sys::Array::from_iter(cs)); let vars = js_sys::Map::new(); let mut out = vec![]; std::mem::swap(&mut out, &mut vm.out); map_set(&vars, "out", out); map_set::(&vars, "heading", vm.heading.into()); map_set::(&vars, "speed", vm.speed.into()); map_set::(&vars, "doppler", vm.doppler.into()); map_set(&res, "vars", js_sys::Object::from_entries(&vars)?); Ok(js_sys::Object::from_entries(&res)?) } } #[wasm_bindgen] pub fn make_vm() -> ExportedVM { ExportedVM::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(()) }