low_luajit/lua/type/string.rs
1use core::{ffi::CStr, ptr, slice, str};
2use crate::{AmountReturned, LuaState, ffi, map_ptr};
3
4/// String related functions.
5impl LuaState {
6 /// Returns `true` if the value at the given acceptable `index` is a string (or number convertible to string), `false` otherwise.
7 #[inline]
8 #[must_use]
9 pub fn is_string(&self, index: i32) -> bool {
10 // SAFETY: self.as_ptr() is always valid.
11 unsafe { ffi::lua_isstring(self.as_ptr(), index) != 0 }
12 }
13
14 /// Converts the value at the given acceptable `index` to a string and returns it as a string slice.
15 ///
16 /// Only valid UTF-8 strings or numbers are accepted. Anything else will return `None`.
17 ///
18 /// This function *does* affect the element in the stack itself:
19 /// numbers will be replaced by the converted string of said number.
20 ///
21 /// To instead obtain a printable representation of any value, see [`LuaState::display`].
22 #[inline]
23 #[must_use]
24 pub fn to_string(&self, index: i32) -> Option<&str> {
25 str::from_utf8(self.to_byte_string(index)?).ok()
26 }
27
28 /// Indices that are not positive, not a pseudo-index, and go out of bounds may convert a number that's used elsewhere to a string.
29 /// This conversion usually leads to UB soon after, which is pretty bad.
30 ///
31 /// This function checks if the index is invalid in this specific context and panics if it is.
32 ///
33 /// As this panic is very niche, it is currently undocumented. This may change in the future.
34 #[inline]
35 pub(crate) fn assert_string_index(&self, index: i32) {
36 assert!(
37 index.is_positive() || index <= Self::REGISTRY_INDEX || self.is_index_valid(index) || !self.is_number(index),
38 concat!(
39 "cannot convert the number at index {} to a string, as there are only {} elements\n",
40 "(converting this out of bounds number would very likely cause UB)"
41 ),
42 index,
43 self.get_top()
44 );
45 }
46
47 /// Converts the value at the given acceptable `index` to a string and returns it as a byte slice.
48 ///
49 /// Only strings or numbers are accepted. Anything else will return `None`.
50 ///
51 /// This function *does* affect the element in the stack itself:
52 /// numbers will be replaced by the converted string of said number.
53 #[inline]
54 #[must_use]
55 pub fn to_byte_string(&self, index: i32) -> Option<&[u8]> {
56 self.assert_string_index(index);
57
58 // SAFETY: self.as_ptr() is always valid, len is initialized right before passing its pointer,
59 // and lua_tolstring returns valid for len amount of values string pointers when not null.
60 let mut len = 0;
61 let string = unsafe { ffi::lua_tolstring(self.as_ptr(), index, &raw mut len) };
62 map_ptr!(string, unsafe { slice::from_raw_parts(string.cast(), len) })
63 }
64
65 /// Converts the value at the given acceptable `index` to a string and returns it as a `CStr`.
66 ///
67 /// Only strings or numbers are accepted. Anything else will return `None`.
68 ///
69 /// This function *does* affect the element in the stack itself:
70 /// numbers will be replaced by the converted string of said number.
71 #[inline]
72 #[must_use]
73 pub fn to_cstring(&self, index: i32) -> Option<&CStr> {
74 self.assert_string_index(index);
75
76 // SAFETY: self.as_ptr() is always valid and lua_tostring returns valid nul-terminated string pointers when not null.
77 let cstring = unsafe { ffi::lua_tostring(self.as_ptr(), index) };
78 map_ptr!(cstring, unsafe { CStr::from_ptr(cstring) })
79 }
80
81 /// Pushes a copy of the given `string` onto the stack.
82 #[inline]
83 pub fn push_string(&mut self, string: impl AsRef<[u8]>) {
84 self.push_byte_string(string.as_ref());
85 }
86
87 /// Pushes a copy of the given `byte_string` onto the stack.
88 #[inline]
89 pub fn push_byte_string(&mut self, byte_string: &[u8]) {
90 // SAFETY: self.as_ptr()is always valid, the pointer remains valid for the duration of this function,
91 // and the length is correct as it's returned from the array itself.
92 unsafe { ffi::lua_pushlstring(self.as_ptr(), byte_string.as_ptr().cast(), byte_string.len()); }
93 }
94
95 /// Pushes a copy of the given `cstring` onto the stack.
96 ///
97 /// If `cstring` is `None`, `nil` will be pushed onto the stack instead.
98 #[inline]
99 pub fn push_cstring<'a>(&mut self, cstring: impl Into<Option<&'a CStr>>) {
100 // SAFETY: self.as_ptr() is always valid and the pointer either remains valid for the duration of this function
101 // or is null which lua_pushstring accepts.
102 unsafe { ffi::lua_pushstring(self.as_ptr(), cstring.into().map_or(core::ptr::null(), CStr::as_ptr)); }
103 }
104
105 /// Checks if there's a valid UTF-8 string (or number convertible to string) at the given index `arg_n` and returns it as a string slice.
106 ///
107 /// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
108 ///
109 /// This function has the same caveats as [`LuaState::to_string`].
110 ///
111 /// # Lua Errors
112 ///
113 /// Intentionally errors if the check fails.
114 #[inline]
115 #[allow(clippy::must_use_candidate)]
116 pub fn check_string(&self, arg_n: i32) -> &str {
117 match str::from_utf8(self.check_byte_string(arg_n)) {
118 Ok(string) => string,
119 Err(_) => self.arg_error(arg_n, c"string contains invalid UTF-8"),
120 }
121 }
122
123 /// Checks if there's a string (or number convertible to string) at the given index `arg_n` and returns it as a byte slice.
124 ///
125 /// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
126 ///
127 /// This function has the same caveats as [`LuaState::to_cstring`].
128 ///
129 /// # Lua Errors
130 ///
131 /// Intentionally errors if the check fails.
132 #[inline]
133 #[allow(clippy::must_use_candidate)]
134 pub fn check_byte_string(&self, arg_n: i32) -> &[u8] {
135 self.assert_string_index(arg_n);
136
137 // SAFETY: self.as_ptr() is always valid, len is initialized right before passing its pointer,
138 // and string should always be non-null and valid for len amount of values.
139 let mut len = 0;
140 let string = unsafe { ffi::luaL_checklstring(self.as_ptr(), arg_n, &raw mut len) };
141 unsafe { slice::from_raw_parts(string.cast(), len) }
142 }
143
144 /// Checks if there's a string (or number convertible to string) at the given index `arg_n` and returns it as a `CStr`.
145 ///
146 /// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
147 ///
148 /// This function has the same caveats as [`LuaState::to_byte_string`].
149 ///
150 /// # Lua Errors
151 ///
152 /// Intentionally errors if the check fails.
153 #[inline]
154 #[allow(clippy::must_use_candidate)]
155 pub fn check_cstring(&self, arg_n: i32) -> &CStr {
156 self.assert_string_index(arg_n);
157
158 // SAFETY: self.as_ptr() is always valid and the returned pointer should be non-null, nul terminated, and valid for all values.
159 unsafe { CStr::from_ptr(ffi::luaL_checkstring(self.as_ptr(), arg_n)) }
160 }
161
162 /// Behaves exactly as [`LuaState::check_string`],
163 /// but returns `default` instead of erroring when the element at the given index `arg_n` is `nil` (or non-existing).
164 #[inline]
165 #[allow(clippy::must_use_candidate)]
166 pub fn opt_string<'a>(&'a self, arg_n: i32, default: &'a str) -> &'a str {
167 self.assert_string_index(arg_n);
168
169 // SAFETY: self.as_ptr() is always valid, luaL_optlstring accepts a null pointer for the default value,
170 // and len is initialized right before passing its pointer.
171 let mut len = 0;
172 let string = unsafe { ffi::luaL_optlstring(self.as_ptr(), arg_n, ptr::null(), &raw mut len) };
173
174 if string.is_null() {
175 return default;
176 }
177
178 // SAFETY: string should always be non-null and valid for len amount of values.
179 match str::from_utf8(unsafe { slice::from_raw_parts(string.cast(), len) }) {
180 Ok(string) => string,
181 Err(_) => self.arg_error(arg_n, c"string contains invalid UTF-8"),
182 }
183 }
184
185 /// Behaves exactly as [`LuaState::check_byte_string`],
186 /// but returns `default` instead of erroring when the element at the given index `arg_n` is `nil` (or non-existing).
187 #[inline]
188 #[allow(clippy::must_use_candidate)]
189 pub fn opt_byte_string<'a>(&'a self, arg_n: i32, default: &'a CStr) -> &'a [u8] {
190 self.assert_string_index(arg_n);
191
192 // SAFETY: self.as_ptr() and default are always valid, len is initialized right before passing its pointer,
193 // and string should always be non-null and valid for len amount of values.
194 let mut len = 0;
195 let string = unsafe { ffi::luaL_optlstring(self.as_ptr(), arg_n, default.as_ptr(), &raw mut len) };
196 unsafe { slice::from_raw_parts(string.cast(), len) }
197 }
198
199 /// Behaves exactly as [`LuaState::check_cstring`],
200 /// but returns `default` instead of erroring when the element at the given index `arg_n` is `nil` (or non-existing).
201 #[inline]
202 #[allow(clippy::must_use_candidate)]
203 pub fn opt_cstring<'a>(&'a self, arg_n: i32, default: &'a CStr) -> &'a CStr {
204 self.assert_string_index(arg_n);
205
206 // SAFETY: self.as_ptr() and default are always valid, len is initialized right before passing its pointer,
207 // and string should always be non-null and valid for len amount of values.
208 unsafe { CStr::from_ptr(ffi::luaL_optstring(self.as_ptr(), arg_n, default.as_ptr())) }
209 }
210
211 /// Finishes the currently called [`CFunction`], returning the given `string` to the caller.
212 ///
213 /// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
214 ///
215 /// [`CFunction`]: ./type.CFunction.html
216 #[inline]
217 pub fn return_string(self, string: impl AsRef<[u8]>) -> AmountReturned {
218 self.return_byte_string(string.as_ref())
219 }
220
221 /// Finishes the currently called [`CFunction`], returning the given `byte_string` to the caller.
222 ///
223 /// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
224 ///
225 /// [`CFunction`]: ./type.CFunction.html
226 #[inline]
227 pub fn return_byte_string(mut self, byte_string: &[u8]) -> AmountReturned {
228 self.push_byte_string(byte_string);
229
230 // SAFETY: There should always be at least one element in the stack.
231 unsafe { AmountReturned::unchecked(1) }
232 }
233
234 /// Finishes the currently called [`CFunction`], returning the given `cstring` to the caller.
235 ///
236 /// If `cstring` is `None`, `nil` will be returned to the caller instead.
237 ///
238 /// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
239 ///
240 /// [`CFunction`]: ./type.CFunction.html
241 #[inline]
242 pub fn return_cstring<'a>(mut self, cstring: impl Into<Option<&'a CStr>>) -> AmountReturned {
243 self.push_cstring(cstring);
244
245 // SAFETY: There should always be at least one element in the stack.
246 unsafe { AmountReturned::unchecked(1) }
247 }
248
249 /// Creates a copy of `string` where every occurrence of `pattern` is replaced by `replacement`.
250 ///
251 /// The resulting string is both pushed onto the stack and returned.
252 #[inline]
253 pub fn gsub(&mut self, string: &CStr, pattern: &CStr, replacement: &CStr) -> &CStr {
254 // SAFETY: self.as_ptr() and string, pattern, and replacement's pointers are always valid and luaL_gsub always returns a valid string pointer.
255 unsafe { CStr::from_ptr(ffi::luaL_gsub(self.as_ptr(), string.as_ptr(), pattern.as_ptr(), replacement.as_ptr())) }
256 }
257}