♻️ refactor: split out tests and state module
							parent
							
								
									8a2807f879
								
							
						
					
					
						commit
						89c3b6e0ca
					
				
							
								
								
									
										395
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										395
									
								
								src/main.rs
								
								
								
								
							|  | @ -1,279 +1,24 @@ | |||
| use std::collections::BTreeMap; | ||||
| mod state; | ||||
| 
 | ||||
| #[derive (Debug)] | ||||
| enum Instruction { | ||||
| 	Add (u8, u8, u8), | ||||
| 	
 | ||||
| 	Call (u8, u8, u8), | ||||
| 	Closure (u8, i32), | ||||
| 	
 | ||||
| 	// Equals Constant?
 | ||||
| 	EqK (u8, u8, u8), | ||||
| 	
 | ||||
| 	// Get Table, Upvalue
 | ||||
| 	GetTabUp (u8, u8, u8), | ||||
| 	
 | ||||
| 	// Get Immediate?
 | ||||
| 	GetI (u8, u8, u8), | ||||
| 	
 | ||||
| 	// Jump
 | ||||
| 	Jmp (i32), | ||||
| 	
 | ||||
| 	// Load Integer?
 | ||||
| 	LoadI (u8, i32), | ||||
| 	
 | ||||
| 	// Load Constant
 | ||||
| 	LoadK (u8, i32), | ||||
| 	
 | ||||
| 	// MetaMethod, Binary
 | ||||
| 	MmBin (u8, u8, u8), | ||||
| 	
 | ||||
| 	Move (u8, u8), | ||||
| 	
 | ||||
| 	// (A, B, _C) Return B - 1 registers starting with  A
 | ||||
| 	Return (u8, u8, u8), | ||||
| 	
 | ||||
| 	VarArgPrep (i32), | ||||
| } | ||||
| 
 | ||||
| #[derive (Clone, Debug, PartialEq)] | ||||
| enum Value { | ||||
| 	Nil, | ||||
| 	False, | ||||
| 	True, | ||||
| 	Float (f64), | ||||
| 	String (String), | ||||
| 	
 | ||||
| 	// These are all bogus, I haven't figured out how to implement
 | ||||
| 	// tables and function pointers yet
 | ||||
| 	
 | ||||
| 	BogusArg (Vec <String>), | ||||
| 	BogusEnv (BTreeMap <String, Value>), | ||||
| 	BogusPrint, | ||||
| } | ||||
| 
 | ||||
| impl Default for Value { | ||||
| 	fn default () -> Self { | ||||
| 		Self::Nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <String> for Value { | ||||
| 	fn from (x: String) -> Self { | ||||
| 		Self::String (x) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <&str> for Value { | ||||
| 	fn from (x: &str) -> Self { | ||||
| 		Self::from (String::from (x)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <i32> for Value { | ||||
| 	fn from (x: i32) -> Self { | ||||
| 		Self::Float (f64::try_from (x).unwrap ()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <f64> for Value { | ||||
| 	fn from (x: f64) -> Self { | ||||
| 		Self::Float (x) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Value { | ||||
| 	fn as_float (&self) -> Option <f64> { | ||||
| 		match self { | ||||
| 			Self::Float (x) => Some (*x), | ||||
| 			_ => None, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| struct Chunk { | ||||
| 	instructions: Vec <Instruction>, | ||||
| 	constants: Vec <Value>, | ||||
| } | ||||
| 
 | ||||
| struct VirtualMachine { | ||||
| 	registers: Vec <Value>, | ||||
| 	
 | ||||
| 	// i32 makes it a little easier to implement jumps
 | ||||
| 	program_counter: i32, | ||||
| } | ||||
| 
 | ||||
| impl Default for VirtualMachine { | ||||
| 	fn default () -> Self { | ||||
| 		Self { | ||||
| 			registers: vec! [Value::Nil; 256], | ||||
| 			program_counter: 0, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl VirtualMachine { | ||||
| 	fn upvalues_from_args <I: Iterator <Item = String>> (args: I) -> Vec <Value> | ||||
| 	{ | ||||
| 		let arg = args.map (|s| s.to_string ()).collect (); | ||||
| 		
 | ||||
| 		let env = BTreeMap::from_iter ([ | ||||
| 			("arg", Value::BogusArg (arg)), | ||||
| 			("print", Value::BogusPrint), | ||||
| 		].map (|(k, v)| (k.to_string (), v)).into_iter ()); | ||||
| 		
 | ||||
| 		vec! [ | ||||
| 			Value::BogusEnv (env), | ||||
| 		] | ||||
| 	} | ||||
| 	
 | ||||
| 	fn execute_chunk (&mut self, chunk: &Chunk, upvalues: &Vec <Value>) 
 | ||||
| 	-> Vec <Value> { | ||||
| 		let max_iters = 2000; | ||||
| 		
 | ||||
| 		for _ in 0..max_iters { | ||||
| 			let instruction = chunk.instructions.get (usize::try_from (self.program_counter).unwrap ()).unwrap (); | ||||
| 			
 | ||||
| 			let r = &mut self.registers; | ||||
| 			let k = &chunk.constants; | ||||
| 			
 | ||||
| 			match instruction { | ||||
| 				Instruction::Add (a, b, c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					let c = usize::try_from (*c).unwrap (); | ||||
| 					
 | ||||
| 					let v_b = r [b].as_float ().unwrap (); | ||||
| 					let v_c = r [c].as_float ().unwrap (); | ||||
| 					
 | ||||
| 					r [a] = (v_b + v_c).into (); | ||||
| 				}, | ||||
| 				Instruction::Call (a, b, c) => { | ||||
| 					// Take arguments from registers [a + 1, a + b)
 | ||||
| 					// Call the function in register [a]
 | ||||
| 					// Return values in registers [a, a + c - 1)
 | ||||
| 					// 
 | ||||
| 					// That is, call a with b - 1 arguments and expect c returns
 | ||||
| 					// 
 | ||||
| 					// e.g. CALL 0 2 1 mean "Call 0 with 1 argument, return 1 value", like for printing a constant
 | ||||
| 					
 | ||||
| 					// TODO: Only implement printing values for now
 | ||||
| 					
 | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					
 | ||||
| 					assert_eq! (r.get (a).unwrap (), &Value::BogusPrint); | ||||
| 					assert_eq! (*b, 2); | ||||
| 					assert_eq! (*c, 1); | ||||
| 					
 | ||||
| 					println! ("{:?}", r.get (a + 1).unwrap ()); | ||||
| 				}, | ||||
| 				Instruction::EqK (a, b, c_k) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					let equal = r [a] == k [b]; | ||||
| 					
 | ||||
| 					match (equal, c_k) { | ||||
| 						(true, 0) => self.program_counter += 1, | ||||
| 						(false, 1) => self.program_counter += 1, | ||||
| 						_ => (), | ||||
| 					} | ||||
| 				}, | ||||
| 				Instruction::GetTabUp (a, b, c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					let c = usize::try_from (*c).unwrap (); | ||||
| 					
 | ||||
| 					let env = match upvalues.get (b).unwrap () { | ||||
| 						Value::BogusEnv (x) => x, | ||||
| 						_ => panic! ("Only allowed upvalue is BogusEnv"), | ||||
| 					}; | ||||
| 					
 | ||||
| 					let key = match k.get (c).unwrap () { | ||||
| 						Value::String (s) => s, | ||||
| 						_ => panic! ("GetTabUp only supports string keys"), | ||||
| 					}; | ||||
| 					
 | ||||
| 					
 | ||||
| 					let value = env.get (key).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = value.clone (); | ||||
| 				}, | ||||
| 				Instruction::GetI (a, b, c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					let c = usize::try_from (*c).unwrap (); | ||||
| 					
 | ||||
| 					let table = r.get (b).unwrap (); | ||||
| 					let value = match table { | ||||
| 						Value::BogusArg (arg) => arg.get (c).map (|x| x.as_str().into ()).unwrap_or_default(), | ||||
| 						_ => unimplemented!(), | ||||
| 					}; | ||||
| 					
 | ||||
| 					r [a] = value; | ||||
| 				}, | ||||
| 				Instruction::Jmp (s_j) => self.program_counter += s_j, | ||||
| 				Instruction::LoadI (a, sbx) => { | ||||
| 					let a  = usize::try_from  (*a).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = (*sbx).into (); | ||||
| 				}, | ||||
| 				Instruction::LoadK (a, bx) => { | ||||
| 					let a  = usize::try_from  (*a).unwrap (); | ||||
| 					let bx = usize::try_from (*bx).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = k [bx].clone (); | ||||
| 				}, | ||||
| 				Instruction::MmBin (a, b, c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					let a = &r [a]; | ||||
| 					let b = &r [b]; | ||||
| 					
 | ||||
| 					if a.as_float().is_some() && b.as_float().is_some () { | ||||
| 						// No need for metamethods
 | ||||
| 					} | ||||
| 					else { | ||||
| 						panic! ("Not sure how to implememtn OP_MMBIN for these 2 values {a:?}, {b:?}"); | ||||
| 					} | ||||
| 				}, | ||||
| 				Instruction::Move (a, b) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = r [b].clone (); | ||||
| 				}, | ||||
| 				Instruction::Return (a, b, _c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					return self.registers [a..(a + b - 1)].to_vec(); | ||||
| 				}, | ||||
| 				Instruction::VarArgPrep (_) => (), | ||||
| 				x => panic! ("Unimplemented instruction {x:?}"), | ||||
| 			} | ||||
| 			
 | ||||
| 			self.program_counter += 1; | ||||
| 		} | ||||
| 		
 | ||||
| 		panic! ("Hit max iterations before chunk returned"); | ||||
| 	} | ||||
| } | ||||
| #[cfg (test)] | ||||
| mod tests; | ||||
| 
 | ||||
| fn main() { | ||||
| 	let chunk = Chunk { | ||||
| 	use state::Instruction as Inst; | ||||
| 	use state::State; | ||||
| 	
 | ||||
| 	let chunk = state::Chunk { | ||||
| 		instructions: vec! [ | ||||
| 			Instruction::VarArgPrep (0), | ||||
| 			Instruction::LoadK (0, 0), | ||||
| 			Instruction::LoadI (1, 3), | ||||
| 			Instruction::Add (2, 0, 1), | ||||
| 			Instruction::MmBin (0, 1, 6), | ||||
| 			Instruction::GetTabUp (3, 0, 1), | ||||
| 			Instruction::Move (4, 2), | ||||
| 			Instruction::Call (3, 2, 1), | ||||
| 			Instruction::Return (2, 2, 1), | ||||
| 			Instruction::Return (3, 1, 1), | ||||
| 			Inst::VarArgPrep (0), | ||||
| 			Inst::LoadK (0, 0), | ||||
| 			Inst::LoadI (1, 3), | ||||
| 			Inst::Add (2, 0, 1), | ||||
| 			Inst::MmBin (0, 1, 6), | ||||
| 			Inst::GetTabUp (3, 0, 1), | ||||
| 			Inst::Move (4, 2), | ||||
| 			Inst::Call (3, 2, 1), | ||||
| 			Inst::Return (2, 2, 1), | ||||
| 			Inst::Return (3, 1, 1), | ||||
| 		], | ||||
| 		constants: vec! [ | ||||
| 			0.5.into (), | ||||
|  | @ -281,109 +26,7 @@ fn main() { | |||
| 		], | ||||
| 	}; | ||||
| 	
 | ||||
| 	let upvalues = VirtualMachine::upvalues_from_args (std::env::args ()); | ||||
| 	
 | ||||
| 	let mut vm = VirtualMachine::default (); | ||||
| 	let mut vm = State::default (); | ||||
| 	let upvalues = State::upvalues_from_args (std::env::args ()); | ||||
| 	println! ("Returned: {:?}", vm.execute_chunk (&chunk, &upvalues)); | ||||
| } | ||||
| 
 | ||||
| #[cfg (test)] | ||||
| mod tests { | ||||
| 	use super::*; | ||||
| 	
 | ||||
| 	#[test] | ||||
| 	fn floats () { | ||||
| 		/* | ||||
| 		local a = 0.5 | ||||
| 		local b = 3 | ||||
| 
 | ||||
| 		local x = a + b | ||||
| 
 | ||||
| 		print (x) | ||||
| 		return x | ||||
| 		*/ | ||||
| 		
 | ||||
| 		let chunk = Chunk { | ||||
| 			instructions: vec! [ | ||||
| 				Instruction::VarArgPrep (0), | ||||
| 				Instruction::LoadK (0, 0), | ||||
| 				Instruction::LoadI (1, 3), | ||||
| 				Instruction::Add (2, 0, 1), | ||||
| 				Instruction::MmBin (0, 1, 6), | ||||
| 				Instruction::GetTabUp (3, 0, 1), | ||||
| 				Instruction::Move (4, 2), | ||||
| 				Instruction::Call (3, 2, 1), | ||||
| 				Instruction::Return (2, 2, 1), | ||||
| 				Instruction::Return (3, 1, 1), | ||||
| 			], | ||||
| 			constants: vec! [ | ||||
| 				0.5.into (), | ||||
| 				"print".into (), | ||||
| 			], | ||||
| 		}; | ||||
| 		
 | ||||
| 		for (arg, expected) in [ | ||||
| 			(vec! ["_exe_name"], vec! [3.5.into ()]), | ||||
| 		] { | ||||
| 			let mut vm = VirtualMachine::default (); | ||||
| 			let upvalues = VirtualMachine::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); | ||||
| 			let actual = vm.execute_chunk (&chunk, &upvalues); | ||||
| 			
 | ||||
| 			assert_eq! (actual, expected); | ||||
| 		} | ||||
| 	} | ||||
| 	
 | ||||
| 	#[test] | ||||
| 	fn is_93 () { | ||||
| 		/* | ||||
| 		if arg [1] == "93" then | ||||
| 			print "it's 93" | ||||
| 			return 0 | ||||
| 		else | ||||
| 			print "it's not 93" | ||||
| 			return 1 | ||||
| 		end | ||||
| 		*/ | ||||
| 		
 | ||||
| 		let chunk = Chunk { | ||||
| 			instructions: vec! [ | ||||
| 				Instruction::VarArgPrep (0), | ||||
| 				Instruction::GetTabUp (1, 0, 0), | ||||
| 				Instruction::GetI (1, 1, 1), | ||||
| 				Instruction::EqK (1, 1, 0), | ||||
| 				Instruction::Jmp (6), | ||||
| 				Instruction::GetTabUp (1, 0, 2), | ||||
| 				Instruction::LoadK (2, 3), | ||||
| 				Instruction::Call (1, 2, 1), | ||||
| 				Instruction::LoadI (1, 0), | ||||
| 				Instruction::Return (1, 2, 1), | ||||
| 				Instruction::Jmp (5), | ||||
| 				Instruction::GetTabUp (1, 0, 2), | ||||
| 				Instruction::LoadK (2, 4), | ||||
| 				Instruction::Call (1, 2, 1), | ||||
| 				Instruction::LoadI (1, 1), | ||||
| 				Instruction::Return (1, 2, 1), | ||||
| 				Instruction::Return (1, 1, 1), | ||||
| 			], | ||||
| 			constants: vec! [ | ||||
| 				"arg", | ||||
| 				"93", | ||||
| 				"print", | ||||
| 				"it's 93", | ||||
| 				"it's not 93", | ||||
| 			].into_iter ().map (|s| Value::from (s)).collect (), | ||||
| 		}; | ||||
| 		
 | ||||
| 		for (arg, expected) in [ | ||||
| 			(vec! ["_exe_name"], vec! [1.into ()]), | ||||
| 			(vec! ["_exe_name", "93"], vec! [0.into ()]), | ||||
| 			(vec! ["_exe_name", "94"], vec! [1.into ()]), | ||||
| 		] { | ||||
| 			let mut vm = VirtualMachine::default (); | ||||
| 			let upvalues = VirtualMachine::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); | ||||
| 			let actual = vm.execute_chunk (&chunk, &upvalues); | ||||
| 			
 | ||||
| 			assert_eq! (actual, expected); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,262 @@ | |||
| use std::collections::BTreeMap; | ||||
| 
 | ||||
| #[derive (Debug)] | ||||
| pub enum Instruction { | ||||
| 	Add (u8, u8, u8), | ||||
| 	
 | ||||
| 	Call (u8, u8, u8), | ||||
| 	Closure (u8, i32), | ||||
| 	
 | ||||
| 	// Equals Constant?
 | ||||
| 	EqK (u8, u8, u8), | ||||
| 	
 | ||||
| 	// Get Table, Upvalue
 | ||||
| 	GetTabUp (u8, u8, u8), | ||||
| 	
 | ||||
| 	// Get Immediate?
 | ||||
| 	GetI (u8, u8, u8), | ||||
| 	
 | ||||
| 	// Jump
 | ||||
| 	Jmp (i32), | ||||
| 	
 | ||||
| 	// Load Integer?
 | ||||
| 	LoadI (u8, i32), | ||||
| 	
 | ||||
| 	// Load Constant
 | ||||
| 	LoadK (u8, i32), | ||||
| 	
 | ||||
| 	// MetaMethod, Binary
 | ||||
| 	MmBin (u8, u8, u8), | ||||
| 	
 | ||||
| 	Move (u8, u8), | ||||
| 	
 | ||||
| 	// (A, B, _C) Return B - 1 registers starting with  A
 | ||||
| 	Return (u8, u8, u8), | ||||
| 	
 | ||||
| 	VarArgPrep (i32), | ||||
| } | ||||
| 
 | ||||
| #[derive (Clone, Debug, PartialEq)] | ||||
| pub enum Value { | ||||
| 	Nil, | ||||
| 	False, | ||||
| 	True, | ||||
| 	Float (f64), | ||||
| 	String (String), | ||||
| 	
 | ||||
| 	// These are all bogus, I haven't figured out how to implement
 | ||||
| 	// tables and function pointers yet
 | ||||
| 	
 | ||||
| 	BogusArg (Vec <String>), | ||||
| 	BogusEnv (BTreeMap <String, Value>), | ||||
| 	BogusPrint, | ||||
| } | ||||
| 
 | ||||
| impl Default for Value { | ||||
| 	fn default () -> Self { | ||||
| 		Self::Nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <String> for Value { | ||||
| 	fn from (x: String) -> Self { | ||||
| 		Self::String (x) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <&str> for Value { | ||||
| 	fn from (x: &str) -> Self { | ||||
| 		Self::from (String::from (x)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <i32> for Value { | ||||
| 	fn from (x: i32) -> Self { | ||||
| 		Self::Float (f64::try_from (x).unwrap ()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From <f64> for Value { | ||||
| 	fn from (x: f64) -> Self { | ||||
| 		Self::Float (x) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Value { | ||||
| 	fn as_float (&self) -> Option <f64> { | ||||
| 		match self { | ||||
| 			Self::Float (x) => Some (*x), | ||||
| 			_ => None, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub struct Chunk { | ||||
| 	pub instructions: Vec <Instruction>, | ||||
| 	pub constants: Vec <Value>, | ||||
| } | ||||
| 
 | ||||
| pub struct State { | ||||
| 	registers: Vec <Value>, | ||||
| 	
 | ||||
| 	// i32 makes it a little easier to implement jumps
 | ||||
| 	program_counter: i32, | ||||
| } | ||||
| 
 | ||||
| impl Default for State { | ||||
| 	fn default () -> Self { | ||||
| 		Self { | ||||
| 			registers: vec! [Value::Nil; 256], | ||||
| 			program_counter: 0, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl State { | ||||
| 	pub fn upvalues_from_args <I: Iterator <Item = String>> (args: I) -> Vec <Value> | ||||
| 	{ | ||||
| 		let arg = args.map (|s| s.to_string ()).collect (); | ||||
| 		
 | ||||
| 		let env = BTreeMap::from_iter ([ | ||||
| 			("arg", Value::BogusArg (arg)), | ||||
| 			("print", Value::BogusPrint), | ||||
| 		].map (|(k, v)| (k.to_string (), v)).into_iter ()); | ||||
| 		
 | ||||
| 		vec! [ | ||||
| 			Value::BogusEnv (env), | ||||
| 		] | ||||
| 	} | ||||
| 	
 | ||||
| 	pub fn execute_chunk (&mut self, chunk: &Chunk, upvalues: &Vec <Value>) 
 | ||||
| 	-> Vec <Value> { | ||||
| 		let max_iters = 2000; | ||||
| 		
 | ||||
| 		for _ in 0..max_iters { | ||||
| 			let instruction = chunk.instructions.get (usize::try_from (self.program_counter).unwrap ()).unwrap (); | ||||
| 			
 | ||||
| 			let r = &mut self.registers; | ||||
| 			let k = &chunk.constants; | ||||
| 			
 | ||||
| 			match instruction { | ||||
| 				Instruction::Add (a, b, c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					let c = usize::try_from (*c).unwrap (); | ||||
| 					
 | ||||
| 					let v_b = r [b].as_float ().unwrap (); | ||||
| 					let v_c = r [c].as_float ().unwrap (); | ||||
| 					
 | ||||
| 					r [a] = (v_b + v_c).into (); | ||||
| 				}, | ||||
| 				Instruction::Call (a, b, c) => { | ||||
| 					// Take arguments from registers [a + 1, a + b)
 | ||||
| 					// Call the function in register [a]
 | ||||
| 					// Return values in registers [a, a + c - 1)
 | ||||
| 					// 
 | ||||
| 					// That is, call a with b - 1 arguments and expect c returns
 | ||||
| 					// 
 | ||||
| 					// e.g. CALL 0 2 1 mean "Call 0 with 1 argument, return 1 value", like for printing a constant
 | ||||
| 					
 | ||||
| 					// TODO: Only implement printing values for now
 | ||||
| 					
 | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					
 | ||||
| 					assert_eq! (r.get (a).unwrap (), &Value::BogusPrint); | ||||
| 					assert_eq! (*b, 2); | ||||
| 					assert_eq! (*c, 1); | ||||
| 					
 | ||||
| 					println! ("{:?}", r.get (a + 1).unwrap ()); | ||||
| 				}, | ||||
| 				Instruction::EqK (a, b, c_k) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					let equal = r [a] == k [b]; | ||||
| 					
 | ||||
| 					match (equal, c_k) { | ||||
| 						(true, 0) => self.program_counter += 1, | ||||
| 						(false, 1) => self.program_counter += 1, | ||||
| 						_ => (), | ||||
| 					} | ||||
| 				}, | ||||
| 				Instruction::GetTabUp (a, b, c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					let c = usize::try_from (*c).unwrap (); | ||||
| 					
 | ||||
| 					let env = match upvalues.get (b).unwrap () { | ||||
| 						Value::BogusEnv (x) => x, | ||||
| 						_ => panic! ("Only allowed upvalue is BogusEnv"), | ||||
| 					}; | ||||
| 					
 | ||||
| 					let key = match k.get (c).unwrap () { | ||||
| 						Value::String (s) => s, | ||||
| 						_ => panic! ("GetTabUp only supports string keys"), | ||||
| 					}; | ||||
| 					
 | ||||
| 					
 | ||||
| 					let value = env.get (key).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = value.clone (); | ||||
| 				}, | ||||
| 				Instruction::GetI (a, b, c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					let c = usize::try_from (*c).unwrap (); | ||||
| 					
 | ||||
| 					let table = r.get (b).unwrap (); | ||||
| 					let value = match table { | ||||
| 						Value::BogusArg (arg) => arg.get (c).map (|x| x.as_str().into ()).unwrap_or_default(), | ||||
| 						_ => unimplemented!(), | ||||
| 					}; | ||||
| 					
 | ||||
| 					r [a] = value; | ||||
| 				}, | ||||
| 				Instruction::Jmp (s_j) => self.program_counter += s_j, | ||||
| 				Instruction::LoadI (a, sbx) => { | ||||
| 					let a  = usize::try_from  (*a).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = (*sbx).into (); | ||||
| 				}, | ||||
| 				Instruction::LoadK (a, bx) => { | ||||
| 					let a  = usize::try_from  (*a).unwrap (); | ||||
| 					let bx = usize::try_from (*bx).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = k [bx].clone (); | ||||
| 				}, | ||||
| 				Instruction::MmBin (a, b, _c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					let a = &r [a]; | ||||
| 					let b = &r [b]; | ||||
| 					
 | ||||
| 					if a.as_float().is_some() && b.as_float().is_some () { | ||||
| 						// No need for metamethods
 | ||||
| 					} | ||||
| 					else { | ||||
| 						panic! ("Not sure how to implememtn OP_MMBIN for these 2 values {a:?}, {b:?}"); | ||||
| 					} | ||||
| 				}, | ||||
| 				Instruction::Move (a, b) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = r [b].clone (); | ||||
| 				}, | ||||
| 				Instruction::Return (a, b, _c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					return self.registers [a..(a + b - 1)].to_vec(); | ||||
| 				}, | ||||
| 				Instruction::VarArgPrep (_) => (), | ||||
| 				x => panic! ("Unimplemented instruction {x:?}"), | ||||
| 			} | ||||
| 			
 | ||||
| 			self.program_counter += 1; | ||||
| 		} | ||||
| 		
 | ||||
| 		panic! ("Hit max iterations before chunk returned"); | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,102 @@ | |||
| use crate::state::{ | ||||
| 	Chunk, | ||||
| 	Instruction as Inst, | ||||
| 	State, | ||||
| 	Value, | ||||
| }; | ||||
| 
 | ||||
| #[test] | ||||
| fn floats () { | ||||
| 	/* | ||||
| 	local a = 0.5 | ||||
| 	local b = 3 | ||||
| 
 | ||||
| 	local x = a + b | ||||
| 
 | ||||
| 	print (x) | ||||
| 	return x | ||||
| 	*/ | ||||
| 	
 | ||||
| 	let chunk = Chunk { | ||||
| 		instructions: vec! [ | ||||
| 			Inst::VarArgPrep (0), | ||||
| 			Inst::LoadK (0, 0), | ||||
| 			Inst::LoadI (1, 3), | ||||
| 			Inst::Add (2, 0, 1), | ||||
| 			Inst::MmBin (0, 1, 6), | ||||
| 			Inst::GetTabUp (3, 0, 1), | ||||
| 			Inst::Move (4, 2), | ||||
| 			Inst::Call (3, 2, 1), | ||||
| 			Inst::Return (2, 2, 1), | ||||
| 			Inst::Return (3, 1, 1), | ||||
| 		], | ||||
| 		constants: vec! [ | ||||
| 			0.5.into (), | ||||
| 			"print".into (), | ||||
| 		], | ||||
| 	}; | ||||
| 	
 | ||||
| 	for (arg, expected) in [ | ||||
| 		(vec! ["_exe_name"], vec! [3.5.into ()]), | ||||
| 	] { | ||||
| 		let mut vm = State::default (); | ||||
| 		let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); | ||||
| 		let actual = vm.execute_chunk (&chunk, &upvalues); | ||||
| 		
 | ||||
| 		assert_eq! (actual, expected); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn is_93 () { | ||||
| 	/* | ||||
| 	if arg [1] == "93" then | ||||
| 		print "it's 93" | ||||
| 		return 0 | ||||
| 	else | ||||
| 		print "it's not 93" | ||||
| 		return 1 | ||||
| 	end | ||||
| 	*/ | ||||
| 	
 | ||||
| 	let chunk = Chunk { | ||||
| 		instructions: vec! [ | ||||
| 			Inst::VarArgPrep (0), | ||||
| 			Inst::GetTabUp (1, 0, 0), | ||||
| 			Inst::GetI (1, 1, 1), | ||||
| 			Inst::EqK (1, 1, 0), | ||||
| 			Inst::Jmp (6), | ||||
| 			Inst::GetTabUp (1, 0, 2), | ||||
| 			Inst::LoadK (2, 3), | ||||
| 			Inst::Call (1, 2, 1), | ||||
| 			Inst::LoadI (1, 0), | ||||
| 			Inst::Return (1, 2, 1), | ||||
| 			Inst::Jmp (5), | ||||
| 			Inst::GetTabUp (1, 0, 2), | ||||
| 			Inst::LoadK (2, 4), | ||||
| 			Inst::Call (1, 2, 1), | ||||
| 			Inst::LoadI (1, 1), | ||||
| 			Inst::Return (1, 2, 1), | ||||
| 			Inst::Return (1, 1, 1), | ||||
| 		], | ||||
| 		constants: vec! [ | ||||
| 			"arg", | ||||
| 			"93", | ||||
| 			"print", | ||||
| 			"it's 93", | ||||
| 			"it's not 93", | ||||
| 		].into_iter ().map (|s| Value::from (s)).collect (), | ||||
| 	}; | ||||
| 	
 | ||||
| 	for (arg, expected) in [ | ||||
| 		(vec! ["_exe_name"], vec! [1.into ()]), | ||||
| 		(vec! ["_exe_name", "93"], vec! [0.into ()]), | ||||
| 		(vec! ["_exe_name", "94"], vec! [1.into ()]), | ||||
| 	] { | ||||
| 		let mut vm = State::default (); | ||||
| 		let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); | ||||
| 		let actual = vm.execute_chunk (&chunk, &upvalues); | ||||
| 		
 | ||||
| 		assert_eq! (actual, expected); | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	 _
						_