use core::{cmp::Ordering, fmt, ptr};
use std::collections::HashSet;
use luau_common::ByteSlice;
use luau_vm::Thread as VmThread;
use luau_vm::thread::{LUA_TNONE, StackGuard};
use luau_vm::types::{
LUA_TBOOLEAN, LUA_TBUFFER, LUA_TCLASS, LUA_TFUNCTION, LUA_TINTEGER, LUA_TLIGHTUSERDATA,
LUA_TNIL, LUA_TNUMBER, LUA_TOBJECT, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA,
LUA_TVECTOR,
};
use crate::buffer::Buffer;
use crate::class::{Class, Object};
use crate::error::Error;
use crate::function::Function;
use crate::light_userdata::LightUserdata;
use crate::string::LuaString;
use crate::table::Table;
use crate::thread::Thread;
use crate::userdata::AnyUserdata;
use crate::vector::Vector;
mod conversion;
mod multi;
mod reference;
mod type_key;
pub use conversion::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
pub use multi::{MultiValue, Variadic};
pub(crate) use reference::ValueRef;
pub(crate) use type_key::LuaType;
pub enum Value<'lua> {
Nil,
Boolean(bool),
LightUserdata(LightUserdata),
Integer(i64),
Number(f64),
Vector(Vector),
String(LuaString<'lua>),
Buffer(Buffer<'lua>),
Table(Table<'lua>),
Function(Function<'lua>),
Thread(Thread<'lua>),
Userdata(AnyUserdata<'lua>),
Class(Class<'lua>),
Object(Object<'lua>),
Error(Box<Error>),
}
impl<'lua, 'value> IntoLua<'lua> for Value<'value>
where
'value: 'lua,
{
fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
Ok(self)
}
unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
self.push_to(thread)
}
}
impl<'lua, 'value> IntoLua<'lua> for &Value<'value>
where
'value: 'lua,
{
fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
self.try_clone()
}
unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
self.push_to(thread)
}
}
impl<'lua> FromLua<'lua> for Value<'lua> {
fn from_lua(value: Value<'lua>, _: crate::LuaRef<'lua>) -> Result<Self, Error> {
Ok(value)
}
}
impl<'lua> Value<'lua> {
pub const NULL: Self = Self::LightUserdata(LightUserdata::NULL);
pub const fn type_name(&self) -> &'static str {
match self {
Self::Nil => "nil",
Self::Boolean(_) => "boolean",
Self::LightUserdata(_) => "lightuserdata",
Self::Integer(_) => "integer",
Self::Number(_) => "number",
Self::Vector(_) => "vector",
Self::String(_) => "string",
Self::Buffer(_) => "buffer",
Self::Table(_) => "table",
Self::Function(_) => "function",
Self::Thread(_) => "thread",
Self::Userdata(_) => "userdata",
Self::Class(_) => "class",
Self::Object(_) => "object",
Self::Error(_) => "error",
}
}
pub fn equals(&self, other: &Self) -> Result<bool, Error> {
match (self, other) {
(Self::Table(left), Self::Table(right)) => left.equals(right),
(Self::Userdata(left), Self::Userdata(right)) => left.equals(right),
(Self::Object(left), Self::Object(right)) => left.equals(right),
(left, right) => Ok(left == right),
}
}
pub fn try_clone(&self) -> Result<Self, Error> {
Ok(match self {
Self::Nil => Self::Nil,
Self::Boolean(value) => Self::Boolean(*value),
Self::LightUserdata(value) => Self::LightUserdata(*value),
Self::Integer(value) => Self::Integer(*value),
Self::Number(value) => Self::Number(*value),
Self::Vector(value) => Self::Vector(*value),
Self::String(value) => Self::String(value.try_clone()?),
Self::Buffer(value) => Self::Buffer(value.try_clone()?),
Self::Table(value) => Self::Table(value.try_clone()?),
Self::Function(value) => Self::Function(value.try_clone()?),
Self::Thread(value) => Self::Thread(value.try_clone()?),
Self::Userdata(value) => Self::Userdata(value.try_clone()?),
Self::Class(value) => Self::Class(value.try_clone()?),
Self::Object(value) => Self::Object(value.try_clone()?),
Self::Error(error) => Self::Error(error.clone()),
})
}
pub(crate) fn sort_cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Nil, Self::Nil) => Ordering::Equal,
(Self::Nil, _) => Ordering::Less,
(_, Self::Nil) => Ordering::Greater,
(Self::LightUserdata(left), Self::LightUserdata(right)) if left == right => {
Ordering::Equal
}
(Self::LightUserdata(left), _) if left.is_null() => Ordering::Less,
(_, Self::LightUserdata(right)) if right.is_null() => Ordering::Greater,
(Self::Boolean(left), Self::Boolean(right)) => left.cmp(right),
(Self::Boolean(_), _) => Ordering::Less,
(_, Self::Boolean(_)) => Ordering::Greater,
(Self::Integer(left), Self::Integer(right)) => left.cmp(right),
(Self::Integer(_), _) => Ordering::Less,
(_, Self::Integer(_)) => Ordering::Greater,
(Self::Number(left), Self::Number(right)) => {
left.partial_cmp(right).unwrap_or(Ordering::Equal)
}
(Self::Number(_), _) => Ordering::Less,
(_, Self::Number(_)) => Ordering::Greater,
(Self::Vector(left), Self::Vector(right)) => {
left.partial_cmp(right).unwrap_or(Ordering::Equal)
}
(Self::Vector(_), _) => Ordering::Less,
(_, Self::Vector(_)) => Ordering::Greater,
(Self::String(left), Self::String(right)) => left.as_bytes().cmp(right.as_bytes()),
(Self::String(_), _) => Ordering::Less,
(_, Self::String(_)) => Ordering::Greater,
(left, right) => left.to_pointer().cmp(&right.to_pointer()),
}
}
pub fn to_pointer(&self) -> *const () {
match self {
Self::LightUserdata(value) => value.as_ptr().cast_const(),
Self::String(value) => value.to_pointer(),
Self::Buffer(value) => value.pointer(),
Self::Table(value) => value.pointer(),
Self::Function(value) => value.pointer(),
Self::Thread(value) => value.pointer(),
Self::Userdata(value) => value.pointer(),
Self::Class(value) => value.to_pointer(),
Self::Object(value) => value.to_pointer(),
_ => ptr::null(),
}
}
pub fn to_string(&self) -> Result<String, Error> {
match self {
Self::Nil => Ok("nil".to_string()),
Self::Boolean(value) => Ok(value.to_string()),
Self::LightUserdata(value) if value.is_null() => Ok("null".to_string()),
Self::LightUserdata(value) => Ok(format!("lightuserdata: {:p}", value.as_ptr())),
Self::Integer(value) => Ok(value.to_string()),
Self::Number(value) => Ok(value.to_string()),
Self::Vector(value) => Ok(value.to_string()),
Self::String(value) => value.to_str().map(str::to_owned),
Self::Error(error) => Ok(error.to_string()),
Self::Buffer(_)
| Self::Table(_)
| Self::Function(_)
| Self::Thread(_)
| Self::Userdata(_)
| Self::Class(_)
| Self::Object(_) => self.to_lua_string(),
}
}
pub const fn is_nil(&self) -> bool {
matches!(self, Self::Nil)
}
pub const fn is_null(&self) -> bool {
match self {
Self::LightUserdata(value) => value.is_null(),
_ => false,
}
}
pub const fn is_boolean(&self) -> bool {
matches!(self, Self::Boolean(_))
}
pub const fn as_boolean(&self) -> Option<bool> {
match self {
Self::Boolean(value) => Some(*value),
_ => None,
}
}
pub const fn is_light_userdata(&self) -> bool {
matches!(self, Self::LightUserdata(_))
}
pub const fn as_light_userdata(&self) -> Option<LightUserdata> {
match self {
Self::LightUserdata(value) => Some(*value),
_ => None,
}
}
pub const fn is_integer(&self) -> bool {
matches!(self, Self::Integer(_))
}
pub const fn as_integer(&self) -> Option<i64> {
match self {
Self::Integer(value) => Some(*value),
_ => None,
}
}
pub fn as_i32(&self) -> Option<i32> {
match self {
Self::Integer(value) => num_traits::cast(*value),
Self::Number(value) => num_traits::cast(*value),
_ => None,
}
}
pub fn as_u32(&self) -> Option<u32> {
match self {
Self::Integer(value) => num_traits::cast(*value),
Self::Number(value) => num_traits::cast(*value),
_ => None,
}
}
pub const fn as_i64(&self) -> Option<i64> {
self.as_integer()
}
pub fn as_u64(&self) -> Option<u64> {
match self {
Self::Integer(value) => num_traits::cast(*value),
Self::Number(value) => num_traits::cast(*value),
_ => None,
}
}
pub fn as_isize(&self) -> Option<isize> {
match self {
Self::Integer(value) => num_traits::cast(*value),
Self::Number(value) => num_traits::cast(*value),
_ => None,
}
}
pub fn as_usize(&self) -> Option<usize> {
match self {
Self::Integer(value) => num_traits::cast(*value),
Self::Number(value) => num_traits::cast(*value),
_ => None,
}
}
pub const fn is_number(&self) -> bool {
matches!(self, Self::Number(_))
}
pub const fn as_number(&self) -> Option<f64> {
match self {
Self::Number(value) => Some(*value),
_ => None,
}
}
pub(crate) fn as_table_array_index(&self) -> Result<Option<i64>, Error> {
match self {
Self::Integer(value) => {
if *value < 1 {
return Err(Error::index_out_of_bounds());
}
Ok(Some(*value))
}
Self::Number(value) if value.is_finite() && value.fract() == 0.0 => {
let Some(value) = num_traits::cast::<_, i64>(*value) else {
return Err(Error::index_out_of_bounds());
};
if value < 1 {
return Err(Error::index_out_of_bounds());
}
Ok(Some(value))
}
_ => Ok(None),
}
}
pub fn as_f32(&self) -> Option<f32> {
self.as_number().map(|value| value as f32)
}
pub const fn as_f64(&self) -> Option<f64> {
self.as_number()
}
pub const fn is_vector(&self) -> bool {
matches!(self, Self::Vector(_))
}
pub const fn as_vector(&self) -> Option<Vector> {
match self {
Self::Vector(value) => Some(*value),
_ => None,
}
}
pub const fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
pub const fn as_string(&self) -> Option<&LuaString<'lua>> {
match self {
Self::String(value) => Some(value),
_ => None,
}
}
pub const fn is_buffer(&self) -> bool {
matches!(self, Self::Buffer(_))
}
pub const fn as_buffer(&self) -> Option<&Buffer<'lua>> {
match self {
Self::Buffer(value) => Some(value),
_ => None,
}
}
pub const fn is_table(&self) -> bool {
matches!(self, Self::Table(_))
}
pub const fn as_table(&self) -> Option<&Table<'lua>> {
match self {
Self::Table(value) => Some(value),
_ => None,
}
}
pub const fn is_function(&self) -> bool {
matches!(self, Self::Function(_))
}
pub const fn as_function(&self) -> Option<&Function<'lua>> {
match self {
Self::Function(value) => Some(value),
_ => None,
}
}
pub const fn is_thread(&self) -> bool {
matches!(self, Self::Thread(_))
}
pub const fn as_thread(&self) -> Option<&Thread<'lua>> {
match self {
Self::Thread(value) => Some(value),
_ => None,
}
}
pub const fn is_userdata(&self) -> bool {
matches!(self, Self::Userdata(_))
}
pub const fn as_userdata(&self) -> Option<&AnyUserdata<'lua>> {
match self {
Self::Userdata(value) => Some(value),
_ => None,
}
}
pub const fn is_class(&self) -> bool {
matches!(self, Self::Class(_))
}
pub const fn as_class(&self) -> Option<&Class<'lua>> {
match self {
Self::Class(value) => Some(value),
_ => None,
}
}
pub const fn is_object(&self) -> bool {
matches!(self, Self::Object(_))
}
pub const fn as_object(&self) -> Option<&Object<'lua>> {
match self {
Self::Object(value) => Some(value),
_ => None,
}
}
pub const fn is_error(&self) -> bool {
matches!(self, Self::Error(_))
}
pub const fn as_error(&self) -> Option<&Error> {
match self {
Self::Error(error) => Some(error),
_ => None,
}
}
pub(crate) fn from_stack(thread: &Thread<'lua>, index: i32) -> Result<Self, Error> {
unsafe {
let vm_thread = thread.as_vm();
match vm_thread.type_of(index) {
LUA_TNONE | LUA_TNIL => Ok(Self::Nil),
LUA_TBOOLEAN => Ok(Self::Boolean(vm_thread.to_boolean(index) != 0)),
LUA_TLIGHTUSERDATA => Ok(Self::LightUserdata(LightUserdata::from_ptr(
vm_thread.to_light_userdata(index),
))),
LUA_TNUMBER => vm_thread
.to_number(index)
.map(Self::Number)
.ok_or_else(|| unsupported_stack_value(vm_thread, index)),
LUA_TINTEGER => vm_thread
.to_integer64(index)
.map(Self::Integer)
.ok_or_else(|| unsupported_stack_value(vm_thread, index)),
LUA_TVECTOR => vm_thread
.to_vector(index)
.map(Vector)
.map(Self::Vector)
.ok_or_else(|| unsupported_stack_value(vm_thread, index)),
LUA_TSTRING => LuaString::from_stack(thread, index)
.map(Self::String)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit)),
LUA_TBUFFER => Buffer::from_stack(thread, index)
.map(Self::Buffer)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit)),
LUA_TTABLE => Ok(Self::Table(
Table::from_stack(thread, index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
)),
LUA_TFUNCTION => Function::from_stack(thread, index)
.map(Self::Function)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit)),
LUA_TTHREAD => Thread::from_stack(thread, index)
.map(Self::Thread)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit)),
LUA_TUSERDATA => match Error::from_wrapped_stack(vm_thread, index) {
Some(error) => Ok(Self::Error(Box::new(error))),
None => AnyUserdata::from_stack(thread, index).map(Self::Userdata),
},
LUA_TCLASS => Class::from_stack(thread, index)
.map(Self::Class)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit)),
LUA_TOBJECT => Object::from_stack(thread, index)
.map(Self::Object)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit)),
_ => Err(unsupported_stack_value(vm_thread, index)),
}
}
}
pub(crate) fn push_to(&self, target: &Thread<'_>) -> Result<(), Error> {
unsafe {
let vm_thread = target.as_vm();
match self {
Self::Nil => vm_thread
.push_nil()
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
Self::Boolean(value) => vm_thread
.push_boolean(i32::from(*value))
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
Self::LightUserdata(value) => vm_thread
.push_light_userdata(value.as_ptr())
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
Self::Integer(value) => vm_thread
.push_integer64(*value)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
Self::Number(value) => vm_thread
.push_number(*value)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
Self::Vector(value) => vm_thread
.push_vector(value.0)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?,
Self::String(value) => value.push_to(vm_thread)?,
Self::Buffer(value) => value.push_to(vm_thread)?,
Self::Table(value) => value.push_to(vm_thread)?,
Self::Function(value) => value.push_to(vm_thread)?,
Self::Thread(value) => value.push_to(vm_thread)?,
Self::Userdata(value) => value.push_to(vm_thread)?,
Self::Class(value) => value.push_to(vm_thread)?,
Self::Object(value) => value.push_to(vm_thread)?,
Self::Error(error) => (**error).clone().push_into_stack(target)?,
}
}
Ok(())
}
fn to_lua_string(&self) -> Result<String, Error> {
unsafe {
let thread = match self {
Self::Buffer(value) => value.thread(),
Self::Table(value) => value.thread(),
Self::Function(value) => value.thread(),
Self::Thread(value) => Thread::new(value.reference_thread(), value.runtime()),
Self::Userdata(value) => value.thread(),
Self::Class(value) => value.thread(),
Self::Object(value) => value.thread(),
_ => unreachable!("only VM-owned values require Lua string conversion"),
};
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
self.push_to(&thread)?;
let bytes = vm_thread
.lua_to_string(-1)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
bytes.to_str().map(str::to_owned).map_err(|error| {
let message = error.to_string();
Error::from_lua_conversion("string", "String", Some(message.as_str()))
})
}
}
pub(crate) fn fmt_pretty(
&self,
formatter: &mut fmt::Formatter<'_>,
recursive: bool,
indent: usize,
visited: &mut HashSet<*const ()>,
) -> fmt::Result {
match self {
Self::Nil => formatter.write_str("nil"),
Self::Boolean(value) => write!(formatter, "{value}"),
Self::LightUserdata(value) if value.is_null() => formatter.write_str("null"),
Self::LightUserdata(value) => write!(formatter, "lightuserdata: {:p}", value.as_ptr()),
Self::Integer(value) => write!(formatter, "{value}"),
Self::Number(value) => write!(formatter, "{value}"),
Self::Vector(value) => write!(formatter, "{value}"),
Self::String(value) => write!(formatter, "{value:?}"),
Self::Buffer(value) => write!(formatter, "buffer: {:p}", value.pointer()),
Self::Table(value) if recursive && !visited.contains(&value.pointer()) => {
value.fmt_pretty(formatter, indent, visited)
}
Self::Table(value) => write!(formatter, "table: {:p}", value.pointer()),
Self::Function(value) => write!(formatter, "function: {:p}", value.pointer()),
Self::Thread(value) => write!(formatter, "thread: {:p}", value.pointer()),
Self::Userdata(value) => value.fmt_pretty(formatter),
Self::Class(value) => write!(formatter, "class: {:p}", value.to_pointer()),
Self::Object(value) => write!(formatter, "object: {:p}", value.to_pointer()),
Self::Error(error) if recursive => write!(formatter, "{error:?}"),
Self::Error(_) => formatter.write_str("error"),
}
}
}
fn unsupported_stack_value(thread: &VmThread, index: i32) -> Error {
let tag = unsafe { thread.type_of(index) };
let type_name = unsafe { thread.type_name(tag) };
let type_name = type_name.as_bytes().to_str_lossy();
Error::from_lua_conversion(type_name.as_ref(), "Value", Some("unsupported value type"))
}
impl PartialEq for Value<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Nil, Self::Nil) => true,
(Self::Boolean(left), Self::Boolean(right)) => left == right,
(Self::LightUserdata(left), Self::LightUserdata(right)) => left == right,
(Self::Integer(left), Self::Integer(right)) => left == right,
(Self::Number(left), Self::Number(right)) => left == right,
(Self::Vector(left), Self::Vector(right)) => left == right,
(Self::String(left), Self::String(right)) => left == right,
(Self::Buffer(left), Self::Buffer(right)) => left == right,
(Self::Table(left), Self::Table(right)) => left == right,
(Self::Function(left), Self::Function(right)) => left == right,
(Self::Thread(left), Self::Thread(right)) => left == right,
(Self::Userdata(left), Self::Userdata(right)) => left == right,
(Self::Class(left), Self::Class(right)) => left == right,
(Self::Object(left), Self::Object(right)) => left == right,
_ => false,
}
}
}
impl fmt::Debug for Value<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if formatter.alternate() {
return self.fmt_pretty(formatter, true, 0, &mut HashSet::new());
}
match self {
Self::Nil => formatter.write_str("Nil"),
Self::Boolean(value) => formatter.debug_tuple("Boolean").field(value).finish(),
Self::LightUserdata(value) => {
formatter.debug_tuple("LightUserdata").field(value).finish()
}
Self::Integer(value) => formatter.debug_tuple("Integer").field(value).finish(),
Self::Number(value) => formatter.debug_tuple("Number").field(value).finish(),
Self::Vector(value) => formatter.debug_tuple("Vector").field(value).finish(),
Self::String(value) => formatter.debug_tuple("String").field(value).finish(),
Self::Buffer(value) => fmt::Debug::fmt(value, formatter),
Self::Table(value) => fmt::Debug::fmt(value, formatter),
Self::Function(value) => fmt::Debug::fmt(value, formatter),
Self::Thread(value) => fmt::Debug::fmt(value, formatter),
Self::Userdata(value) => fmt::Debug::fmt(value, formatter),
Self::Class(value) => fmt::Debug::fmt(value, formatter),
Self::Object(value) => fmt::Debug::fmt(value, formatter),
Self::Error(error) => formatter.debug_tuple("Error").field(error).finish(),
}
}
}