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#[allow(
76 clippy::missing_safety_doc,
77 reason = "all methods share the capability-level safety contract"
78)]
79pub trait StringFormatting: Sealed {
80 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 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 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}