diff --git a/src/callable.rs b/src/callable.rs new file mode 100644 index 0000000000000000000000000000000000000000..083d7eb550179b684fd49ae4c89f1d63fc9d86a1 --- /dev/null +++ b/src/callable.rs @@ -0,0 +1,27 @@ +use std::fmt::Debug; +use crate::function::Function; +use crate::native::NativeFunction; +use crate::tvm::Tvm; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Callable { + Function(Function), + NativeFunction(NativeFunction), +} + +pub trait Caller: Debug + Clone { + fn call(callable: Callable); +} + +impl Caller for Tvm { + fn call(callable: Callable) { + match callable { + Callable::Function(function) => { + println!("Calling function: {:?}", function); + }, + Callable::NativeFunction(native_function) => { + println!("Calling native function: {:?}", native_function); + }, + } + } +} \ No newline at end of file diff --git a/src/frame.rs b/src/frame.rs new file mode 100644 index 0000000000000000000000000000000000000000..686f8f2adcb112caf85b0dc2728e2f537f5acb8d --- /dev/null +++ b/src/frame.rs @@ -0,0 +1,18 @@ +use crate::callable::Callable; +use crate::instruction::Instruction; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Frame { + pub id: usize, + pub name: String, + pub data: Vec<FrameData>, + pub pc: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FrameData { + Frame(Frame), + Callable(Callable, Vec<i32>), // TODO: Maybe this should be a reference to the callable id? + Instruction(Instruction, Vec<i32>), + Primitive(i32), +} \ No newline at end of file diff --git a/src/function.rs b/src/function.rs new file mode 100644 index 0000000000000000000000000000000000000000..1b31fbadba209d501193d5563e631e0f8a478c86 --- /dev/null +++ b/src/function.rs @@ -0,0 +1,10 @@ +use crate::frame::Frame; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Function { + pub id: usize, + pub name: String, + pub args: usize, + pub locals: usize, + pub frame: Frame, +} \ No newline at end of file diff --git a/src/heap.rs b/src/heap.rs new file mode 100644 index 0000000000000000000000000000000000000000..3881f37c92fbd86996a8827ef5a33a552d89063f --- /dev/null +++ b/src/heap.rs @@ -0,0 +1,65 @@ +use crate::tvm::Tvm; + +trait HeapHolder { + fn get_heap(&self) -> &[i32]; + fn get_heap_size(&self) -> usize; + fn allocate(&mut self, size: usize) -> usize; + fn deallocate(&mut self, address: usize); +} + +impl HeapHolder for Tvm { + fn get_heap(&self) -> &[i32] { + &self.memory[..self.heap_size] + } + + fn get_heap_size(&self) -> usize { + self.heap_size + } + + fn allocate(&mut self, size: usize) -> usize { + let address = self.heap_size; + self.heap_size += size; + address + } + + fn deallocate(&mut self, address: usize) { + self.heap_size = address; + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_get_heap() { + let mut tvm = Tvm::default(); + tvm.allocate(3); + assert_eq!(tvm.get_heap(), &[0, 0, 0]); + } + + #[test] + fn test_get_heap_size() { + let mut tvm = Tvm::default(); + tvm.allocate(3); + assert_eq!(tvm.get_heap_size(), 3); + } + + #[test] + fn test_allocate() { + let mut tvm = Tvm::default(); + let address = tvm.allocate(3); + assert_eq!(address, 0); + assert_eq!(tvm.get_heap(), &[0, 0, 0]); + } + + #[test] + fn test_deallocate() { + let mut tvm = Tvm::default(); + let address = tvm.allocate(3); + assert_eq!(address, 0); + assert_eq!(tvm.get_heap(), &[0, 0, 0]); + tvm.deallocate(address); + assert_eq!(tvm.get_heap(), &[]); + } +} \ No newline at end of file diff --git a/src/instruction.rs b/src/instruction.rs new file mode 100644 index 0000000000000000000000000000000000000000..f343b05086c9f64e7af90bdcc453003f8ad12be9 --- /dev/null +++ b/src/instruction.rs @@ -0,0 +1,44 @@ +use std::fmt::Debug; +use crate::tvm::Tvm; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Instruction { + Push { op: u32, name: String, num_operands: u32 }, + Fetch { op: u32, name: String, num_operands: u32 }, + Store { op: u32, name: String, num_operands: u32 }, + IF { op: u32, name: String, num_operands: u32 }, + Loop { op: u32, name: String, num_operands: u32 }, + Break { op: u32, name: String, num_operands: u32 }, + Return { op: u32, name: String, num_operands: u32 }, + Call { op: u32, name: String, num_operands: u32 }, + FPPlus { op: u32, name: String, num_operands: u32 }, + Add { op: u32, name: String, num_operands: u32 }, + Sub { op: u32, name: String, num_operands: u32 }, + Mul { op: u32, name: String, num_operands: u32 }, + Div { op: u32, name: String, num_operands: u32 }, + Mod { op: u32, name: String, num_operands: u32 }, + Not { op: u32, name: String, num_operands: u32 }, + And { op: u32, name: String, num_operands: u32 }, + OR { op: u32, name: String, num_operands: u32 }, + Xor { op: u32, name: String, num_operands: u32 }, + EQ { op: u32, name: String, num_operands: u32 }, + Neq { op: u32, name: String, num_operands: u32 }, + LT { op: u32, name: String, num_operands: u32 }, + Leq { op: u32, name: String, num_operands: u32 }, + GT { op: u32, name: String, num_operands: u32 }, + Geq { op: u32, name: String, num_operands: u32 }, + Pop { op: u32, name: String, num_operands: u32 }, + LShift { op: u32, name: String, num_operands: u32 }, + RShift { op: u32, name: String, num_operands: u32 }, + Unknown(u32), +} + +pub trait Evaluator: Debug + Clone { + fn eval(&mut self, instruction: Instruction); +} + +impl Evaluator for Tvm { + fn eval(&mut self, instruction: Instruction) { + println!("Evaluating instruction: {:?}", instruction); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e7a11a969c037e00a796aafeff6258501ec15e9a..8c18c67bc18ad499efae52db7d0946eeee3994c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,17 @@ +use crate::tvm::Tvm; + +mod tvm; +mod state; +mod stack; +mod program; +mod heap; +mod callable; +mod frame; +mod native; +mod function; +mod instruction; + fn main() { - println!("Hello, world!"); + let tvm = Tvm::default(); + println!("{:?}", tvm); } diff --git a/src/native.rs b/src/native.rs new file mode 100644 index 0000000000000000000000000000000000000000..2a3aea551df2cf7ab5d158abffd77d94c0506ff7 --- /dev/null +++ b/src/native.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NativeFunction { + pub id: i32, + pub name: String, + pub args: usize, +} \ No newline at end of file diff --git a/src/program.rs b/src/program.rs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/stack.rs b/src/stack.rs new file mode 100644 index 0000000000000000000000000000000000000000..e3217f09a3179d8414fc65936ce0063fe02161bf --- /dev/null +++ b/src/stack.rs @@ -0,0 +1,103 @@ +use crate::tvm::Tvm; + +pub trait StackHolder { + fn get_stack(&self) -> &[i32]; + fn get_stack_size(&self) -> usize; + fn get_stack_pointer(&self) -> usize; + fn pop(&mut self) -> i32; + fn push(&mut self, value: i32); + fn peek(&self) -> i32; +} + +impl StackHolder for Tvm { + fn get_stack(&self) -> &[i32] { + &self.memory[self.stack_pointer..][1..] + } + + fn get_stack_size(&self) -> usize { + self.memory.len() - (self.stack_pointer + 1) + } + + fn get_stack_pointer(&self) -> usize { + self.stack_pointer + } + + fn pop(&mut self) -> i32 { + self.stack_pointer += 1; + self.memory[self.stack_pointer] + } + + fn push(&mut self, value: i32) { + self.memory[self.stack_pointer] = value; + self.stack_pointer -= 1; + } + + fn peek(&self) -> i32 { + self.memory[self.stack_pointer + 1] + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_get_stack() { + let mut tvm = Tvm::default(); + tvm.push(1); + tvm.push(2); + tvm.push(3); + assert_eq!(tvm.get_stack(), &[3, 2, 1]); + } + + #[test] + fn test_get_stack_size() { + let mut tvm = Tvm::default(); + tvm.push(1); + tvm.push(2); + tvm.push(3); + assert_eq!(tvm.get_stack_size(), 3); + } + + #[test] + fn test_get_stack_pointer() { + let mut tvm = Tvm::default(); + tvm.push(1); + tvm.push(2); + tvm.push(3); + // 65535 - 3 = 65532 + assert_eq!(tvm.get_stack_pointer(), 65532); + } + + #[test] + fn test_pop() { + let mut tvm = Tvm::default(); + tvm.push(1); + tvm.push(2); + tvm.push(3); + assert_eq!(tvm.pop(), 3); + assert_eq!(tvm.pop(), 2); + assert_eq!(tvm.pop(), 1); + } + + #[test] + fn test_push() { + let mut tvm = Tvm::default(); + tvm.push(1); + tvm.push(2); + tvm.push(3); + assert_eq!(tvm.get_stack(), &[3, 2, 1]); + } + + #[test] + fn test_peek() { + let mut tvm = Tvm::default(); + tvm.push(1); + tvm.push(2); + tvm.push(3); + let sp = tvm.get_stack_pointer(); + assert_eq!(tvm.peek(), 3, "peek() should return the top of the stack"); + assert_eq!(tvm.peek(), tvm.peek(), "peek() should be equal to itself"); + assert_eq!(tvm.get_stack_pointer(), sp, "stack pointer should not change"); // stack pointer should not change + } +} \ No newline at end of file diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000000000000000000000000000000000000..fded70a6901a76ed8a4237dcf25a1d276b1afb2a --- /dev/null +++ b/src/state.rs @@ -0,0 +1,64 @@ +use std::fmt::Debug; +use crate::callable::Callable; +use crate::frame::Frame; +use crate::tvm::Tvm; + +pub trait State : Debug + Clone { + fn pause(&mut self); + fn resume(&mut self); + fn tick(&mut self) -> StateResult; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StateResult { + Return(i32), + Break, + Continue, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TvmState { + Waiting, + Paused, + Call(Callable), + Eval(Frame, usize), + FrameEval(Frame), + Halted, +} + +pub trait Stateful : Debug { + fn get_state(&self) -> TvmState; + fn set_state(&mut self, state: TvmState); + fn get_ticks(&self) -> usize; + fn increment_ticks(&mut self); + fn previous_state(&self) -> Option<TvmState>; + fn is_paused(&self) -> bool; +} + +impl Stateful for Tvm { + fn get_state(&self) -> TvmState { + self.state.clone() + } + + fn set_state(&mut self, state: TvmState) { + self.previous_state = Some(self.state.clone()); + self.state = state; + } + + fn get_ticks(&self) -> usize { + self.ticks + } + + fn increment_ticks(&mut self) { + self.ticks += 1; + } + + fn previous_state(&self) -> Option<TvmState> { + self.previous_state.clone() + } + + fn is_paused(&self) -> bool { + self.state == TvmState::Paused + } +} + diff --git a/src/tvm.rs b/src/tvm.rs new file mode 100644 index 0000000000000000000000000000000000000000..8f1e08e2184bd8d420109b98d64bfe7d0cea2899 --- /dev/null +++ b/src/tvm.rs @@ -0,0 +1,50 @@ +use crate::frame::Frame; +use crate::state::{TvmState}; + +#[derive(Debug, Clone)] +pub struct Tvm { + pub memory: [i32; 65536], + pub stack_pointer: usize, + pub frame_pointer: usize, + pub heap_size: usize, + pub state: TvmState, + pub ticks: usize, + pub previous_state: Option<TvmState>, +} + +impl Default for Tvm { + fn default() -> Self { + Tvm { + memory: [0; 65536], + stack_pointer: 65535, + frame_pointer: 65535, + heap_size: 0, + state: TvmState::Waiting, + ticks: 0, + previous_state: None, + } + } +} + +impl Tvm { + pub fn frame_eval(&mut self, frame: Frame) { + self.state = TvmState::FrameEval(frame); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_default() { + let tvm = Tvm::default(); + assert_eq!(tvm.memory.len(), 65536); + assert_eq!(tvm.stack_pointer, 65535); + assert_eq!(tvm.frame_pointer, 65535); + assert_eq!(tvm.heap_size, 0); + assert_eq!(tvm.state, TvmState::Waiting); + assert_eq!(tvm.ticks, 0); + assert_eq!(tvm.previous_state, None); + } +} \ No newline at end of file diff --git a/tvm.js b/tvm.js new file mode 100644 index 0000000000000000000000000000000000000000..7f511f5c327ace29655dd181e86a5e374a7219c2 --- /dev/null +++ b/tvm.js @@ -0,0 +1,478 @@ +var mem=[] +mem.length = 65536 +var sp +var fp +var hwin +var imgnum, butnum, labnum, tabnum +var edata + +startvm() + +function push(x) { + mem[sp] = x + sp-- +} + +function pop(x) { + sp++ + return mem[sp] +} + +function a2s(x) { + s = "" + while(mem[x] != 0) { + s += String.fromCharCode(mem[x]) + x++ + } + return s +} + +function leval(l) { + pc = 0 + while(pc >= 0) { + pc = eval(l, pc) + } + return pc +} + +function call(n) { + // Convert the old numbering to the new + if(n > -100) { + switch(n) { + case -1: n = -101; break; + case -2: n = -102; break; + // case -3: n = -103: break; + // case -4: n = -104; break; + case -5: n = -105; break; + case -10: n = -106; break; + case -11: n = -107; break; + case -12: n = -108; break; + case -13: n = -201; break; + case -14: n = -202; break; + case -15: n = -203; break; + case -16: n = -204; break; + case -17: n = -205; break; + case -18: n = -206; break; + case -19: n = -109; break; + case -20: n = -110; break; + case -21: n = -111; break; + case -22: n = -207; break; + case -23: n = -208; break; + case -24: n = -209; break; + case -25: n = -103; break; + case -26: n = -104; break; + case -27: n = -210; break; + } + } + if(n < -200 && hwin == null) { + hwin = window.open() + } + switch(n) { + case -101: /* iprint */ + x = pop() + stdout.value += x + push(0) + break + case -102: /* sprint */ + x = pop() + s = a2s(x) + stdout.value += s + push(0) + break + case -103: /* iread */ + p = pop() + if(p == 0-1) { + x = window.prompt("Integer input:") + } + else { + s = a2s(p) + x = window.prompt(s) + } + push(parseInt(x, 10)) + break + case -104: /* sread */ + a = pop() + p = pop() + if(p == -1) { + x = window.prompt("String input:") + } + else { + s = a2s(p) + x = window.prompt(s) + } + for(i = 0; i < x.length; i++) + mem[a+i] = x.charCodeAt(i) + mem[a+i] = 0 + push(0) + break + case -105: /* nl */ + stdout.value += "\n" + push(0) + break + case -106: /* random */ + n = pop() + n = Math.floor(Math.random() * n) + push(n) + break + case -107: /* timer */ + var f + to = pop() + f = pop() + n = setTimeout(function(){call(f); pop();}, to) + push(n) + break + case -108: /* stoptimer */ + n = pop() + clearTimeout(n) + push(0) + break + case -201: /* makeimg */ + hwin.document.write('<img id=img' + imgnum + ' />\n') + push(imgnum) + imgnum++ + break + case -202: /* setimg */ + n = pop() + src = pop() + s = a2s(src) + i = hwin.document.getElementById('img'+n) + i.src = s + push(0) + break + case -203: /* button */ + butname = pop() + n = pop() + s = a2s(butname) + t1 = '<button id=but' + butnum + ' onclick="window.opener.call(' + n + ');window.opener.pop()">' + hwin.document.write(t1 + s + '</button>\n') + push(butnum) + butnum++ + break + case -204: /* html */ + x = pop() + s = a2s(x) + hwin.document.write(s) + push(0) + break + case -205: /* makelabel */ + labtxt = pop() + s = a2s(labtxt) + hwin.document.write('<label id=lab' + labnum + '>' + s + '</label>\n') + push(labnum) + labnum++ + break + case -206: /* setlabel */ + n = pop() + label = pop() + s = a2s(label) + l = hwin.document.getElementById('lab'+n) + l.innerHTML = s + push(0) + break + case -109: /* alloc */ + n = pop() + push(edata) + edata += n + break + case -110: /* free */ + a = pop() + push(0) + break + case -111: /* i2s */ + s = pop() + n = pop() + istr = n.toString(10) + for(i = 0; i < istr.length; i++) + mem[s + i] = istr.charCodeAt(i) + mem[s+i] = 0 + push(istr.length) + break + case -207: /* maketable */ + r = pop() + c = pop() + f = pop() + hwin.document.write('<table id=tab' + tabnum + '>\n') + for(i = 0; i < r; i++) { + hwin.document.write('<tr>\n') + for(j = 0; j < c; j++) { + hwin.document.write('<td onclick="window.opener.cellclick(' + tabnum + ',' + i + ',' + j + ',' + f + ')"></td>\n') + } + hwin.document.write('</tr>\n') + } + hwin.document.write('</table>\n') + push(tabnum) + tabnum++ + break + case -208: /* setcell */ + tnum = pop() + r = pop() + c = pop() + cval = pop() + s = a2s(cval) + t = hwin.document.getElementById('tab' + tnum) + t.rows[r].cells[c].innerHTML = s + push(0) + break + case -209: /* setcellcolor */ + tnum = pop() + r = pop() + c = pop() + cval = pop() + s = a2s(cval) + t = hwin.document.getElementById('tab' + tnum) + t.rows[r].cells[c].style = "background-color:" + s + push(0) + break + case -210: /* buttonlabel */ + bnum = pop() + nlab = pop() + s = a2s(nlab) + b = hwin.document.getElementById('but' + bnum) + b.innerHTML=s + push(0) + break + case -3: /* old iread */ + x = window.prompt("Integer input:") + push(parseInt(x, 10)) + break + case -4: /* old sread */ + a = pop() + x = window.prompt("String input:") + for(i = 0; i < x.length; i++) + mem[a+i] = x.charCodeAt(i) + mem[a+i] = 0 + push(0) + break + default: + if(n < 0) { + console.log("Invalid function call", n) + return + } + for(i = 0; i < m[n+2][3]; i++) + push(0) + mem[sp] = fp + fp = sp + sp-- + leval(m[n+2][4]) + r = pop() + sp = fp + fp = mem[sp] + sp += m[n+2][2] + m[n+2][3] + push(r) + break + } +} + +function cellclick(t, r, c, f) { + push(c) + push(r) + for(i = 0; i < m[f+2][3]; i++) + push(0) + mem[sp] = fp + fp = sp + sp-- + leval(m[f+2][4]) + r = pop() + sp = fp + fp = mem[sp] + sp += m[f+2][2] + m[f+2][3] +// push(r) +} + +function eval(l, pc) { + if(pc >= l.length) { + return -1 + } + ir = l[pc] + pc++ + switch(ir) { + case 1: /* push */ + push(l[pc]) + pc++ + break + case 2: /* fetch */ + a = pop() + push(mem[a]) + break + case 3: /* store */ + v = pop() + a = pop() + mem[a] = v + break + case 4: /* if */ + x = pop() + if(x != 0) { + r = leval(l[pc]) + pc += 2 + } + else { + pc++ + r = leval(l[pc]) + pc++ + } + if(r <= -2) + return r + break + case 5: /* loop */ + while(1) { + r = leval(l[pc]) + if(r == -2) + break // does not exit the + else if(r == -3) + return -3 + } + pc++ + break + case 6: /* break */ + x = pop() + if(x != 0) + return -2 + break + case 7: /* return */ + return -3 + break + case 8: /* call */ + call(l[pc]) + pc++ + break + case 9: /* fpplus */ + a = pop() + a += fp + push(a) + break + case 10: /* add */ + y = pop() + x = pop() + push(x+y) + break + case 11: /* sub */ + y = pop() + x = pop() + push(x-y) + break + case 12: /* mul */ + y = pop() + x = pop() + push(x*y) + break + case 13: /* div */ + y = pop() + x = pop() + push(Math.floor(x/y)) + break + case 14: /* mod */ + y = pop() + x = pop() + push(x%y) + break + case 15: /* not */ + x = pop() + push(~x) + break + case 16: /* and */ + y = pop() + x = pop() + push(x&y) + break + case 17: /* or */ + y = pop() + x = pop() + push(x|y) + break + case 18: /* xor */ + y = pop() + x = pop() + push(x^y) + break + case 19: /* eq */ + y = pop() + x = pop() + if(x == y) + push(1) + else + push(0) + break + case 20: /* neq */ + y = pop() + x = pop() + if(x != y) + push(1) + else + push(0) + break + case 21: /* lt */ + y = pop() + x = pop() + if(x < y) + push(1) + else + push(0) + break + case 22: /* leq */ + y = pop() + x = pop() + if(x <= y) + push(1) + else + push(0) + break + case 23: /* gt */ + y = pop() + x = pop() + if(x > y) + push(1) + else + push(0) + break + case 24: /* geq */ + y = pop() + x = pop() + if(x >= y) + push(1) + else + push(0) + break + case 25: /* pop */ + pop() + break + case 26: /* lshift */ + s = pop() + x = pop() + push(x << s) + break + case 27: /* rshift */ + s = pop() + x = pop() + push(x >> s) + break + default: + console.log("unknown opcode ", ir) + break + } + return pc +} + +function start() { + edata = m[0][1] + call(m[0][0]) + console.log("halt") +} + +function startvm() { + hwin = null + imgnum = 0 + butnum = 0 + labnum = 0 + tabnum = 0 + document.write("<html>\n<body>\n") + document.write('<button onclick="start();">Start</button><br>\n') + document.write("<label>Standard Output</label><br>\n") + document.write('<textarea id="stdout" cols="80" rows="20"></textarea><br>\n') + document.write("</body>\n</html>\n") + m = JSON.parse(tape) + sp = 65535 + fp = 65535 + for(n = 0; n < m[1].length; n++) { + mem[m[1][n][0]] = m[1][n][1] + } +} \ No newline at end of file