diff options
Diffstat (limited to 'crates/sloth_vm/src')
| -rw-r--r-- | crates/sloth_vm/src/lib.rs | 155 | ||||
| -rw-r--r-- | crates/sloth_vm/src/native.rs | 19 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/file.rs | 83 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/misc.rs | 39 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/mod.rs | 43 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/rand.rs | 48 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/stdio.rs | 91 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/term.rs | 41 | ||||
| -rw-r--r-- | crates/sloth_vm/src/sloth_std/time.rs | 29 | ||||
| -rw-r--r-- | crates/sloth_vm/src/value.rs | 53 | ||||
| -rw-r--r-- | crates/sloth_vm/src/vm.rs | 610 |
11 files changed, 0 insertions, 1211 deletions
diff --git a/crates/sloth_vm/src/lib.rs b/crates/sloth_vm/src/lib.rs deleted file mode 100644 index 9cf552b..0000000 --- a/crates/sloth_vm/src/lib.rs +++ /dev/null @@ -1,155 +0,0 @@ -#![allow(dead_code)] -#![warn( - clippy::wildcard_imports, - clippy::string_add, - clippy::string_add_assign, - clippy::manual_ok_or, - unused_lifetimes -)] - -pub mod native; -pub mod sloth_std; -pub mod value; -pub mod vm; - -use std::ops::{Index, IndexMut}; - -use value::{Object, ObjectType}; - -use crate::value::Primitive; -pub use crate::vm::VM; - -#[derive(Default)] -pub struct Chunk { - pub constants: Vec<Primitive>, - pub code: Vec<u8>, -} - -const STACK_SIZE: usize = 1024; - -#[derive(Debug)] -pub struct Stack { - stack: [Primitive; STACK_SIZE], - top: usize, -} - -impl Default for Stack { - fn default() -> Self { - Self { - top: Default::default(), - stack: [Primitive::Empty; STACK_SIZE], - } - } -} - -impl Stack { - #[inline(always)] - pub fn push(&mut self, value: Primitive) { - if self.top >= STACK_SIZE { - panic!("Stack overflow"); - } - - self.stack[self.top] = value; - self.top += 1; - } - - #[inline(always)] - pub fn pop(&mut self) -> Primitive { - if self.top == 0 { - panic!("Stack underflow"); - } - - self.top -= 1; - self.stack[self.top] - } - - #[inline(always)] - pub fn pop2(&mut self) -> (Primitive, Primitive) { - (self.pop(), self.pop()) - } - - #[inline(always)] - pub fn peek(&self) -> Primitive { - self.stack[self.top - 1] - } - - #[inline(always)] - pub fn peek_nth(&self, nth: usize) -> Primitive { - self.stack[self.top - 1 - nth] - } -} - -impl Index<usize> for Stack { - type Output = Primitive; - - fn index(&self, index: usize) -> &Self::Output { - &self.stack[index] - } -} - -impl IndexMut<usize> for Stack { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.stack[index] - } -} - -pub struct ObjectMap { - free: usize, - heap: Vec<Object>, -} - -impl Default for ObjectMap { - fn default() -> Self { - Self::with_capacity(32) - } -} - -impl From<Vec<Object>> for ObjectMap { - fn from(heap: Vec<Object>) -> Self { - let mut free = heap.len(); - for (idx, obj) in heap.iter().enumerate() { - if let ObjectType::Free { .. } = obj.typ { - free = idx; - break; - } - } - - Self { free, heap } - } -} - -impl ObjectMap { - pub fn with_capacity(capacity: usize) -> Self { - let mut heap = Vec::with_capacity(capacity); - for i in 0..capacity { - heap.push(Object::new(ObjectType::Free { next: i + 1 })); - } - - Self { free: 0, heap } - } - - pub fn allocate(&mut self, object: Object) -> usize { - let current = self.free; - if current >= self.heap.len() { - self.heap - .push(Object::new(ObjectType::Free { next: current + 1 })) - } - - let ObjectType::Free { next } = self.heap[current].typ else { - panic!("Allocation failed: Expected free location wasn't free"); - }; - - self.heap[current] = object; - self.free = next; - - current - } - - pub fn get(&self, idx: usize) -> Option<&Object> { - self.heap.get(idx) - } - - pub fn get_mut(&mut self, idx: usize) -> Option<&mut Object> { - self.heap.get_mut(idx) - } -} diff --git a/crates/sloth_vm/src/native.rs b/crates/sloth_vm/src/native.rs deleted file mode 100644 index fbd2626..0000000 --- a/crates/sloth_vm/src/native.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::value::Primitive; -use crate::VM; - -pub type NativeFunctionResult = Result<Primitive, Error>; -pub type NativeFunctionInput = fn(&mut VM, &[Primitive]) -> NativeFunctionResult; - -pub enum Error { - InvalidArgument, - Unknown(String), -} - -#[allow(clippy::type_complexity)] -pub struct NativeFunction { - pub name: &'static str, - pub function: NativeFunctionInput, - pub arity: u8, - pub returns_value: bool, - pub doc: Option<&'static str>, -} diff --git a/crates/sloth_vm/src/sloth_std/file.rs b/crates/sloth_vm/src/sloth_std/file.rs deleted file mode 100644 index b0b476a..0000000 --- a/crates/sloth_vm/src/sloth_std/file.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::fs; - -use crate::native::{self, NativeFunction, NativeFunctionResult}; -use crate::value::{Object, ObjectType, Primitive}; -use crate::VM; - -fn file_read(vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let Some(Primitive::Object(ptr)) = args.get(0).cloned() else { - return Err(native::Error::InvalidArgument); - }; - - let object = vm - .objects() - .get(ptr as usize) - .ok_or(native::Error::InvalidArgument)?; - - let ObjectType::String(str) = &object.typ else { - return Err(native::Error::InvalidArgument); - }; - - let contents = fs::read_to_string(str).expect("IO Error: Failed to read file!"); - - let object = Object::new(ObjectType::String(contents)); - let ptr = vm.objects_mut().allocate(object); - - Ok(Primitive::Object(ptr as u32)) -} - -pub const FILE_READ: NativeFunction = NativeFunction { - name: "file$read", - function: file_read, - arity: 1, - returns_value: true, - doc: Some( - "NativeFunction file$read: \n\targs: path (str)\n\tdesc: Returns the contents of a file \ - at <path>\n\tExample: `var todo = file$read('/home/sloth/todo.txt'); # Assuming the \ - contents of todo.txt are 'Take a nap' then todo = 'Take a nap'`", - ), -}; - -fn file_write(vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let Some(Primitive::Object(path_ptr)) = args.get(0).cloned() else { - return Err(native::Error::InvalidArgument); - }; - - let path_object = vm - .objects() - .get(path_ptr as usize) - .ok_or(native::Error::InvalidArgument)?; - - let ObjectType::String(path) = &path_object.typ else { - return Err(native::Error::InvalidArgument); - }; - - let Some(Primitive::Object(content_ptr)) = args.get(1).cloned() else { - return Err(native::Error::InvalidArgument); - }; - - let content_object = vm - .objects() - .get(content_ptr as usize) - .ok_or(native::Error::InvalidArgument)?; - - let ObjectType::String(content) = &content_object.typ else { - return Err(native::Error::InvalidArgument); - }; - - let _ = fs::write(path, content); - - Ok(Primitive::Empty) -} - -pub const FILE_WRITE: NativeFunction = NativeFunction { - name: "file$write", - function: file_write, - arity: 2, - returns_value: false, - doc: Some( - "NativeFunction file$write: \n\targs: path (str), content (str)\n\tdesc: Writes <content> \ - to file at <path>\n\tExample: `file$write('/home/sloth/todo.txt', 'Take a nap'); # \ - todo.txt now contains the string 'Take a nap'`", - ), -}; diff --git a/crates/sloth_vm/src/sloth_std/misc.rs b/crates/sloth_vm/src/sloth_std/misc.rs deleted file mode 100644 index ca08d1d..0000000 --- a/crates/sloth_vm/src/sloth_std/misc.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::native::{self, NativeFunction, NativeFunctionResult}; -use crate::value::{Object, ObjectType, Primitive}; -use crate::VM; - -fn get_doc(vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let Some(Primitive::Object(ptr)) = args.get(0).cloned() else { - return Err(native::Error::InvalidArgument); - }; - - let object = vm - .objects() - .get(ptr as usize) - .ok_or(native::Error::InvalidArgument)?; - - let ObjectType::NativeFunction(fnc) = &object.typ else { - return Err(native::Error::InvalidArgument); - }; - - let docs = fnc - .doc - .expect("Oopsie Poopsie the stringy no worky") - .to_string(); - let object = Object::new(ObjectType::String(docs)); - let ptr = vm.objects_mut().allocate(object); - - Ok(Primitive::Object(ptr as u32)) -} - -pub const DOCS: NativeFunction = NativeFunction { - name: "docs", - function: get_doc, - arity: 1, - returns_value: true, - doc: Some( - "NativeFunction docs: \n\targs: name (str)\n\tdesc: Returns documentaiton on a function \ - with name <str>\n\tExample: `var doc = docs('wait'); # Returns the documentation of the \ - 'wait' function to doc`", - ), -}; diff --git a/crates/sloth_vm/src/sloth_std/mod.rs b/crates/sloth_vm/src/sloth_std/mod.rs deleted file mode 100644 index ff761a6..0000000 --- a/crates/sloth_vm/src/sloth_std/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::collections::HashMap; - -use once_cell::sync::Lazy; - -use crate::native::NativeFunction; - -pub mod file; -pub mod misc; -pub mod rand; -pub mod stdio; -pub mod term; -pub mod time; - -pub static NATIVE_LIBRARY: Lazy<HashMap<&'static str, NativeFunction>> = Lazy::new(|| { - let mut map = HashMap::new(); - - // rand - map.insert("rand$gen", rand::GEN_FUNCTION); - map.insert("rand$gen_range", rand::GEN_RANGE_FUNCTION); - - // stdio - map.insert("write", stdio::WRITE_FUNCTION); - map.insert("writeln", stdio::WRITELN_FUNCTION); - map.insert("read", stdio::READ_FUNCTION); - - // term - map.insert("term$clear", term::TERM_CLEAR); - map.insert("term$setpos", term::TERM_SETPOS); - - // filesystem - // TODO: Make the files commands work by making a global file variable with - // certain permissions created by 'file.open' instead of just reading the file. - map.insert("file$read", file::FILE_READ); - map.insert("file$write", file::FILE_WRITE); - - // time - map.insert("wait", time::WAIT); - - // doc - map.insert("docs", misc::DOCS); - - map -}); diff --git a/crates/sloth_vm/src/sloth_std/rand.rs b/crates/sloth_vm/src/sloth_std/rand.rs deleted file mode 100644 index 870cca1..0000000 --- a/crates/sloth_vm/src/sloth_std/rand.rs +++ /dev/null @@ -1,48 +0,0 @@ -use rand::Rng; - -use crate::native::{self, NativeFunction, NativeFunctionResult}; -use crate::value::Primitive; -use crate::value::Primitive::{Float, Integer}; -use crate::VM; - -fn gen(_vm: &mut VM, _args: &[Primitive]) -> NativeFunctionResult { - let value = rand::thread_rng().gen_range(0.0..1.0); - - Ok(Float(value)) -} - -pub const GEN_FUNCTION: NativeFunction = NativeFunction { - name: "rand$gen", - function: gen, - arity: 0, - returns_value: true, - doc: Some( - "NativeFunction rand$gen:\n\tdesc: Returns a random number in the range `0.0 .. \ - 1.0`\n\tExample: `var num = rand$gen(); # num could be any number from 0.0 to 1.0`", - ), -}; - -fn gen_range(_vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let min = args.get(0).cloned(); - let max = args.get(1).cloned(); - - let (Some(Integer(min)), Some(Integer(max))) = (min, max) else { - return Err(native::Error::InvalidArgument); - }; - - let value = rand::thread_rng().gen_range(min..max); - - Ok(Integer(value)) -} - -pub const GEN_RANGE_FUNCTION: NativeFunction = NativeFunction { - name: "rand$gen_range", - function: gen_range, - arity: 2, - returns_value: true, - doc: Some( - "NativeFunction rand$gen_range: \n\targs: min (int), max (int)\n\tdesc: Returns a random \ - numnber in the range <min> .. <max>\n\tExample: `var num = rand$gen_range(20, 76); # num \ - could be any number from 20 to 76`", - ), -}; diff --git a/crates/sloth_vm/src/sloth_std/stdio.rs b/crates/sloth_vm/src/sloth_std/stdio.rs deleted file mode 100644 index f56b604..0000000 --- a/crates/sloth_vm/src/sloth_std/stdio.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::io::{stdin, BufRead}; - -use crate::native::{self, NativeFunction, NativeFunctionResult}; -use crate::value::{Object, ObjectType, Primitive}; -use crate::VM; - -fn write(vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let Some(Primitive::Object(ptr)) = args.get(0).cloned() else { - return Err(native::Error::InvalidArgument); - }; - - let object = vm - .objects() - .get(ptr as usize) - .ok_or(native::Error::InvalidArgument)?; - - let ObjectType::String(str) = &object.typ else { - return Err(native::Error::InvalidArgument); - }; - - print!("{str}"); - - Ok(Primitive::Empty) -} - -pub const WRITE_FUNCTION: NativeFunction = NativeFunction { - name: "write", - function: write, - arity: 1, - returns_value: false, - doc: Some( - "NativeFunction write: \n\targs: string (str)\n\tdesc: Writes <string> to the \ - terminal.\n\tExample: `write(\"I'm sleepy...\"); # Output: I'm sleepy...`", - ), -}; - -fn writeln(vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let Some(Primitive::Object(ptr)) = args.get(0).cloned() else { - return Err(native::Error::InvalidArgument); - }; - - let object = vm - .objects() - .get(ptr as usize) - .ok_or(native::Error::InvalidArgument)?; - - let ObjectType::String(str) = &object.typ else { - return Err(native::Error::InvalidArgument); - }; - - println!("{str}"); - - Ok(Primitive::Empty) -} - -pub const WRITELN_FUNCTION: NativeFunction = NativeFunction { - name: "writeln", - function: writeln, - arity: 1, - returns_value: false, - doc: Some( - "NativeFunction writeln: \n\targs: string (str)\n\tdesc: Writes <string> to the terminal \ - and starts a new line.\n\tExample: `writeln(\"I'm sleepy...\"); # Output: I'm \ - sleepy...\n # This is a new line`", - ), -}; - -fn read(vm: &mut VM, _args: &[Primitive]) -> NativeFunctionResult { - let mut line = String::new(); - stdin() - .lock() - .read_line(&mut line) - .map_err(|it| native::Error::Unknown(it.to_string()))?; - - let object = Object::new(ObjectType::String(line)); - let ptr = vm.objects_mut().allocate(object); - - Ok(Primitive::Object(ptr as u32)) -} - -pub const READ_FUNCTION: NativeFunction = NativeFunction { - name: "read", - function: read, - arity: 0, - returns_value: true, - doc: Some( - "NativeFunction read:\n\tdesc: Reads input from the terminal and returns what was \ - read.\n\tExample: `var input = read(); # Hello World <execute code> input = 'Hello \ - World'`", - ), -}; diff --git a/crates/sloth_vm/src/sloth_std/term.rs b/crates/sloth_vm/src/sloth_std/term.rs deleted file mode 100644 index f61321c..0000000 --- a/crates/sloth_vm/src/sloth_std/term.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::native::{self, NativeFunction, NativeFunctionResult}; -use crate::value::Primitive; -use crate::value::Primitive::Integer; -use crate::VM; - -pub const TERM_CLEAR: NativeFunction = NativeFunction { - name: "term$clear", - function: |_vm, _args| { - print!("\x1b[2J\x1b[H"); - Ok(Primitive::Empty) - }, - arity: 0, - returns_value: false, - doc: Some( - "NativeFunction term$clear: \n\tdesc: Clears the terminal\n\tExample: `term$clear(); # \ - Clears the terminal`", - ), -}; - -fn term_setpos(_vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let x = args.get(0).cloned(); - let y = args.get(1).cloned(); - - let (Some(Integer(x)), Some(Integer(y))) = (x, y) else { - return Err(native::Error::InvalidArgument); - }; - print!("\x1b[{x};{y}H"); - Ok(Primitive::Empty) -} - -pub const TERM_SETPOS: NativeFunction = NativeFunction { - name: "term$setpos", - function: term_setpos, - arity: 2, - returns_value: false, - doc: Some( - "NativeFunction term$setpos: \n\targs: x (int), y (int)\n\tdesc: Sets the cursors \ - position to (<x>, <y>)\n\tExample: `term$setpos(5, 17); # Sets the position of the \ - cursor to (5, 17)`", - ), -}; diff --git a/crates/sloth_vm/src/sloth_std/time.rs b/crates/sloth_vm/src/sloth_std/time.rs deleted file mode 100644 index b27e0b5..0000000 --- a/crates/sloth_vm/src/sloth_std/time.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::{thread, time}; - -use crate::native::{self, NativeFunction, NativeFunctionResult}; -use crate::value::Primitive; -use crate::value::Primitive::Integer; -use crate::VM; - -fn wait(_vm: &mut VM, args: &[Primitive]) -> NativeFunctionResult { - let sec = args.get(0).cloned(); - - let Some(Integer(sec)) = sec else { - return Err(native::Error::InvalidArgument); - }; - - thread::sleep(time::Duration::from_secs(sec.try_into().unwrap())); - - Ok(Primitive::Empty) -} - -pub const WAIT: NativeFunction = NativeFunction { - name: "wait", - function: wait, - arity: 1, - returns_value: false, - doc: Some( - "NativeFunction wait: \n\targs: sec (int)\n\tdesc: Waits for <sec> seconds.\n\tExample: \ - `wait(10); # Waits 10 seconds`", - ), -}; diff --git a/crates/sloth_vm/src/value.rs b/crates/sloth_vm/src/value.rs deleted file mode 100644 index 4450b5a..0000000 --- a/crates/sloth_vm/src/value.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::native::NativeFunction; -use crate::Chunk; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Primitive { - Integer(i128), - Float(f64), - Bool(bool), - /// Pointer to an object living on heap - Object(u32), - Empty, -} - -pub struct Object { - /// If the object has been marked by the VM or not - pub(crate) marked: bool, - pub(crate) typ: ObjectType, -} - -impl Object { - pub fn new(typ: ObjectType) -> Self { - Self { marked: false, typ } - } -} - -pub enum ObjectType { - Box(Primitive), - String(String), - List(Vec<Primitive>), - - Function(Function), - NativeFunction(NativeFunction), - - Free { next: usize }, -} - -pub struct Function { - pub name: Option<String>, - pub chunk: Chunk, - pub arity: u8, - pub returns_value: bool, -} - -impl Function { - pub fn root(chunk: Chunk) -> Self { - Self { - name: None, - chunk, - arity: 0, - returns_value: false, - } - } -} diff --git a/crates/sloth_vm/src/vm.rs b/crates/sloth_vm/src/vm.rs deleted file mode 100644 index 3600719..0000000 --- a/crates/sloth_vm/src/vm.rs +++ /dev/null @@ -1,610 +0,0 @@ -use std::mem::MaybeUninit; - -use sloth_bytecode::Opcode; - -use crate::value::{Function, Object, ObjectType, Primitive}; -use crate::{native, vm, ObjectMap, Stack}; - -#[derive(Clone, Copy)] -pub struct CallFrame { - pointer: usize, - stack_offset: usize, - function: *const Function, // TODO: Safety -} - -impl CallFrame { - fn new(stack_offset: usize, function: &Function) -> Self { - Self { - pointer: 0, - stack_offset, - function: function as *const _, - } - } - - #[inline] - fn function(&self) -> &Function { - unsafe { &*self.function } - } -} - -const CALL_STACK_SIZE: usize = 1024; - -pub struct CallStack { - top: usize, - frames: [MaybeUninit<CallFrame>; CALL_STACK_SIZE], -} - -impl Default for CallStack { - fn default() -> Self { - Self { - top: 0, - frames: [MaybeUninit::uninit(); CALL_STACK_SIZE], - } - } -} - -impl CallStack { - fn push(&mut self, frame: CallFrame) { - self.frames[self.top].write(frame); - self.top += 1; - } - - fn pop(&mut self) { - self.top -= 1; - } - - fn peek(&self) -> &CallFrame { - unsafe { self.frames[self.top - 1].assume_init_ref() } - } - - fn peek_mut(&mut self) -> &mut CallFrame { - unsafe { self.frames[self.top - 1].assume_init_mut() } - } -} - -// TODO: Fix visibility -pub struct VM { - pub stack: Stack, - call_stack: CallStack, - pub objects: ObjectMap, -} - -impl Default for VM { - fn default() -> Self { - Self::init(ObjectMap::default()) - } -} - -impl VM { - pub fn init(objects: ObjectMap) -> Self { - Self { - stack: Stack::default(), - call_stack: CallStack::default(), - objects, - } - } - - pub fn new(objects: ObjectMap, mut root: Function) -> Self { - let mut this = Self::init(objects); - - // Allocating the root function - root.chunk.code.push(Opcode::Halt as u8); - this.call_stack.push(CallFrame::new(0, &root)); - this.objects - .allocate(Object::new(ObjectType::Function(root))); - - this - } - - pub fn step(&mut self) -> bool { - use Primitive::*; - - let opcode = self.read_u8(); - - match Opcode::from_u8(opcode) { - Opcode::Constant => { - let idx = self.read_u16() as usize; - let value = self.call_stack.peek().function().chunk.constants[idx]; - - self.stack.push(value); - } - - Opcode::Dup => { - let value = self.stack.pop(); - self.stack.push(value); - self.stack.push(value); - } - Opcode::Pop => { - self.stack.pop(); - } - Opcode::GetLocal => { - let idx = self.read_u16() as usize; - let value = self.stack[self.call_stack.peek().stack_offset + idx]; - - self.stack.push(value); - } - Opcode::SetLocal => { - let idx = self.read_u16() as usize; - let value = self.stack.pop(); - - self.stack[self.call_stack.peek().stack_offset + idx] = value; - } - Opcode::Box => { - // FIXME: TODO: MEGA CURSED - let pos = self.read_u16() as usize; - let value = self.stack.pop(); - - let object = vm::Object::new(ObjectType::Box(value)); - - self.objects.heap[pos] = object; - self.stack.push(Object(pos as u32)); - } - - Opcode::Add => { - let value = match self.stack.pop2() { - (Integer(lhs), Integer(rhs)) => Integer(lhs + rhs), - (Float(lhs), Float(rhs)) => Float(lhs + rhs), - _ => panic!(), - }; - - self.stack.push(value); - } - Opcode::Sub => { - let value = match self.stack.pop2() { - (Integer(lhs), Integer(rhs)) => Integer(lhs - rhs), - (Float(lhs), Float(rhs)) => Float(lhs - rhs), - _ => panic!(), - }; - - self.stack.push(value); - } - Opcode::Mul => { - let value = match self.stack.pop2() { - (Integer(lhs), Integer(rhs)) => Integer(lhs * rhs), - (Float(lhs), Float(rhs)) => Float(lhs * rhs), - _ => panic!(), - }; - - self.stack.push(value); - } - Opcode::Div => { - let value = match self.stack.pop2() { - (Integer(_), Integer(0)) => panic!("Divide by 0"), - (Integer(lhs), Integer(rhs)) => Integer(lhs / rhs), - (Float(lhs), Float(rhs)) => Float(lhs / rhs), - _ => panic!(), - }; - - self.stack.push(value); - } - Opcode::Mod => { - let value = match self.stack.pop2() { - (Integer(lhs), Integer(rhs)) => Integer(lhs % rhs), - (Float(lhs), Float(rhs)) => Float(lhs % rhs), - _ => panic!(), - }; - - self.stack.push(value); - } - - Opcode::Eq => { - let value = match self.stack.pop2() { - (Integer(lhs), Integer(rhs)) => Bool(lhs == rhs), - (Float(lhs), Float(rhs)) => Bool(lhs == rhs), - (Bool(lhs), Bool(rhs)) => Bool(lhs == rhs), - (Object(lhs), Object(rhs)) => Bool(lhs == rhs), - (Empty, Empty) => Bool(true), - _ => Bool(false), - }; - - self.stack.push(value); - } - Opcode::Ne => { - let value = match self.stack.pop2() { - (Integer(lhs), Integer(rhs)) => Bool(lhs != rhs), - (Float(lhs), Float(rhs)) => Bool(lhs != rhs), - (Bool(lhs), Bool(rhs)) => Bool(lhs != rhs), - (Object(lhs), Object(rhs)) => Bool(lhs != rhs), - (Empty, Empty) => Bool(false), - _ => Bool(false), - }; - - self.stack.push(value); - } - - Opcode::Jump => { - let to = self.read_u16(); - self.call_stack.peek_mut().pointer = to as usize; - } - Opcode::JumpIf => { - let to = self.read_u16(); - let value = self.stack.pop(); - - if let Bool(true) = value { - self.call_stack.peek_mut().pointer = to as usize; - } - } - - Opcode::Call => { - let Primitive::Object(ptr) = self.stack.pop() else { - panic!("Last element on stack was not an object"); - }; - - self.call(ptr as usize); - } - - Opcode::Return => { - self.call_return(); - } - - Opcode::Halt => return false, - - opcode => unimplemented!("Opcode {:?} unimplemented", opcode), - } - - true - } - - pub fn run(&mut self) { - while self.step() {} - } - - pub fn call(&mut self, ptr: usize) { - let Some(obj) = self.objects.get(ptr) else { - panic!("Pointer referenced nothing"); - }; - - match &obj.typ { - ObjectType::Function(function) => { - // Add a callstack entry for the function - let offset = self.stack.top - (function.arity as usize); - self.call_stack.push(CallFrame::new(offset, function)); - } - ObjectType::NativeFunction(function) => { - let mut args = Vec::with_capacity(function.arity as usize); - for _ in 0..function.arity { - args.push(self.stack.pop()); - } - - let name = function.name; - let returns_value = function.returns_value; - - let internal = function.function; - let result = internal(self, &args); - - match result { - Ok(value) => { - if returns_value { - self.stack.push(value); - } - } - Err(error) => match error { - native::Error::InvalidArgument => { - panic!("Invalid argument provided to '{name}'"); - } - native::Error::Unknown(msg) => { - panic!("Native function '{name}' failed due to '{msg}'"); - } - }, - } - } - _ => panic!("Object was not a function"), - } - } - - fn call_return(&mut self) { - let function = self.call_stack.peek().function(); - let stack_offset = self.call_stack.peek().stack_offset; - - let return_value = if function.returns_value { - Some(self.stack.pop()) - } else { - None - }; - - self.stack.top = stack_offset; - - if let Some(return_value) = return_value { - self.stack.push(return_value); - } - - self.call_stack.pop(); - } - - fn unwind(&mut self) { - unimplemented!("Implement unwinding for error handling"); - } - - #[inline(always)] - fn read_u8(&mut self) -> u8 { - let frame = self.call_stack.peek_mut(); - let function = frame.function(); - let byte = function.chunk.code[frame.pointer]; - frame.pointer += 1; - byte - } - - #[inline(always)] - fn read_u16(&mut self) -> u16 { - let frame = self.call_stack.peek_mut(); - let chunk = &frame.function().chunk; - - let bytes = (chunk.code[frame.pointer], chunk.code[frame.pointer + 1]); - - frame.pointer += 2; - - ((bytes.0 as u16) << 8) + (bytes.1 as u16) - } - - #[inline(always)] - pub fn objects(&self) -> &ObjectMap { - &self.objects - } - - #[inline(always)] - pub fn objects_mut(&mut self) -> &mut ObjectMap { - &mut self.objects - } -} - -#[cfg(test)] -mod tests { - use crate::value::{Function, Object, ObjectType, Primitive}; - use crate::{sloth_std, Chunk, ObjectMap, VM}; - - #[test] - fn arithmetic_ops() { - // Addition - let mut vm = VM::new( - ObjectMap::default(), - Function::root(Chunk { - constants: vec![Primitive::Integer(7)], - code: vec![ - 0x00, 0, 0, // Load constant from 0 - 0x10, // Duplicate - 0x20, // Add - 0xE0, - ], - }), - ); - - vm.run(); - assert_eq!(vm.stack.peek(), Primitive::Integer(14)); - - let mut vm = VM::new( - ObjectMap::default(), - Function::root(Chunk { - constants: vec![Primitive::Integer(2), Primitive::Integer(11)], - code: vec![ - 0x00, 0, 0, // Load constant from 0 - 0x00, 0, 1, // Load constant from 1 - 0x20, // Add - 0xE0, - ], - }), - ); - - vm.run(); - assert_eq!(vm.stack.peek(), Primitive::Integer(13)); - } - - #[test] - fn basic_function() { - let mut vm = VM::new( - ObjectMap::from(vec![Object::new(ObjectType::Function(Function { - name: Some("add".to_string()), - chunk: Chunk { - constants: vec![], - code: vec![0x14, 0, 0, 0x14, 0, 1, 0x20, 0x52], - }, - arity: 2, - returns_value: true, - }))]), - Function::root(Chunk { - constants: vec![ - Primitive::Integer(6), - Primitive::Integer(3), - Primitive::Object(0), - Primitive::Object(1), - Primitive::Object(2), - ], - code: vec![ - 0x00, 0, 0, // Load first function parameter from 0 - 0x00, 0, 1, // Load second function parameter from 1 - 0x00, 0, 2, // Load function constant from 2 - 0x50, // Call function - ], - }), - ); - - vm.run(); - - assert_eq!(vm.stack.peek(), Primitive::Integer(9)); - } - - #[test] - fn native_function() { - let mut vm = VM::new( - ObjectMap::from(vec![ - Object::new(ObjectType::NativeFunction(sloth_std::rand::GEN_FUNCTION)), - Object::new(ObjectType::NativeFunction( - sloth_std::rand::GEN_RANGE_FUNCTION, - )), - ]), - Function::root(Chunk { - constants: vec![ - Primitive::Object(0), - Primitive::Object(1), - Primitive::Integer(5), - Primitive::Integer(10), - ], - code: vec![ - // First part - 0x00, 0, 0, // - 0x50, // - 0xE0, // - // Second part - 0x00, 0, 3, // - 0x00, 0, 2, // - 0x00, 0, 1, // - 0x50, // - ], - }), - ); - - vm.run(); - - assert!({ - let Primitive::Float(i) = vm.stack.peek() else { panic!(); }; - (0.0..=1.0).contains(&i) - }); - - vm.run(); - - assert!({ - let Primitive::Integer(i) = vm.stack.peek() else { panic!(); }; - (5..10).contains(&i) - }); - } - - #[test] - fn fibonacci() { - #[rustfmt::skip] - let mut vm = VM::new( - ObjectMap::default(), - Function::root(Chunk { - constants: vec![ - Primitive::Integer(0), - Primitive::Integer(1), - Primitive::Integer(10), - ], - code: vec![ - // Load variables - 0x00, 0, 0, // 0 Index - 0x00, 0, 0, // 3 Me - 0x00, 0, 0, // 6 Parent - 0x00, 0, 1, // 9 Grandparent - - // Load parent and grandparent, sum them and put the value in me - 0x14, 0, 2, // 12 - 0x14, 0, 3, // 15 - 0x20, // 16 - 0x15, 0, 1, // 19 - - // Set grandparent to parent - 0x14, 0, 2, // 22 - 0x15, 0, 3, // 25 - - // Set parent to me - 0x14, 0, 1, // 28 - 0x15, 0, 2, // 31 - - // Increment Index by 1 - 0x00, 0, 1, // 34 - 0x14, 0, 0, // 37 Index - 0x20, // 40 - 0x15, 0, 0, // 41 Index - - // Load me - 0x14, 0, 1, // 44 - 0xE0, // 47 - 0x11, // 48 - - // Repeat until Index is 9 - 0x00, 0, 2, // 49 - 0x14, 0, 0, // 52 Index - 0x31, // 55 - 0x41, 0, 12, // 56 - ], - }), - ); - - let mut values = Vec::new(); - for _ in 0..10 { - vm.run(); - values.push(vm.stack.peek()); - } - - assert_eq!(&values, &[ - Primitive::Integer(1), - Primitive::Integer(1), - Primitive::Integer(2), - Primitive::Integer(3), - Primitive::Integer(5), - Primitive::Integer(8), - Primitive::Integer(13), - Primitive::Integer(21), - Primitive::Integer(34), - Primitive::Integer(55), - ]); - } - - #[test] - fn fibonacci_recursive() { - #[rustfmt::skip] - let mut vm = VM::new( - ObjectMap::from(vec![Object::new(ObjectType::Function(Function { - name: Some("fib".to_owned()), - chunk: Chunk { - constants: vec![ - Primitive::Object(0), - Primitive::Integer(0), - Primitive::Integer(1), - Primitive::Integer(2), - ], - code: vec![ - 0x14, 0, 0, // 0 - 0x00, 0, 1, // 3 - 0x31, // 6 - 0x41, 0, 14, // 7 - 0x00, 0, 1, // 10 - 0x52, // 13 - - 0x14, 0, 0, // 14 - 0x00, 0, 2, // 17 - 0x31, // 20 - 0x41, 0, 28, // 21 - 0x00, 0, 2, // 24 - 0x52, // 27 - - // fib(n - 1) - 0x00, 0, 2, // 28 - 0x14, 0, 0, // 31 - 0x21, // 34 - 0x00, 0, 0, // 35 - 0x50, // 38 - - // fib(n - 2) - 0x00, 0, 3, // 39 - 0x14, 0, 0, // 42 - 0x21, // 45 - 0x00, 0, 0, // 46 - 0x50, // 49 - - // add & return - 0x20, // 50 - 0x52, // 51 - ], - }, - arity: 1, - returns_value: true, - }))]), - Function::root(Chunk { - constants: vec![ - Primitive::Object(0), - Primitive::Integer(10), - ], - code: vec![ - // Load n and the function and call it - 0x00, 0, 1, // 0 - 0x00, 0, 0, // 3 - 0x50, // 6 - ], - }), - ); - - vm.run(); - - assert_eq!(Primitive::Integer(55), vm.stack.peek()); - } -} |
