use std::any::Any;
use crate::error::{LuaError, LuaResult, RuntimeError};
use crate::vm::closure::Closure;
use crate::vm::gc::arena::GcRef;
use crate::vm::state::{LuaState, LuaThread, ThreadStatus};
use crate::vm::value::{Userdata, Val};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Table(pub(crate) GcRef<crate::vm::table::Table>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Function(pub(crate) GcRef<Closure>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Thread(pub(crate) GcRef<LuaThread>);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnyUserData(pub(crate) GcRef<Userdata>);
impl Table {
pub fn raw_get(&self, state: &LuaState, key: Val) -> LuaResult<Val> {
let table = state.gc.tables.get(self.0).ok_or_else(|| {
LuaError::Runtime(RuntimeError {
message: "table has been collected".into(),
level: 0,
traceback: vec![],
})
})?;
Ok(table.get(key, &state.gc.string_arena))
}
pub fn raw_set(&self, state: &mut LuaState, key: Val, value: Val) -> LuaResult<()> {
let table = state.gc.tables.get_mut(self.0).ok_or_else(|| {
LuaError::Runtime(RuntimeError {
message: "table has been collected".into(),
level: 0,
traceback: vec![],
})
})?;
table.raw_set(key, value, &state.gc.string_arena)
}
pub fn raw_len(&self, state: &LuaState) -> i64 {
state
.gc
.tables
.get(self.0)
.map_or(0, |t| t.len(&state.gc.string_arena) as i64)
}
pub fn set_metatable(&self, state: &mut LuaState, mt: Option<Self>) -> LuaResult<()> {
let table = state.gc.tables.get_mut(self.0).ok_or_else(|| {
LuaError::Runtime(RuntimeError {
message: "table has been collected".into(),
level: 0,
traceback: vec![],
})
})?;
table.set_metatable(mt.map(|t| t.0));
Ok(())
}
pub fn gc_ref(self) -> GcRef<crate::vm::table::Table> {
self.0
}
}
impl Function {
pub fn gc_ref(self) -> GcRef<Closure> {
self.0
}
}
impl Thread {
pub fn status(&self, state: &LuaState) -> ThreadStatus {
state
.gc
.threads
.get(self.0)
.map_or(ThreadStatus::Dead, |t| t.status)
}
pub fn gc_ref(self) -> GcRef<LuaThread> {
self.0
}
}
impl AnyUserData {
pub fn borrow<'a, T: Any>(&self, state: &'a LuaState) -> Option<&'a T> {
state.gc.userdata.get(self.0)?.downcast_ref::<T>()
}
pub fn borrow_mut<'a, T: Any>(&self, state: &'a mut LuaState) -> Option<&'a mut T> {
state.gc.userdata.get_mut(self.0)?.downcast_mut::<T>()
}
pub fn set_metatable(&self, state: &mut LuaState, mt: Option<Table>) -> LuaResult<()> {
let ud = state.gc.userdata.get_mut(self.0).ok_or_else(|| {
LuaError::Runtime(RuntimeError {
message: "userdata has been collected".into(),
level: 0,
traceback: vec![],
})
})?;
ud.set_metatable(mt.map(|t| t.0));
Ok(())
}
pub fn metatable(&self, state: &LuaState) -> Option<Table> {
let ud = state.gc.userdata.get(self.0)?;
ud.metatable().map(Table)
}
pub fn gc_ref(self) -> GcRef<Userdata> {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vm::value::Userdata;
#[test]
fn table_raw_get_set() {
let mut state = LuaState::new();
let t = state.gc.alloc_table(crate::vm::table::Table::new());
let handle = Table(t);
let key = Val::Num(1.0);
let value = Val::Num(42.0);
handle.raw_set(&mut state, key, value).ok();
let got = handle.raw_get(&state, key).ok();
assert_eq!(got, Some(Val::Num(42.0)));
}
#[test]
fn table_raw_len_empty() {
let mut state = LuaState::new();
let t = state.gc.alloc_table(crate::vm::table::Table::new());
let handle = Table(t);
assert_eq!(handle.raw_len(&state), 0);
}
#[test]
fn function_gc_ref_round_trip() {
let mut state = LuaState::new();
let cl = Closure::Rust(crate::vm::closure::RustClosure::new(|_| Ok(0), "test"));
let r = state.gc.alloc_closure(cl);
let handle = Function(r);
assert_eq!(handle.gc_ref(), r);
}
#[test]
fn table_set_metatable() {
let mut state = LuaState::new();
let t = state.gc.alloc_table(crate::vm::table::Table::new());
let mt = state.gc.alloc_table(crate::vm::table::Table::new());
let handle = Table(t);
let mt_handle = Table(mt);
handle.set_metatable(&mut state, Some(mt_handle)).ok();
let table = state.gc.tables.get(t);
assert!(table.is_some());
assert_eq!(table.and_then(crate::vm::table::Table::metatable), Some(mt));
}
#[test]
fn create_userdata_and_borrow() {
let mut state = LuaState::new();
let ud = Userdata::new(Box::new(42i64));
let r = state.gc.alloc_userdata(ud);
let handle = AnyUserData(r);
let val = handle.borrow::<i64>(&state);
assert_eq!(val, Some(&42i64));
}
#[test]
fn create_userdata_type_mismatch() {
let mut state = LuaState::new();
let ud = Userdata::new(Box::new(42i64));
let r = state.gc.alloc_userdata(ud);
let handle = AnyUserData(r);
let val = handle.borrow::<String>(&state);
assert!(val.is_none());
}
#[test]
fn userdata_borrow_mut() {
let mut state = LuaState::new();
let ud = Userdata::new(Box::new(10i64));
let r = state.gc.alloc_userdata(ud);
let handle = AnyUserData(r);
if let Some(val) = handle.borrow_mut::<i64>(&mut state) {
*val = 99;
}
let val = handle.borrow::<i64>(&state);
assert_eq!(val, Some(&99i64));
}
#[test]
fn userdata_set_metatable() {
let mut state = LuaState::new();
let ud = Userdata::new(Box::new(()));
let r = state.gc.alloc_userdata(ud);
let handle = AnyUserData(r);
assert!(handle.metatable(&state).is_none());
let mt = state.gc.alloc_table(crate::vm::table::Table::new());
let mt_handle = Table(mt);
handle.set_metatable(&mut state, Some(mt_handle)).ok();
assert_eq!(handle.metatable(&state), Some(mt_handle));
handle.set_metatable(&mut state, None).ok();
assert!(handle.metatable(&state).is_none());
}
#[test]
fn userdata_gc_ref_round_trip() {
let mut state = LuaState::new();
let ud = Userdata::new(Box::new("test".to_string()));
let r = state.gc.alloc_userdata(ud);
let handle = AnyUserData(r);
assert_eq!(handle.gc_ref(), r);
}
}