diff options
| author | Brian Cully <bjc@spork.org> | 2025-12-18 15:10:08 -0500 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-12-18 15:10:08 -0500 |
| commit | 7522618ac3e913b904ada9d2e2529afa7acabad3 (patch) | |
| tree | c3dda56d921b42c3d039330078927576574a0e3c | |
| parent | 3d0d63ede8e267b320c672c248c9e731b0ed95c2 (diff) | |
| download | automathon-7522618ac3e913b904ada9d2e2529afa7acabad3.tar.gz automathon-7522618ac3e913b904ada9d2e2529afa7acabad3.zip | |
rust, js: do plain old object translation is wasm. use workers
| -rw-r--r-- | site/main.mjs | 29 | ||||
| -rw-r--r-- | site/robo.mjs | 59 | ||||
| -rwxr-xr-x | src/lib.rs | 227 |
3 files changed, 126 insertions, 189 deletions
diff --git a/site/main.mjs b/site/main.mjs index 5358616..31f1375 100644 --- a/site/main.mjs +++ b/site/main.mjs @@ -167,38 +167,37 @@ async function loaded() { speedy: 0, }; roboWorker.onmessage = e => { - const { kind, res, vm } = e.data; + const { kind, res, trans } = e.data; switch (kind) { case 'compile': if (res) { let wordlistContainer = document.querySelector(WORDLIST_SELECTOR); - console.debug('bjc new vm', vm); - const wordlist = wordlistElts(vm.wordlist); + const wordlist = wordlistElts(trans.wordlist); wordlist.forEach(elt => wordlistContainer.appendChild(elt)); initWordlist(); - renderStack(vm.stack); - renderCallStack(vm.callstack); - renderVars(vm.vars); - renderTextHighlight(vm); + renderStack(trans.stack); + renderCallStack(trans.callstack); + renderVars(trans.vars); + renderTextHighlight(trans); renderArena([robo]); } break; case 'tick': - const { word, offset } = vm.ip; + const { word, offset } = trans.ip; document.querySelectorAll(IP_SELECTOR).forEach(e => e.classList.remove('ip')); const sel = selectorForIP(word, offset); document.querySelectorAll(sel).forEach(e => { e.classList.add('ip'); }); - renderStack(vm.stack); - renderCallStack(vm.callstack); - renderVars(vm.vars); - renderTextHighlight(vm); + renderStack(trans.stack); + renderCallStack(trans.callstack); + renderVars(trans.vars); + renderTextHighlight(trans); robo.lastTick = document.timeline.currentTime; - robo.heading = vm.vars.heading; - robo.speed = vm.vars.speed; + robo.heading = trans.vars.heading; + robo.speed = trans.vars.speed; const [speedx, speedy] = [ Math.cos(2 * Math.PI * robo.heading / 360), Math.sin(2 * Math.PI * robo.heading / 360) @@ -217,7 +216,7 @@ async function loaded() { console.error('error in roboWorker', e); }; - document.querySelectorAll(SRC_SELECT_SELECTOR).forEach(async sel => { + document.querySelectorAll(SRC_SELECT_SELECTOR).forEach(sel => { sel.onchange = _ => loadForth(sel.value); loadForth(sel.value); }); diff --git a/site/robo.mjs b/site/robo.mjs index e6db244..1b7c3d8 100644 --- a/site/robo.mjs +++ b/site/robo.mjs @@ -2,59 +2,22 @@ import init, { make_vm } from './wasm/automathon.js'; let vm; -function vmData(vm) { - const wordlist = vm.wordlist().map(bc => { - let res = []; - for (let i = 0; i < bc.len(); i++) { - res.push(bc.at(i)) - } - return res; - }); - const callstack = vm.callstack().map(e => { return { word: e.word, offset: e.offset }; }); - const vars = Object.fromEntries(['out', 'heading', 'speed', 'doppler'].map(name => [name, vm[name]()])); - const ip = {word: vm.ip().word, offset: vm.ip().offset}; - const annos = vm.annotations().map(word_annos => { - let res = []; - for (let i = 0; i < word_annos.len(); i++) { - const anno = word_annos.at(i); - res.push({ start: anno.start, end: anno.end }) - } - return res; - }); - return { - wordlist, - stack: vm.stack(), - callstack, - vars, - ip, - annos, - } -} - function compile(text) { const start = performance.now(); const res = vm.compile(text); const end = performance.now(); console.info(`compile took ${end-start} ms`); - postMessage({ kind: 'compile', res, vm: vmData(vm) }); + postMessage({ kind: 'compile', res, trans: vm.trans() }); } function tick() { if (!vm.tick()) { vm.reset_ip(); } - postMessage({ kind: 'tick', vm: vmData(vm) }); + postMessage({ kind: 'tick', trans: vm.trans() }); } -let mod; async function messageHandler(e) { - if (mod === undefined) { - console.debug('worker running init'); - mod = await init(); - console.debug('worker init done'); - vm = make_vm(); - console.debug('worker vm made'); - } const { kind } = e.data; switch (kind) { case 'compile': @@ -69,5 +32,19 @@ async function messageHandler(e) { } } -console.debug('loading robo worker'); -addEventListener('message', messageHandler); +let mod; +async function loaded() { + console.debug('loading robo worker'); + if (mod === undefined) { + console.debug('worker running init'); + mod = await init(); + console.debug('worker init done'); + vm = make_vm(); + console.debug('worker vm made'); + } + + addEventListener('message', messageHandler); + console.debug('bjc loaded robo'); +} + +loaded(); @@ -4,116 +4,70 @@ use wasm_bindgen::prelude::*; pub mod forth; pub mod robo; -#[wasm_bindgen] -pub struct ExportedInstructionPointer { - pub word: usize, - pub offset: usize, -} +use web_sys::js_sys; -impl From<&forth::vm::InstructionPointer> for ExportedInstructionPointer { - fn from(ip: &forth::vm::InstructionPointer) -> Self { - Self { - word: ip.word, - offset: ip.offset, - } - } +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() } -// wasm can't wrap Vec<Vec<String>>, so we need a custom type -// - 23-aug-2025 -#[wasm_bindgen] -pub struct ExportedByteCode(Vec<String>); - -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() - } +fn map_set<T: Into::<JsValue>>(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::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() - } +impl From<&forth::vm::InstructionPointer> for js_sys::Map { + fn from(ip: &forth::vm::InstructionPointer) -> Self { + let res = Self::new(); - pub fn at(&self, offset: usize) -> String { - self.0[offset].clone() - } -} + let w: js_sys::JsString = "word".to_string().into(); + let wv = (ip.word as i32).into(); + res.set(&w, &wv); -// ibid. -#[wasm_bindgen] -#[derive(Clone)] -pub struct ExportedAnnotation { - pub start: usize, - pub end: usize, -} + let o: js_sys::JsString = "offset".to_string().into(); + let ov = (ip.offset as i32).into(); + res.set(&o, &ov); -impl From<&forth::compiler::Annotation> for ExportedAnnotation { - fn from(v: &forth::compiler::Annotation) -> Self { - Self { - start: v.loc.0, - end: v.loc.1, - } + res } } -// ibid. -#[wasm_bindgen] -#[derive(Clone)] -pub struct ExportedWordAnnotations(Vec<ExportedAnnotation>); +impl From<&forth::compiler::Annotation> for js_sys::Map { + fn from(anno: &forth::compiler::Annotation) -> Self { + let res = Self::new(); -impl FromIterator<ExportedAnnotation> for ExportedWordAnnotations { - fn from_iter<T: IntoIterator<Item = ExportedAnnotation>>(iter: T) -> Self { - ExportedWordAnnotations(iter.into_iter().collect()) - } -} + let w: js_sys::JsString = "start".to_string().into(); + let wv = (anno.loc.0 as i32).into(); + res.set(&w, &wv); -#[wasm_bindgen] -impl ExportedWordAnnotations { - pub fn len(&self) -> usize { - self.0.len() - } + let o: js_sys::JsString = "end".to_string().into(); + let ov = (anno.loc.1 as i32).into(); + res.set(&o, &ov); - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn at(&self, offset: usize) -> ExportedAnnotation { - self.0[offset].clone() + res } } #[wasm_bindgen] pub struct ExportedVM { - annos: Vec<ExportedWordAnnotations>, vm: Option<forth::vm::VM>, + annos: Vec<Vec<forth::compiler::Annotation>>, } #[wasm_bindgen] impl ExportedVM { fn new() -> Self { Self { - annos: vec![], vm: None, + annos: vec![], } } @@ -123,15 +77,10 @@ impl ExportedVM { 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); + self.annos = comp.annotations; true } @@ -152,38 +101,6 @@ impl ExportedVM { vm.run().map_err(|err| format!("runtime error: {err:?}")) } - pub fn stack(&self) -> Vec<forth::vm::DataStackType> { - let Some(vm) = &self.vm else { return vec![] }; - - vm.stack.0.clone() - } - - pub fn wordlist(&self) -> Vec<ExportedByteCode> { - let Some(vm) = &self.vm else { return vec![] }; - - vm.wordlist.iter().map(|bc| bc.into()).collect() - } - - pub fn annotations(&self) -> Vec<ExportedWordAnnotations> { - self.annos.clone() - } - - pub fn annotation_at(&self, ip: &ExportedInstructionPointer) -> ExportedAnnotation { - self.annos[ip.word].0[ip.offset].clone() - } - - pub fn callstack(&self) -> Vec<ExportedInstructionPointer> { - 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; @@ -199,19 +116,63 @@ impl ExportedVM { res } - pub fn heading(&mut self) -> isize { - let Some(vm) = &self.vm else { return 0 }; - vm.heading - } + pub fn trans(&mut self) -> js_sys::Object { + let Some(vm) = &self.vm else { return js_sys::Object::new() }; - pub fn speed(&mut self) -> isize { - let Some(vm) = &self.vm else { return 0 }; - vm.speed - } + let res = js_sys::Map::new(); + + // wordlist + let wl = vm.wordlist.iter().map( + |dfn| { + let ops = + dfn.iter() + .map(|op| { + Into::<js_sys::JsString>::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(); + let o: js_sys::Object = js_sys::Object::from_entries(&a).expect("can't make object from anno map"); + o + }); + 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(); + let o: js_sys::Object = js_sys::Object::from_entries(&ip).expect("can't make object from ip map"); + map_set(&res, "ip", o); + + // stack + let s = vm.stack.0.iter().map(|v| Into::<JsValue>::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(); + let o: js_sys::Object = js_sys::Object::from_entries(&ip).expect("can't make object from ip map"); + o + }); + map_set(&res, "callstack", js_sys::Array::from_iter(cs)); + + let vars = js_sys::Map::new(); + map_set::<isize>(&vars, "heading", vm.heading.into()); + map_set::<isize>(&vars, "speed", vm.speed.into()); + map_set::<isize>(&vars, "doppler", vm.doppler.into()); + map_set(&res, "vars", js_sys::Object::from_entries(&vars).expect("can't make object from vars map")); - pub fn doppler(&mut self) -> isize { - let Some(vm) = &self.vm else { return 0 }; - vm.doppler + js_sys::Object::from_entries(&res).expect("can't make object from results map") } } |
