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