Skip to main content

luau_vm/string/
mod.rs

1use luau_common::{BStr, ByteSlice};
2use luau_printf::Arg;
3
4use crate::debug::DebugRuntime;
5use crate::handle::sealed::Sealed;
6use crate::state::ThreadState;
7use crate::thread::{LUA_BUFFER_SIZE, Thread};
8use crate::{VmError, VmErrorResult};
9
10mod intern;
11
12pub(crate) use self::intern::LUA_MIN_STRING_TABLE_SIZE;
13pub use self::intern::{
14    ATOM_UNDEFINED, MAX_STRING_SIZE, RawTString, StringRuntime, StringTable, TString, hash,
15};
16
17#[derive(Clone, Copy)]
18pub(crate) enum LuaStringRepr {
19    Static(&'static BStr),
20    Interned(TString),
21}
22
23#[derive(Clone, Copy)]
24pub struct LuaString(pub(crate) LuaStringRepr);
25
26impl LuaString {
27    pub const fn from_static(value: &'static BStr) -> Self {
28        Self(LuaStringRepr::Static(value))
29    }
30
31    pub const fn from_interned(value: TString) -> Self {
32        Self(LuaStringRepr::Interned(value))
33    }
34
35    pub fn as_bytes(&self) -> &[u8] {
36        match &self.0 {
37            LuaStringRepr::Static(value) => value.as_bytes(),
38            LuaStringRepr::Interned(value) => unsafe { value.as_bytes() },
39        }
40    }
41
42    pub fn as_bstr(&self) -> &BStr {
43        self.as_bytes().as_bstr()
44    }
45}
46
47impl AsRef<[u8]> for LuaString {
48    fn as_ref(&self) -> &[u8] {
49        self.as_bytes()
50    }
51}
52
53impl<'a> luau_printf::ToArg<'a> for &'a LuaString {
54    fn to_arg(self) -> luau_printf::Arg<'a> {
55        Arg::string(self.as_bstr())
56    }
57}
58
59pub(crate) fn printf_error_message(error: &luau_printf::Error) -> &'static [u8] {
60    match error {
61        luau_printf::Error::BadFormatString => b"invalid format string",
62        luau_printf::Error::MissingArg => b"missing format argument",
63        luau_printf::Error::BadArgType => b"format argument type mismatch",
64        luau_printf::Error::Overflow => b"format precision is too large",
65        luau_printf::Error::Io(_) => b"format output failed",
66    }
67}
68
69/// Unstable VM string-formatting capability.
70///
71/// # Safety
72///
73/// The thread must be live, format arguments must remain valid for the call,
74/// and the caller must account for stack growth, allocation, errors, and GC.
75#[allow(
76    clippy::missing_safety_doc,
77    reason = "all methods share the capability-level safety contract"
78)]
79pub trait StringFormatting: Sealed {
80    /// `luaO_pushvfstring`
81    unsafe fn push_vfstring_internal<'a, A>(
82        &self,
83        format: &str,
84        args: A,
85    ) -> VmErrorResult<LuaString>
86    where
87        A: AsMut<[luau_printf::Arg<'a>]>;
88
89    /// `luaO_pushfstring`
90    unsafe fn push_fstring_internal<'a, A>(&self, format: &str, args: A) -> VmErrorResult<LuaString>
91    where
92        A: AsMut<[luau_printf::Arg<'a>]>,
93    {
94        unsafe { self.push_vfstring_internal(format, args) }
95    }
96}
97
98impl StringFormatting for Thread {
99    /// `luaO_pushvfstring`
100    unsafe fn push_vfstring_internal<'a, A>(
101        &self,
102        format: &str,
103        mut args: A,
104    ) -> VmErrorResult<LuaString>
105    where
106        A: AsMut<[luau_printf::Arg<'a>]>,
107    {
108        unsafe {
109            let mut result = Vec::with_capacity(format.len());
110            if let Err(error) = luau_printf::printf_c_locale(
111                &mut result,
112                luau_printf::BStr::new(format.as_bytes()),
113                args.as_mut(),
114            ) {
115                self.push_error(printf_error_message(&error).as_bstr())?;
116                return Err(VmError::Runtime);
117            }
118
119            if result.len() > LUA_BUFFER_SIZE - 1 {
120                result.truncate(LUA_BUFFER_SIZE - 1);
121            }
122            if let Some(length) = result.iter().position(|byte| *byte == 0) {
123                result.truncate(length);
124            }
125
126            let interned = self.intern_string(result.as_slice().as_bstr())?;
127            let top = self.stack_top();
128            top.value_unchecked().set_string_value(interned);
129            self.set_stack_top(top.add(1));
130
131            Ok(LuaString::from_interned(interned))
132        }
133    }
134}