Skip to main content

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}