From 73e33444c9e7a33e8db782549690ee232161be2e Mon Sep 17 00:00:00 2001 From: Sofia Date: Wed, 18 Mar 2026 00:03:13 +0200 Subject: [PATCH] Add table length --- examples/test.lua | 1 + luac.out | Bin 187 -> 188 bytes src/ast.rs | 3 ++- src/compile.rs | 1 + src/vm.rs | 66 +++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/examples/test.lua b/examples/test.lua index 84e21ee..9674c22 100644 --- a/examples/test.lua +++ b/examples/test.lua @@ -22,6 +22,7 @@ end global sometable = {} sometable["hello"] = { add(10)(15) } +print(#sometable["hello"]) sometable["hello"].there = "my dude" print(sometable.hello.there) diff --git a/luac.out b/luac.out index 7b9b0ba2bd9fb5f7f4f16ac5f9184324bd396a04..40c57968a2dc97ba8c5539cf808112e234604e86 100644 GIT binary patch delta 141 zcmdnZxQB6qc|FU-Kn4Z|VIU3y;>l2&n~{OBv5}?0g^`J=k&zulGn@m;2{AA*b~7+A z@i8(mm;l9&FfcJ5V_;-#W@*bv&B@7UX-P~;VQDKU%FHWi1gdG6$OtqEs1XdnjE2Up W;{4pylEkE()aHq7jji#neqjI=J{>Lq delta 140 zcmWlPu?_)I6hvpPr&jnFU!Yx~(8|hgM-&t~bJ^GIYy1zTtiKX>Cpnp%$-MdEb8(xL zHGowx3+8}rqDw$e`oN4dRWJ--OkLeHPQc|4B4v$FLhjIjgfBv*92Zc2zbdQHekpv6 Uyg#3g?WNuy+P?TS@0Qi}5913VcmMzZ diff --git a/src/ast.rs b/src/ast.rs index ca82baf..3918799 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -372,6 +372,7 @@ pub struct PrimaryExpression(Node); #[derive(Debug, Clone, Copy)] pub enum UnaryOperator { Negation, + Length, } impl Parse for UnaryOperator { @@ -379,6 +380,7 @@ impl Parse for UnaryOperator { if let Some(token) = stream.next() { match token { Token::Symbol('-') => Ok(UnaryOperator::Negation), + Token::Symbol('#') => Ok(UnaryOperator::Length), _ => Err(stream.expected_err("unop")), } } else { @@ -530,7 +532,6 @@ impl Parse for PrimaryExpression { Token::Symbol('[') | Token::Symbol('.') => { let meta = Metadata::produce(&mut stream, pre.clone()); let IndexedAccess(accesses) = stream.parse()?; - dbg!(&accesses, stream.peek()); for access in accesses { expression = Expression::IndexedAccess( Box::new(Node { diff --git a/src/compile.rs b/src/compile.rs index 0949960..f3078ee 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -525,6 +525,7 @@ impl Expression { for reg in ®isters { match op { UnaryOperator::Negation => instructions.push(Instruction::Unm(*reg, *reg)), + UnaryOperator::Length => instructions.push(Instruction::Len(*reg, *reg)), } } (instructions, registers) diff --git a/src/vm.rs b/src/vm.rs index 02954c8..8149c69 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -7,7 +7,7 @@ use crate::{ ast::{BinaryOperator, UnaryOperator}, }; -#[derive(Clone, Hash, PartialEq, Eq, Copy)] +#[derive(Clone, Hash, PartialEq, Eq, Copy, PartialOrd, Ord)] pub struct VMFloat(u64); impl VMFloat { @@ -37,7 +37,7 @@ impl Debug for LuaFloat { } } -#[derive(Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct LuaInteger(pub i64); impl Debug for LuaInteger { @@ -58,7 +58,7 @@ impl From<&LuaInteger> for LuaFloat { } } -#[derive(Clone, Copy, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct LuaBool(pub bool); impl Debug for LuaBool { @@ -134,6 +134,8 @@ pub enum Instruction { Add(u16, u16, u16), /// R(A) := -R(B) Unm(u16, u16), + /// R(A) := #R(B) (length operator) + Len(u16, u16), /// R(A) := R(B) == R(C) Equal(u16, u16, u16), /// R(A) := R(B) < R(C) @@ -190,6 +192,7 @@ impl Debug for Instruction { Instruction::Add(arg0, arg1, arg2) => write!(f, "ADD {} {} {}", arg0, arg1, arg2), Instruction::LoadNil(arg0, arg1) => write!(f, "LOADNIL {} {}", arg0, arg1), Instruction::Unm(arg0, arg1) => write!(f, "UNM {} {}", arg0, arg1), + Instruction::Len(arg0, arg1) => write!(f, "LEN {} {}", arg0, arg1), Instruction::Or(arg0, arg1, arg2) => write!(f, "OR {} {} {}", arg0, arg1, arg2), Instruction::And(arg0, arg1, arg2) => write!(f, "AND {} {} {}", arg0, arg1, arg2), } @@ -212,6 +215,8 @@ pub enum RuntimeError { NotTable(Value), #[error("Value is not coercable to a float: {0:?}")] NotFloatable(Value), + #[error("Value does not have a length: {0:?}")] + NotLengthable(Value), #[error("{0}")] Custom(String), } @@ -292,7 +297,7 @@ impl Value { } } -#[derive(Debug, PartialEq, Eq, Hash, Clone)] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Ord, PartialOrd)] pub enum IndexableValue { String(String), Float(VMFloat), @@ -396,6 +401,47 @@ impl Value { } } + pub fn len(&self) -> Result { + match self { + Value::String(value) => Ok(Self::Integer(LuaInteger(value.len() as i64))), + Value::Table(table) => { + let table = table.borrow(); + let mut int_indexes = table + .keys() + .filter(|v| match v { + IndexableValue::Integer(_) => true, + _ => false, + }) + .map(|v| match v { + IndexableValue::Integer(int) => int.0, + _ => panic!(), + }) + .collect::>(); + int_indexes.push(0); + int_indexes.sort(); + + for idx in int_indexes { + let idx_value = table.get(&IndexableValue::Integer(LuaInteger(idx))); + let idx_value_after = idx + .checked_add_unsigned(1) + .and_then(|i| table.get(&IndexableValue::Integer(LuaInteger(i)))); + + // Directly from https://www.lua.org/manual/5.5/manual.html#3.4.7 + if (idx == 0 || idx_value.is_some()) && idx_value_after.is_none() { + return Ok(Self::Integer(LuaInteger(idx))); + } + } + panic!() + } + Value::Float(_) => Err(RuntimeError::NotLengthable(self.clone())), + Value::Integer(_) => Err(RuntimeError::NotLengthable(self.clone())), + Value::Boolean(_) => Err(RuntimeError::NotLengthable(self.clone())), + Value::RustFunction(_) => Err(RuntimeError::NotLengthable(self.clone())), + Value::Function(_) => Err(RuntimeError::NotLengthable(self.clone())), + Value::Nil => Err(RuntimeError::NotLengthable(self.clone())), + } + } + pub fn and(&self, other: &Value) -> Result { if self.is_truthy() { Ok(self.clone()) @@ -986,6 +1032,18 @@ impl ClosureRunner { ), ); } + Instruction::Len(res, reg) => { + self.set_stack( + *res, + StackValue::Value( + self.stack + .get(reg) + .map(|v| v.borrow().clone()) + .unwrap_or(Value::Nil) + .len()?, + ), + ); + } Instruction::Or(res, lhs, rhs) => { let lhs = self .stack