aboutsummaryrefslogtreecommitdiff
path: root/crates/sloth_vm/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/sloth_vm/src')
-rw-r--r--crates/sloth_vm/src/lib.rs146
-rw-r--r--crates/sloth_vm/src/native.rs18
-rw-r--r--crates/sloth_vm/src/sloth_std/mod.rs24
-rw-r--r--crates/sloth_vm/src/sloth_std/rand.rs39
-rw-r--r--crates/sloth_vm/src/sloth_std/stdio.rs51
-rw-r--r--crates/sloth_vm/src/value.rs53
-rw-r--r--crates/sloth_vm/src/vm.rs599
7 files changed, 919 insertions, 11 deletions
diff --git a/crates/sloth_vm/src/lib.rs b/crates/sloth_vm/src/lib.rs
index 1dfb191..295827d 100644
--- a/crates/sloth_vm/src/lib.rs
+++ b/crates/sloth_vm/src/lib.rs
@@ -7,24 +7,148 @@
unused_lifetimes
)]
-const STACK_SIZE: usize = 1024;
+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;
pub struct Chunk {
+ constants: Vec<Primitive>,
code: Vec<u8>,
- constants: Vec<u64>,
}
-pub struct VM {
- stack: [u8; STACK_SIZE],
- constants: 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 VM {
- //
+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]
+ }
}
-#[cfg(test)]
-mod tests {
- #[test]
- fn add_program() {}
+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
new file mode 100644
index 0000000..8790cbd
--- /dev/null
+++ b/crates/sloth_vm/src/native.rs
@@ -0,0 +1,18 @@
+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,
+}
diff --git a/crates/sloth_vm/src/sloth_std/mod.rs b/crates/sloth_vm/src/sloth_std/mod.rs
new file mode 100644
index 0000000..86611d7
--- /dev/null
+++ b/crates/sloth_vm/src/sloth_std/mod.rs
@@ -0,0 +1,24 @@
+use std::collections::HashMap;
+
+use once_cell::sync::Lazy;
+
+use crate::native::NativeFunction;
+
+pub mod rand;
+pub mod stdio;
+
+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("read", stdio::READ_FUNCTION);
+
+ // filesystem
+
+ map
+});
diff --git a/crates/sloth_vm/src/sloth_std/rand.rs b/crates/sloth_vm/src/sloth_std/rand.rs
new file mode 100644
index 0000000..bae0606
--- /dev/null
+++ b/crates/sloth_vm/src/sloth_std/rand.rs
@@ -0,0 +1,39 @@
+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,
+};
+
+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,
+};
diff --git a/crates/sloth_vm/src/sloth_std/stdio.rs b/crates/sloth_vm/src/sloth_std/stdio.rs
new file mode 100644
index 0000000..a743ad1
--- /dev/null
+++ b/crates/sloth_vm/src/sloth_std/stdio.rs
@@ -0,0 +1,51 @@
+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);
+ };
+
+ println!("{str}");
+
+ Ok(Primitive::Empty)
+}
+
+pub const WRITE_FUNCTION: NativeFunction = NativeFunction {
+ name: "write",
+ function: write,
+ arity: 1,
+ returns_value: false,
+};
+
+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,
+};
diff --git a/crates/sloth_vm/src/value.rs b/crates/sloth_vm/src/value.rs
new file mode 100644
index 0000000..f149c0e
--- /dev/null
+++ b/crates/sloth_vm/src/value.rs
@@ -0,0 +1,53 @@
+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(crate) name: Option<String>,
+ pub(crate) chunk: Chunk,
+ pub(crate) arity: u8,
+ pub(crate) returns_value: bool,
+}
+
+impl Function {
+ pub(crate) 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
new file mode 100644
index 0000000..2d58c3d
--- /dev/null
+++ b/crates/sloth_vm/src/vm.rs
@@ -0,0 +1,599 @@
+use std::mem::MaybeUninit;
+
+use sloth_bytecode::Opcode;
+
+use crate::value::{Function, Object, ObjectType, Primitive};
+use crate::{native, 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() }
+ }
+}
+
+pub struct VM {
+ stack: Stack,
+ call_stack: CallStack,
+ 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::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());
+ }
+}