From 72b2d6e656a24301804ebd769eb5ac96ac3a34d8 Mon Sep 17 00:00:00 2001 From: _ <_@_> Date: Tue, 26 Sep 2023 15:49:12 -0500 Subject: [PATCH] :star: can create an empty table --- src/loader.rs | 4 ++ src/state.rs | 25 +++++++-- src/tests.rs | 35 ++++++------- src/value.rs | 114 +++++++++++++++++++++++++++++++++++++++-- test_vectors/hello.lua | 3 ++ test_vectors/hello.txt | 35 +++++++++++++ 6 files changed, 189 insertions(+), 27 deletions(-) create mode 100644 test_vectors/hello.txt diff --git a/src/loader.rs b/src/loader.rs index f8e39c8..61835f2 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -45,6 +45,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option let a = (buf [0] >> 7) | ((buf [1] & 0x7f) << 1); let b = buf [2]; + let ax = a as u32 + ((b as u32) << 8); let c = buf [3]; let bx = (((buf [1] >> 7) as u32) << 0) | @@ -67,6 +68,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option 0x0b => Inst::GetTabUp (a, b, c), 0x0d => Inst::GetI (a, b, c), 0x0f => Inst::SetTabUp (a, b, c), + 0x13 => Inst::NewTable (a), 0x22 => Inst::Add (a, b, c), 0x24 => Inst::Mul (a, b, c), 0x2e => Inst::MmBin (a, b, c), @@ -81,6 +83,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option 0x48 => Inst::Return1 (a), 0x4f => Inst::Closure (a, bx), 0x51 => Inst::VarArgPrep (a.into ()), + 0x52 => Inst::ExtraArg (ax), _ => return None, }) } @@ -264,6 +267,7 @@ mod tests { ([0xbc, 0x00, 0x01, 0x00], Inst::EqK (1, 1, 0)), ([0xb8, 0x02, 0x00, 0x80], Inst::Jmp (6)), ([0x38, 0x02, 0x00, 0x80], Inst::Jmp (5)), + ([0x52, 0x00, 0x00, 0x00], Inst::ExtraArg (0)), ] { let actual = super::parse_inst (input).unwrap (); assert_eq!(actual, expected); diff --git a/src/state.rs b/src/state.rs index 034f511..878ec80 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,8 @@ -use std::collections::BTreeMap; +use std::collections::HashMap; use crate::value::{ BogusClosure, + Table, Value, }; @@ -15,6 +16,8 @@ pub enum Instruction { // Equals Constant? EqK (u8, u8, u8), + ExtraArg (u32), + // Get Immediate? GetI (u8, u8, u8), @@ -48,6 +51,8 @@ pub enum Instruction { Mul (u8, u8, u8), + NewTable (u8), + Not (u8, u8), // (A, B, _C, k) Return B - 1 registers starting with A @@ -129,7 +134,7 @@ impl State { { let arg: Vec <_> = args.map (|s| s.to_string ()).collect (); - let env = BTreeMap::from_iter ([ + let env = HashMap::from_iter ([ ("arg", Value::BogusArg (arg.into ())), ("print", Value::BogusPrint), ].map (|(k, v)| (k.to_string (), v))); @@ -247,6 +252,7 @@ impl State { Value::Float (x) => println! ("{:?}", x), Value::Integer (x) => println! ("{}", x), Value::String (s) => println! ("{}", s), + Value::Table (t) => println! ("table: {:?}", std::rc::Rc::as_ptr (t)), _ => unimplemented! (), }; @@ -285,6 +291,12 @@ impl State { _ => (), } }, + Instruction::ExtraArg (ax) => { + // This is used for NewTable. Maybe it's for reserving + // capacity in the array or something? + + assert_eq! (*ax, 0, "implemented only for ax == 0"); + }, Instruction::GetTabUp (a, b, c) => { let a = usize::try_from (*a).unwrap (); let b = usize::try_from (*b).unwrap (); @@ -383,6 +395,11 @@ impl State { let r = self.register_window_mut(); r [a] = r [b].clone (); }, + Instruction::Mul (_a, _b, _c) => unimplemented!(), + Instruction::NewTable (a) => { + let a = usize::try_from (*a).unwrap (); + self.register_window_mut ()[a] = Value::Table (Default::default ()); + }, Instruction::Not (a, b) => { let a = usize::try_from (*a).unwrap (); let b = usize::try_from (*b).unwrap (); @@ -444,6 +461,7 @@ impl State { return self.registers [a..(a + b - 1)].to_vec(); } }, + Instruction::Return0 => unimplemented! (), Instruction::Return1 (a) => { let a = usize::try_from (*a).unwrap (); let popped_frame = self.stack.pop ().unwrap (); @@ -467,6 +485,8 @@ impl State { let offset = popped_frame.register_offset; self.registers [offset - 1] = self.registers [offset + a].take (); }, + Instruction::SetTabUp (_a, _b, _c) => unimplemented! (), + Instruction::TailCall (_a, _b, _c, _k) => unimplemented! (), Instruction::Test (a, _k) => { let a = usize::try_from (*a).unwrap (); @@ -481,7 +501,6 @@ impl State { } }, Instruction::VarArgPrep (_) => (), - x => panic! ("Unimplemented instruction {x:?}"), } next_pc += 1; diff --git a/src/tests.rs b/src/tests.rs index b27198c..1aa6d04 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,8 +1,11 @@ -use crate::state::{ - Block, - Chunk, - Instruction as Inst, - State, +use crate::{ + state::{ + Block, + Chunk, + Instruction as Inst, + State, + }, + value::Value, }; #[test] @@ -75,6 +78,8 @@ fn bools () { (vec! ["_exe_name"], vec! [98.into ()]), (vec! ["_exe_name", "asdf"], vec! [99.into ()]), ] { + let expected: Vec = expected; + let mut vm = State::default (); let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); @@ -94,6 +99,7 @@ fn closure () { (vec! ["_exe_name"], vec! [23.0.into ()]), (vec! ["_exe_name"], vec! [23.0.into ()]), ] { + let expected: Vec = expected; let mut vm = State::default (); let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); let actual = vm.execute_chunk (&file, &upvalues); @@ -141,6 +147,7 @@ fn floats () { (vec! ["_exe_name"], vec! [3.5.into ()]), (vec! ["_exe_name", " "], vec! [3.5.into ()]), ] { + let expected: Vec = expected; 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); @@ -160,6 +167,7 @@ fn fma () { (vec! ["_exe_name"], vec! [122.into ()]), (vec! ["_exe_name"], vec! [122.into ()]), ] { + let expected: Vec = expected; let mut vm = State::default (); let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); let actual = vm.execute_chunk (&file, &upvalues); @@ -184,6 +192,7 @@ fn is_93 () { (vec! ["_exe_name", "93"], vec! [0.into ()]), (vec! ["_exe_name", "94"], vec! [1.into ()]), ] { + let expected: Vec = expected; let mut vm = State::default (); let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ())); let actual = vm.execute_chunk (&file, &upvalues); @@ -200,28 +209,16 @@ fn value_size () { // Lua's tagged union values are 12-16 bytes on a 32-bit system // with 64-bit floats // - // It is very nice if LunarWaveVM is the same or better. - // There is some exploratory things in this test, too + // It would be nice if LunarWaveVM is the same or better. + // There are some exploratory things in this test, too use std::{ mem::size_of, - rc::Rc, }; assert! (size_of::> () <= 8); assert! (size_of::> () <= 8); - enum Value { - Nil, - Boolean (bool), - Float (f64), - String (Rc ), - } - - let sz = size_of:: (); - let expected = 16; - assert! (sz <= expected, "{sz} > {expected}"); - let sz = size_of:: (); let expected = 16; assert! (sz <= expected, "{sz} > {expected}"); diff --git a/src/value.rs b/src/value.rs index db60274..18624b2 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,9 +1,13 @@ use std::{ - collections::BTreeMap, + cmp::{ + Eq, + PartialEq, + }, + collections::HashMap, rc::Rc, }; -#[derive (Clone, Debug, Hash, PartialEq)] +#[derive (Debug, Eq, PartialEq)] pub struct BogusClosure { pub idx: usize, pub upvalues: Vec , @@ -13,16 +17,21 @@ pub struct BogusClosure { pub enum Value { Nil, Boolean (bool), + + // Rust is very strict about float equality, so some of my code + // here is probably wrong in subtle ways. Float (f64), + Integer (i64), String (Rc ), + Table (Rc ), // These are all bogus, I haven't figured out how to implement // tables and function pointers yet BogusArg (Rc >), BogusClosure (Rc ), - BogusEnv (Rc >), + BogusEnv (Rc >), BogusPrint, } @@ -46,7 +55,13 @@ impl From <&str> for Value { impl From for Value { fn from (x: i32) -> Self { - Self::Integer (i64::try_from (x).unwrap ()) + Self::Integer (i64::from (x)) + } +} + +impl From for Value { + fn from (x: i64) -> Self { + Self::Integer (x) } } @@ -56,6 +71,14 @@ impl From for Value { } } +impl From
for Value { + fn from (x: Table) -> Self { + Self::Table (x.into ()) + } +} + +impl Eq for Value {} + impl std::hash::Hash for Value { fn hash (&self, state: &mut H) { // Per https://doc.rust-lang.org/std/hash/trait.Hash.html#prefix-collisions @@ -67,7 +90,11 @@ impl std::hash::Hash for Value { Self::Boolean (x) => x.hash (state), Self::Float (x) => x.to_ne_bytes ().hash (state), Self::Integer (x) => x.hash (state), - Self::String (x) => x.as_ptr ().hash (state), + + // TODO: Implement string interning so we don't hash the whole string here + Self::String (x) => x.hash (state), + Self::Table (x) => Rc::as_ptr (&x).hash (state), + Self::BogusArg (_) => panic! ("can't hash Bogus values"), Self::BogusClosure (_) => panic! ("can't hash Bogus values"), Self::BogusEnv (_) => panic! ("can't hash Bogus values"), @@ -76,6 +103,12 @@ impl std::hash::Hash for Value { } } +impl PartialEq for Value { + fn eq (&self, rhs: &i64) -> bool { + *self == Value::from (*rhs) + } +} + impl Value { pub fn as_float (&self) -> Option { match self { @@ -102,3 +135,74 @@ impl Value { x } } + +#[derive (Debug, Default, Eq, PartialEq)] +pub struct Table { + array: Vec , + hash: HashMap , +} + +impl Table { + fn get_inner (&self, key: &Value) -> Value { + self.hash.get (key).cloned ().unwrap_or_default () + } + + pub fn get > (&self, key: A) -> Value { + self.get_inner (&(key.into ())) + } + + fn insert_inner (&mut self, a: Value, b: Value) { + self.hash.insert (a, b); + } + + pub fn insert , B: Into > ( + &mut self, + a: A, + b: B, + ) { + self.insert_inner (a.into (), b.into ()) + } +} + +#[cfg (test)] +mod tests { + use std::collections::HashMap; + use super::{ + Table, + Value, + }; + + #[test] + fn smoke () { + let v_a = Value::from (5.0); + let v_b = Value::from (5); + + assert_eq! (v_a.as_float (), Some (5.0)); + assert_eq! (v_b.as_float (), Some (5.0)); + } + + #[test] + fn tables () { + let nil = Value::Nil; + + assert_ne! (Value::from (18.0), Value::from (19.0)); + + let mut t = HashMap::new (); + t.insert (Value::from ("x"), Value::from (19.0)); + assert_eq! (t.get (&Value::from ("x")), Some (&Value::from (19.0))); + + let mut t = Table::default (); + + assert_eq! (t.get ("a"), nil); + assert_eq! (t.get ("b"), nil); + + t.insert ("a", 1993); + t.insert ("b", 2007); + + assert_eq! (t.get ("a"), 1993); + assert_eq! (t.get ("b"), 2007); + + t.insert (19, 93); + assert_eq! (t.get (19), 93); + } +} diff --git a/test_vectors/hello.lua b/test_vectors/hello.lua index 8a91042..838bf06 100644 --- a/test_vectors/hello.lua +++ b/test_vectors/hello.lua @@ -4,3 +4,6 @@ print (true) print (1993) print (1993.00) print "Hello." + +local t = {} +print (t) diff --git a/test_vectors/hello.txt b/test_vectors/hello.txt new file mode 100644 index 0000000..8da9e13 --- /dev/null +++ b/test_vectors/hello.txt @@ -0,0 +1,35 @@ + +main (25 instructions at 0x55938922ecd0) +0+ params, 3 slots, 1 upvalue, 1 local, 2 constants, 0 functions + 1 [1] VARARGPREP 0 + 2 [1] GETTABUP 0 0 0 ; _ENV "print" + 3 [1] LOADNIL 1 0 ; 1 out + 4 [1] CALL 0 2 1 ; 1 in 0 out + 5 [2] GETTABUP 0 0 0 ; _ENV "print" + 6 [2] LOADFALSE 1 + 7 [2] CALL 0 2 1 ; 1 in 0 out + 8 [3] GETTABUP 0 0 0 ; _ENV "print" + 9 [3] LOADTRUE 1 + 10 [3] CALL 0 2 1 ; 1 in 0 out + 11 [4] GETTABUP 0 0 0 ; _ENV "print" + 12 [4] LOADI 1 1993 + 13 [4] CALL 0 2 1 ; 1 in 0 out + 14 [5] GETTABUP 0 0 0 ; _ENV "print" + 15 [5] LOADF 1 1993 + 16 [5] CALL 0 2 1 ; 1 in 0 out + 17 [6] GETTABUP 0 0 0 ; _ENV "print" + 18 [6] LOADK 1 1 ; "Hello." + 19 [6] CALL 0 2 1 ; 1 in 0 out + 20 [8] NEWTABLE 0 0 0 ; 0 + 21 [8] EXTRAARG 0 + 22 [9] GETTABUP 1 0 0 ; _ENV "print" + 23 [9] MOVE 2 0 + 24 [9] CALL 1 2 1 ; 1 in 0 out + 25 [9] RETURN 1 1 1 ; 0 out +constants (2) for 0x55938922ecd0: + 0 S "print" + 1 S "Hello." +locals (1) for 0x55938922ecd0: + 0 t 22 26 +upvalues (1) for 0x55938922ecd0: + 0 _ENV 1 0