aboutsummaryrefslogtreecommitdiff
path: root/crates/sloth_vm/src
diff options
context:
space:
mode:
authorCody <cody@codyq.dev>2023-06-07 03:28:40 -0500
committerCody <cody@codyq.dev>2023-06-07 03:28:40 -0500
commit6f6613419f1511c5637c9f69b3caa5ae838270b9 (patch)
treee203d6cdc0eb2140ae6f0a430e76f2992de66bec /crates/sloth_vm/src
parent25c5ccb29a6f2387a04bfb5d50874e00084c15d6 (diff)
downloadsloth-6f6613419f1511c5637c9f69b3caa5ae838270b9.tar.gz
Moving over from a VM interpreter to natively compiled w/ LLVM
Diffstat (limited to 'crates/sloth_vm/src')
-rw-r--r--crates/sloth_vm/src/lib.rs155
-rw-r--r--crates/sloth_vm/src/native.rs19
-rw-r--r--crates/sloth_vm/src/sloth_std/file.rs83
-rw-r--r--crates/sloth_vm/src/sloth_std/misc.rs39
-rw-r--r--crates/sloth_vm/src/sloth_std/mod.rs43
-rw-r--r--crates/sloth_vm/src/sloth_std/rand.rs48
-rw-r--r--crates/sloth_vm/src/sloth_std/stdio.rs91
-rw-r--r--crates/sloth_vm/src/sloth_std/term.rs41
-rw-r--r--crates/sloth_vm/src/sloth_std/time.rs29
-rw-r--r--crates/sloth_vm/src/value.rs53
-rw-r--r--crates/sloth_vm/src/vm.rs610
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());
- }
-}