⭐ allow calling closures
							parent
							
								
									a2c91757cf
								
							
						
					
					
						commit
						81efebdda2
					
				
							
								
								
									
										88
									
								
								src/main.rs
								
								
								
								
							
							
						
						
									
										88
									
								
								src/main.rs
								
								
								
								
							|  | @ -4,29 +4,81 @@ mod state; | |||
| mod tests; | ||||
| 
 | ||||
| fn main() { | ||||
| 	use state::Instruction as Inst; | ||||
| 	use state::State; | ||||
| 	use state::{ | ||||
| 		Block, | ||||
| 		Chunk, | ||||
| 		Instruction as Inst, | ||||
| 		State, | ||||
| 	}; | ||||
| 	
 | ||||
| 	let chunk = state::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 (), | ||||
| 	/* | ||||
| 	local function bool_to_x (b) | ||||
| 		if b then | ||||
| 			return 99 | ||||
| 		else | ||||
| 			return 98 | ||||
| 		end | ||||
| 	end | ||||
| 
 | ||||
| 	local x = bool_to_x (not not arg [1]) | ||||
| 	print (x) | ||||
| 	return x | ||||
| 	*/ | ||||
| 	
 | ||||
| 	let chunk = Chunk { | ||||
| 		blocks: vec! [ | ||||
| 			Block { | ||||
| 				instructions: vec! [ | ||||
| 					Inst::VarArgPrep (0), | ||||
| 					Inst::Closure (0, 0), | ||||
| 					Inst::GetTabUp (1, 0, 0), | ||||
| 					Inst::Move (2, 0), | ||||
| 					Inst::LoadFalse (3), | ||||
| 					Inst::Call (2, 2, 0), | ||||
| 					Inst::Call (1, 0, 1), | ||||
| 					Inst::GetTabUp (1, 0, 0), | ||||
| 					Inst::Move (2, 0), | ||||
| 					Inst::LoadTrue (3), | ||||
| 					Inst::Call (2, 2, 0), | ||||
| 					Inst::Call (1, 0, 1), | ||||
| 					Inst::Move (1, 0), | ||||
| 					Inst::GetTabUp (2, 0, 1), | ||||
| 					Inst::GetI (2, 2, 1), | ||||
| 					Inst::Not (2, 2), | ||||
| 					Inst::Not (2, 2), | ||||
| 					Inst::Call (1, 2, 2), | ||||
| 					Inst::GetTabUp (2, 0, 0), | ||||
| 					Inst::Move (3, 1), | ||||
| 					Inst::Call (2, 2, 1), | ||||
| 					Inst::Return (1, 2, 1), | ||||
| 					Inst::Return (2, 1, 1), | ||||
| 				], | ||||
| 				constants: vec! [ | ||||
| 					"print".into (), | ||||
| 					"arg".into (), | ||||
| 				], | ||||
| 			}, | ||||
| 			Block { | ||||
| 				instructions: vec! [ | ||||
| 					Inst::Test (0, 0), | ||||
| 					Inst::Jmp (3), | ||||
| 					Inst::LoadI (1, 99), | ||||
| 					Inst::Return1 (1), | ||||
| 					Inst::Jmp (2), | ||||
| 					Inst::LoadI (1, 98), | ||||
| 					Inst::Return1 (1), | ||||
| 					Inst::Return0, | ||||
| 				], | ||||
| 				constants: vec! [], | ||||
| 			}, | ||||
| 		], | ||||
| 	}; | ||||
| 	
 | ||||
| 	let mut vm = State::default (); | ||||
| 	if std::env::var("LUA_DEBUG").is_ok() { | ||||
| 		vm.debug_print = true; | ||||
| 	} | ||||
| 	
 | ||||
| 	let upvalues = State::upvalues_from_args (std::env::args ()); | ||||
| 	println! ("Returned: {:?}", vm.execute_chunk (&chunk, &upvalues)); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										185
									
								
								src/state.rs
								
								
								
								
							
							
						
						
									
										185
									
								
								src/state.rs
								
								
								
								
							|  | @ -19,28 +19,40 @@ pub enum Instruction { | |||
| 	// Jump
 | ||||
| 	Jmp (i32), | ||||
| 	
 | ||||
| 	LoadFalse (u8), | ||||
| 	
 | ||||
| 	// Load Integer?
 | ||||
| 	LoadI (u8, i32), | ||||
| 	
 | ||||
| 	// Load Constant
 | ||||
| 	LoadK (u8, i32), | ||||
| 	
 | ||||
| 	LoadTrue (u8), | ||||
| 	
 | ||||
| 	// MetaMethod, Binary
 | ||||
| 	MmBin (u8, u8, u8), | ||||
| 	
 | ||||
| 	Move (u8, u8), | ||||
| 	
 | ||||
| 	Not (u8, u8), | ||||
| 	
 | ||||
| 	// (A, B, _C) Return B - 1 registers starting with  A
 | ||||
| 	Return (u8, u8, u8), | ||||
| 	
 | ||||
| 	Return0, | ||||
| 	
 | ||||
| 	// Return just one register
 | ||||
| 	Return1 (u8), | ||||
| 	
 | ||||
| 	Test (u8, i32), | ||||
| 	
 | ||||
| 	VarArgPrep (i32), | ||||
| } | ||||
| 
 | ||||
| #[derive (Clone, Debug, PartialEq)] | ||||
| pub enum Value { | ||||
| 	Nil, | ||||
| 	False, | ||||
| 	True, | ||||
| 	Boolean (bool), | ||||
| 	Float (f64), | ||||
| 	String (String), | ||||
| 	
 | ||||
|  | @ -48,6 +60,7 @@ pub enum Value { | |||
| 	// tables and function pointers yet
 | ||||
| 	
 | ||||
| 	BogusArg (Vec <String>), | ||||
| 	BogusClosure (usize), | ||||
| 	BogusEnv (BTreeMap <String, Value>), | ||||
| 	BogusPrint, | ||||
| } | ||||
|  | @ -89,25 +102,50 @@ impl Value { | |||
| 			_ => None, | ||||
| 		} | ||||
| 	} | ||||
| 	
 | ||||
| 	fn is_truthy (&self) -> bool { | ||||
| 		// And this is something Lua does better than JS and Python.
 | ||||
| 		
 | ||||
| 		match self { | ||||
| 			Value::Nil => false, | ||||
| 			Value::Boolean (false) => false, | ||||
| 			_ => true, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub struct Chunk { | ||||
| pub struct Block { | ||||
| 	pub instructions: Vec <Instruction>, | ||||
| 	pub constants: Vec <Value>, | ||||
| } | ||||
| 
 | ||||
| pub struct Chunk { | ||||
| 	pub blocks: Vec <Block>, | ||||
| } | ||||
| 
 | ||||
| #[derive (Debug)] | ||||
| struct StackFrame { | ||||
| 	// i32 makes it a little easier to implement jumps
 | ||||
| 	// Starts at 0 right after OP_CALL
 | ||||
| 	
 | ||||
| 	program_counter: i32, | ||||
| 	
 | ||||
| 	// Starts from 0 for main and 1 for the first closure
 | ||||
| 	block_idx: usize, | ||||
| 	
 | ||||
| 	register_offset: usize, | ||||
| } | ||||
| 
 | ||||
| pub struct State { | ||||
| 	registers: Vec <Value>, | ||||
| 	
 | ||||
| 	// i32 makes it a little easier to implement jumps
 | ||||
| 	program_counter: i32, | ||||
| 	pub debug_print: bool, | ||||
| } | ||||
| 
 | ||||
| impl Default for State { | ||||
| 	fn default () -> Self { | ||||
| 		Self { | ||||
| 			registers: vec! [Value::Nil; 256], | ||||
| 			program_counter: 0, | ||||
| 			debug_print: false, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -131,11 +169,32 @@ impl State { | |||
| 	-> Vec <Value> { | ||||
| 		let max_iters = 2000; | ||||
| 		
 | ||||
| 		let mut stack = vec! [ | ||||
| 			StackFrame { | ||||
| 				program_counter: 0, | ||||
| 				block_idx: 0, | ||||
| 				register_offset: 0, | ||||
| 			}, | ||||
| 		]; | ||||
| 		
 | ||||
| 		for _ in 0..max_iters { | ||||
| 			let instruction = chunk.instructions.get (usize::try_from (self.program_counter).unwrap ()).unwrap (); | ||||
| 			let stack_idx = stack.len () - 1; | ||||
| 			let frame = stack.get_mut (stack_idx).unwrap (); | ||||
| 			let block = chunk.blocks.get (frame.block_idx).unwrap (); | ||||
| 			
 | ||||
| 			let r = &mut self.registers; | ||||
| 			let k = &chunk.constants; | ||||
| 			let mut next_pc = frame.program_counter; | ||||
| 			
 | ||||
| 			let pc = usize::try_from (frame.program_counter).expect ("program_counter is not a valid usize"); | ||||
| 			let instruction = match block.instructions.get (pc) { | ||||
| 				Some (x) => x, | ||||
| 				None => { | ||||
| 					dbg! (&stack); | ||||
| 					panic! ("program_counter went out of bounds"); | ||||
| 				} | ||||
| 			}; | ||||
| 			
 | ||||
| 			let r = &mut self.registers [frame.register_offset..]; | ||||
| 			let k = &block.constants; | ||||
| 			
 | ||||
| 			match instruction { | ||||
| 				Instruction::Add (a, b, c) => { | ||||
|  | @ -160,12 +219,47 @@ impl State { | |||
| 					// TODO: Only implement printing values for now
 | ||||
| 					
 | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let v_a = r.get (a).unwrap (); | ||||
| 					
 | ||||
| 					assert_eq! (r.get (a).unwrap (), &Value::BogusPrint); | ||||
| 					assert_eq! (*b, 2); | ||||
| 					assert_eq! (*c, 1); | ||||
| 					match v_a { | ||||
| 						Value::BogusClosure (idx) => { | ||||
| 							let block_idx = frame.block_idx; | ||||
| 							let target_block = idx + 1; | ||||
| 							
 | ||||
| 							let current_frame = &stack [stack.len () - 1]; | ||||
| 							
 | ||||
| 							stack.push (StackFrame { | ||||
| 								program_counter: 0, | ||||
| 								block_idx: target_block, | ||||
| 								register_offset: current_frame.register_offset + a + 1, | ||||
| 							}); | ||||
| 							
 | ||||
| 							if self.debug_print { | ||||
| 								println! ("Inst {block_idx}:{pc} calls {target_block}:0"); | ||||
| 								let stack_depth = stack.len (); | ||||
| 								println! ("stack_depth: {stack_depth}"); | ||||
| 							} | ||||
| 							
 | ||||
| 							// Skip the PC increment at the bottom of the loop
 | ||||
| 							continue; | ||||
| 						}, | ||||
| 						Value::BogusPrint => { | ||||
| 							// In real Lua, print is a function inside
 | ||||
| 							// the runtime. Here it's bogus.
 | ||||
| 							
 | ||||
| 							// assert_eq! (*b, 2);
 | ||||
| 							assert_eq! (*c, 1); | ||||
| 							
 | ||||
| 							println! ("{:?}", r.get (a + 1).unwrap ()); | ||||
| 						}, | ||||
| 						_ => panic! ("Cannot call value {a:?}. backtrace: {stack:?}"), | ||||
| 					} | ||||
| 				}, | ||||
| 				Instruction::Closure (a, b) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					println! ("{:?}", r.get (a + 1).unwrap ()); | ||||
| 					r [a] = Value::BogusClosure (b); | ||||
| 				}, | ||||
| 				Instruction::EqK (a, b, c_k) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
|  | @ -174,8 +268,8 @@ impl State { | |||
| 					let equal = r [a] == k [b]; | ||||
| 					
 | ||||
| 					match (equal, c_k) { | ||||
| 						(true, 0) => self.program_counter += 1, | ||||
| 						(false, 1) => self.program_counter += 1, | ||||
| 						(true, 0) => next_pc += 1, | ||||
| 						(false, 1) => next_pc += 1, | ||||
| 						_ => (), | ||||
| 					} | ||||
| 				}, | ||||
|  | @ -212,7 +306,11 @@ impl State { | |||
| 					
 | ||||
| 					r [a] = value; | ||||
| 				}, | ||||
| 				Instruction::Jmp (s_j) => self.program_counter += s_j, | ||||
| 				Instruction::Jmp (s_j) => next_pc += s_j, | ||||
| 				Instruction::LoadFalse (a) => { | ||||
| 					let a  = usize::try_from  (*a).unwrap (); | ||||
| 					r [a] = Value::Boolean (false); | ||||
| 				}, | ||||
| 				Instruction::LoadI (a, sbx) => { | ||||
| 					let a  = usize::try_from  (*a).unwrap (); | ||||
| 					
 | ||||
|  | @ -224,6 +322,10 @@ impl State { | |||
| 					
 | ||||
| 					r [a] = k [bx].clone (); | ||||
| 				}, | ||||
| 				Instruction::LoadTrue (a) => { | ||||
| 					let a  = usize::try_from  (*a).unwrap (); | ||||
| 					r [a] = Value::Boolean (true); | ||||
| 				}, | ||||
| 				Instruction::MmBin (a, b, _c) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
|  | @ -244,19 +346,62 @@ impl State { | |||
| 					
 | ||||
| 					r [a] = r [b].clone (); | ||||
| 				}, | ||||
| 				Instruction::Not (a, b) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let b = usize::try_from (*b).unwrap (); | ||||
| 					
 | ||||
| 					r [a] = Value::Boolean (! r [b].is_truthy()); | ||||
| 				} | ||||
| 				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(); | ||||
| 					return r [a..(a + b - 1)].to_vec(); | ||||
| 				}, | ||||
| 				Instruction::Return1 (a) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					let popped_frame = stack.pop ().unwrap (); | ||||
| 					
 | ||||
| 					self.registers [popped_frame.register_offset - 1] = r [a].clone (); | ||||
| 					
 | ||||
| 					let stack_idx = stack.len () - 1; | ||||
| 					let frame = stack.get (stack_idx).unwrap (); | ||||
| 					let new_block = frame.block_idx; | ||||
| 					next_pc = frame.program_counter; | ||||
| 					
 | ||||
| 					if self.debug_print { | ||||
| 						let old_block = popped_frame.block_idx; | ||||
| 						let old_pc = popped_frame.program_counter; | ||||
| 						println! ("Inst {old_block}:{old_pc} returns to inst {new_block}:{next_pc}"); | ||||
| 						let stack_depth = stack.len (); | ||||
| 						println! ("stack_depth: {stack_depth}"); | ||||
| 					} | ||||
| 				}, | ||||
| 				Instruction::Test (a, _k) => { | ||||
| 					let a = usize::try_from (*a).unwrap (); | ||||
| 					
 | ||||
| 					let a = r.get (a).unwrap (); | ||||
| 					
 | ||||
| 					if self.debug_print { | ||||
| 						println! ("Test {a:?}"); | ||||
| 					} | ||||
| 					
 | ||||
| 					if a.is_truthy() { | ||||
| 						next_pc += 1; | ||||
| 					} | ||||
| 				}, | ||||
| 				Instruction::VarArgPrep (_) => (), | ||||
| 				x => panic! ("Unimplemented instruction {x:?}"), | ||||
| 			} | ||||
| 			
 | ||||
| 			self.program_counter += 1; | ||||
| 			next_pc += 1; | ||||
| 			{ | ||||
| 				let stack_idx = stack.len () - 1; | ||||
| 				let frame = stack.get_mut (stack_idx).unwrap (); | ||||
| 				frame.program_counter = next_pc; | ||||
| 			} | ||||
| 		} | ||||
| 		
 | ||||
| 		panic! ("Hit max iterations before chunk returned"); | ||||
| 		panic! ("Hit max iterations before block returned"); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
							
								
								
									
										87
									
								
								src/tests.rs
								
								
								
								
							
							
						
						
									
										87
									
								
								src/tests.rs
								
								
								
								
							|  | @ -1,10 +1,87 @@ | |||
| use crate::state::{ | ||||
| 	Block, | ||||
| 	Chunk, | ||||
| 	Instruction as Inst, | ||||
| 	State, | ||||
| 	Value, | ||||
| }; | ||||
| 
 | ||||
| #[test] | ||||
| fn bools () { | ||||
| 	/* | ||||
| 	local function bool_to_x (b) | ||||
| 		if b then | ||||
| 			return 99 | ||||
| 		else | ||||
| 			return 98 | ||||
| 		end | ||||
| 	end | ||||
| 
 | ||||
| 	local x = bool_to_x (not not arg [1]) | ||||
| 	print (x) | ||||
| 	return x | ||||
| 	*/ | ||||
| 	
 | ||||
| 	let chunk = Chunk { | ||||
| 		blocks: vec! [ | ||||
| 			Block { | ||||
| 				instructions: vec! [ | ||||
| 					Inst::VarArgPrep (0), | ||||
| 					Inst::Closure (0, 0), | ||||
| 					Inst::Move (1, 0), | ||||
| 					
 | ||||
| 					Inst::LoadFalse (2), | ||||
| 					Inst::Call (1, 2, 1), | ||||
| 					
 | ||||
| 					Inst::Move (1, 0), | ||||
| 					Inst::LoadTrue (2), | ||||
| 					Inst::Call (1, 2, 1), | ||||
| 					
 | ||||
| 					Inst::Move (1, 0), | ||||
| 					Inst::GetTabUp (2, 0, 0), | ||||
| 					Inst::GetI (2, 2, 1), | ||||
| 					Inst::Not (2, 2), | ||||
| 					Inst::Not (2, 2), | ||||
| 					Inst::Call (1, 2, 2), | ||||
| 					Inst::GetTabUp (2, 0, 1), | ||||
| 					Inst::Move (3, 1), | ||||
| 					Inst::Call (2, 2, 1), | ||||
| 					Inst::Return (1, 2, 1), | ||||
| 					Inst::Return (2, 1, 1), | ||||
| 				], | ||||
| 				constants: vec! [ | ||||
| 					"arg".into (), | ||||
| 					"print".into (), | ||||
| 				], | ||||
| 			}, | ||||
| 			Block { | ||||
| 				instructions: vec! [ | ||||
| 					Inst::Test (0, 0), | ||||
| 					Inst::Jmp (3), | ||||
| 					Inst::LoadI (1, 99), | ||||
| 					Inst::Return1 (1), | ||||
| 					Inst::Jmp (2), | ||||
| 					Inst::LoadI (1, 98), | ||||
| 					Inst::Return1 (1), | ||||
| 					Inst::Return0, | ||||
| 				], | ||||
| 				constants: vec! [], | ||||
| 			}, | ||||
| 		], | ||||
| 	}; | ||||
| 	
 | ||||
| 	for (arg, expected) in [ | ||||
| 		(vec! ["_exe_name"], vec! [98.into ()]), | ||||
| 		(vec! ["_exe_name", "asdf"], vec! [99.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 floats () { | ||||
| 	/* | ||||
|  | @ -17,7 +94,7 @@ fn floats () { | |||
| 	return x | ||||
| 	*/ | ||||
| 	
 | ||||
| 	let chunk = Chunk { | ||||
| 	let block = Block { | ||||
| 		instructions: vec! [ | ||||
| 			Inst::VarArgPrep (0), | ||||
| 			Inst::LoadK (0, 0), | ||||
|  | @ -35,6 +112,9 @@ fn floats () { | |||
| 			"print".into (), | ||||
| 		], | ||||
| 	}; | ||||
| 	let chunk = Chunk { | ||||
| 		blocks: vec! [block], | ||||
| 	}; | ||||
| 	
 | ||||
| 	for (arg, expected) in [ | ||||
| 		(vec! ["_exe_name"], vec! [3.5.into ()]), | ||||
|  | @ -60,7 +140,7 @@ fn is_93 () { | |||
| 	end | ||||
| 	*/ | ||||
| 	
 | ||||
| 	let chunk = Chunk { | ||||
| 	let block = Block { | ||||
| 		instructions: vec! [ | ||||
| 			Inst::VarArgPrep (0), | ||||
| 			Inst::GetTabUp (1, 0, 0), | ||||
|  | @ -88,6 +168,9 @@ fn is_93 () { | |||
| 			"it's not 93", | ||||
| 		].into_iter ().map (Value::from).collect (), | ||||
| 	}; | ||||
| 	let chunk = Chunk { | ||||
| 		blocks: vec! [block], | ||||
| 	}; | ||||
| 	
 | ||||
| 	for (arg, expected) in [ | ||||
| 		(vec! ["_exe_name"], vec! [1.into ()]), | ||||
|  |  | |||
|  | @ -0,0 +1,14 @@ | |||
| local function bool_to_x (b) | ||||
| 	if b then | ||||
| 		return 99 | ||||
| 	else | ||||
| 		return 98 | ||||
| 	end | ||||
| end | ||||
| 
 | ||||
| print (bool_to_x (false)) | ||||
| print (bool_to_x (true)) | ||||
| 
 | ||||
| local x = bool_to_x (not not arg [1]) | ||||
| print (x) | ||||
| return x | ||||
		Loading…
	
		Reference in New Issue
	
	 _
						_