use luau_common::{BStr, BString, ByteSlice};
use luau_printf::Arg;
use super::stack::RawStackAccess;
use super::{LUA_BUFFER_SIZE, LUA_TNONE, Thread};
use crate::debug::LuaDebug;
use crate::metamethod::MetamethodRuntime;
use crate::state::ThreadState;
use crate::string::{LuaString, printf_error_message};
use crate::types::{
LUA_TBOOLEAN, LUA_TBUFFER, LUA_TINTEGER, LUA_TNIL, LUA_TNUMBER, LUA_TSTRING, LUA_TVECTOR,
};
use crate::{VmError, VmErrorResult, VmResult};
fn current_native_function_name(thread: &Thread) -> Option<LuaString> {
if unsafe { thread.current_call_info() == thread.base_call_info() } {
return None;
}
let closure = unsafe { thread.current_function() };
if unsafe { !closure.is_native() } {
return None;
}
let debug_name = unsafe { closure.native_debug_name() }?;
if debug_name.as_bytes() == b"__namecall" {
unsafe { thread.name_call() }.map(LuaString::from_interned)
} else {
Some(debug_name)
}
}
fn tag_error<T>(thread: &Thread, argument: i32, tag: i32) -> VmErrorResult<T> {
let expected = unsafe { thread.type_name(tag) };
unsafe { thread.lua_type_error(argument, expected.as_bytes()) }
}
fn where_bytes(thread: &Thread, level: i32) -> VmErrorResult<Option<BString>> {
let mut ar = LuaDebug::default();
if unsafe { thread.get_info(level, "sl", &mut ar)? } == 0 || ar.currentline <= 0 {
return Ok(None);
}
Ok(Some(luau_printf::sprintf!(
"%s:%d: ",
ar.short_src().as_bstr(),
ar.currentline
)))
}
impl Thread {
pub unsafe fn push_where(&self, level: i32) -> VmErrorResult {
unsafe {
self.raw_check_stack(1)?;
if let Some(prefix) = where_bytes(self, level)? {
self.push_string(prefix.as_slice())?;
} else {
self.push_string("")?;
}
}
Ok(())
}
pub unsafe fn lua_error<'a, T>(
&self,
format: impl AsRef<[u8]>,
mut args: impl AsMut<[Arg<'a>]>,
) -> VmErrorResult<T> {
let format = format.as_ref();
let args = args.as_mut();
let mut formatted = Vec::new();
let message_bytes = if args.is_empty() {
format
} else {
formatted.reserve(format.len());
match luau_printf::printf_c_locale(&mut formatted, luau_printf::BStr::new(format), args)
{
Ok(_) => formatted.as_slice(),
Err(error) => printf_error_message(&error),
}
};
let message_len = message_bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(message_bytes.len())
.min(LUA_BUFFER_SIZE - 1);
let mut message = match where_bytes(self, 1) {
Ok(Some(message)) => message,
Ok(None) => BString::default(),
Err(error) => return Err(error),
};
message.extend_from_slice(&message_bytes[..message_len]);
unsafe { self.raw_check_stack(1) }?;
unsafe { self.push_string(message.as_slice()) }?;
Err(VmError::Runtime)
}
pub unsafe fn lua_type_error<T>(
&self,
argument: i32,
expected_type: impl AsRef<[u8]>,
) -> VmErrorResult<T> {
unsafe {
let function_name = current_native_function_name(self);
let object = self.to_object(argument);
let expected_type = expected_type.as_ref();
if object.is_none() {
if let Some(name) = function_name {
crate::error!(
self,
"missing argument #%d to '%s' (%s expected)",
argument,
&name,
expected_type
)
} else {
crate::error!(
self,
"missing argument #%d (%s expected)",
argument,
expected_type
)
}
} else {
let got = self.obj_type_name(object.unwrap_unchecked());
if let Some(name) = function_name {
crate::error!(
self,
"invalid argument #%d to '%s' (%s expected, got %s)",
argument,
&name,
expected_type,
&got
)
} else {
crate::error!(
self,
"invalid argument #%d (%s expected, got %s)",
argument,
expected_type,
&got
)
}
}
}
}
pub unsafe fn lua_arg_expected(
&self,
condition: bool,
argument: i32,
expected_type: &str,
) -> VmErrorResult {
if !condition {
return unsafe { self.lua_type_error(argument, expected_type) };
}
Ok(())
}
pub unsafe fn lua_arg_error<T>(
&self,
argument: i32,
message: impl AsRef<[u8]>,
) -> VmErrorResult<T> {
let function_name = current_native_function_name(self);
let message = message.as_ref().as_bstr();
if let Some(name) = function_name {
unsafe {
crate::error!(
self,
"invalid argument #%d to '%s' (%s)",
argument,
&name,
message
)
}
} else {
unsafe { crate::error!(self, "invalid argument #%d (%s)", argument, message) }
}
}
}
impl Thread {
pub unsafe fn lua_check_stack(&self, size: i32, message: Option<&str>) -> VmErrorResult {
unsafe {
if self.check_stack(size) == 0 {
if let Some(message) = message {
crate::error!(self, "stack overflow (%s)", message)
} else {
crate::error!(self, "stack overflow")
}
} else {
Ok(())
}
}
}
pub unsafe fn check_type(&self, argument: i32, expected_type: i32) -> VmErrorResult {
if unsafe { self.type_of(argument) } != expected_type {
tag_error(self, argument, expected_type)
} else {
Ok(())
}
}
pub unsafe fn check_any(&self, argument: i32) -> VmErrorResult {
if unsafe { self.type_of(argument) } == LUA_TNONE {
unsafe { crate::error!(self, "missing argument #%d", argument) }
} else {
Ok(())
}
}
pub unsafe fn check_string(&self, argument: i32) -> VmErrorResult<&BStr> {
if let Some(bytes) = unsafe { self.to_string(argument)? } {
Ok(bytes)
} else {
tag_error(self, argument, LUA_TSTRING)
}
}
pub unsafe fn opt_string(&self, argument: i32) -> VmErrorResult<Option<&BStr>> {
let tag = unsafe { self.type_of(argument) };
if tag == LUA_TNONE || tag == LUA_TNIL {
Ok(None)
} else {
Ok(Some(unsafe { self.check_string(argument)? }))
}
}
pub unsafe fn check_buffer(&self, argument: i32) -> VmErrorResult<(*mut u8, usize)> {
if let Some(buffer) = unsafe { self.to_buffer(argument) } {
Ok(buffer)
} else {
tag_error(self, argument, LUA_TBUFFER)
}
}
pub unsafe fn check_userdata(&self, argument: i32, type_name: &str) -> VmErrorResult<*mut ()> {
unsafe {
let userdata = self.to_userdata(argument);
if !userdata.is_null() && self.get_metatable(argument)? != 0 {
self.raw_get_field(crate::thread::LUA_REGISTRY_INDEX, type_name)?;
if self.raw_equal(-1, -2) != 0 {
self.pop(2);
return Ok(userdata);
}
}
self.lua_type_error(argument, type_name)
}
}
pub unsafe fn check_userdata_tagged(&self, argument: i32, tag: i32) -> VmErrorResult<*mut ()> {
let userdata = unsafe { self.to_userdata_tagged(argument, tag) };
if !userdata.is_null() {
return Ok(userdata);
}
let type_name = unsafe { self.get_userdata_name(tag) };
unsafe { self.lua_type_error(argument, type_name.as_bytes()) }
}
pub unsafe fn check_number(&self, argument: i32) -> VmErrorResult<f64> {
if let Some(value) = unsafe { self.to_number(argument) } {
Ok(value)
} else {
tag_error(self, argument, LUA_TNUMBER)
}
}
pub unsafe fn opt_number(&self, argument: i32, default: f64) -> VmErrorResult<f64> {
let tag = unsafe { self.type_of(argument) };
if tag == LUA_TNONE || tag == LUA_TNIL {
Ok(default)
} else {
unsafe { self.check_number(argument) }
}
}
pub unsafe fn check_boolean(&self, argument: i32) -> VmErrorResult<i32> {
if unsafe { self.type_of(argument) } != LUA_TBOOLEAN {
tag_error(self, argument, LUA_TBOOLEAN)
} else {
Ok(unsafe { self.to_boolean(argument) })
}
}
pub unsafe fn opt_boolean(&self, argument: i32, default: i32) -> VmErrorResult<i32> {
let tag = unsafe { self.type_of(argument) };
if tag == LUA_TNONE || tag == LUA_TNIL {
Ok(default)
} else {
unsafe { self.check_boolean(argument) }
}
}
pub unsafe fn check_integer(&self, argument: i32) -> VmErrorResult<i32> {
if let Some(value) = unsafe { self.to_integer(argument) } {
Ok(value)
} else {
tag_error(self, argument, LUA_TNUMBER)
}
}
pub unsafe fn opt_integer(&self, argument: i32, default: i32) -> VmErrorResult<i32> {
let tag = unsafe { self.type_of(argument) };
if tag == LUA_TNONE || tag == LUA_TNIL {
Ok(default)
} else {
unsafe { self.check_integer(argument) }
}
}
pub unsafe fn check_integer64(&self, argument: i32) -> VmErrorResult<i64> {
if let Some(value) = unsafe { self.to_integer64(argument) } {
Ok(value)
} else {
tag_error(self, argument, LUA_TINTEGER)
}
}
pub unsafe fn opt_integer64(&self, argument: i32, default: i64) -> VmErrorResult<i64> {
let tag = unsafe { self.type_of(argument) };
if tag == LUA_TNONE || tag == LUA_TNIL {
Ok(default)
} else {
unsafe { self.check_integer64(argument) }
}
}
pub unsafe fn check_unsigned(&self, argument: i32) -> VmErrorResult<u32> {
if let Some(value) = unsafe { self.to_unsigned(argument) } {
Ok(value)
} else {
tag_error(self, argument, LUA_TNUMBER)
}
}
pub unsafe fn opt_unsigned(&self, argument: i32, default: u32) -> VmErrorResult<u32> {
let tag = unsafe { self.type_of(argument) };
if tag == LUA_TNONE || tag == LUA_TNIL {
Ok(default)
} else {
unsafe { self.check_unsigned(argument) }
}
}
pub unsafe fn check_vector(
&self,
argument: i32,
) -> VmErrorResult<[f32; crate::types::LUA_VECTOR_SIZE]> {
if let Some(vector) = unsafe { self.to_vector(argument) } {
Ok(vector)
} else {
tag_error(self, argument, LUA_TVECTOR)
}
}
pub unsafe fn opt_vector(
&self,
argument: i32,
) -> VmErrorResult<Option<[f32; crate::types::LUA_VECTOR_SIZE]>> {
let tag = unsafe { self.type_of(argument) };
if tag == LUA_TNONE || tag == LUA_TNIL {
Ok(None)
} else {
Ok(Some(unsafe { self.check_vector(argument)? }))
}
}
pub unsafe fn check_option(
&self,
argument: i32,
default: Option<&str>,
options: &[&str],
) -> VmErrorResult<i32> {
unsafe {
let name = if let Some(default) = default {
let tag = self.type_of(argument);
if tag == LUA_TNONE || tag == LUA_TNIL {
default.as_bytes()
} else {
self.check_string(argument)?.as_bytes()
}
} else {
self.check_string(argument)?.as_bytes()
};
for (index, option) in options.iter().enumerate() {
if option.as_bytes() == name {
return Ok(index as i32);
}
}
let message = luau_printf::sprintf!("invalid option '%s'", name.as_bstr());
self.lua_arg_error(argument, message.as_bstr())
}
}
}
impl Thread {
fn append_pointer_hex(bytes: &mut BString, mut value: usize) {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut buffer = [0u8; 2 * core::mem::size_of::<usize>()];
for digit in buffer.iter_mut().rev() {
*digit = HEX[value & 0xf];
value >>= 4;
}
bytes.extend_from_slice(&buffer);
}
pub unsafe fn lua_type_name(&self, index: i32) -> LuaString {
let object = unsafe { self.to_object(index) };
if object.is_none() {
LuaString::from_static(b"no value".as_bstr())
} else {
unsafe { self.obj_type_name(object.unwrap_unchecked()) }
}
}
pub unsafe fn lua_to_string(&self, index: i32) -> VmResult<&BStr> {
unsafe {
if self.call_meta(index, "__tostring")? != 0 {
let Some(result) = self.to_string(-1)? else {
return crate::error!(self, "'__tostring' must return a string")
.map_err(Into::into);
};
return Ok(result);
}
match self.type_of(index) {
LUA_TNIL => self.push_string("nil")?,
LUA_TBOOLEAN => self.push_string(if self.to_boolean(index) != 0 {
"true"
} else {
"false"
})?,
LUA_TNUMBER => {
let mut buffer = [0u8; crate::number::LUAI_MAXNUM2STR];
let number_len = crate::number::num_to_str(
&mut buffer,
self.to_number(index).unwrap_or(0.0),
);
self.push_string(&buffer[..number_len])?;
}
LUA_TVECTOR => {
let vector = self.to_vector(index).unwrap_unchecked();
let mut bytes = BString::new(Vec::with_capacity(
crate::number::LUAI_MAXNUM2STR * crate::types::LUA_VECTOR_SIZE,
));
for (component_index, component) in vector.iter().enumerate() {
if component_index != 0 {
bytes.extend_from_slice(b", ");
}
let mut component_buffer = [0u8; crate::number::LUAI_MAXNUM2STR];
let component_len =
crate::number::num_to_str(&mut component_buffer, f64::from(*component));
bytes.extend_from_slice(&component_buffer[..component_len]);
}
self.push_string(bytes.as_slice())?;
}
LUA_TSTRING => self.push_value(index)?,
LUA_TINTEGER => {
let mut buffer = [0u8; crate::number::LUAI_MAXINT2STR];
let integer_len = crate::number::int_to_str(
&mut buffer,
self.to_integer64(index).unwrap_or(0),
);
self.push_string(&buffer[..integer_len])?;
}
_ => {
let type_name = self.lua_type_name(index);
let mut bytes = BString::new(Vec::new());
bytes.extend_from_slice(type_name.as_bytes());
bytes.extend_from_slice(b": 0x");
let encoded = self.encode_pointer(self.to_pointer(index) as usize);
Self::append_pointer_hex(&mut bytes, encoded);
self.push_string(bytes.as_slice())?;
}
}
Ok(self.to_string(-1)?.unwrap_unchecked())
}
}
}