add a vm and stack for execution.

add some basic opt tests
This commit is contained in:
publicmatt 2024-05-02 15:10:26 -07:00
parent 5cee345e8b
commit c80c8248a2
4 changed files with 276 additions and 21 deletions

View File

@ -1,6 +1,17 @@
use mox::value::Value; use crate::value::Value;
use mox::{Chunk, ConversionError, OptCode}; 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) { pub fn disassemble_chunk(chunk: &Chunk, name: &str) {
// println!("{:?}", chunk); // println!("{:?}", chunk);
println!("== {} ==", name); println!("== {} ==", name);
@ -9,8 +20,13 @@ pub fn disassemble_chunk(chunk: &Chunk, name: &str) {
offset = disassemble_instruction(&chunk, offset); 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); print!("{:04} ", offset);
if offset > 0 && chunk.lines[offset] == chunk.lines[offset - 1] {
print!(" | ");
} else {
print!("{:>4} ", chunk.lines[offset]);
}
let instruction: Result<OptCode, ConversionError> = chunk.code[offset].try_into(); let instruction: Result<OptCode, ConversionError> = chunk.code[offset].try_into();
match instruction { match instruction {
Ok(code) => match code { Ok(code) => match code {
@ -20,9 +36,15 @@ fn disassemble_instruction(chunk: &Chunk, offset: usize) -> usize {
OptCode::OpReturn => { OptCode::OpReturn => {
return simple_instruction("OpReturn", offset); 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) => { Err(e) => {
println!("Unknown optcode {:?}", e); println!("{:}: '{:}'", e.message, e.invalid_value);
return offset + 1; return offset + 1;
} }
} }
@ -39,6 +61,6 @@ fn constant_instruction(name: &str, chunk: &Chunk, offset: usize) -> usize {
println!(); println!();
return offset + 2; return offset + 2;
} }
fn print_value(value: Value) { pub fn print_value(value: Value) {
print!("{:}", value); print!("'{:}'", value);
} }

View File

@ -1,4 +1,9 @@
pub mod debug;
pub mod value; pub mod value;
pub mod vm;
use std::fmt;
use std::u16;
use value::Value; use value::Value;
@ -6,11 +11,17 @@ use value::Value;
pub enum OptCode { pub enum OptCode {
OpReturn, OpReturn,
OpConstant, OpConstant,
OpNegate,
OpAdd,
OpSubstract,
OpMultiply,
OpDivide,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Chunk { pub struct Chunk {
pub code: Vec<u8>, pub code: Vec<u8>,
pub constants: Vec<Value>, pub constants: Vec<Value>,
pub lines: Vec<u16>,
} }
impl From<OptCode> for u8 { impl From<OptCode> for u8 {
@ -18,11 +29,45 @@ impl From<OptCode> for u8 {
match code { match code {
OptCode::OpReturn => 0, OptCode::OpReturn => 0,
OptCode::OpConstant => 1, 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)] #[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<u8> for OptCode { impl TryFrom<u8> for OptCode {
type Error = ConversionError; type Error = ConversionError;
@ -30,7 +75,12 @@ impl TryFrom<u8> for OptCode {
match code { match code {
0 => Ok(OptCode::OpReturn), 0 => Ok(OptCode::OpReturn),
1 => Ok(OptCode::OpConstant), 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 { Self {
code: vec![], code: vec![],
constants: 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.code.push(byte);
self.lines.push(line);
} }
pub fn write_value(&mut self, add: Value) -> u8 { pub fn write_value(&mut self, add: Value) -> u8 {
self.constants.push(add); self.constants.push(add);
@ -67,8 +119,8 @@ mod tests {
let mut chunk = Chunk::new(); let mut chunk = Chunk::new();
let constant_idx: u8 = chunk.write_value(1.2); let constant_idx: u8 = chunk.write_value(1.2);
assert_eq!(constant_idx, 0); assert_eq!(constant_idx, 0);
chunk.write(OptCode::OpConstant.into()); chunk.write(OptCode::OpConstant.into(), 123);
chunk.write(constant_idx); chunk.write(constant_idx, 123);
let pred: OptCode = chunk.code[0].try_into().unwrap(); let pred: OptCode = chunk.code[0].try_into().unwrap();
assert_eq!(pred, OptCode::OpConstant); assert_eq!(pred, OptCode::OpConstant);
} }
@ -76,7 +128,7 @@ mod tests {
fn try_into_constant() { fn try_into_constant() {
let mut chunk = Chunk::new(); let mut chunk = Chunk::new();
let constant_idx: u8 = chunk.write_value(1.2); let constant_idx: u8 = chunk.write_value(1.2);
chunk.write(OptCode::OpConstant.into()); chunk.write(OptCode::OpConstant.into(), 123);
chunk.write(constant_idx); chunk.write(constant_idx, 123);
} }
} }

View File

@ -1,15 +1,22 @@
use mox::vm::{InterpretResult, VM};
use mox::Chunk; use mox::Chunk;
use mox::OptCode; use mox::OptCode;
mod debug;
use debug::disassemble_chunk;
fn main() { fn main() {
let mut chunk = Chunk::new(); 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);
} }

174
src/vm.rs Normal file
View File

@ -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<Value>,
}
#[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<OptCode, ConversionError> = 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);
}
}