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",
}
}
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait StringFormatting: Sealed {
unsafe fn push_vfstring_internal<'a, A>(
&self,
format: &str,
args: A,
) -> VmErrorResult<LuaString>
where
A: AsMut<[luau_printf::Arg<'a>]>;
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 {
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))
}
}
}