use core::fmt;
use std::borrow::Cow;
use std::cmp;
use std::hash::{Hash, Hasher};
use std::ptr::NonNull;
use luau_common::{BStr, ByteSlice};
use luau_vm::VmErrorResult;
use luau_vm::thread::{StackGuard, Thread as VmThread};
use crate::error::Error;
use crate::lua::{Lua, LuaRef};
use crate::thread::Thread;
use crate::value::ValueRef;
mod conversion;
pub struct LuaString<'lua> {
reference: ValueRef<'lua>,
bytes: NonNull<BStr>,
}
impl Lua {
pub fn create_string(&self, bytes: impl AsRef<[u8]>) -> Result<LuaString<'_>, Error> {
self.lua_ref().create_string(bytes)
}
}
impl<'lua> LuaRef<'lua> {
pub fn create_string(&self, bytes: impl AsRef<[u8]>) -> Result<LuaString<'lua>, Error> {
LuaString::from_bytes(&self.current_thread(), bytes)
}
}
impl<'lua> LuaString<'lua> {
pub(crate) fn from_bytes(
thread: &Thread<'lua>,
bytes: impl AsRef<[u8]>,
) -> Result<Self, Error> {
unsafe {
let vm_thread = thread.as_vm();
let _stack = StackGuard::new(vm_thread);
vm_thread
.push_string(bytes.as_ref())
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?;
Self::from_stack(thread, -1).map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub(crate) unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> VmErrorResult<Self> {
let stack_thread = thread.as_vm();
debug_assert_ne!(unsafe { stack_thread.is_string(index) }, 0);
let bytes = unsafe {
stack_thread
.to_string(index)?
.expect("string stack slot should contain a string")
};
Ok(Self {
reference: ValueRef::from_stack(thread, index)?,
bytes: NonNull::from(bytes),
})
}
pub(crate) fn coerce_from_stack(thread: &Thread<'lua>, index: i32) -> Result<Self, Error> {
unsafe {
let vm_thread = thread.as_vm();
if vm_thread
.to_string(index)
.map_err(|exit| Error::from_thread_exit(vm_thread, exit))?
.is_none()
{
let type_name = vm_thread.type_name(vm_thread.type_of(index));
let type_name = type_name.as_bytes().to_str_lossy();
return Err(Error::from_lua_conversion(
type_name.as_ref(),
"string",
Some("expected string or number"),
));
}
Self::from_stack(thread, index).map_err(|exit| Error::from_thread_exit(vm_thread, exit))
}
}
pub fn try_clone(&self) -> Result<Self, Error> {
Ok(Self {
reference: self.reference.try_clone()?,
bytes: self.bytes,
})
}
pub fn as_bytes(&self) -> &BStr {
unsafe {
self.bytes.as_ref()
}
}
pub fn to_str(&self) -> Result<&str, Error> {
self.as_bytes().to_str().map_err(|error| {
let message = error.to_string();
Error::from_lua_conversion("string", "&str", Some(message.as_str()))
})
}
pub fn to_str_lossy(&self) -> Cow<'_, str> {
self.as_bytes().to_str_lossy()
}
pub const fn to_pointer(&self) -> *const () {
self.reference.pointer()
}
pub(crate) fn push_to(&self, target: impl AsRef<VmThread>) -> Result<(), Error> {
self.reference.push_to(target)
}
}
impl fmt::Display for LuaString<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_bytes(), formatter)
}
}
impl fmt::Debug for LuaString<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.to_str() {
Ok(value) => fmt::Debug::fmt(value, formatter),
Err(_) => {
formatter.write_str("b")?;
fmt::Debug::fmt(self.as_bytes(), formatter)
}
}
}
}
impl PartialEq for LuaString<'_> {
fn eq(&self, other: &Self) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<T> PartialEq<T> for LuaString<'_>
where
T: AsRef<[u8]> + ?Sized,
{
fn eq(&self, other: &T) -> bool {
self.as_bytes() == other.as_ref()
}
}
impl Eq for LuaString<'_> {}
impl<T> PartialOrd<T> for LuaString<'_>
where
T: AsRef<[u8]> + ?Sized,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.as_bytes().partial_cmp(other.as_ref())
}
}
impl PartialOrd for LuaString<'_> {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for LuaString<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.as_bytes().cmp(other.as_bytes())
}
}
impl Hash for LuaString<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}