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 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);
|
||||||
}
|
}
|
||||||
|
|
66
src/lib.rs
66
src/lib.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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