From 9d29aeb43b4e202abd4a79a9a74fe053ab2738b0 Mon Sep 17 00:00:00 2001 From: _ <_@_> Date: Tue, 26 Sep 2023 20:31:00 -0500 Subject: [PATCH] :star: tables are working okay Still missing a lot of stuff. And I'm pretty sure the Rc handling code is totally wrong. And they don't have the Lua 5.0 optimization of an internal array, but that's probably easy to put in. --- src/instruction.rs | 10 +++++ src/loader.rs | 33 +++++++++++++-- src/state.rs | 96 ++++++++++++++++++++++++++++++++++++++++-- src/value.rs | 7 +++ test_vectors/hello.lua | 13 ++++++ 5 files changed, 151 insertions(+), 8 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index e723f55..ed5b22d 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -10,9 +10,13 @@ pub enum Instruction { ExtraArg (u32), + GetField (u8, u8, u8), + // Get Immediate? GetI (u8, u8, u8), + GetTable (u8, u8, u8), + // Get Table, Upvalue GetTabUp (u8, u8, u8), @@ -55,6 +59,12 @@ pub enum Instruction { // Return just one register Return1 (u8), + SetField (u8, u8, u8, bool), + + SetI (u8, u8, u8, bool), + + SetList (u8, u8, u8, bool), + SetTabUp (u8, u8, u8), TailCall (u8, u8, u8, bool), diff --git a/src/loader.rs b/src/loader.rs index c112129..d3c09aa 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -69,8 +69,12 @@ pub fn parse_inst (buf: [u8; 4]) -> Option 0x08 => Inst::LoadNil (a), 0x09 => Inst::GetUpVal (a, b), 0x0b => Inst::GetTabUp (a, b, c), + 0x0c => Inst::GetTable (a, b, c), 0x0d => Inst::GetI (a, b, c), + 0x0e => Inst::GetField (a, b, c), 0x0f => Inst::SetTabUp (a, b, c), + 0x11 => Inst::SetI (a, b, c, k), + 0x12 => Inst::SetField (a, b, c, k), 0x13 => Inst::NewTable (a), 0x22 => Inst::Add (a, b, c), 0x24 => Inst::Mul (a, b, c), @@ -84,6 +88,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option 0x46 => Inst::Return (a, b, c, k), 0x47 => Inst::Return0, 0x48 => Inst::Return1 (a), + 0x4e => Inst::SetList (a, b, c, k), 0x4f => Inst::Closure (a, bx), 0x51 => Inst::VarArgPrep (a.into ()), 0x52 => Inst::ExtraArg (ax), @@ -118,11 +123,23 @@ fn parse_byte (rdr: &mut R) -> Option Some (buf [0]) } +fn parse_float (rdr: &mut R) -> Option { + let mut buf = [0u8; 8]; + rdr.read_exact (&mut buf).ok ()?; + Some (f64::from_ne_bytes(buf)) +} + fn parse_int (rdr: &mut R) -> Option { Some ((parse_byte (rdr)? - 0x80) as u32) } +fn parse_i64 (rdr: &mut R) -> Option { + let mut buf = [0u8; 8]; + rdr.read_exact (&mut buf).ok ()?; + Some (i64::from_ne_bytes(buf)) +} + // I'm doing this recursively so it's easy to match with the PUC Lua // code, but I don't like recursion in general, and I don't know // why PUC wrote it that way. @@ -132,6 +149,8 @@ pub fn parse_block (rdr: &mut R, blocks: &mut Vec ) { // Ignore things I haven't implemented yet + use crate::value::Value; + parse_string (rdr)?; // function name parse_int (rdr)?; // start line in source code parse_int (rdr)?; // last line in source code @@ -152,12 +171,18 @@ pub fn parse_block (rdr: &mut R, blocks: &mut Vec ) let mut constants = Vec::with_capacity (constant_count as usize); - for _ in 0..constant_count { + for i in 0..constant_count { + // LUA_TNIL and friends from `lua.h` in PUC Lua let const_type = parse_byte (rdr)?; - assert_eq! (const_type, 0x04); - let s = parse_string (rdr)?; - constants.push (s.into ()); + let val = match const_type { + 3 => Value::from (parse_i64 (rdr)?), + 4 => parse_string (rdr)?.into (), + 19 => Value::from (parse_float (rdr)?), + x => panic! ("Constant {} has type {}", i, x), + }; + + constants.push (val); } let upvalue_count = parse_int (rdr)? as usize; diff --git a/src/state.rs b/src/state.rs index 3db4ac5..3600467 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,7 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + rc::Rc, +}; use crate::{ instruction::Instruction, @@ -212,6 +215,31 @@ impl State { assert_eq! (*ax, 0, "implemented only for ax == 0"); }, + Instruction::GetField (a, b, c) => { + let t = match self.reg (*b) { + Value::Table (t) => t, + _ => panic! ("R[B] must be a table"), + }; + + let key = match &k [usize::from (*c)] { + Value::String (s) => s, + _ => panic! ("K[C] must be a string"), + }; + + *self.reg_mut (*a) = t.get (Value::String (Rc::clone (key))); + }, + Instruction::GetTable (a, b, c) => { + let t = match self.reg (*b) { + Value::Table (t) => t, + _ => panic! ("R[B] must be a table"), + }; + + let key = self.reg (*c); + + let val = t.get (key.clone ()); + + *self.reg_mut (*a) = val; + }, Instruction::GetTabUp (a, b, c) => { let b = usize::try_from (*b).unwrap (); let c = usize::try_from (*c).unwrap (); @@ -230,12 +258,16 @@ impl State { *self.reg_mut (*a) = value.clone (); }, Instruction::GetI (a, b, c) => { - let c = usize::try_from (*c).unwrap (); + let key = usize::try_from (*c).unwrap (); let table = self.reg (*b); let value = match table { - Value::BogusArg (arg) => arg.get (c).map (|x| x.as_str().into ()).unwrap_or_default(), - _ => unimplemented!(), + Value::BogusArg (arg) => arg.get (key).map (|x| x.as_str().into ()).unwrap_or_default(), + Value::Table (t) => { + let key = Value::from (i64::try_from (key).unwrap ()); + t.get (key) + }, + _ => unimplemented! (), }; *self.reg_mut (*a) = value; @@ -371,6 +403,62 @@ impl State { let offset = popped_frame.register_offset; self.registers [offset - 1] = self.registers [offset + a].take (); }, + Instruction::SetField (a, b, c, k_flag) => { + let value = if *k_flag { + &k [usize::from (*c)] + } + else { + self.reg (*c) + } + .clone (); + + let b = usize::try_from (*b).unwrap (); + + let key = match k.get (b).unwrap () { + Value::String (s) => s.as_ref (), + _ => panic! ("GetTabUp only supports string keys"), + }; + + let dst = self.reg_mut (*a).as_table_mut () + .expect ("SetField only works on tables"); + + Rc::get_mut (dst).expect ("shrug").insert (Value::from (key.as_str ()), value); + }, + Instruction::SetI (a, b, c, k_flag) => { + let value = if *k_flag { + &k [usize::from (*c)] + } + else { + self.reg (*c) + } + .clone (); + + let dst = self.reg_mut (*a).as_table_mut () + .expect ("SetI only works on tables"); + + Rc::get_mut (dst).expect ("shrug").insert (i64::from (*b), value); + }, + Instruction::SetList (a, b, c, k) => { + if *b == 0 { + panic! ("SetList with b == 0 not implemented"); + } + if *k { + panic! ("SetList with k = true not implemented"); + } + + // Temporarily move the table out + + let mut dst = Rc::into_inner (std::mem::replace (self.reg_mut (*a).as_table_mut ().expect ("SetList only works on tables"), Default::default ())).unwrap (); + + for i in 1..=*b { + let src = self.reg (*a + i); + dst.insert (Value::from (i64::from (*c + i)), src.clone ()); + } + + // Put the table back and pretend it's all okay + + *self.reg_mut (*a) = Value::Table (dst.into ()); + }, Instruction::SetTabUp (_a, _b, _c) => unimplemented! (), Instruction::TailCall (_a, _b, _c, _k) => unimplemented! (), Instruction::Test (a, _k) => { diff --git a/src/value.rs b/src/value.rs index 6c6ff68..2fbe524 100644 --- a/src/value.rs +++ b/src/value.rs @@ -145,6 +145,13 @@ impl Value { } } + pub fn as_table_mut (&mut self) -> Option <&mut Rc > { + match self { + Self::Table (t) => Some (t), + _ => None, + } + } + pub fn is_truthy (&self) -> bool { // And this is something Lua does better than JS and Python. diff --git a/test_vectors/hello.lua b/test_vectors/hello.lua index 838bf06..fb08d11 100644 --- a/test_vectors/hello.lua +++ b/test_vectors/hello.lua @@ -1,3 +1,5 @@ +print "# 1" + print (nil) print (false) print (true) @@ -5,5 +7,16 @@ print (1993) print (1993.00) print "Hello." +print "# 2" + local t = {} print (t) +print (t [1]) +t [1] = "asdf" +print (t [1]) +t.x = 1993 +print (t.x) + +t.t = { 3.14159 } +print (t ["t"][1]) +