use super::Result;
use super::State;
use super::lua_val::Val;
use crate::error::ErrorKind;
use crate::instr::{ArgCount, RetCount};
pub(super) const MAX_METAMETHOD_DEPTH: u32 = 200;
impl State {
pub(super) fn get_table_with_key(&mut self, idx: usize, key: Val) -> Result<()> {
let table_val = self.stack[idx];
let obj_ptr = table_val.as_object_ptr();
let (val, mt_ptr) = match obj_ptr.and_then(|ptr| self.heap.as_table_ref(ptr)) {
Some(t) => {
let val = t.get(&key);
let mt_ptr = t.get_metatable();
(val, mt_ptr)
}
None => {
return Err(
self.type_error(super::TypeError::TableIndex(self.stack[idx].typ_simple()))
);
}
};
if matches!(val, Val::Nil) {
if let Some(mt_ptr) = mt_ptr {
self.stack.push(key);
let index_key = self.alloc_string("__index");
let key = self.pop_val();
let index_handler = self
.heap
.as_table_ref(mt_ptr)
.map_or(Val::Nil, |mt| mt.get(&index_key));
if !matches!(index_handler, Val::Nil) {
return self.handle_index_metamethod(index_handler, idx, key);
}
}
}
self.stack.push(val);
Ok(())
}
fn handle_index_metamethod(&mut self, handler: Val, table_idx: usize, key: Val) -> Result<()> {
if self.metamethod_depth >= MAX_METAMETHOD_DEPTH {
return Err(self.error(ErrorKind::MetamethodDepthExceeded {
depth: self.metamethod_depth,
}));
}
self.metamethod_depth += 1;
let result = self.handle_index_metamethod_inner(handler, table_idx, key);
self.metamethod_depth -= 1;
result
}
fn handle_index_metamethod_inner(
&mut self,
handler: Val,
table_idx: usize,
key: Val,
) -> Result<()> {
match handler {
Val::Obj(ptr) => {
let is_table = self.heap.as_table_ref(ptr).is_some();
let is_function = self.heap.as_lua_function(ptr).is_some();
if is_table {
self.stack.push(Val::Obj(ptr));
let new_idx = self.stack.len() - 1;
self.get_table_with_key(new_idx, key)?;
let val = self.pop_val();
self.pop(1);
self.stack.push(val);
Ok(())
} else if is_function {
let table_val = self.stack[table_idx];
self.stack.push(Val::Obj(ptr));
self.stack.push(table_val);
self.stack.push(key);
self.call(ArgCount::Fixed(2), RetCount::Fixed(1))?;
Ok(())
} else {
Err(self.type_error(super::TypeError::TableIndex(Val::Obj(ptr).typ_simple())))
}
}
Val::RustFn(f) => {
let table_val = self.stack[table_idx];
self.stack.push(Val::RustFn(f));
self.stack.push(table_val);
self.stack.push(key);
self.call(ArgCount::Fixed(2), RetCount::Fixed(1))?;
Ok(())
}
_ => Err(self.type_error(super::TypeError::TableIndex(handler.typ_simple()))),
}
}
pub(super) fn set_table_with_key(&mut self, idx: usize, key: Val, val: Val) -> Result<()> {
let table_val = self.stack[idx];
let obj_ptr = table_val.as_object_ptr();
let (existing, mt_ptr) = match obj_ptr.and_then(|ptr| self.heap.as_table_ref(ptr)) {
Some(t) => {
let existing = t.get(&key);
let mt_ptr = t.get_metatable();
(existing, mt_ptr)
}
None => {
return Err(
self.type_error(super::TypeError::TableIndex(self.stack[idx].typ_simple()))
);
}
};
if matches!(existing, Val::Nil) {
if let Some(mt_ptr) = mt_ptr {
self.stack.push(key);
self.stack.push(val);
let newindex_key = self.alloc_string("__newindex");
self.pop(2);
let newindex_handler = self
.heap
.as_table_ref(mt_ptr)
.map_or(Val::Nil, |mt| mt.get(&newindex_key));
if !matches!(newindex_handler, Val::Nil) {
return self.handle_newindex_metamethod(newindex_handler, idx, key, val);
}
}
}
if let Some(ptr) = obj_ptr
&& let Some(t) = self.heap.as_table(ptr)
{
t.insert(key, val)?;
}
Ok(())
}
fn handle_newindex_metamethod(
&mut self,
handler: Val,
table_idx: usize,
key: Val,
val: Val,
) -> Result<()> {
if self.metamethod_depth >= MAX_METAMETHOD_DEPTH {
return Err(self.error(ErrorKind::MetamethodDepthExceeded {
depth: self.metamethod_depth,
}));
}
self.metamethod_depth += 1;
let result = self.handle_newindex_metamethod_inner(handler, table_idx, key, val);
self.metamethod_depth -= 1;
result
}
fn handle_newindex_metamethod_inner(
&mut self,
handler: Val,
table_idx: usize,
key: Val,
val: Val,
) -> Result<()> {
match handler {
Val::Obj(ptr) => {
let is_table = self.heap.as_table_ref(ptr).is_some();
let is_function = self.heap.as_lua_function(ptr).is_some();
if is_table {
self.stack.push(Val::Obj(ptr));
let new_idx = self.stack.len() - 1;
self.set_table_with_key(new_idx, key, val)?;
self.pop(1); Ok(())
} else if is_function {
let table_val = self.stack[table_idx];
self.stack.push(Val::Obj(ptr));
self.stack.push(table_val);
self.stack.push(key);
self.stack.push(val);
self.call(ArgCount::Fixed(3), RetCount::Fixed(0))?;
Ok(())
} else {
Err(self.type_error(super::TypeError::TableIndex(Val::Obj(ptr).typ_simple())))
}
}
Val::RustFn(f) => {
let table_val = self.stack[table_idx];
self.stack.push(Val::RustFn(f));
self.stack.push(table_val);
self.stack.push(key);
self.stack.push(val);
self.call(ArgCount::Fixed(3), RetCount::Fixed(0))?;
Ok(())
}
_ => Err(self.type_error(super::TypeError::TableIndex(handler.typ_simple()))),
}
}
}