From 81efebdda24503c07ddded08e0615aa6f94b57d6 Mon Sep 17 00:00:00 2001 From: _ <_@_> Date: Sun, 24 Sep 2023 19:47:17 -0500 Subject: [PATCH] :star: allow calling closures --- src/main.rs | 88 ++++++++++++++++---- src/state.rs | 185 ++++++++++++++++++++++++++++++++++++----- src/tests.rs | 87 ++++++++++++++++++- test_vectors/bools.lua | 14 ++++ 4 files changed, 334 insertions(+), 40 deletions(-) create mode 100644 test_vectors/bools.lua diff --git a/src/main.rs b/src/main.rs index d125781..f75dc63 100644 --- a/src/main.rs +++ b/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)); } diff --git a/src/state.rs b/src/state.rs index 8abf8fa..0313bdb 100644 --- a/src/state.rs +++ b/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 ), + BogusClosure (usize), BogusEnv (BTreeMap ), 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 , pub constants: Vec , } +pub struct Chunk { + pub blocks: Vec , +} + +#[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 , - - // 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 { 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"); } } diff --git a/src/tests.rs b/src/tests.rs index 4b32591..25a2303 100644 --- a/src/tests.rs +++ b/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 ()]), diff --git a/test_vectors/bools.lua b/test_vectors/bools.lua new file mode 100644 index 0000000..1509e74 --- /dev/null +++ b/test_vectors/bools.lua @@ -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