luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use luau_common::{BStr, BString};
use luau_vm::thread::{StackGuard, Thread as VmThread};

use super::LuaString;
use crate::error::Error;
use crate::thread::Thread;
use crate::value::{FromLua, IntoLua, LuaType, Value};

struct WrappedString<T>(T);

impl LuaString<'_> {
    /// Wraps bytes so they can be converted into a Luau string.
    pub fn wrap<T>(data: T) -> impl for<'value> IntoLua<'value>
    where
        T: AsRef<[u8]>,
    {
        WrappedString(data)
    }
}

impl LuaType for LuaString<'_> {
    fn push_type_key(thread: impl AsRef<VmThread>) -> Result<(), Error> {
        let thread = thread.as_ref();
        unsafe {
            thread
                .push_string("")
                .map_err(|exit| Error::from_thread_exit(thread, exit))
        }
    }
}

impl<'lua> IntoLua<'lua> for &str {
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        LuaString::from_bytes(&thread.current_thread(), self).map(Value::String)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe {
            thread
                .as_vm()
                .push_string(self)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
        }
        Ok(())
    }
}

impl<'lua> IntoLua<'lua> for String {
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        LuaString::from_bytes(&thread.current_thread(), self).map(Value::String)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe {
            thread
                .as_vm()
                .push_string(self)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
        }
        Ok(())
    }
}

impl<'lua> IntoLua<'lua> for &String {
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        self.as_str().into_lua(thread)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe { self.as_str().push_into_stack(thread) }
    }
}

impl<'lua> IntoLua<'lua> for &[u8] {
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        LuaString::from_bytes(&thread.current_thread(), self).map(Value::String)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe {
            thread
                .as_vm()
                .push_string(self)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
        }
        Ok(())
    }
}

impl<'lua> IntoLua<'lua> for &BStr {
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        LuaString::from_bytes(&thread.current_thread(), self).map(Value::String)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe {
            thread
                .as_vm()
                .push_string(self)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
        }
        Ok(())
    }
}

impl<'lua> IntoLua<'lua> for BString {
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        LuaString::from_bytes(&thread.current_thread(), &self).map(Value::String)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe {
            thread
                .as_vm()
                .push_string(self)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
        }
        Ok(())
    }
}

impl<'lua> IntoLua<'lua> for &BString {
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        LuaString::from_bytes(&thread.current_thread(), self).map(Value::String)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe {
            thread
                .as_vm()
                .push_string(self)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
        }
        Ok(())
    }
}

impl<'lua, 'string> IntoLua<'lua> for LuaString<'string>
where
    'string: 'lua,
{
    fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        Ok(Value::String(self))
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        self.push_to(thread)
    }
}

impl<'lua, 'string> IntoLua<'lua> for &LuaString<'string>
where
    'string: 'lua,
{
    fn into_lua(self, _: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        Ok(Value::String(self.try_clone()?))
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        self.push_to(thread)
    }
}

impl<'lua> FromLua<'lua> for LuaString<'lua> {
    fn from_lua(value: Value<'lua>, lua: crate::LuaRef<'lua>) -> Result<Self, Error> {
        match value {
            Value::String(value) => Ok(value),
            value => {
                let thread = lua.current_thread();
                let vm_thread = thread.as_vm();
                unsafe {
                    let _stack = StackGuard::new(vm_thread);
                    value.push_to(&thread)?;
                    LuaString::coerce_from_stack(&thread, -1)
                }
            }
        }
    }

    unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> Result<Self, Error> {
        LuaString::coerce_from_stack(thread, index)
    }
}

impl<'lua> FromLua<'lua> for BString {
    fn from_lua(value: Value<'lua>, lua: crate::LuaRef<'lua>) -> Result<Self, Error> {
        match value {
            Value::String(value) => Ok(value.as_bytes().to_owned()),
            Value::Buffer(value) => Ok(value.to_vec().into()),
            value => LuaString::from_lua(value, lua).map(|value| value.as_bytes().to_owned()),
        }
    }

    unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> Result<Self, Error> {
        if let Some((data, len)) = unsafe { thread.as_vm().to_buffer(index) } {
            if len == 0 {
                return Ok(BString::default());
            }
            let bytes = unsafe { core::slice::from_raw_parts(data.cast_const(), len) };
            return Ok(bytes.into());
        }

        let value = LuaString::coerce_from_stack(thread, index)?;
        Ok(value.as_bytes().to_owned())
    }
}

impl<'lua> FromLua<'lua> for String {
    fn from_lua(value: Value<'lua>, lua: crate::LuaRef<'lua>) -> Result<Self, Error> {
        let value = LuaString::from_lua(value, lua)?;
        value.to_str().map(str::to_owned)
    }

    unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> Result<Self, Error> {
        let value = LuaString::coerce_from_stack(thread, index)?;
        value.to_str().map(str::to_owned)
    }
}

impl<'lua, T> IntoLua<'lua> for WrappedString<T>
where
    T: AsRef<[u8]>,
{
    fn into_lua(self, thread: crate::LuaRef<'lua>) -> Result<Value<'lua>, Error> {
        LuaString::from_bytes(&thread.current_thread(), self.0).map(Value::String)
    }

    unsafe fn push_into_stack(self, thread: &Thread<'lua>) -> Result<(), Error> {
        unsafe {
            thread
                .as_vm()
                .push_string(self.0)
                .map_err(|exit| Error::from_thread_exit(thread, exit))?;
        }
        Ok(())
    }
}