use super::gc::arena::GcRef;
use super::gc::trace::Trace;
use super::proto::ProtoRef;
use super::table::Table;
use super::value::Val;
use crate::error::LuaResult;
use super::state::LuaState;
#[derive(Debug, Clone)]
pub enum UpvalueState {
Open { stack_index: usize },
Closed { value: Val },
}
#[derive(Debug, Clone)]
pub struct Upvalue {
pub state: UpvalueState,
}
impl Upvalue {
#[must_use]
pub fn new_open(stack_index: usize) -> Self {
Self {
state: UpvalueState::Open { stack_index },
}
}
#[must_use]
pub fn new_closed(value: Val) -> Self {
Self {
state: UpvalueState::Closed { value },
}
}
#[must_use]
pub fn is_open(&self) -> bool {
matches!(self.state, UpvalueState::Open { .. })
}
#[must_use]
pub fn stack_index(&self) -> Option<usize> {
match self.state {
UpvalueState::Open { stack_index } => Some(stack_index),
UpvalueState::Closed { .. } => None,
}
}
#[must_use]
pub fn get(&self, stack: &[Val]) -> Val {
match self.state {
UpvalueState::Open { stack_index } => {
if stack_index < stack.len() {
stack[stack_index]
} else {
Val::Nil
}
}
UpvalueState::Closed { value } => value,
}
}
pub fn set(&mut self, stack: &mut [Val], val: Val) {
match &mut self.state {
UpvalueState::Open { stack_index } => {
if (*stack_index) < stack.len() {
stack[*stack_index] = val;
}
}
UpvalueState::Closed { value } => {
*value = val;
}
}
}
pub fn close(&mut self, stack: &[Val]) {
if let UpvalueState::Open { stack_index } = self.state {
let value = if stack_index < stack.len() {
stack[stack_index]
} else {
Val::Nil
};
self.state = UpvalueState::Closed { value };
}
}
}
impl Trace for Upvalue {
fn trace(&self) {
}
}
pub type RustFn = fn(&mut LuaState) -> LuaResult<u32>;
#[derive(Debug)]
pub struct LuaClosure {
pub proto: ProtoRef,
pub upvalues: Vec<GcRef<Upvalue>>,
pub env: GcRef<Table>,
}
impl LuaClosure {
#[must_use]
pub fn new(proto: ProtoRef, env: GcRef<Table>) -> Self {
let num_upvalues = proto.num_upvalues as usize;
Self {
proto,
upvalues: Vec::with_capacity(num_upvalues),
env,
}
}
}
pub struct RustClosure {
pub func: RustFn,
pub upvalues: Vec<Val>,
pub name: String,
pub env: Option<GcRef<Table>>,
}
impl std::fmt::Debug for RustClosure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RustClosure")
.field("name", &self.name)
.field("upvalues", &self.upvalues.len())
.finish_non_exhaustive()
}
}
impl RustClosure {
#[must_use]
pub fn new(func: RustFn, name: &str) -> Self {
Self {
func,
upvalues: Vec::new(),
name: name.to_string(),
env: None,
}
}
}
#[derive(Debug)]
pub enum Closure {
Lua(LuaClosure),
Rust(RustClosure),
}
impl Closure {
#[must_use]
pub fn is_rust(&self) -> bool {
matches!(self, Self::Rust(_))
}
#[must_use]
pub fn is_lua(&self) -> bool {
matches!(self, Self::Lua(_))
}
#[must_use]
pub fn as_lua(&self) -> Option<&LuaClosure> {
match self {
Self::Lua(cl) => Some(cl),
Self::Rust(_) => None,
}
}
pub fn as_lua_mut(&mut self) -> Option<&mut LuaClosure> {
match self {
Self::Lua(cl) => Some(cl),
Self::Rust(_) => None,
}
}
#[must_use]
pub fn as_rust(&self) -> Option<&RustClosure> {
match self {
Self::Rust(cl) => Some(cl),
Self::Lua(_) => None,
}
}
pub fn as_rust_mut(&mut self) -> Option<&mut RustClosure> {
match self {
Self::Rust(cl) => Some(cl),
Self::Lua(_) => None,
}
}
}
impl Trace for Closure {
fn trace(&self) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vm::gc::Color;
use crate::vm::gc::arena::Arena;
use crate::vm::proto::Proto;
use crate::vm::table::Table;
#[allow(clippy::unnecessary_wraps)]
fn dummy_rust_fn(_state: &mut LuaState) -> LuaResult<u32> {
Ok(0)
}
#[test]
fn upvalue_new_open() {
let uv = Upvalue::new_open(5);
assert!(uv.is_open());
assert_eq!(uv.stack_index(), Some(5));
}
#[test]
fn upvalue_new_closed() {
let uv = Upvalue::new_closed(Val::Num(42.0));
assert!(!uv.is_open());
assert_eq!(uv.stack_index(), None);
}
#[test]
fn upvalue_get_open() {
let stack = vec![Val::Nil, Val::Num(10.0), Val::Num(20.0)];
let uv = Upvalue::new_open(1);
assert_eq!(uv.get(&stack), Val::Num(10.0));
}
#[test]
fn upvalue_get_open_out_of_bounds() {
let stack = vec![Val::Nil];
let uv = Upvalue::new_open(100);
assert!(uv.get(&stack).is_nil());
}
#[test]
fn upvalue_get_closed() {
let stack = vec![Val::Nil];
let uv = Upvalue::new_closed(Val::Bool(true));
assert_eq!(uv.get(&stack), Val::Bool(true));
}
#[test]
fn upvalue_set_open() {
let mut stack = vec![Val::Nil, Val::Nil, Val::Nil];
let mut uv = Upvalue::new_open(1);
uv.set(&mut stack, Val::Num(99.0));
assert_eq!(stack[1], Val::Num(99.0));
}
#[test]
fn upvalue_set_closed() {
let mut stack = vec![Val::Nil];
let mut uv = Upvalue::new_closed(Val::Num(1.0));
uv.set(&mut stack, Val::Num(2.0));
assert_eq!(uv.get(&stack), Val::Num(2.0));
}
#[test]
fn upvalue_close() {
let stack = vec![Val::Nil, Val::Num(42.0)];
let mut uv = Upvalue::new_open(1);
assert!(uv.is_open());
uv.close(&stack);
assert!(!uv.is_open());
assert_eq!(uv.get(&[]), Val::Num(42.0));
}
#[test]
fn upvalue_close_already_closed() {
let stack = vec![Val::Nil];
let mut uv = Upvalue::new_closed(Val::Bool(true));
uv.close(&stack); assert_eq!(uv.get(&stack), Val::Bool(true));
}
#[test]
fn upvalue_shared_mutation() {
let mut stack = vec![Val::Nil, Val::Num(0.0)];
let mut uv1 = Upvalue::new_open(1);
let uv2 = Upvalue::new_open(1);
uv1.set(&mut stack, Val::Num(5.0));
assert_eq!(uv2.get(&stack), Val::Num(5.0));
}
#[test]
fn lua_closure_new() {
let mut tables: Arena<Table> = Arena::new();
let env = tables.alloc(Table::new(), Color::White0);
let mut proto = Proto::new("test");
proto.num_upvalues = 2;
let cl = LuaClosure::new(ProtoRef::new(proto), env);
assert_eq!(cl.proto.source, "test");
assert!(cl.upvalues.is_empty()); assert_eq!(cl.upvalues.capacity(), 2);
assert_eq!(cl.env, env);
}
#[test]
fn rust_closure_new() {
let cl = RustClosure::new(dummy_rust_fn, "print");
assert_eq!(cl.name, "print");
assert!(cl.upvalues.is_empty());
}
#[test]
fn rust_closure_with_upvalues() {
let mut cl = RustClosure::new(dummy_rust_fn, "counter");
cl.upvalues.push(Val::Num(0.0));
assert_eq!(cl.upvalues.len(), 1);
assert_eq!(cl.upvalues[0], Val::Num(0.0));
}
#[test]
fn closure_is_lua() {
let mut tables: Arena<Table> = Arena::new();
let env = tables.alloc(Table::new(), Color::White0);
let cl = Closure::Lua(LuaClosure::new(ProtoRef::new(Proto::new("test")), env));
assert!(cl.is_lua());
assert!(!cl.is_rust());
assert!(cl.as_lua().is_some());
assert!(cl.as_rust().is_none());
}
#[test]
fn closure_is_rust() {
let cl = Closure::Rust(RustClosure::new(dummy_rust_fn, "f"));
assert!(cl.is_rust());
assert!(!cl.is_lua());
assert!(cl.as_rust().is_some());
assert!(cl.as_lua().is_none());
}
#[test]
fn closure_debug_format() {
let cl = Closure::Rust(RustClosure::new(dummy_rust_fn, "test_fn"));
let debug = format!("{cl:?}");
assert!(debug.contains("Rust"));
assert!(debug.contains("test_fn"));
}
#[test]
fn upvalue_trace_is_stub() {
let uv = Upvalue::new_open(0);
uv.trace(); }
#[test]
fn closure_trace_is_stub() {
let cl = Closure::Rust(RustClosure::new(dummy_rust_fn, "f"));
cl.trace(); }
}