use crate::LuaType;
use crate::Result;
use crate::State;
use crate::error::{ArgError, ErrorKind, PrefixLocation};
use crate::numeral::parse_lua_numeral;
fn digit_value(byte: u8) -> Option<u32> {
match byte {
b'0'..=b'9' => Some((byte - b'0') as u32),
b'a'..=b'z' => Some((byte - b'a' + 10) as u32),
b'A'..=b'Z' => Some((byte - b'A' + 10) as u32),
_ => None,
}
}
fn parse_integer_with_base(bytes: &[u8], base: u32) -> Option<f64> {
let trimmed = bytes.trim_ascii();
let (negative, digits) = match trimmed {
[b'-', rest @ ..] => (true, rest),
[b'+', rest @ ..] => (false, rest),
_ => (false, trimmed),
};
if digits.is_empty() {
return None;
}
let mut value = 0.0;
for byte in digits {
let digit = digit_value(*byte)?;
if digit >= base {
return None;
}
value = value * f64::from(base) + f64::from(digit);
}
if negative { Some(-value) } else { Some(value) }
}
fn base_unpack_values(state: &mut State) -> Result<u8> {
super::unpack_values(state)
}
pub(crate) fn base_ipairs(state: &mut State) -> Result<u8> {
state.check_type(1, LuaType::Table)?;
state.set_top(1)?;
state.push_rust_fn(base_ipairs_iter)?;
state.push_value(1)?;
state.remove(1)?;
state.push_number(0.0)?;
Ok(3)
}
pub(crate) fn base_ipairs_iter(state: &mut State) -> Result<u8> {
state.check_type(1, LuaType::Table)?;
state.check_type(2, LuaType::Number)?;
state.set_top(2)?;
let old_index = state.to_number(2)?;
let new_index = old_index + 1.0;
state.pop(1)?; state.push_number(new_index)?;
state.get_table(1)?;
if state.typ(-1) != LuaType::Nil {
state.push_number(new_index)?;
state.replace(1)?; Ok(2)
} else {
state.set_top(0)?;
state.push_nil()?;
Ok(1)
}
}
pub(crate) fn base_next(state: &mut State) -> Result<u8> {
state.check_type(1, LuaType::Table)?;
if state.get_top() < 2 {
state.push_nil()?;
}
state.set_top(2)?;
let has_more = state.table_next(1)?;
if has_more {
state.remove(1)?;
Ok(2)
} else {
state.remove(1)?;
Ok(1)
}
}
pub(crate) fn base_pairs(state: &mut State) -> Result<u8> {
state.check_type(1, LuaType::Table)?;
state.set_top(1)?;
state.push_rust_fn(base_next)?;
state.push_value(1)?; state.push_nil()?; state.remove(1)?; Ok(3)
}
pub(crate) fn open_base(state: &mut State) -> Result<()> {
let mut add = |name, func| {
#[cfg(feature = "snapshot")]
state.set_global_stdlib_rust_fn(name, name, func);
#[cfg(not(feature = "snapshot"))]
state.set_global_rust_fn(name, func);
};
add("ipairs", base_ipairs);
add("next", base_next);
add("pairs", base_pairs);
add("print", |state| {
let top = state.get_top();
let mut message = String::new();
for i in 1..=top {
if i != 1 {
crate::vm::checked_string_growth(message.len(), 1)?;
message.push('\t');
}
if state.typ(i as isize) == LuaType::String {
let raw_len = state.to_bytes(i as isize)?.len();
crate::vm::checked_string_growth(message.len(), raw_len)?;
}
let part = state.to_string_with_meta(i as isize)?;
crate::vm::checked_string_growth(message.len(), part.len())?;
message.push_str(&part);
}
state.host_print(&message);
Ok(0)
});
add("error", |state| {
let message = if state.get_top() >= 1 {
state.to_string_with_meta(1)?
} else {
"(error raised with no message)".to_string()
};
let prefix_location = if state.get_top() < 2 {
PrefixLocation::Current
} else {
state.check_type(2, LuaType::Number)?;
let level = state.to_number(2)?;
if !level.is_finite() || level.fract() != 0.0 {
return Err(state.error(ErrorKind::RuntimeError(
"bad argument #2 to 'error' (number has no integer representation)".into(),
)));
}
if level <= 0.0 {
PrefixLocation::Suppressed
} else {
PrefixLocation::TraceFrame((level as u32).saturating_sub(1))
}
};
Err(state.error(ErrorKind::ScriptError {
message,
prefix: prefix_location,
}))
});
add("type", |state| {
state.check_any(1)?;
let typ = state.typ(1);
state.set_top(0)?;
state.push_string(typ.as_str())?;
Ok(1)
});
add("tonumber", |state| {
state.check_any(1)?;
if !state.is_none_or_nil(2) {
state.check_type(1, LuaType::String)?;
state.check_type(2, LuaType::Number)?;
let base_num = state.to_number(2)?;
let base = base_num as i64;
if !base_num.is_finite()
|| (base_num - base as f64).abs() > f64::EPSILON
|| !(2..=36).contains(&base)
{
let e = ArgError {
arg_number: 2,
func_name: Some("tonumber".to_string()),
expected: Some(LuaType::Number),
received: Some(LuaType::Number),
};
return Err(state.error(ErrorKind::ArgError(e)));
}
let num = parse_integer_with_base(state.to_bytes(1)?, base as u32);
state.pop(state.get_top() as isize)?;
if let Some(num) = num {
state.push_number(num)?;
} else {
state.push_nil()?;
}
return Ok(1);
}
let typ = state.typ(1);
match typ {
LuaType::Number => {
let num = state.to_number(1)?;
state.pop(state.get_top() as isize)?;
state.push_number(num)?;
Ok(1)
}
LuaType::String => {
let parsed = parse_lua_numeral(state.to_bytes(1)?);
state.set_top(0)?;
if let Some(num) = parsed {
state.push_number(num)?;
} else {
state.push_nil()?;
}
Ok(1)
}
_ => {
state.pop(state.get_top() as isize)?;
state.push_nil()?;
Ok(1)
}
}
});
add("tostring", |state| {
state.check_any(1)?;
if state.typ(1) == LuaType::String {
state.set_top(1)?;
} else {
let s = state.to_string_with_meta(1)?;
state.set_top(0)?;
state.push_string(s)?;
}
Ok(1)
});
add("unpack", base_unpack_values);
add("getmetatable", |state| {
state.check_any(1)?;
state.set_top(1)?;
raw_metafield(state, 1)?;
state.remove(1)?;
Ok(1)
});
add("setmetatable", |state| {
state.check_type(1, LuaType::Table)?;
state.check_any(2)?;
if !matches!(state.typ(2), LuaType::Table | LuaType::Nil) {
return Err(arg_type_error(state, 2, "setmetatable", LuaType::Table));
}
state.set_top(2)?;
if raw_metafield(state, 1)? {
return Err(state.error(ErrorKind::RuntimeError(
"cannot change a protected metatable".to_string(),
)));
}
state.pop(1)?;
state.set_metatable_of(1)?;
Ok(1)
});
add("rawget", |state| {
state.check_type(1, LuaType::Table)?;
state.check_any(2)?;
state.set_top(2)?;
state.push_value(2)?; state.get_table_raw(1)?;
state.remove(1)?;
state.remove(1)?;
Ok(1)
});
add("rawset", |state| {
state.check_type(1, LuaType::Table)?;
state.check_any(2)?;
state.check_any(3)?;
state.set_top(3)?;
state.set_table_raw(1)?;
Ok(1)
});
add("rawequal", |state| {
state.check_any(1)?;
state.check_any(2)?;
let equal = state.raw_equal(1, 2);
state.set_top(0)?;
state.push_boolean(equal)?;
Ok(1)
});
add("rawlen", |state| {
state.check_any(1)?;
let typ = state.typ(1);
let len = match typ {
LuaType::String => state.to_bytes(1)?.len(),
LuaType::Table => state.table_len(1),
_ => {
let e = ArgError {
arg_number: 1,
func_name: Some("rawlen".to_string()),
expected: Some(LuaType::Table),
received: Some(typ),
};
return Err(state.error(ErrorKind::ArgError(e)));
}
};
state.set_top(0)?;
state.push_number(len as f64)?;
Ok(1)
});
add("select", |state| {
state.check_any(1)?;
let num_args = state.get_top();
if state.typ(1) == LuaType::String && state.to_bytes(1)? == b"#" {
state.set_top(0)?;
state.push_number((num_args - 1) as f64)?;
return Ok(1);
}
state.check_type(1, LuaType::Number)?;
let raw_index = state.to_number(1)? as isize;
let vararg_count = num_args - 1;
let index = if raw_index > 0 {
raw_index as usize
} else if raw_index < 0 {
let index = vararg_count as isize + raw_index + 1;
if index < 1 {
let e = ArgError {
arg_number: 1,
func_name: Some("select".to_string()),
expected: Some(LuaType::Number),
received: Some(LuaType::Number),
};
return Err(state.error(ErrorKind::ArgError(e)));
}
index as usize
} else {
let e = ArgError {
arg_number: 1,
func_name: Some("select".to_string()),
expected: Some(LuaType::Number),
received: Some(LuaType::Number),
};
return Err(state.error(ErrorKind::ArgError(e)));
};
if index > vararg_count {
state.set_top(0)?;
return Ok(0);
}
let start_pos = 1 + index; let count = vararg_count - index + 1;
for _ in 1..start_pos {
state.remove(1)?;
}
u8::try_from(count).map_err(|_| {
state.error(ErrorKind::RuntimeError(
"too many results (limit 255)".into(),
))
})
});
state.new_table()?; state.new_table()?;
#[cfg(feature = "snapshot")]
state.set_table_str_key_named_rust_fn(-1, "__index", "_G.__index", global_env_index)?;
#[cfg(not(feature = "snapshot"))]
state.set_table_str_key_rust_fn(-1, "__index", global_env_index)?;
#[cfg(feature = "snapshot")]
state.set_table_str_key_named_rust_fn(
-1,
"__newindex",
"_G.__newindex",
global_env_newindex,
)?;
#[cfg(not(feature = "snapshot"))]
state.set_table_str_key_rust_fn(-1, "__newindex", global_env_newindex)?;
state.set_metatable_of(1)?;
state.set_global("_G");
Ok(())
}
fn raw_metafield(state: &mut State, idx: isize) -> Result<bool> {
state.get_metatable_of(idx)?;
if state.typ(-1) == LuaType::Nil {
return Ok(false);
}
state.push_value(-1)?;
state.push_string("__metatable")?;
state.get_table_raw(-2)?;
if state.typ(-1) != LuaType::Nil {
state.remove(-2)?;
state.remove(-2)?;
Ok(true)
} else {
state.pop(2)?;
Ok(false)
}
}
fn arg_type_error(
state: &State,
arg_number: isize,
func_name: &str,
expected: LuaType,
) -> crate::error::Error {
state.error(ErrorKind::ArgError(ArgError {
arg_number,
func_name: Some(func_name.to_string()),
expected: Some(expected),
received: Some(state.typ(arg_number)),
}))
}
fn global_env_index(state: &mut State) -> Result<u8> {
state.check_any(2)?;
if state.typ(2) == LuaType::String {
let key = state.to_string(2)?;
state.set_top(0)?;
state.get_global(&key)?;
} else {
state.push_value(2)?;
state.get_table_raw(1)?;
}
Ok(1)
}
fn global_env_newindex(state: &mut State) -> Result<u8> {
state.check_any(2)?;
state.check_any(3)?;
if state.typ(2) == LuaType::String {
let key = state.to_string(2)?;
state.push_value(3)?;
state.set_global(&key);
} else {
state.push_value(2)?;
state.push_value(3)?;
state.set_table_raw(1)?;
}
state.set_top(0)?;
Ok(0)
}