summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Cully <bjc@spork.org>2025-12-18 15:10:08 -0500
committerBrian Cully <bjc@spork.org>2025-12-18 15:10:08 -0500
commit7522618ac3e913b904ada9d2e2529afa7acabad3 (patch)
treec3dda56d921b42c3d039330078927576574a0e3c
parent3d0d63ede8e267b320c672c248c9e731b0ed95c2 (diff)
downloadautomathon-7522618ac3e913b904ada9d2e2529afa7acabad3.tar.gz
automathon-7522618ac3e913b904ada9d2e2529afa7acabad3.zip
rust, js: do plain old object translation is wasm. use workers
-rw-r--r--site/main.mjs29
-rw-r--r--site/robo.mjs59
-rwxr-xr-xsrc/lib.rs227
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();
diff --git a/src/lib.rs b/src/lib.rs
index 371d30f..7f65a80 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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")
}
}