diff --git a/src/main.rs b/src/main.rs index 033655b..eb27c9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + enum Instruction { VarArgPrep (i32), GetTabUp (u8, u8, u8), @@ -21,7 +23,8 @@ enum Value { // These are all bogus, I haven't figured out how to implement // tables and function pointers yet - BogusArg, + BogusArg (Vec ), + BogusEnv (BTreeMap ), BogusPrint, } @@ -48,6 +51,115 @@ struct Chunk { constants: Vec , } +struct VirtualMachine { + registers: Vec , + + // 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 execute_chunk (&mut self, chunk: &Chunk, upvalues: &Vec ) { + 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::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 constants for now + + let a = usize::try_from (*a).unwrap (); + + assert_eq! (*b, 2); + assert_eq! (*c, 1); + + println! ("{:?}", r [a + 1]); + }, + 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).unwrap ().as_str().into (), + _ => unimplemented!(), + }; + + r [a] = value; + }, + Instruction::Jmp (sJ) => self.program_counter += sJ, + Instruction::LoadK (a, bx) => { + let a = usize::try_from (*a).unwrap (); + let bx = usize::try_from (*bx).unwrap (); + + r [a] = k [bx].clone (); + }, + Instruction::Return (_a, _b, _c) => { + break; + }, + Instruction::VarArgPrep (_) => (), + _ => (), + } + + self.program_counter += 1; + } + } +} + fn main() { let arg: Vec <_> = std::env::args ().collect (); @@ -76,96 +188,15 @@ fn main() { ].into_iter ().map (|s| Value::from (s)).collect (), }; - let mut registers = vec! [Value::default (); 256]; + let env = BTreeMap::from_iter ([ + ("arg", Value::BogusArg (arg.clone ())), + ("print", Value::BogusPrint), + ].map (|(k, v)| (k.to_string (), v)).into_iter ()); + let upvalues = vec! [ + Value::BogusEnv (env), + ]; - let mut program_counter = 0i32; - let max_iters = 2000; - - for _ in 0..max_iters { - let instruction = chunk.instructions.get (usize::try_from (program_counter).unwrap ()).unwrap (); - - let r = &mut registers; - let k = &chunk.constants; - - match instruction { - 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 constants for now - - let a = usize::try_from (*a).unwrap (); - - assert_eq! (*b, 2); - assert_eq! (*c, 1); - - println! ("{:?}", r [a + 1]); - }, - 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) => program_counter += 1, - (false, 1) => 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 (); - - // Only supported upvalue is `_ENV` - assert_eq! (b, 0); - - let key = k.get (c).unwrap (); - let value = match key { - Value::String (s) => match s.as_str() { - "arg" => Value::BogusArg, - "print" => Value::BogusPrint, - _ => panic! ("key not in _ENV upvalue"), - }, - _ => unimplemented!(), - }; - - r [a] = value; - }, - 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.get (c).unwrap ().as_str().into (), - _ => unimplemented!(), - }; - - r [a] = value; - }, - Instruction::Jmp (sJ) => program_counter += sJ, - Instruction::LoadK (a, bx) => { - let a = usize::try_from (*a).unwrap (); - let bx = usize::try_from (*bx).unwrap (); - - r [a] = k [bx].clone (); - }, - Instruction::Return (_a, _b, _c) => { - break; - }, - Instruction::VarArgPrep (_) => (), - _ => (), - } - - program_counter += 1; - } + let mut vm = VirtualMachine::default (); + vm.execute_chunk (&chunk, &upvalues); }