summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Cully <bjc@spork.org>2025-08-23 10:22:11 -0400
committerBrian Cully <bjc@spork.org>2025-08-23 11:36:31 -0400
commit5b8962e35836cf7ccbfdbca312f6b0eb9269e2a6 (patch)
treee537f04279c4b1ef27f7040c25beff2551c8bf84
parent12c06171b3f94696e852c3910c116f56cbfc5b64 (diff)
downloadautomathon-5b8962e35836cf7ccbfdbca312f6b0eb9269e2a6.tar.gz
automathon-5b8962e35836cf7ccbfdbca312f6b0eb9269e2a6.zip
show wordlist in html on compile
-rw-r--r--main.css18
-rw-r--r--main.mjs43
-rw-r--r--src/forth/interp.rs17
-rw-r--r--src/forth/parser.rs3
-rwxr-xr-xsrc/lib.rs106
5 files changed, 130 insertions, 57 deletions
diff --git a/main.css b/main.css
index c6603e1..15d721d 100644
--- a/main.css
+++ b/main.css
@@ -6,3 +6,21 @@ textarea {
width: 25em;
height: 25ex;
}
+
+x-bytecode {
+ display: block;
+}
+
+x-bytecode::before {
+ display: inline-block;
+ width: 1em;
+ content: attr(x-index);
+ text-align: right;
+ padding-right: 0.4em;
+}
+
+x-op {
+ display: inline-block;
+ padding: 5px;
+ border: 1px solid grey;
+}
diff --git a/main.mjs b/main.mjs
index a555a60..908aaa2 100644
--- a/main.mjs
+++ b/main.mjs
@@ -1,26 +1,53 @@
-import init, { compile, run, tick, wordlist, ip, interp } from './pkg/automathon.js';
+import init, { make_interp } from './pkg/automathon.js';
+
+function wordlistElts(wordlist) {
+ return wordlist.map((bc, i) => {
+ const bcElt = document.createElement('x-bytecode');
+ bcElt.setAttribute('x-index', i);
+ for (let i = 0; i < bc.len(); i++) {
+ const opElt = document.createElement('x-op');
+ opElt.setAttribute('x-index', i);
+ opElt.textContent = bc.at(i);
+ bcElt.appendChild(opElt);
+ }
+ return bcElt;
+ })
+}
async function loaded() {
console.debug('running init');
const mod = await init();
console.debug('init done', mod);
+ const interp = make_interp();
document.querySelector('#compile').onclick = e => {
console.debug('compile clicked', e);
- let text = document.querySelector('textarea').textContent;
- compile(text);
+
+ let wordlistContainer = document.querySelector('#wordlist');
+ while (wordlistContainer.lastChild) {
+ console.debug('removing child', wordlistContainer.lastChild)
+ wordlistContainer.removeChild(wordlistContainer.lastChild);
+ }
+ console.debug('container has firstchild', wordlistContainer.firstChild)
+
+ // always add a newline until i decide what to do with the parser.
+ const text = document.querySelector('textarea').value + '\n';
+ const start = performance.now();
+ const res = interp.compile(text);
+ const end = performance.now();
+ console.info(`compile took ${end-start} ms`);
+ if (res) {
+ const wordlist = wordlistElts(interp.wordlist());
+ wordlist.forEach(elt => wordlistContainer.appendChild(elt));
+ }
};
document.querySelector('#tick').onclick = e => {
console.debug('tick clicked', e);
- tick();
- console.debug('result of ip', ip());
- console.debug('result of wordlist', wordlist());
- console.debug('result of interp', interp());
};
document.querySelector('#run').onclick = e => {
console.debug('run clicked', e);
- run();
};
+ window.interp = interp;
}
document.addEventListener('DOMContentLoaded', loaded);
diff --git a/src/forth/interp.rs b/src/forth/interp.rs
index cacc5e3..e41f0d9 100644
--- a/src/forth/interp.rs
+++ b/src/forth/interp.rs
@@ -29,6 +29,12 @@ pub enum OpCode {
#[derive(Clone, Debug)]
pub struct ByteCode(pub Vec<OpCode>);
+impl std::ops::Deref for ByteCode {
+ type Target = Vec<OpCode>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
impl ByteCode {
pub fn len(&self) -> usize {
@@ -67,15 +73,20 @@ pub struct CallStack(pub Vec<InstructionPointer>);
#[derive(Clone, Debug)]
pub struct WordList(pub Vec<ByteCode>);
+impl std::ops::Deref for WordList {
+ type Target = Vec<ByteCode>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
#[derive(Debug)]
pub struct Interp {
// todo: don't be pub, probably
pub stack: DataStack,
- callstack: CallStack,
- // todo: don't be pub
+ pub callstack: CallStack,
pub wordlist: WordList,
- ip: InstructionPointer,
+ pub ip: InstructionPointer,
}
#[derive(Debug)]
diff --git a/src/forth/parser.rs b/src/forth/parser.rs
index ac6715d..1eb93df 100644
--- a/src/forth/parser.rs
+++ b/src/forth/parser.rs
@@ -1,7 +1,5 @@
use super::interp::{ByteCode, OpCode, WordList};
-use log::debug;
-
use std::collections::HashMap;
use std::iter::{Enumerate, Iterator};
use std::str::Chars;
@@ -81,7 +79,6 @@ impl<'a> Parser<'a> {
self.enumerator.by_ref()
.find(|(_i, c)| c.is_whitespace())?;
let word = &self.text[start..end];
- debug!("Parser::next_word → ‘{}’ ({} → {})", word, start, end);
Some((word, start, end))
}
diff --git a/src/lib.rs b/src/lib.rs
index ddb87fd..588f873 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-use log::{Level, debug, info};
+use log::{Level, debug, error, info};
use console_log;
use wasm_bindgen::prelude::*;
@@ -10,75 +10,95 @@ pub struct ExportedInstructionPointer {
pub offset: usize,
}
+// 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 {
+ pub fn from_bc(bc: &forth::interp::ByteCode) -> Self {
+ fn tr(op: &forth::interp::OpCode) -> String {
+ use forth::interp::OpCode::*;
+ let s = match op {
+ If(t, None) => format!("If({}, none)", t),
+ If(t, Some(f)) => format!("If({}, {})", t, f),
+ TIf(t, None) => format!("TIf({}, none)", t),
+ TIf(t, Some(f)) => format!("TIf({}, {})", t, f),
+ other => format!("{:?}", other),
+ };
+ s.to_string()
+ }
+
+ ExportedByteCode(bc.iter().map(tr).collect())
+ }
+}
+
#[wasm_bindgen]
impl ExportedByteCode {
pub fn len(&self) -> usize {
self.0.len()
}
- pub fn at(&self, index: usize) -> String {
- self.0[index].clone()
+ pub fn at(&self, offset: usize) -> String {
+ self.0[offset].clone()
}
}
#[wasm_bindgen]
-pub fn compile(text: &str) {
- info!("compiling code: `{}`", text);
- let mut p = forth::parser::Parser::new(&text);
- p.parse().expect("couldn't parse text");
- debug!("wordlist: {:?}", &p.wordlist);
- let interp = forth::interp::Interp::new(p.wordlist);
- debug!("interp: {:?}", interp);
-}
-
-#[wasm_bindgen]
-pub fn tick() {
- info!("executing single instruction");
+pub struct ExportedInterp {
+ i: Option<forth::interp::Interp>,
}
#[wasm_bindgen]
-pub fn ip() -> ExportedInstructionPointer {
- ExportedInstructionPointer {
- word: 0,
- offset: 0,
+impl ExportedInterp {
+ fn new() -> Self {
+ Self { i: None }
}
-}
-#[wasm_bindgen]
-pub fn wordlist() -> Vec<ExportedByteCode> {
- vec![
- ExportedByteCode(vec!["NOP".to_string(), "2".to_string(), "DUP".to_string()]),
- ]
-}
+ pub fn compile(&mut self, text: &str) -> bool {
+ let mut p = forth::parser::Parser::new(&text);
+ if let Err(e) = p.parse() {
+ error!("couldn't parse program text: {:?}", e);
+ return false
+ }
+ debug!("wordlist: {:?}", &p.wordlist);
+ let interp = forth::interp::Interp::new(p.wordlist);
+ let _ = self.i.insert(interp);
+ true
+ }
-#[wasm_bindgen]
-pub struct ExportedInterp {
- i: forth::interp::Interp,
-}
+ pub fn tick(&mut self) {
+ info!("executing single instruction");
+ }
-#[wasm_bindgen]
-impl ExportedInterp {
- fn from_interp(i: forth::interp::Interp) -> Self {
- Self { i }
+ pub fn run(&mut self) {
+ info!("running to completion");
}
- pub fn foo(&self) {
- info!("in interp::foo: {:?}", self.i.wordlist);
+ pub fn wordlist(&self) -> Vec<ExportedByteCode> {
+ let Some(interp) = self.i.as_ref() else {
+ return vec![]
+ };
+
+ info!("wordlist: ‘{:?}’", interp.wordlist);
+ interp.wordlist.iter().map(|bc| ExportedByteCode::from_bc(bc)).collect()
}
-}
-#[wasm_bindgen]
-pub fn interp() -> ExportedInterp {
- let i = forth::interp::Interp::new(forth::interp::WordList(vec![]));
- ExportedInterp::from_interp(i)
+ pub fn ip(&self) -> ExportedInstructionPointer {
+ let Some(interp) = self.i.as_ref() else {
+ return ExportedInstructionPointer { word: 0, offset: 0 }
+ };
+
+ ExportedInstructionPointer {
+ word: interp.ip.word,
+ offset: interp.ip.offset,
+ }
+ }
}
#[wasm_bindgen]
-pub fn run() {
- info!("running to completion");
+pub fn make_interp() -> ExportedInterp {
+ ExportedInterp::new()
}
#[wasm_bindgen(start)]