From c80c8248a2be766350f37ae4820f7ba1bff5234e Mon Sep 17 00:00:00 2001 From: publicmatt Date: Thu, 2 May 2024 15:10:26 -0700 Subject: [PATCH] add a vm and stack for execution. add some basic opt tests --- src/debug.rs | 34 ++++++++-- src/lib.rs | 66 ++++++++++++++++--- src/main.rs | 23 ++++--- src/vm.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 276 insertions(+), 21 deletions(-) create mode 100644 src/vm.rs diff --git a/src/debug.rs b/src/debug.rs index 6fa2ff5..e38affa 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,6 +1,17 @@ -use mox::value::Value; -use mox::{Chunk, ConversionError, OptCode}; +use crate::value::Value; +use crate::{Chunk, ConversionError, OptCode}; +use std::env; +pub fn trace_enabled() -> bool { + match env::var("DEBUG_TRACE_EXECUTION") { + Ok(value) => match value.to_lowercase().as_str() { + "true" | "1" | "yes" | "on" => true, + "false" | "0" | "no" | "off" => false, + _ => false, + }, + Err(_e) => false, + } +} pub fn disassemble_chunk(chunk: &Chunk, name: &str) { // println!("{:?}", chunk); println!("== {} ==", name); @@ -9,8 +20,13 @@ pub fn disassemble_chunk(chunk: &Chunk, name: &str) { offset = disassemble_instruction(&chunk, offset); } } -fn disassemble_instruction(chunk: &Chunk, offset: usize) -> usize { +pub fn disassemble_instruction(chunk: &Chunk, offset: usize) -> usize { print!("{:04} ", offset); + if offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1] { + print!(" | "); + } else { + print!("{:>4} ", chunk.lines[offset]); + } let instruction: Result = chunk.code[offset].try_into(); match instruction { Ok(code) => match code { @@ -20,9 +36,15 @@ fn disassemble_instruction(chunk: &Chunk, offset: usize) -> usize { OptCode::OpReturn => { return simple_instruction("OpReturn", offset); } + OptCode::OpNegate => { + return simple_instruction(&format!("{:}", code), offset); + } + OptCode::OpAdd | OptCode::OpSubstract | OptCode::OpMultiply | OptCode::OpDivide => { + return simple_instruction(&format!("{:}", code), offset); + } }, Err(e) => { - println!("Unknown optcode {:?}", e); + println!("{:}: '{:}'", e.message, e.invalid_value); return offset + 1; } } @@ -39,6 +61,6 @@ fn constant_instruction(name: &str, chunk: &Chunk, offset: usize) -> usize { println!(); return offset + 2; } -fn print_value(value: Value) { - print!("{:}", value); +pub fn print_value(value: Value) { + print!("'{:}'", value); } diff --git a/src/lib.rs b/src/lib.rs index d02ac1c..2dcadca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,9 @@ +pub mod debug; pub mod value; +pub mod vm; + +use std::fmt; +use std::u16; use value::Value; @@ -6,11 +11,17 @@ use value::Value; pub enum OptCode { OpReturn, OpConstant, + OpNegate, + OpAdd, + OpSubstract, + OpMultiply, + OpDivide, } #[derive(Debug)] pub struct Chunk { pub code: Vec, pub constants: Vec, + pub lines: Vec, } impl From for u8 { @@ -18,11 +29,45 @@ impl From for u8 { match code { OptCode::OpReturn => 0, OptCode::OpConstant => 1, + OptCode::OpNegate => 2, + OptCode::OpAdd => 3, + OptCode::OpSubstract => 4, + OptCode::OpMultiply => 5, + OptCode::OpDivide => 6, } } } +impl fmt::Display for OptCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + OptCode::OpConstant => "OpConstant", + OptCode::OpNegate => "OpNegate", + OptCode::OpReturn => "OpReturn", + OptCode::OpAdd => "OpAdd", + OptCode::OpSubstract => "OpSubtract", + OptCode::OpMultiply => "OpMultiply", + OptCode::OpDivide => "OpDivide", + } + ) + } +} #[derive(Debug)] -pub struct ConversionError; +pub struct ConversionError { + invalid_value: u8, + message: String, +} + +impl ConversionError { + fn new(invalid_value: u8) -> Self { + ConversionError { + invalid_value, + message: format!("Invalid opcode"), + } + } +} impl TryFrom for OptCode { type Error = ConversionError; @@ -30,7 +75,12 @@ impl TryFrom for OptCode { match code { 0 => Ok(OptCode::OpReturn), 1 => Ok(OptCode::OpConstant), - _ => Err(ConversionError), + 2 => Ok(OptCode::OpNegate), + 3 => Ok(OptCode::OpAdd), + 4 => Ok(OptCode::OpSubstract), + 5 => Ok(OptCode::OpMultiply), + 6 => Ok(OptCode::OpDivide), + _ => Err(ConversionError::new(code)), } } } @@ -40,10 +90,12 @@ impl Chunk { Self { code: vec![], constants: vec![], + lines: vec![], } } - pub fn write(&mut self, byte: u8) { + pub fn write(&mut self, byte: u8, line: u16) { self.code.push(byte); + self.lines.push(line); } pub fn write_value(&mut self, add: Value) -> u8 { self.constants.push(add); @@ -67,8 +119,8 @@ mod tests { let mut chunk = Chunk::new(); let constant_idx: u8 = chunk.write_value(1.2); assert_eq!(constant_idx, 0); - chunk.write(OptCode::OpConstant.into()); - chunk.write(constant_idx); + chunk.write(OptCode::OpConstant.into(), 123); + chunk.write(constant_idx, 123); let pred: OptCode = chunk.code[0].try_into().unwrap(); assert_eq!(pred, OptCode::OpConstant); } @@ -76,7 +128,7 @@ mod tests { fn try_into_constant() { let mut chunk = Chunk::new(); let constant_idx: u8 = chunk.write_value(1.2); - chunk.write(OptCode::OpConstant.into()); - chunk.write(constant_idx); + chunk.write(OptCode::OpConstant.into(), 123); + chunk.write(constant_idx, 123); } } diff --git a/src/main.rs b/src/main.rs index fee608d..4102eae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,22 @@ +use mox::vm::{InterpretResult, VM}; use mox::Chunk; use mox::OptCode; -mod debug; - -use debug::disassemble_chunk; fn main() { let mut chunk = Chunk::new(); - let constant_idx: u8 = chunk.write_value(1.2); - chunk.write(OptCode::OpConstant.into()); - chunk.write(constant_idx); - chunk.write(OptCode::OpReturn.into()); - disassemble_chunk(&chunk, "test chunk"); + let constant_idx: u8 = chunk.write_value(1.2); + chunk.write(OptCode::OpConstant.into(), 123); + chunk.write(constant_idx, 123); + + let constant_idx: u8 = chunk.write_value(5.2); + chunk.write(OptCode::OpConstant.into(), 125); + chunk.write(constant_idx, 125); + + chunk.write(OptCode::OpAdd.into(), 124); + + chunk.write(OptCode::OpReturn.into(), 126); + + let mut vm: VM = VM::new(); + let _: InterpretResult = vm.interpret(&chunk); } diff --git a/src/vm.rs b/src/vm.rs new file mode 100644 index 0000000..5f314c9 --- /dev/null +++ b/src/vm.rs @@ -0,0 +1,174 @@ +use crate::debug::{disassemble_instruction, print_value, trace_enabled}; +use crate::Value; +use crate::{Chunk, ConversionError, OptCode}; + +pub struct VM<'a> { + chunk: Option<&'a Chunk>, + ip: usize, + stack: Vec, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum InterpretResult { + InterpretOk, + InterpretCompileError, + InterpretRuntimeError, +} + +impl<'a> VM<'a> { + pub fn new() -> Self { + VM { + chunk: None, + ip: 0, + stack: vec![], + } + } + pub fn interpret(&mut self, chunk: &'a Chunk) -> InterpretResult { + self.chunk = Some(chunk); + self.ip = 0; + self.run() + } + + fn read_byte(&mut self) -> u8 { + match self.chunk { + Some(chunk) => { + let result = chunk.code[self.ip]; + self.ip += 1; + return result; + } + None => { + panic!("could not read byte") + } + } + } + fn read_constant(&mut self) -> f64 { + match self.chunk { + Some(chunk) => chunk.constants[self.read_byte() as usize], + None => { + panic!("could not read constant") + } + } + } + fn run(&mut self) -> InterpretResult { + while let Some(chunk) = self.chunk { + if self.ip >= chunk.code.len() { + break; + } + if trace_enabled() { + print!(" "); + println!("{:?}", self.stack); + disassemble_instruction(&chunk, self.ip); + } + let byte: Result = self.read_byte().try_into(); + + match byte { + Ok(instruction) => match instruction { + OptCode::OpReturn => match self.stack.pop() { + Some(v) => { + print_value(v); + println!(); + return InterpretResult::InterpretOk; + } + None => { + return InterpretResult::InterpretOk; + } + }, + OptCode::OpConstant => { + let constant: Value = self.read_constant(); + self.stack.push(constant); + } + OptCode::OpNegate => match self.stack.pop() { + Some(v) => { + self.stack.push(-v); + } + None => { + return InterpretResult::InterpretRuntimeError; + } + }, + OptCode::OpAdd => match (self.stack.pop(), self.stack.pop()) { + (Some(b), Some(a)) => { + self.stack.push(a + b); + } + (_, _) => { + return InterpretResult::InterpretRuntimeError; + } + }, + OptCode::OpSubstract => match (self.stack.pop(), self.stack.pop()) { + (Some(b), Some(a)) => { + self.stack.push(a - b); + } + (_, _) => { + return InterpretResult::InterpretRuntimeError; + } + }, + OptCode::OpMultiply => match (self.stack.pop(), self.stack.pop()) { + (Some(b), Some(a)) => { + self.stack.push(a * b); + } + (_, _) => { + return InterpretResult::InterpretRuntimeError; + } + }, + OptCode::OpDivide => match (self.stack.pop(), self.stack.pop()) { + (Some(b), Some(a)) => { + self.stack.push(a / b); + } + (_, _) => { + return InterpretResult::InterpretRuntimeError; + } + }, + }, + Err(_e) => { + return InterpretResult::InterpretRuntimeError; + } + } + } + return InterpretResult::InterpretRuntimeError; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unary_opts() { + let mut chunk = Chunk::new(); + + let constant_idx: u8 = chunk.write_value(1.2); + chunk.write(OptCode::OpConstant.into(), 123); + chunk.write(constant_idx, 123); + chunk.write(OptCode::OpNegate.into(), 124); + chunk.write(OptCode::OpReturn.into(), 125); + + let mut vm: VM = VM::new(); + let result: InterpretResult = vm.interpret(&chunk); + assert_eq!(result, InterpretResult::InterpretOk); + } + #[test] + fn binary_opts() { + let mut chunk = Chunk::new(); + + let constant_idx: u8 = chunk.write_value(1.2); + chunk.write(OptCode::OpConstant.into(), 123); + chunk.write(constant_idx, 123); + + let constant_idx: u8 = chunk.write_value(5.2); + chunk.write(OptCode::OpConstant.into(), 125); + chunk.write(constant_idx, 125); + + chunk.write(OptCode::OpAdd.into(), 126); + + let constant_idx: u8 = chunk.write_value(3.0); + chunk.write(OptCode::OpConstant.into(), 127); + chunk.write(constant_idx, 127); + + chunk.write(OptCode::OpDivide.into(), 128); + + chunk.write(OptCode::OpReturn.into(), 126); + + let mut vm: VM = VM::new(); + let result: InterpretResult = vm.interpret(&chunk); + assert_eq!(result, InterpretResult::InterpretOk); + } +}