✅ test: fix up some bugs to support an embedding example
							parent
							
								
									ffb1950f80
								
							
						
					
					
						commit
						700b273a11
					
				|  | @ -1,4 +1,5 @@ | |||
| [workspace] | ||||
| resolver = "2" | ||||
| members = [ | ||||
| 	"lunar_wave_cli", | ||||
| 	"lunar_wave_vm", | ||||
|  |  | |||
|  | @ -1,5 +0,0 @@ | |||
| fn main () -> Result <(), ()> { | ||||
| 	println! ("Embedding"); | ||||
| 	
 | ||||
| 	Ok (()) | ||||
| } | ||||
|  | @ -3,7 +3,7 @@ name = "lunar_wave_cli" | |||
| description = "A Lua CLI implementation" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| author = "ReactorScram" | ||||
| authors = ["ReactorScram"] | ||||
| 
 | ||||
| [dependencies] | ||||
| lunar_wave_vm = { path = "../lunar_wave_vm" } | ||||
|  |  | |||
|  | @ -3,4 +3,4 @@ name = "lunar_wave_vm" | |||
| description = "A Lua virtual machine implementation" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| author = "ReactorScram" | ||||
| authors = ["ReactorScram"] | ||||
|  |  | |||
|  | @ -4,11 +4,13 @@ mod state; | |||
| mod value; | ||||
| 
 | ||||
| pub use loader::compile_bytecode_from_file as compile_bytecode_from_file; | ||||
| pub use loader::compile_bytecode_from_stdin as compile_bytecode_from_stdin; | ||||
| pub use loader::parse_chunk as parse_chunk; | ||||
| pub use state::Breakpoint as Breakpoint; | ||||
| pub use state::State as State; | ||||
| pub use state::StepError as StepError; | ||||
| pub use state::StepOutput as StepOutput; | ||||
| pub use value::Value as Value; | ||||
| 
 | ||||
| #[cfg (test)] | ||||
| mod tests; | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ pub fn compile_bytecode_from_file (path: &str) -> Vec <u8> { | |||
| /// 
 | ||||
| /// `source` is a Vec because we move it to a worker thread
 | ||||
| 
 | ||||
| pub (crate) fn compile_bytecode_from_stdin (source: Vec <u8>) -> Vec <u8> { | ||||
| pub fn compile_bytecode_from_stdin (source: Vec <u8>) -> Vec <u8> { | ||||
| 	use std::{ | ||||
| 		io::Write, | ||||
| 		process::{ | ||||
|  |  | |||
|  | @ -206,12 +206,12 @@ impl <'a> State <'a> { | |||
| 	
 | ||||
| 	/// Short form to get access to a register within our window
 | ||||
| 	
 | ||||
| 	fn reg (&self, i: u8) -> &Value { | ||||
| 	pub fn reg (&self, i: u8) -> &Value { | ||||
| 		let frame = self.stack.last ().unwrap (); | ||||
| 		&self.registers [frame.register_offset + i as usize] | ||||
| 	} | ||||
| 	
 | ||||
| 	fn reg_mut (&mut self, i: u8) -> &mut Value { | ||||
| 	pub fn reg_mut (&mut self, i: u8) -> &mut Value { | ||||
| 		let frame = self.stack.last ().unwrap (); | ||||
| 		&mut self.registers [frame.register_offset + i as usize] | ||||
| 	} | ||||
|  | @ -330,22 +330,24 @@ impl <'a> State <'a> { | |||
| 					Value::RsFunc (x) => { | ||||
| 						let current_frame = self.stack.last ().unwrap (); | ||||
| 						let new_offset = current_frame.register_offset + usize::from (*a) + 1; | ||||
| 						
 | ||||
| 						// Trash the stack frame so it doesn't point to a
 | ||||
| 						// valid Lua function
 | ||||
| 						self.stack.push (StackFrame { | ||||
| 							program_counter: 65535, // Bogus for native functions
 | ||||
| 							block_idx: 65535, // Bogus
 | ||||
| 							register_offset: new_offset, | ||||
| 						}); | ||||
| 						
 | ||||
| 						// No clue what the '1' is doing here
 | ||||
| 						let b = if b == 0 { | ||||
| 						let num_args = if b == 0 { | ||||
| 							self.top - *a as usize | ||||
| 						} | ||||
| 						else { | ||||
| 							b | ||||
| 							b - 1 | ||||
| 						}; | ||||
| 						
 | ||||
| 						// Call
 | ||||
| 						let num_results = x (self, b - 1); | ||||
| 						let num_results = x (self, num_args); | ||||
| 						
 | ||||
| 						let popped_frame = self.stack.pop ().unwrap (); | ||||
| 						let offset = popped_frame.register_offset - 1; | ||||
|  | @ -678,7 +680,7 @@ impl <'a> State <'a> { | |||
| 					self.top = popped_frame.register_offset - 1 + b - 1; | ||||
| 				} | ||||
| 				else { | ||||
| 					// Return from the entire program
 | ||||
| 					// Return from the entire chunk
 | ||||
| 					return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + b - 1)].to_vec()))); | ||||
| 				} | ||||
| 			}, | ||||
|  | @ -778,26 +780,30 @@ impl <'a> State <'a> { | |||
| 				
 | ||||
| 				*self.reg_mut (*a) = x; | ||||
| 			}, | ||||
| 			Instruction::TailCall (a, b, _c, k) => { | ||||
| 			Instruction::TailCall (a, b, c, k) => { | ||||
| 				let a = usize::from (*a); | ||||
| 				assert! (!k, "closing over values in tail calls not implemented"); | ||||
| 				
 | ||||
| 				// Shift closure and inputs into place
 | ||||
| 				let a = usize::from (*a); | ||||
| 				let b = usize::from (*b); | ||||
| 				let offset = frame.register_offset - 1; | ||||
| 				for i in (offset)..(offset + b) { | ||||
| 					self.registers [i] = self.registers [i + a + 1].take (); | ||||
| 				} | ||||
| 				let offset = frame.register_offset; | ||||
| 				let value = self.registers [offset + a].take (); | ||||
| 				match value { | ||||
| 					Value::BogusClosure (closure) => { | ||||
| 						let closure = closure.borrow (); | ||||
| 						
 | ||||
| 				let value = &self.registers [offset]; | ||||
| 				let closure = if let Some (x) = value.as_closure () { | ||||
| 					x | ||||
| 						// Shift inputs into place
 | ||||
| 						
 | ||||
| 						let b = usize::from (*b); | ||||
| 						
 | ||||
| 						let num_args = if b == 0 { | ||||
| 							self.top - a | ||||
| 						} | ||||
| 						else { | ||||
| 					dbg! (self); | ||||
| 					panic! ("OP_TAILCALL only implemented for closures"); | ||||
| 							b - 1 | ||||
| 						}; | ||||
| 				let closure = closure.borrow (); | ||||
| 						
 | ||||
| 						for i in (offset)..(offset + num_args) { | ||||
| 							self.registers [i] = self.registers [i + a + 1].take (); | ||||
| 						} | ||||
| 						
 | ||||
| 						// Jump into the other function
 | ||||
| 						
 | ||||
|  | @ -808,6 +814,50 @@ impl <'a> State <'a> { | |||
| 						// Skip the PC increment
 | ||||
| 						return Ok (None); | ||||
| 					}, | ||||
| 					Value::RsFunc (x) => { | ||||
| 						// Shift inputs into place
 | ||||
| 						let b = usize::from (*b); | ||||
| 						for i in (offset)..(offset + b) { | ||||
| 							self.registers [i] = self.registers [i + a + 1].take (); | ||||
| 						} | ||||
| 						
 | ||||
| 						let frame = self.stack.last_mut ().unwrap (); | ||||
| 						// Trash the stack frame so it doesn't point
 | ||||
| 						// to any valid Lua function
 | ||||
| 						*frame = StackFrame { | ||||
| 							block_idx: 65535, | ||||
| 							program_counter: 65535, | ||||
| 							register_offset: offset, | ||||
| 						}; | ||||
| 						
 | ||||
| 						let num_args = if b == 0 { | ||||
| 							self.top - a | ||||
| 						} | ||||
| 						else { | ||||
| 							b - 1 | ||||
| 						}; | ||||
| 						
 | ||||
| 						// Call
 | ||||
| 						let num_results = x (self, num_args); | ||||
| 						let popped_frame = self.stack.pop ().unwrap (); | ||||
| 						
 | ||||
| 						if self.stack.is_empty () { | ||||
| 							// The whole chunk is exiting
 | ||||
| 							return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + num_results)].to_vec()))); | ||||
| 						} | ||||
| 						else { | ||||
| 							// Set up top for the next call
 | ||||
| 							if *c == 0 { | ||||
| 								self.top = popped_frame.register_offset - 1 + num_results; | ||||
| 							} | ||||
| 						} | ||||
| 					}, | ||||
| 					_ => { | ||||
| 						dbg! (&self.stack); | ||||
| 						panic! ("OP_TAILCALL argument must be a function"); | ||||
| 					}, | ||||
| 				} | ||||
| 			}, | ||||
| 			Instruction::Test (a, k) => { | ||||
| 				if self.reg (*a).is_truthy() != *k { | ||||
| 					next_pc += 1; | ||||
|  |  | |||
|  | @ -359,6 +359,25 @@ fn tables_2 () { | |||
| 	run_source (&[], src); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn tailcall () { | ||||
| 	use crate::instruction::Instruction; | ||||
| 	
 | ||||
| 	let src = r#" | ||||
| 	return tonumber ("5") | ||||
| 	"#;
 | ||||
| 	
 | ||||
| 	let bc = loader::compile_bytecode_from_stdin (src.as_bytes ().to_vec ()); | ||||
| 	let chunk = loader::parse_chunk_from_bytes (&bc).unwrap (); | ||||
| 	
 | ||||
| 	assert_eq! (chunk.blocks [0].instructions [3], Instruction::TailCall (0, 2, 1, false)); | ||||
| 	
 | ||||
| 	let actual = run_chunk (&[], &chunk); | ||||
| 	let expected = vec! [Value::from (5)]; | ||||
| 	
 | ||||
| 	assert_eq! (actual, expected); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn value_size () { | ||||
| 	// Per https://www.lua.org/doc/jucs05.pdf,
 | ||||
|  |  | |||
|  | @ -0,0 +1,42 @@ | |||
| use lunar_wave_vm as lwvm; | ||||
| 
 | ||||
| #[test] | ||||
| fn embedding () { | ||||
| 	use lwvm::{ | ||||
| 		State, | ||||
| 		Value, | ||||
| 	}; | ||||
| 	
 | ||||
| 	let src = br#" | ||||
| 	return host_lib.add (14, 12) | ||||
| 	"#;
 | ||||
| 	
 | ||||
| 	fn host_add (l: &mut State, num_args: usize) -> usize { | ||||
| 		assert_eq! (num_args, 2); | ||||
| 		let a = l.reg (0).as_int ().unwrap (); | ||||
| 		let b = l.reg (1).as_int ().unwrap (); | ||||
| 		*l.reg_mut (0) = Value::from (a + b + 1993); | ||||
| 		1 | ||||
| 	} | ||||
| 	
 | ||||
| 	let bytecode = lwvm::compile_bytecode_from_stdin (src.to_vec ()); | ||||
| 	let mut rdr = std::io::Cursor::new (bytecode); | ||||
| 	let chunk = lwvm::parse_chunk (&mut rdr).unwrap (); | ||||
| 	
 | ||||
| 	let host_lib = [ | ||||
| 		("add", Value::RsFunc (host_add)), | ||||
| 	].into_iter ().map (|(k, v)| (k.to_string (), v)); | ||||
| 	
 | ||||
| 	let env = [ | ||||
| 		("host_lib", Value::from_iter (host_lib.into_iter ())), | ||||
| 	].into_iter ().map (|(k, v)| (k.to_string (), v)); | ||||
| 	
 | ||||
| 	let upvalues = vec! [ | ||||
| 		Value::from_iter (env.into_iter ()), | ||||
| 	]; | ||||
| 	
 | ||||
| 	let mut vm = State::new (&chunk, &upvalues); | ||||
| 	let output = vm.execute_chunk (&vec! []).unwrap (); | ||||
| 	
 | ||||
| 	assert_eq! (output, vec! [Value::from (2019)]); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	 _
						_