add a vm and stack for execution.
add some basic opt tests
This commit is contained in:
parent
5cee345e8b
commit
c80c8248a2
34
src/debug.rs
34
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<OptCode, ConversionError> = 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);
|
||||
}
|
||||
|
|
66
src/lib.rs
66
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<u8>,
|
||||
pub constants: Vec<Value>,
|
||||
pub lines: Vec<u16>,
|
||||
}
|
||||
|
||||
impl From<OptCode> for u8 {
|
||||
|
@ -18,11 +29,45 @@ impl From<OptCode> 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<u8> for OptCode {
|
||||
type Error = ConversionError;
|
||||
|
@ -30,7 +75,12 @@ impl TryFrom<u8> 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);
|
||||
}
|
||||
}
|
||||
|
|
23
src/main.rs
23
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue