use std::borrow::Cow;
use crate::state::LuaState;
#[allow(unused_imports)] use crate::prelude::*;
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) -> 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 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> {
Ok(obj_type_name_cow(o).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)
}
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 !call_bin_tm(state, p1, p2, res, event)? {
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)? {
return Err(LuaError::concat_error(&p1, &p2));
}
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)
}
pub(crate) fn call_order_tm(
state: &mut LuaState,
p1: &LuaValue,
p2: &LuaValue,
event: TagMethod,
) -> Result<bool, LuaError> {
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)));
}
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;
if let crate::state::CallInfoFrame::Lua { ref mut nextraargs, .. } = state.call_info[ci_idx.as_usize()].u {
*nextraargs = 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;
debug_assert!(state.top_idx().0 <= state.call_info[ci_idx.as_usize()].top.0);
Ok(())
}
pub(crate) fn get_varargs(
state: &mut LuaState,
ci_idx: CallInfoIdx,
where_idx: StackIdx,
wanted: i32,
) -> Result<(), LuaError> {
let nextra: i32 = if let crate::state::CallInfoFrame::Lua { nextraargs, .. } = state.call_info[ci_idx.as_usize()].u { nextraargs } else { 0 };
let wanted: i32 = if wanted < 0 {
state.check_stack(nextra)?;
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(())
}