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() } 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) } ); res.set(&"wordlist".into(), &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) } ); res.set(&"annos".into(), &js_sys::Array::from_iter(annos)); // instruction pointer let ip: js_sys::Map = (&vm.ip).into(); res.set(&"ip".into(), &js_sys::Object::from_entries(&ip)?.into()); // stack let s = vm.stack.0.iter().map(|v| Into::::into(*v as i32)); res.set(&"stack".into(), &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") }); res.set(&"callstack".into(), &js_sys::Array::from_iter(cs)); let vars = js_sys::Map::new(); let mut out = vec![]; std::mem::swap(&mut out, &mut vm.out); vars.set(&"out".into(), &out.into()); vars.set(&"heading".into(), &vm.heading.into()); vars.set(&"speed".into(), &vm.speed.into()); vars.set(&"doppler".into(), &vm.doppler.into()); res.set(&"vars".into(), &js_sys::Object::from_entries(&vars)?.into()); 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(()) }