1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
use log::{Level, debug, error, info};
use console_log;
use wasm_bindgen::prelude::*;
pub mod forth;
#[wasm_bindgen]
pub struct ExportedInstructionPointer {
pub word: usize,
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, offset: usize) -> String {
self.0[offset].clone()
}
}
#[wasm_bindgen]
pub struct ExportedInterp {
i: Option<forth::interp::Interp>,
}
#[wasm_bindgen]
impl ExportedInterp {
fn new() -> Self {
Self { i: None }
}
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
}
pub fn tick(&mut self) {
info!("executing single instruction");
}
pub fn run(&mut self) -> Result<usize, String> {
let Some(interp) = &mut self.i else {
return Err("no interpreter".to_string())
};
interp.ip.word = 0;
interp.ip.offset = 0;
interp.run().or_else(|err| Err(format!("runtime error: {:?}", err)))
}
pub fn stack(&self) -> Vec<i32> {
let Some(interp) = &self.i else {
return vec![]
};
return interp.stack.0.clone()
}
pub fn wordlist(&self) -> Vec<ExportedByteCode> {
let Some(interp) = &self.i else {
return vec![]
};
info!("wordlist: ‘{:?}’", interp.wordlist);
interp.wordlist.iter().map(|bc| ExportedByteCode::from_bc(bc)).collect()
}
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 make_interp() -> ExportedInterp {
ExportedInterp::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(())
}
|