use std::borrow::Cow;
#[allow(unused_imports)]
use crate::prelude::*;
use crate::state::LuaState;
use lua_types::{CallInfoIdx, GcRef, LuaError, LuaType, LuaValue, StackIdx};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub(crate) enum TagMethod {
Index = 0,
NewIndex,
Gc,
Mode,
Len,
Eq,
Add,
Sub,
Mul,
Mod,
Pow,
Div,
IDiv,
BAnd,
BOr,
BXor,
Shl,
Shr,
Unm,
BNot,
Lt,
Le,
Concat,
Call,
Close,
N,
}
impl TagMethod {
pub(crate) fn from_u8(v: u8) -> Self {
match v {
0 => TagMethod::Index,
1 => TagMethod::NewIndex,
2 => TagMethod::Gc,
3 => TagMethod::Mode,
4 => TagMethod::Len,
5 => TagMethod::Eq,
6 => TagMethod::Add,
7 => TagMethod::Sub,
8 => TagMethod::Mul,
9 => TagMethod::Mod,
10 => TagMethod::Pow,
11 => TagMethod::Div,
12 => TagMethod::IDiv,
13 => TagMethod::BAnd,
14 => TagMethod::BOr,
15 => TagMethod::BXor,
16 => TagMethod::Shl,
17 => TagMethod::Shr,
18 => TagMethod::Unm,
19 => TagMethod::BNot,
20 => TagMethod::Lt,
21 => TagMethod::Le,
22 => TagMethod::Concat,
23 => TagMethod::Call,
24 => TagMethod::Close,
_ => TagMethod::N,
}
}
}
pub(crate) const TM_N: usize = TagMethod::N as usize;
pub(crate) static TYPE_NAMES: &[&[u8]] = &[
b"no value", b"nil", b"boolean", b"userdata", b"number", b"string", b"table", b"function", b"userdata", b"thread", b"upvalue", b"proto", ];
pub(crate) fn type_name(t: LuaType) -> &'static [u8] {
let idx = (t as i32 + 1) as usize;
TYPE_NAMES.get(idx).copied().unwrap_or(b"?")
}
pub(crate) fn init(state: &mut LuaState) -> Result<(), LuaError> {
const EVENT_NAMES: &[&[u8]] = &[
b"__index",
b"__newindex",
b"__gc",
b"__mode",
b"__len",
b"__eq",
b"__add",
b"__sub",
b"__mul",
b"__mod",
b"__pow",
b"__div",
b"__idiv",
b"__band",
b"__bor",
b"__bxor",
b"__shl",
b"__shr",
b"__unm",
b"__bnot",
b"__lt",
b"__le",
b"__concat",
b"__call",
b"__close",
];
debug_assert!(EVENT_NAMES.len() == TM_N);
if state.global().tmname.len() < TM_N {
let pad = state.intern_str(b"")?;
state.global_mut().tmname.resize(TM_N, pad);
}
for (i, &name) in EVENT_NAMES.iter().enumerate() {
let interned = state.intern_str(name)?;
state.global_mut().tmname[i] = interned.clone();
state.gc().fix_object(&interned);
}
Ok(())
}
pub(crate) fn get_tm_by_obj(state: &mut LuaState, o: &LuaValue, event: TagMethod) -> LuaValue {
let mt: Option<GcRef<lua_types::value::LuaTable>> = match o {
LuaValue::Table(t) => t.metatable(),
LuaValue::UserData(u) => u.metatable(),
_ => {
let type_idx = o.base_type() as usize;
state.global().mt[type_idx].clone()
}
};
match mt {
Some(mt_ref) => {
let ename = state.global().tmname[event as usize].clone();
mt_ref.get_short_str(&ename)
}
None => LuaValue::Nil,
}
}
pub(crate) fn obj_type_name_cow(o: &LuaValue, honors_name: bool) -> Cow<'static, [u8]> {
if matches!(o, LuaValue::LightUserData(_)) {
return Cow::Borrowed(b"light userdata");
}
let mt: Option<GcRef<lua_types::value::LuaTable>> = match o {
LuaValue::Table(t) => t.metatable(),
LuaValue::UserData(u) => u.metatable(),
_ => None,
};
if honors_name {
if let Some(mt_ref) = mt {
let name_val = mt_ref.get_str_bytes(b"__name");
if let LuaValue::Str(s) = name_val {
return Cow::Owned(s.as_bytes().to_vec());
}
}
}
Cow::Borrowed(type_name(o.base_type()))
}
pub(crate) fn obj_type_name(state: &mut LuaState, o: &LuaValue) -> Result<Vec<u8>, LuaError> {
let honors_name = state.global().lua_version.honors_name_metafield();
Ok(obj_type_name_cow(o, honors_name).into_owned())
}
pub(crate) fn call_tm(
state: &mut LuaState,
f: LuaValue,
p1: LuaValue,
p2: LuaValue,
p3: LuaValue,
) -> Result<(), LuaError> {
let func = state.top_idx();
state.push(f);
state.push(p1);
state.push(p2);
state.push(p3);
if state.current_ci().is_lua_code() {
state.do_call(func, 0)?;
} else {
state.do_call_no_yield(func, 0)?;
}
Ok(())
}
pub(crate) fn call_tm_res(
state: &mut LuaState,
f: LuaValue,
p1: LuaValue,
p2: LuaValue,
res: StackIdx,
) -> Result<(), LuaError> {
let func = state.top_idx();
state.push(f);
state.push(p1);
state.push(p2);
if state.current_ci().is_lua_code() {
state.do_call(func, 1)?;
} else {
state.do_call_no_yield(func, 1)?;
}
let result_val = state.pop();
state.set_at(res, result_val);
Ok(())
}
fn call_bin_tm(
state: &mut LuaState,
p1: &LuaValue,
p2: &LuaValue,
res: StackIdx,
event: TagMethod,
) -> Result<bool, LuaError> {
let tm = get_tm_by_obj(state, p1, event);
let tm = if tm.is_nil() {
get_tm_by_obj(state, p2, event)
} else {
tm
};
if tm.is_nil() {
return Ok(false);
}
call_tm_res(state, tm, p1.clone(), p2.clone(), res)?;
Ok(true)
}
fn try_bitwise_strconv_53(
state: &mut LuaState,
p1: &LuaValue,
p1_idx: Option<StackIdx>,
p2: &LuaValue,
p2_idx: Option<StackIdx>,
res: StackIdx,
event: TagMethod,
) -> Option<Result<(), LuaError>> {
let n1 = p1.to_number_with_strconv();
let n2 = p2.to_number_with_strconv();
if n1.is_none() || n2.is_none() {
return None;
}
let i1 = p1.to_integer_with_strconv();
let i2 = p2.to_integer_with_strconv();
let (i1, i2) = match (i1, i2) {
(Some(a), Some(b)) => (a, b),
_ => {
let p1_idx = p1_idx.unwrap_or(StackIdx(0));
let p2_idx = p2_idx.unwrap_or(StackIdx(0));
return Some(Err(crate::debug::to_int_error(
state,
p1,
Some(p1_idx),
p2,
Some(p2_idx),
)));
}
};
let result = match event {
TagMethod::BAnd => i1 & i2,
TagMethod::BOr => i1 | i2,
TagMethod::BXor => i1 ^ i2,
TagMethod::Shl => crate::vm::shiftl(i1, i2),
TagMethod::Shr => crate::vm::shiftl(i1, i2.wrapping_neg()),
TagMethod::BNot => !i1,
_ => return None,
};
state.set_at(res, LuaValue::Int(result));
Some(Ok(()))
}
pub(crate) fn try_bin_tm(
state: &mut LuaState,
p1: &LuaValue,
p1_idx: Option<StackIdx>,
p2: &LuaValue,
p2_idx: Option<StackIdx>,
res: StackIdx,
event: TagMethod,
) -> Result<(), LuaError> {
if matches!(
state.global().lua_version,
lua_types::LuaVersion::V51 | lua_types::LuaVersion::V52 | lua_types::LuaVersion::V53
)
&& matches!(
event,
TagMethod::Add
| TagMethod::Sub
| TagMethod::Mul
| TagMethod::Mod
| TagMethod::Pow
| TagMethod::Div
| TagMethod::IDiv
| TagMethod::Unm
)
&& (matches!(p1, LuaValue::Str(_)) || matches!(p2, LuaValue::Str(_)))
{
use crate::state::LuaValueExt;
let p1_num = p1.to_number_with_strconv().is_some();
let p2_num = p2.to_number_with_strconv().is_some();
if !(p1_num && p2_num) {
let unary = matches!(event, TagMethod::Unm);
let other_has_mm = !unary
&& if matches!(p1, LuaValue::Str(_)) {
!get_tm_by_obj(state, p2, event).is_nil()
} else {
!get_tm_by_obj(state, p1, event).is_nil()
};
if !other_has_mm {
let (bad, bad_idx) = if !p1_num {
(p1, p1_idx.unwrap_or(StackIdx(0)))
} else {
(p2, p2_idx.unwrap_or(StackIdx(0)))
};
return Err(crate::debug::arith_type_error(
state,
bad,
bad_idx,
b"perform arithmetic on",
!unary,
));
}
}
}
if !call_bin_tm(state, p1, p2, res, event)? {
if matches!(state.global().lua_version, lua_types::LuaVersion::V53)
&& matches!(
event,
TagMethod::BAnd
| TagMethod::BOr
| TagMethod::BXor
| TagMethod::Shl
| TagMethod::Shr
| TagMethod::BNot
)
&& (matches!(p1, LuaValue::Str(_)) || matches!(p2, LuaValue::Str(_)))
{
if let Some(result) = try_bitwise_strconv_53(state, p1, p1_idx, p2, p2_idx, res, event)
{
return result;
}
}
match event {
TagMethod::BAnd
| TagMethod::BOr
| TagMethod::BXor
| TagMethod::Shl
| TagMethod::Shr
| TagMethod::BNot => {
if matches!(p1, LuaValue::Int(_) | LuaValue::Float(_))
&& matches!(p2, LuaValue::Int(_) | LuaValue::Float(_))
{
return Err(crate::debug::to_int_error(state, p1, p1_idx, p2, p2_idx));
} else {
let p1_idx = p1_idx.unwrap_or(StackIdx(0));
let p2_idx = p2_idx.unwrap_or(StackIdx(0));
return Err(crate::debug::op_int_error(
state,
p1,
p1_idx,
p2,
p2_idx,
b"perform bitwise operation on",
));
}
}
_ => {
let p1_idx = p1_idx.unwrap_or(StackIdx(0));
let p2_idx = p2_idx.unwrap_or(StackIdx(0));
return Err(crate::debug::op_int_error(
state,
p1,
p1_idx,
p2,
p2_idx,
b"perform arithmetic on",
));
}
}
}
Ok(())
}
pub(crate) fn try_concat_tm(state: &mut LuaState) -> Result<(), LuaError> {
let top = state.top_idx();
let p1 = state.get_at(top - 2).clone();
let p2 = state.get_at(top - 1).clone();
if !call_bin_tm(state, &p1, &p2, top - 2, TagMethod::Concat)? {
let p1_ok = matches!(p1, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_));
let (bad, bad_idx) = if p1_ok {
(&p2, top - 1)
} else {
(&p1, top - 2)
};
return Err(crate::debug::type_error(
state,
bad,
bad_idx,
b"concatenate",
));
}
Ok(())
}
pub(crate) fn try_bin_assoc_tm(
state: &mut LuaState,
p1: &LuaValue,
p1_idx: Option<StackIdx>,
p2: &LuaValue,
p2_idx: Option<StackIdx>,
flip: bool,
res: StackIdx,
event: TagMethod,
) -> Result<(), LuaError> {
if flip {
try_bin_tm(state, p2, p2_idx, p1, p1_idx, res, event)
} else {
try_bin_tm(state, p1, p1_idx, p2, p2_idx, res, event)
}
}
pub(crate) fn try_bini_tm(
state: &mut LuaState,
p1: &LuaValue,
p1_idx: Option<StackIdx>,
i2: i64,
flip: bool,
res: StackIdx,
event: TagMethod,
) -> Result<(), LuaError> {
let aux = LuaValue::Int(i2);
try_bin_assoc_tm(state, p1, p1_idx, &aux, None, flip, res, event)
}
fn operand_metatable(
state: &LuaState,
o: &LuaValue,
) -> Option<GcRef<lua_types::value::LuaTable>> {
match o {
LuaValue::Table(t) => t.metatable(),
LuaValue::UserData(u) => u.metatable(),
_ => {
let type_idx = o.base_type() as usize;
state.global().mt[type_idx].clone()
}
}
}
pub(crate) fn get_comp_tm_51(
state: &mut LuaState,
p1: &LuaValue,
p2: &LuaValue,
event: TagMethod,
) -> LuaValue {
let tm1 = get_tm_by_obj(state, p1, event);
if tm1.is_nil() {
return LuaValue::Nil;
}
let mt1 = operand_metatable(state, p1);
let mt2 = operand_metatable(state, p2);
if let (Some(a), Some(b)) = (&mt1, &mt2) {
if GcRef::ptr_eq(a, b) {
return tm1;
}
}
let tm2 = get_tm_by_obj(state, p2, event);
if tm2.is_nil() {
return LuaValue::Nil;
}
if crate::vm::raw_equal_values(&tm1, &tm2) {
tm1
} else {
LuaValue::Nil
}
}
pub(crate) fn call_order_tm(
state: &mut LuaState,
p1: &LuaValue,
p2: &LuaValue,
event: TagMethod,
) -> Result<bool, LuaError> {
{
use crate::state::LuaValueExt;
if state.global().lua_version == lua_types::LuaVersion::V51
&& p1.base_type() != p2.base_type()
{
return Err(crate::debug::order_error(state, p1, p2));
}
}
if state.global().lua_version == lua_types::LuaVersion::V51 {
let res_idx = state.top_idx();
let tm = get_comp_tm_51(state, p1, p2, event);
if !tm.is_nil() {
call_tm_res(state, tm, p1.clone(), p2.clone(), res_idx)?;
let result = state.get_at(res_idx).clone();
return Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
}
if event == TagMethod::Le {
let tm = get_comp_tm_51(state, p2, p1, TagMethod::Lt);
if !tm.is_nil() {
state.current_call_info_mut().callstatus |= crate::state::CIST_LEQ;
call_tm_res(state, tm, p2.clone(), p1.clone(), res_idx)?;
state.current_call_info_mut().callstatus &= !crate::state::CIST_LEQ;
let result = state.get_at(res_idx).clone();
return Ok(matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
}
}
return Err(crate::debug::order_error(state, p1, p2));
}
let res_idx = state.top_idx();
if call_bin_tm(state, p1, p2, res_idx, event)? {
let result = state.get_at(res_idx).clone();
return Ok(!matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
}
if event == TagMethod::Le
&& matches!(
state.global().lua_version,
lua_types::LuaVersion::V51
| lua_types::LuaVersion::V52
| lua_types::LuaVersion::V53
| lua_types::LuaVersion::V54
)
{
let res_idx = state.top_idx();
state.current_call_info_mut().callstatus |= crate::state::CIST_LEQ;
let called = call_bin_tm(state, p2, p1, res_idx, TagMethod::Lt)?;
state.current_call_info_mut().callstatus &= !crate::state::CIST_LEQ;
if called {
let result = state.get_at(res_idx).clone();
return Ok(matches!(result, LuaValue::Nil | LuaValue::Bool(false)));
}
}
Err(crate::debug::order_error(state, p1, p2))
}
pub(crate) fn call_orderi_tm(
state: &mut LuaState,
p1: &LuaValue,
v2: i32,
flip: bool,
isfloat: bool,
event: TagMethod,
) -> Result<bool, LuaError> {
let aux = if isfloat {
LuaValue::Float(v2 as f64)
} else {
LuaValue::Int(v2 as i64)
};
if flip {
call_order_tm(state, &aux, p1, event)
} else {
call_order_tm(state, p1, &aux, event)
}
}
pub(crate) fn adjust_varargs(
state: &mut LuaState,
nfixparams: i32,
ci_idx: CallInfoIdx,
proto: &GcRef<lua_types::LuaProto>,
) -> Result<(), LuaError> {
let ci_func: StackIdx = state.call_info[ci_idx.as_usize()].func;
let actual = (state.top_idx().0 as i32) - (ci_func.0 as i32) - 1;
let nextra = actual - nfixparams;
state.call_info[ci_idx.as_usize()].set_nextra_args(nextra);
let maxstacksize = proto.maxstacksize as i32;
state.check_stack(maxstacksize + 1)?;
let ci_func: StackIdx = state.call_info[ci_idx.as_usize()].func;
let func_val = state.get_at(ci_func).clone();
state.push(func_val);
for i in 1..=nfixparams {
let src: StackIdx = ci_func + i as i32;
let param_val = state.get_at(src).clone();
state.push(param_val);
state.set_at(src, LuaValue::Nil);
}
let offset = (actual + 1) as i32;
state.call_info[ci_idx.as_usize()].func = state.call_info[ci_idx.as_usize()].func + offset;
state.call_info[ci_idx.as_usize()].top = state.call_info[ci_idx.as_usize()].top + offset;
if state.global().lua_version == lua_types::LuaVersion::V55 {
let base = state.call_info[ci_idx.as_usize()].func + 1;
state.set_at(base + nfixparams, LuaValue::Nil);
}
build_legacy_arg_table(state, ci_idx, proto, nextra)?;
debug_assert!(state.top_idx().0 <= state.call_info[ci_idx.as_usize()].top.0);
Ok(())
}
fn build_legacy_arg_table(
state: &mut LuaState,
ci_idx: CallInfoIdx,
proto: &GcRef<lua_types::LuaProto>,
nextra: i32,
) -> Result<(), LuaError> {
if state.global().lua_version != lua_types::LuaVersion::V51 {
return Ok(());
}
let Some(arg_reg) = legacy_arg_table_reg(proto) else {
return Ok(());
};
let base = state.call_info[ci_idx.as_usize()].func + 1;
let ra = base + arg_reg as i32;
let ci_func = base - 1;
let t = if nextra > 0 {
state.new_table_with_sizes(nextra as u32, 1)?
} else {
state.new_table()
};
for k in 0..nextra {
let src: StackIdx = ci_func - nextra + k;
let val = state.get_at(src);
t.raw_set_int(state, (k + 1) as i64, val)?;
}
let n_key = state.intern_str(b"n")?;
t.raw_set(state, LuaValue::Str(n_key), LuaValue::Int(nextra as i64))?;
state.set_at(ra, LuaValue::Table(t));
Ok(())
}
fn legacy_arg_table_reg(proto: &GcRef<lua_types::LuaProto>) -> Option<u8> {
for inst in proto.code.iter() {
if inst.opcode() == OpCode::VarArgPack {
if inst.test_k() {
return Some(inst.arg_a() as u8);
}
return None;
}
}
None
}
pub(crate) fn get_varargs(
state: &mut LuaState,
ci_idx: CallInfoIdx,
where_idx: StackIdx,
wanted: i32,
) -> Result<(), LuaError> {
let vatab_reg = state
.ci_lua_closure(ci_idx)
.and_then(|cl| cl.proto.vararg_table_reg);
if let Some(reg) = vatab_reg {
let base = state.ci_base(ci_idx);
if let LuaValue::Table(t) = state.get_at(base + reg as i32) {
let n_key = state.intern_str(b"n")?;
let nextra: i32 = match t.get(&LuaValue::Str(n_key)) {
LuaValue::Int(i) if i >= 0 && i <= (i32::MAX / 2) as i64 => i as i32,
_ => {
return Err(LuaError::runtime(format_args!(
"vararg table has no proper 'n'"
)));
}
};
let wanted: i32 = if wanted < 0 {
state.set_top(state.call_info[ci_idx.as_usize()].top);
state.check_stack(nextra)?;
state.gc_pre_collect_clear();
state.gc().check_step();
state.set_top(where_idx + nextra);
nextra
} else {
wanted
};
let copy_count = wanted.min(nextra);
for i in 0..copy_count {
let val = t.get(&LuaValue::Int((i + 1) as i64));
state.set_at(where_idx + i as i32, val);
}
for i in copy_count..wanted {
state.set_at(where_idx + i as i32, LuaValue::Nil);
}
return Ok(());
}
}
let nextra: i32 = state.call_info[ci_idx.as_usize()].nextra_args();
let wanted: i32 = if wanted < 0 {
state.set_top(state.call_info[ci_idx.as_usize()].top);
state.check_stack(nextra)?;
state.gc_pre_collect_clear();
state.gc().check_step();
state.set_top(where_idx + nextra as i32);
nextra
} else {
wanted
};
let ci_func: StackIdx = state.call_info[ci_idx.as_usize()].func;
let copy_count = wanted.min(nextra);
for i in 0..copy_count {
let src: StackIdx = ci_func - nextra as i32 + i as i32;
let val = state.get_at(src).clone();
state.set_at(where_idx + i as i32, val);
}
for i in copy_count..wanted {
state.set_at(where_idx + i as i32, LuaValue::Nil);
}
Ok(())
}