luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use luau_common::{BStr, ByteSlice};
use luau_printf::Arg;

use crate::debug::DebugRuntime;
use crate::handle::sealed::Sealed;
use crate::state::ThreadState;
use crate::thread::{LUA_BUFFER_SIZE, Thread};
use crate::{VmError, VmErrorResult};

mod intern;

pub(crate) use self::intern::LUA_MIN_STRING_TABLE_SIZE;
pub use self::intern::{
    ATOM_UNDEFINED, MAX_STRING_SIZE, RawTString, StringRuntime, StringTable, TString, hash,
};

#[derive(Clone, Copy)]
pub(crate) enum LuaStringRepr {
    Static(&'static BStr),
    Interned(TString),
}

#[derive(Clone, Copy)]
pub struct LuaString(pub(crate) LuaStringRepr);

impl LuaString {
    pub const fn from_static(value: &'static BStr) -> Self {
        Self(LuaStringRepr::Static(value))
    }

    pub const fn from_interned(value: TString) -> Self {
        Self(LuaStringRepr::Interned(value))
    }

    pub fn as_bytes(&self) -> &[u8] {
        match &self.0 {
            LuaStringRepr::Static(value) => value.as_bytes(),
            LuaStringRepr::Interned(value) => unsafe { value.as_bytes() },
        }
    }

    pub fn as_bstr(&self) -> &BStr {
        self.as_bytes().as_bstr()
    }
}

impl AsRef<[u8]> for LuaString {
    fn as_ref(&self) -> &[u8] {
        self.as_bytes()
    }
}

impl<'a> luau_printf::ToArg<'a> for &'a LuaString {
    fn to_arg(self) -> luau_printf::Arg<'a> {
        Arg::string(self.as_bstr())
    }
}

pub(crate) fn printf_error_message(error: &luau_printf::Error) -> &'static [u8] {
    match error {
        luau_printf::Error::BadFormatString => b"invalid format string",
        luau_printf::Error::MissingArg => b"missing format argument",
        luau_printf::Error::BadArgType => b"format argument type mismatch",
        luau_printf::Error::Overflow => b"format precision is too large",
        luau_printf::Error::Io(_) => b"format output failed",
    }
}

/// Unstable VM string-formatting capability.
///
/// # Safety
///
/// The thread must be live, format arguments must remain valid for the call,
/// and the caller must account for stack growth, allocation, errors, and GC.
#[allow(
    clippy::missing_safety_doc,
    reason = "all methods share the capability-level safety contract"
)]
pub trait StringFormatting: Sealed {
    /// `luaO_pushvfstring`
    unsafe fn push_vfstring_internal<'a, A>(
        &self,
        format: &str,
        args: A,
    ) -> VmErrorResult<LuaString>
    where
        A: AsMut<[luau_printf::Arg<'a>]>;

    /// `luaO_pushfstring`
    unsafe fn push_fstring_internal<'a, A>(&self, format: &str, args: A) -> VmErrorResult<LuaString>
    where
        A: AsMut<[luau_printf::Arg<'a>]>,
    {
        unsafe { self.push_vfstring_internal(format, args) }
    }
}

impl StringFormatting for Thread {
    /// `luaO_pushvfstring`
    unsafe fn push_vfstring_internal<'a, A>(
        &self,
        format: &str,
        mut args: A,
    ) -> VmErrorResult<LuaString>
    where
        A: AsMut<[luau_printf::Arg<'a>]>,
    {
        unsafe {
            let mut result = Vec::with_capacity(format.len());
            if let Err(error) = luau_printf::printf_c_locale(
                &mut result,
                luau_printf::BStr::new(format.as_bytes()),
                args.as_mut(),
            ) {
                self.push_error(printf_error_message(&error).as_bstr())?;
                return Err(VmError::Runtime);
            }

            if result.len() > LUA_BUFFER_SIZE - 1 {
                result.truncate(LUA_BUFFER_SIZE - 1);
            }
            if let Some(length) = result.iter().position(|byte| *byte == 0) {
                result.truncate(length);
            }

            let interned = self.intern_string(result.as_slice().as_bstr())?;
            let top = self.stack_top();
            top.value_unchecked().set_string_value(interned);
            self.set_stack_top(top.add(1));

            Ok(LuaString::from_interned(interned))
        }
    }
}