use log::{Level, error, info}; use wasm_bindgen::prelude::*; pub mod forth; pub mod robo; #[wasm_bindgen] pub struct ExportedInstructionPointer { pub word: usize, pub offset: usize, } impl From<&forth::vm::InstructionPointer> for ExportedInstructionPointer { fn from(ip: &forth::vm::InstructionPointer) -> Self { Self { word: ip.word, offset: ip.offset, } } } // wasm can't wrap Vec>, so we need a custom type // - 23-aug-2025 #[wasm_bindgen] pub struct ExportedByteCode(Vec); impl ExportedByteCode { 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::ByteCode> for ExportedByteCode { fn from(v: &forth::vm::ByteCode) -> Self { Self(v.iter().map(Self::tr_op).collect()) } } #[wasm_bindgen] impl ExportedByteCode { pub fn len(&self) -> usize { self.0.len() } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn at(&self, offset: usize) -> String { self.0[offset].clone() } } // ibid. #[wasm_bindgen] #[derive(Clone)] pub struct ExportedAnnotation { pub start: usize, pub end: usize, } impl From<&forth::compiler::Annotation> for ExportedAnnotation { fn from(v: &forth::compiler::Annotation) -> Self { Self { start: v.loc.0, end: v.loc.1, } } } // ibid. #[wasm_bindgen] #[derive(Clone)] pub struct ExportedWordAnnotations(Vec); impl FromIterator for ExportedWordAnnotations { fn from_iter>(iter: T) -> Self { ExportedWordAnnotations(iter.into_iter().collect()) } } #[wasm_bindgen] impl ExportedWordAnnotations { pub fn len(&self) -> usize { self.0.len() } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn at(&self, offset: usize) -> ExportedAnnotation { self.0[offset].clone() } } #[wasm_bindgen] pub struct ExportedVM { annos: Vec, vm: Option, } #[wasm_bindgen] impl ExportedVM { fn new() -> Self { Self { annos: vec![], vm: None, } } 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; } self.annos = comp .annotations .iter() .map(|word_anno| -> ExportedWordAnnotations { word_anno.iter().map(|a| a.into()).collect() }) .collect(); let vm = forth::vm::VM::new(comp.wordlist); let _ = self.vm.insert(vm); true } pub fn tick(&mut self) -> Result { let Some(vm) = &mut self.vm else { return Err("no vm".to_string()); }; vm.tick().map_err(|err| format!("runtime error: {err:?}")) } pub fn run(&mut self) -> Result { let Some(vm) = &mut self.vm else { return Err("no vm".to_string()); }; vm.ip.word = 0; vm.ip.offset = 0; vm.run().map_err(|err| format!("runtime error: {err:?}")) } pub fn stack(&self) -> Vec { let Some(vm) = &self.vm else { return vec![] }; vm.stack.0.clone() } pub fn wordlist(&self) -> Vec { let Some(vm) = &self.vm else { return vec![] }; vm.wordlist.iter().map(|bc| bc.into()).collect() } pub fn annotations(&self) -> Vec { self.annos.clone() } pub fn annotation_at(&self, ip: &ExportedInstructionPointer) -> ExportedAnnotation { self.annos[ip.word].0[ip.offset].clone() } pub fn callstack(&self) -> Vec { let Some(vm) = &self.vm else { return vec![] }; vm.callstack.0.iter().map(|ip| ip.into()).collect() } pub fn ip(&self) -> ExportedInstructionPointer { let Some(vm) = &self.vm else { return ExportedInstructionPointer { word: 0, offset: 0 }; }; (&vm.ip).into() } pub fn reset_ip(&mut self) { let Some(vm) = &mut self.vm else { return; }; vm.ip.word = 0; vm.ip.offset = 0; } pub fn out(&mut self) -> Vec { let Some(vm) = &mut self.vm else { return vec![] }; let mut res = vec![]; std::mem::swap(&mut res, &mut vm.out); res } pub fn heading(&mut self) -> isize { let Some(vm) = &self.vm else { return 0 }; vm.heading } pub fn speed(&mut self) -> isize { let Some(vm) = &self.vm else { return 0 }; vm.speed } pub fn doppler(&mut self) -> isize { let Some(vm) = &self.vm else { return 0 }; vm.doppler } } #[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(()) }