summaryrefslogtreecommitdiffstats
path: root/src/lib.rs
blob: 32a8ffc09101edcbcbf51d6a59da1a77c6a9f89a (plain)
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(())
}