use super::callinfo::{CallInfo, LUA_MULTRET};
use super::closure::{Closure, Upvalue};
use super::gc::Color;
use super::gc::arena::{Arena, GcRef};
use super::gc::trace::Trace;
use super::metatable::{NUM_TYPE_TAGS, TM_N, TM_NAMES};
use super::string::{LuaString, StringTable};
use super::table::Table;
use super::value::{Userdata, Val};
use crate::error::LuaResult;
pub const MAXCALLS: usize = 20_000;
pub const MAXCCALLS: u16 = 200;
pub const LUA_MINSTACK: usize = 20;
const BASIC_STACK_SIZE: usize = 2 * LUA_MINSTACK;
pub(crate) const BASIC_CI_SIZE: usize = 8;
pub const MASK_CALL: u8 = 1 << 0; pub const MASK_RET: u8 = 1 << 1; pub const MASK_LINE: u8 = 1 << 2; pub const MASK_COUNT: u8 = 1 << 3;
pub struct Gc {
pub strings: StringTable,
pub string_arena: Arena<LuaString>,
pub tables: Arena<Table>,
pub closures: Arena<Closure>,
pub upvalues: Arena<Upvalue>,
pub userdata: Arena<Userdata>,
pub threads: Arena<LuaThread>,
pub current_white: Color,
pub type_metatables: [Option<GcRef<Table>>; NUM_TYPE_TAGS],
pub tm_names: [Option<GcRef<LuaString>>; TM_N],
pub gc_state: super::gc::collector::GcState,
}
impl Gc {
fn new() -> Self {
let mut gc = Self {
strings: StringTable::new(),
string_arena: Arena::new(),
tables: Arena::new(),
closures: Arena::new(),
upvalues: Arena::new(),
userdata: Arena::new(),
threads: Arena::new(),
current_white: Color::White0,
type_metatables: [None; NUM_TYPE_TAGS],
tm_names: [None; TM_N],
gc_state: super::gc::collector::GcState::new(),
};
gc.init_tm_names();
gc
}
fn init_tm_names(&mut self) {
for (i, name) in TM_NAMES.iter().enumerate() {
let r = self.intern_string(name.as_bytes());
self.tm_names[i] = Some(r);
}
}
pub fn intern_string(&mut self, data: &[u8]) -> GcRef<LuaString> {
let old_count = self.string_arena.len();
let r = self
.strings
.intern(data, &mut self.string_arena, self.current_white);
if self.string_arena.len() > old_count {
let est = super::gc::collector::EST_STRING_SIZE + data.len();
self.gc_state.track_alloc(est);
}
r
}
pub fn alloc_table(&mut self, table: Table) -> GcRef<Table> {
let est = super::gc::collector::EST_TABLE_SIZE
+ table.array_slice().len() * 16
+ table.hash_size() as usize * 32;
self.gc_state.track_alloc(est);
self.tables.alloc(table, self.current_white)
}
pub fn alloc_closure(&mut self, closure: Closure) -> GcRef<Closure> {
self.gc_state
.track_alloc(super::gc::collector::EST_CLOSURE_SIZE);
self.closures.alloc(closure, self.current_white)
}
pub fn alloc_upvalue(&mut self, upvalue: Upvalue) -> GcRef<Upvalue> {
self.gc_state
.track_alloc(super::gc::collector::EST_UPVALUE_SIZE);
self.upvalues.alloc(upvalue, self.current_white)
}
pub fn alloc_userdata(&mut self, mut userdata: Userdata) -> GcRef<Userdata> {
self.gc_state
.track_alloc(super::gc::collector::EST_USERDATA_SIZE);
let seq = self.gc_state.ud_alloc_seq;
self.gc_state.ud_alloc_seq += 1;
userdata.set_alloc_seq(seq);
self.userdata.alloc(userdata, self.current_white)
}
pub fn alloc_thread(&mut self, thread: LuaThread) -> GcRef<LuaThread> {
self.gc_state
.track_alloc(super::gc::collector::EST_THREAD_SIZE);
self.threads.alloc(thread, self.current_white)
}
pub fn count_blocks(&self) -> usize {
self.string_arena.len() as usize
+ self.tables.len() as usize
+ self.closures.len() as usize
+ self.upvalues.len() as usize
+ self.userdata.len() as usize
+ self.threads.len() as usize
}
#[inline]
pub fn tm_name(&self, event: super::metatable::TMS) -> Option<GcRef<LuaString>> {
self.tm_names[event as usize]
}
pub fn total_alloc(&self) -> usize {
self.gc_state.total_bytes
}
pub fn set_alloc_limit(&mut self, limit: usize) {
self.gc_state.alloc_limit = limit;
if limit < self.gc_state.gc_threshold {
self.gc_state.gc_threshold = limit;
}
}
pub fn check_alloc_limit(&self) -> crate::LuaResult<()> {
if self.gc_state.total_bytes > self.gc_state.alloc_limit {
Err(crate::LuaError::Memory)
} else {
Ok(())
}
}
}
impl Default for Gc {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ThreadStatus {
Initial,
Running,
Suspended,
Normal,
Dead,
}
#[derive(Clone)]
pub struct HookState {
pub hook_func: Val,
pub hook_mask: u8,
pub allow_hook: bool,
pub base_hook_count: i32,
pub hook_count: i32,
pub yield_on_hook: bool,
}
impl HookState {
#[must_use]
pub fn new() -> Self {
Self {
hook_func: Val::Nil,
hook_mask: 0,
allow_hook: true,
base_hook_count: 0,
hook_count: 0,
yield_on_hook: false,
}
}
#[inline]
pub fn is_active(&self) -> bool {
self.hook_mask != 0 && !self.hook_func.is_nil()
}
#[inline]
pub fn should_fire(&self) -> bool {
self.is_active() && self.allow_hook
}
}
impl Default for HookState {
fn default() -> Self {
Self::new()
}
}
pub struct LuaThread {
pub stack: Vec<Val>,
pub base: usize,
pub top: usize,
pub call_stack: Vec<CallInfo>,
pub ci: usize,
pub n_ccalls: u16,
pub call_depth: u16,
pub ci_overflow: bool,
pub open_upvalues: Vec<GcRef<Upvalue>>,
pub suspended_upvals: Vec<(GcRef<Upvalue>, usize)>,
pub error_object: Option<Val>,
pub status: ThreadStatus,
pub global: GcRef<Table>,
pub hook: HookState,
pub yielded_in_hook: bool,
}
impl LuaThread {
pub fn new(func_val: Val, global: GcRef<Table>) -> Self {
let mut stack = vec![Val::Nil; BASIC_STACK_SIZE];
stack[0] = func_val;
let initial_ci = CallInfo::new(0, 1, 1 + LUA_MINSTACK, LUA_MULTRET);
let mut call_stack = Vec::with_capacity(BASIC_CI_SIZE);
call_stack.push(initial_ci);
Self {
stack,
base: 1,
top: 1,
call_stack,
ci: 0,
n_ccalls: 0,
call_depth: 0,
ci_overflow: false,
open_upvalues: Vec::new(),
suspended_upvals: Vec::new(),
error_object: None,
status: ThreadStatus::Initial,
global,
hook: HookState::new(),
yielded_in_hook: false,
}
}
}
impl Trace for LuaThread {
fn trace(&self) {
}
}
pub struct LuaState {
pub stack: Vec<Val>,
pub base: usize,
pub top: usize,
pub call_stack: Vec<CallInfo>,
pub ci: usize,
pub n_ccalls: u16,
pub call_depth: u16,
pub ci_overflow: bool,
pub global: GcRef<Table>,
pub registry: GcRef<Table>,
pub open_upvalues: Vec<GcRef<Upvalue>>,
pub gc: Gc,
pub error_object: Option<Val>,
pub rng_state: u64,
pub current_thread: Option<GcRef<LuaThread>>,
pub hook: HookState,
pub yielded_in_hook: bool,
pub saved_threads: Vec<LuaThread>,
}
impl LuaState {
#[must_use]
pub fn new() -> Self {
let mut gc = Gc::new();
let global = gc.alloc_table(Table::new());
let registry = gc.alloc_table(Table::new());
let stack = vec![Val::Nil; BASIC_STACK_SIZE];
let initial_ci = CallInfo::new(0, 1, 1 + LUA_MINSTACK, LUA_MULTRET);
let mut call_stack = Vec::with_capacity(BASIC_CI_SIZE);
call_stack.push(initial_ci);
Self {
stack,
base: 1,
top: 1,
call_stack,
ci: 0,
n_ccalls: 0,
call_depth: 0,
ci_overflow: false,
global,
registry,
open_upvalues: Vec::new(),
gc,
error_object: None,
rng_state: 1, current_thread: None,
hook: HookState::new(),
yielded_in_hook: false,
saved_threads: Vec::new(),
}
}
#[inline]
pub fn stack_get(&self, idx: usize) -> Val {
if idx < self.stack.len() {
self.stack[idx]
} else {
Val::Nil
}
}
#[inline]
pub fn stack_set(&mut self, idx: usize, val: Val) {
if idx >= self.stack.len() {
self.stack.resize(idx + 1, Val::Nil);
}
self.stack[idx] = val;
}
pub fn ensure_stack(&mut self, n: usize) {
let needed = self.top + n;
if needed > self.stack.len() {
self.stack.resize(needed, Val::Nil);
}
}
pub fn push(&mut self, val: Val) {
if self.top >= self.stack.len() {
self.stack.resize(self.top + 1, Val::Nil);
}
self.stack[self.top] = val;
self.top += 1;
}
pub fn pop(&mut self) -> Val {
if self.top > 0 {
self.top -= 1;
self.stack[self.top]
} else {
Val::Nil
}
}
pub fn gettable(&mut self, t: Val, key: Val) -> LuaResult<Val> {
use super::metatable::{MAXTAGLOOP, TMS, gettmbyobj};
let mut current = t;
for _ in 0..MAXTAGLOOP {
if let Val::Table(table_ref) = current {
let result = self
.gc
.tables
.get(table_ref)
.map_or(Val::Nil, |tbl| tbl.get(key, &self.gc.string_arena));
if !result.is_nil() {
return Ok(result);
}
let tm = gettmbyobj(
current,
TMS::Index,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
);
match tm {
None => return Ok(Val::Nil),
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
let saved_top = self.top;
let call_base = self.top;
self.ensure_stack(call_base + 4);
self.stack_set(call_base, tm_val);
self.stack_set(call_base + 1, current);
self.stack_set(call_base + 2, key);
self.top = call_base + 3;
self.call_function(call_base, 1)?;
let result = self.stack_get(call_base);
self.top = saved_top;
return Ok(result);
}
Some(tm_val) => {
current = tm_val;
}
}
} else {
return Ok(Val::Nil);
}
}
Err(crate::error::LuaError::Runtime(
crate::error::RuntimeError {
message: "loop in gettable".into(),
level: 0,
traceback: vec![],
},
))
}
pub fn settable(&mut self, t: Val, key: Val, value: Val) -> LuaResult<()> {
use super::metatable::{MAXTAGLOOP, TMS, gettmbyobj};
let mut current = t;
for _ in 0..MAXTAGLOOP {
if let Val::Table(table_ref) = current {
let existing = self
.gc
.tables
.get(table_ref)
.map_or(Val::Nil, |tbl| tbl.get(key, &self.gc.string_arena));
if !existing.is_nil() {
let table = self.gc.tables.get_mut(table_ref).ok_or_else(|| {
crate::error::LuaError::Runtime(crate::error::RuntimeError {
message: "invalid table reference".into(),
level: 0,
traceback: vec![],
})
})?;
table.raw_set(key, value, &self.gc.string_arena)?;
self.gc.barrier_back(table_ref);
return Ok(());
}
let tm = {
let table = self.gc.tables.get(table_ref).ok_or_else(|| {
crate::error::LuaError::Runtime(crate::error::RuntimeError {
message: "invalid table reference".into(),
level: 0,
traceback: vec![],
})
})?;
let mt = table.metatable();
match mt {
Some(mt_ref) => {
use super::metatable::fasttm;
fasttm(
&self.gc.tables,
&self.gc.string_arena,
mt_ref,
TMS::NewIndex,
&self.gc.tm_names,
)
}
None => None,
}
};
match tm {
None => {
let table = self.gc.tables.get_mut(table_ref).ok_or_else(|| {
crate::error::LuaError::Runtime(crate::error::RuntimeError {
message: "invalid table reference".into(),
level: 0,
traceback: vec![],
})
})?;
table.raw_set(key, value, &self.gc.string_arena)?;
self.gc.barrier_back(table_ref);
return Ok(());
}
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
let call_base = self.top;
self.ensure_stack(call_base + 5);
self.stack_set(call_base, tm_val);
self.stack_set(call_base + 1, current);
self.stack_set(call_base + 2, key);
self.stack_set(call_base + 3, value);
self.top = call_base + 4;
self.call_function(call_base, 0)?;
return Ok(());
}
Some(tm_val) => {
current = tm_val;
}
}
} else {
let tm = gettmbyobj(
current,
TMS::NewIndex,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
);
match tm {
None => {
return Err(crate::error::LuaError::Runtime(
crate::error::RuntimeError {
message: format!(
"attempt to index a {} value",
current.type_name()
),
level: 0,
traceback: vec![],
},
));
}
Some(tm_val) if matches!(tm_val, Val::Function(_)) => {
let call_base = self.top;
self.ensure_stack(call_base + 5);
self.stack_set(call_base, tm_val);
self.stack_set(call_base + 1, current);
self.stack_set(call_base + 2, key);
self.stack_set(call_base + 3, value);
self.top = call_base + 4;
self.call_function(call_base, 0)?;
return Ok(());
}
Some(tm_val) => {
current = tm_val;
}
}
}
}
Err(crate::error::LuaError::Runtime(
crate::error::RuntimeError {
message: "loop in settable".into(),
level: 0,
traceback: vec![],
},
))
}
pub fn api_lessthan(&mut self, a: Val, b: Val) -> LuaResult<bool> {
use super::metatable::TMS;
match (&a, &b) {
(Val::Num(x), Val::Num(y)) => Ok(x < y),
(Val::Str(x), Val::Str(y)) => {
let sx = self.gc.string_arena.get(*x);
let sy = self.gc.string_arena.get(*y);
match (sx, sy) {
(Some(sx), Some(sy)) => {
Ok(super::execute::l_strcmp(sx.data(), sy.data())
== std::cmp::Ordering::Less)
}
_ => Err(self.compare_error(a, b)),
}
}
_ => {
if std::mem::discriminant(&a) != std::mem::discriminant(&b) {
return Err(self.compare_error(a, b));
}
match self.call_order_tm_api(a, b, TMS::Lt)? {
Some(result) => Ok(result),
None => Err(self.compare_error(a, b)),
}
}
}
}
pub fn api_equal(&mut self, a: Val, b: Val) -> LuaResult<bool> {
use super::metatable::{TMS, gettmbyobj, val_raw_equal};
if val_raw_equal(a, b, &self.gc.tables, &self.gc.string_arena) {
return Ok(true);
}
if std::mem::discriminant(&a) != std::mem::discriminant(&b) {
return Ok(false);
}
if !matches!(a, Val::Table(_) | Val::Userdata(_)) {
return Ok(false);
}
let tm1 = gettmbyobj(
a,
TMS::Eq,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
);
let Some(tm1_val) = tm1 else {
return Ok(false);
};
let tm2 = gettmbyobj(
b,
TMS::Eq,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
);
let tm2_val = tm2.unwrap_or(Val::Nil);
if !val_raw_equal(tm1_val, tm2_val, &self.gc.tables, &self.gc.string_arena) {
return Ok(false);
}
let call_base = self.top;
self.ensure_stack(call_base + 4);
self.stack_set(call_base, tm1_val);
self.stack_set(call_base + 1, a);
self.stack_set(call_base + 2, b);
self.top = call_base + 3;
self.call_function(call_base, 1)?;
let result = self.stack_get(call_base);
self.top = call_base;
Ok(result.is_truthy())
}
pub fn api_concat(&mut self, count: usize) -> LuaResult<()> {
use super::metatable::{TMS, gettmbyobj};
if count == 0 {
let s = self.gc.intern_string(b"");
self.push(Val::Str(s));
return Ok(());
}
if count == 1 {
return Ok(());
}
let mut total = count;
let result_pos = self.top - count;
while total > 1 {
let top = result_pos + total;
let lhs = self.stack_get(top - 2);
let rhs = self.stack_get(top - 1);
let lhs_ok = self.is_string_or_number(lhs);
let rhs_ok = self.is_string_or_number(rhs);
if !lhs_ok || !rhs_ok {
let tm = gettmbyobj(
lhs,
TMS::Concat,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
)
.or_else(|| {
gettmbyobj(
rhs,
TMS::Concat,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
)
});
if let Some(tm_val) = tm {
let call_base = self.top;
self.ensure_stack(call_base + 4);
self.stack_set(call_base, tm_val);
self.stack_set(call_base + 1, lhs);
self.stack_set(call_base + 2, rhs);
self.top = call_base + 3;
self.call_function(call_base, 1)?;
let result = self.stack_get(call_base);
self.stack_set(top - 2, result);
self.top = top - 1;
} else {
let type_name = if lhs_ok {
rhs.type_name()
} else {
lhs.type_name()
};
return Err(crate::error::LuaError::Runtime(
crate::error::RuntimeError {
message: format!("attempt to concatenate a {type_name} value"),
level: 0,
traceback: vec![],
},
));
}
total -= 1;
} else {
let mut n = 2;
while n < total && self.is_string_or_number(self.stack_get(top - n - 1)) {
n += 1;
}
let mut buffer = Vec::new();
for i in (0..n).rev() {
let val = self.stack_get(top - 1 - i);
self.val_to_string_bytes(val, &mut buffer);
}
let r = self.gc.intern_string(&buffer);
self.stack_set(top - n, Val::Str(r));
total -= n - 1;
}
}
self.top = result_pos + 1;
Ok(())
}
fn is_string_or_number(&self, val: Val) -> bool {
matches!(val, Val::Num(_))
|| matches!(val, Val::Str(r) if self.gc.string_arena.get(r).is_some())
}
fn val_to_string_bytes(&self, val: Val, buffer: &mut Vec<u8>) {
match val {
Val::Str(r) => {
if let Some(s) = self.gc.string_arena.get(r) {
buffer.extend_from_slice(s.data());
}
}
Val::Num(_) => {
let formatted = format!("{val}");
buffer.extend_from_slice(formatted.as_bytes());
}
_ => {}
}
}
#[allow(clippy::unused_self)]
fn compare_error(&self, a: Val, b: Val) -> crate::error::LuaError {
crate::error::LuaError::Runtime(crate::error::RuntimeError {
message: format!(
"attempt to compare {} with {}",
a.type_name(),
b.type_name()
),
level: 0,
traceback: vec![],
})
}
fn call_order_tm_api(
&mut self,
lhs: Val,
rhs: Val,
event: super::metatable::TMS,
) -> LuaResult<Option<bool>> {
use super::metatable::{gettmbyobj, val_raw_equal};
let tm1 = gettmbyobj(
lhs,
event,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
);
let Some(tm1_val) = tm1 else {
return Ok(None);
};
let tm2 = gettmbyobj(
rhs,
event,
&self.gc.tables,
&self.gc.string_arena,
&self.gc.type_metatables,
&self.gc.tm_names,
&self.gc.userdata,
);
let tm2_val = tm2.unwrap_or(Val::Nil);
if !val_raw_equal(tm1_val, tm2_val, &self.gc.tables, &self.gc.string_arena) {
return Ok(None);
}
let call_base = self.top;
self.ensure_stack(call_base + 4);
self.stack_set(call_base, tm1_val);
self.stack_set(call_base + 1, lhs);
self.stack_set(call_base + 2, rhs);
self.top = call_base + 3;
self.call_function(call_base, 1)?;
let result = self.stack_get(call_base);
self.top = call_base;
Ok(Some(result.is_truthy()))
}
#[inline]
pub fn ci(&self) -> &CallInfo {
&self.call_stack[self.ci]
}
#[inline]
pub fn ci_mut(&mut self) -> &mut CallInfo {
&mut self.call_stack[self.ci]
}
pub fn push_ci(&mut self, ci: CallInfo) -> &mut CallInfo {
let new_idx = self.ci + 1;
if new_idx < self.call_stack.len() {
self.call_stack[new_idx] = ci;
} else {
self.call_stack.push(ci);
}
self.ci = new_idx;
&mut self.call_stack[self.ci]
}
pub fn pop_ci(&mut self) {
if self.ci > 0 {
self.ci -= 1;
}
}
#[inline]
pub fn get_nargs(&self, func_idx: usize) -> usize {
if self.top > func_idx + 1 {
self.top - func_idx - 1
} else {
0
}
}
pub fn save_thread_state(&mut self) -> LuaThread {
LuaThread {
stack: std::mem::take(&mut self.stack),
base: self.base,
top: self.top,
call_stack: std::mem::take(&mut self.call_stack),
ci: self.ci,
n_ccalls: self.n_ccalls,
call_depth: self.call_depth,
ci_overflow: self.ci_overflow,
open_upvalues: std::mem::take(&mut self.open_upvalues),
suspended_upvals: Vec::new(),
error_object: self.error_object.take(),
status: ThreadStatus::Normal,
global: self.global,
hook: self.hook.clone(),
yielded_in_hook: self.yielded_in_hook,
}
}
pub fn load_thread_by_ref(&mut self, co_ref: GcRef<LuaThread>, new_status: ThreadStatus) {
if let Some(thread) = self.gc.threads.get_mut(co_ref) {
thread.status = new_status;
self.stack = std::mem::take(&mut thread.stack);
self.base = thread.base;
self.top = thread.top;
self.call_stack = std::mem::take(&mut thread.call_stack);
self.ci = thread.ci;
self.n_ccalls = thread.n_ccalls;
self.call_depth = thread.call_depth;
self.ci_overflow = thread.ci_overflow;
self.open_upvalues = std::mem::take(&mut thread.open_upvalues);
self.error_object = thread.error_object.take();
self.global = thread.global;
self.hook = std::mem::take(&mut thread.hook);
self.yielded_in_hook = thread.yielded_in_hook;
let suspended = std::mem::take(&mut thread.suspended_upvals);
for (uv_ref, idx) in &suspended {
if let Some(uv) = self.gc.upvalues.get(*uv_ref)
&& let crate::vm::closure::UpvalueState::Closed { value } = uv.state
&& *idx < self.stack.len()
{
self.stack[*idx] = value;
}
if let Some(uv) = self.gc.upvalues.get_mut(*uv_ref) {
uv.state = crate::vm::closure::UpvalueState::Open { stack_index: *idx };
}
if !self.open_upvalues.contains(uv_ref) {
self.open_upvalues.push(*uv_ref);
}
}
self.open_upvalues.sort_by(|a, b| {
let a_idx = self
.gc
.upvalues
.get(*a)
.and_then(super::closure::Upvalue::stack_index)
.unwrap_or(0);
let b_idx = self
.gc
.upvalues
.get(*b)
.and_then(super::closure::Upvalue::stack_index)
.unwrap_or(0);
b_idx.cmp(&a_idx)
});
}
}
pub fn save_and_restore_by_ref(
&mut self,
co_ref: GcRef<LuaThread>,
co_status: ThreadStatus,
resumer: LuaThread,
) {
let mut suspended = Vec::new();
for &uv_ref in &self.open_upvalues {
if let Some(uv) = self.gc.upvalues.get(uv_ref)
&& let Some(idx) = uv.stack_index()
{
suspended.push((uv_ref, idx));
}
}
for &(uv_ref, _) in &suspended {
if let Some(uv) = self.gc.upvalues.get_mut(uv_ref) {
uv.close(&self.stack);
}
}
if let Some(co_thread) = self.gc.threads.get_mut(co_ref) {
co_thread.stack = std::mem::take(&mut self.stack);
co_thread.base = self.base;
co_thread.top = self.top;
co_thread.call_stack = std::mem::take(&mut self.call_stack);
co_thread.ci = self.ci;
co_thread.n_ccalls = self.n_ccalls;
co_thread.call_depth = self.call_depth;
co_thread.ci_overflow = self.ci_overflow;
co_thread.open_upvalues = std::mem::take(&mut self.open_upvalues);
co_thread.suspended_upvals = suspended;
co_thread.error_object = self.error_object.take();
co_thread.global = self.global;
co_thread.hook = std::mem::take(&mut self.hook);
co_thread.yielded_in_hook = self.yielded_in_hook;
co_thread.status = co_status;
}
self.stack = resumer.stack;
self.base = resumer.base;
self.top = resumer.top;
self.call_stack = resumer.call_stack;
self.ci = resumer.ci;
self.n_ccalls = resumer.n_ccalls;
self.call_depth = resumer.call_depth;
self.ci_overflow = resumer.ci_overflow;
self.open_upvalues = resumer.open_upvalues;
self.error_object = resumer.error_object;
self.global = resumer.global;
self.hook = resumer.hook;
self.yielded_in_hook = resumer.yielded_in_hook;
for (uv_ref, idx) in resumer.suspended_upvals {
if let Some(uv) = self.gc.upvalues.get(uv_ref)
&& let crate::vm::closure::UpvalueState::Closed { value } = uv.state
&& idx < self.stack.len()
{
self.stack[idx] = value;
}
if let Some(uv) = self.gc.upvalues.get_mut(uv_ref) {
uv.state = crate::vm::closure::UpvalueState::Open { stack_index: idx };
}
if !self.open_upvalues.contains(&uv_ref) {
self.open_upvalues.push(uv_ref);
}
}
}
}
impl Default for LuaState {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_state_has_stack() {
let state = LuaState::new();
assert_eq!(state.stack.len(), BASIC_STACK_SIZE);
assert_eq!(state.top, 1);
assert_eq!(state.base, 1);
}
#[test]
fn new_state_has_initial_ci() {
let state = LuaState::new();
assert_eq!(state.call_stack.len(), 1);
assert_eq!(state.ci, 0);
let ci = state.ci();
assert_eq!(ci.func, 0);
assert_eq!(ci.base, 1);
assert_eq!(ci.top, 1 + LUA_MINSTACK);
assert_eq!(ci.num_results, LUA_MULTRET);
}
#[test]
fn new_state_has_global_table() {
let state = LuaState::new();
assert!(state.gc.tables.is_valid(state.global));
}
#[test]
fn new_state_has_registry() {
let state = LuaState::new();
assert!(state.gc.tables.is_valid(state.registry));
}
#[test]
fn new_state_gc_initialized() {
let state = LuaState::new();
assert_eq!(state.gc.tables.len(), 2);
assert_eq!(state.gc.string_arena.len(), TM_N as u32);
assert_eq!(state.gc.closures.len(), 0);
assert_eq!(state.gc.upvalues.len(), 0);
assert_eq!(state.gc.userdata.len(), 0);
}
#[test]
fn new_state_no_ccalls() {
let state = LuaState::new();
assert_eq!(state.n_ccalls, 0);
}
#[test]
fn new_state_no_open_upvalues() {
let state = LuaState::new();
assert!(state.open_upvalues.is_empty());
}
#[test]
fn new_state_no_hooks() {
let state = LuaState::new();
assert_eq!(state.hook.hook_mask, 0);
assert!(state.hook.hook_func.is_nil());
assert!(state.hook.allow_hook);
assert_eq!(state.hook.base_hook_count, 0);
assert_eq!(state.hook.hook_count, 0);
}
#[test]
fn stack_get_valid_index() {
let mut state = LuaState::new();
state.stack[5] = Val::Num(42.0);
assert_eq!(state.stack_get(5), Val::Num(42.0));
}
#[test]
fn stack_get_out_of_bounds() {
let state = LuaState::new();
assert!(state.stack_get(1000).is_nil());
}
#[test]
fn stack_set_within_bounds() {
let mut state = LuaState::new();
state.stack_set(5, Val::Num(99.0));
assert_eq!(state.stack[5], Val::Num(99.0));
}
#[test]
fn stack_set_grows_stack() {
let mut state = LuaState::new();
let old_len = state.stack.len();
state.stack_set(old_len + 10, Val::Bool(true));
assert!(state.stack.len() > old_len);
assert_eq!(state.stack[old_len + 10], Val::Bool(true));
}
#[test]
fn ensure_stack_no_growth_needed() {
let mut state = LuaState::new();
let old_len = state.stack.len();
state.ensure_stack(5);
assert_eq!(state.stack.len(), old_len);
}
#[test]
fn ensure_stack_grows() {
let mut state = LuaState::new();
state.top = BASIC_STACK_SIZE - 2;
state.ensure_stack(10);
assert!(state.stack.len() >= BASIC_STACK_SIZE - 2 + 10);
}
#[test]
fn push_and_pop() {
let mut state = LuaState::new();
state.push(Val::Num(1.0));
state.push(Val::Num(2.0));
state.push(Val::Num(3.0));
assert_eq!(state.top, 4); assert_eq!(state.pop(), Val::Num(3.0));
assert_eq!(state.pop(), Val::Num(2.0));
assert_eq!(state.pop(), Val::Num(1.0));
assert_eq!(state.top, 1);
}
#[test]
fn pop_empty_returns_nil() {
let mut state = LuaState::new();
state.top = 0;
assert!(state.pop().is_nil());
}
#[test]
fn push_grows_stack_if_needed() {
let mut state = LuaState::new();
state.top = state.stack.len();
state.push(Val::Num(42.0));
assert_eq!(state.stack_get(state.top - 1), Val::Num(42.0));
}
#[test]
fn ci_returns_current_frame() {
let state = LuaState::new();
assert_eq!(state.ci().func, 0);
assert_eq!(state.ci().base, 1);
}
#[test]
fn ci_mut_allows_modification() {
let mut state = LuaState::new();
state.ci_mut().saved_pc = 10;
assert_eq!(state.ci().saved_pc, 10);
}
#[test]
fn push_and_pop_ci() {
let mut state = LuaState::new();
assert_eq!(state.ci, 0);
let new_ci = CallInfo::new(5, 6, 26, 1);
state.push_ci(new_ci);
assert_eq!(state.ci, 1);
assert_eq!(state.ci().func, 5);
assert_eq!(state.ci().base, 6);
state.pop_ci();
assert_eq!(state.ci, 0);
assert_eq!(state.ci().func, 0);
}
#[test]
fn nested_ci_push_pop() {
let mut state = LuaState::new();
state.push_ci(CallInfo::new(5, 6, 26, 1));
state.push_ci(CallInfo::new(10, 11, 31, 2));
state.push_ci(CallInfo::new(15, 16, 36, 3));
assert_eq!(state.ci, 3);
assert_eq!(state.call_stack.len(), 4);
state.pop_ci();
assert_eq!(state.ci, 2);
assert_eq!(state.ci().func, 10);
state.pop_ci();
assert_eq!(state.ci, 1);
assert_eq!(state.ci().func, 5);
state.pop_ci();
assert_eq!(state.ci, 0);
assert_eq!(state.ci().func, 0);
}
#[test]
fn pop_ci_does_not_underflow() {
let mut state = LuaState::new();
state.pop_ci(); assert_eq!(state.ci, 0);
}
#[test]
fn gc_intern_string() {
let mut state = LuaState::new();
let r = state.gc.intern_string(b"hello");
assert!(state.gc.string_arena.is_valid(r));
let s = state.gc.string_arena.get(r);
assert!(s.is_some());
assert_eq!(s.map(LuaString::data), Some(b"hello".as_ref()));
}
#[test]
fn gc_intern_string_dedup() {
let mut state = LuaState::new();
let before = state.gc.string_arena.len();
let r1 = state.gc.intern_string(b"test");
let r2 = state.gc.intern_string(b"test");
assert_eq!(r1, r2);
assert_eq!(state.gc.string_arena.len(), before + 1);
}
#[test]
fn gc_alloc_table() {
let mut state = LuaState::new();
let t = state.gc.alloc_table(Table::new());
assert!(state.gc.tables.is_valid(t));
assert_eq!(state.gc.tables.len(), 3);
}
#[test]
fn get_nargs_with_args() {
let mut state = LuaState::new();
state.top = 9;
assert_eq!(state.get_nargs(5), 3);
}
#[test]
fn get_nargs_no_args() {
let mut state = LuaState::new();
state.top = 6;
assert_eq!(state.get_nargs(5), 0);
}
#[test]
fn get_nargs_top_before_func() {
let mut state = LuaState::new();
state.top = 3;
assert_eq!(state.get_nargs(5), 0);
}
#[test]
fn constants_match_puc_rio() {
assert_eq!(MAXCALLS, 20_000);
assert_eq!(MAXCCALLS, 200);
assert_eq!(LUA_MINSTACK, 20);
assert_eq!(BASIC_STACK_SIZE, 40);
assert_eq!(BASIC_CI_SIZE, 8);
}
#[test]
fn default_creates_new_state() {
let state = LuaState::default();
assert_eq!(state.call_stack.len(), 1);
assert_eq!(state.stack.len(), BASIC_STACK_SIZE);
}
#[test]
fn hook_mask_values() {
assert_eq!(MASK_CALL, 1);
assert_eq!(MASK_RET, 2);
assert_eq!(MASK_LINE, 4);
assert_eq!(MASK_COUNT, 8);
}
}