Skip to main content

low_luajit/lua/
buffer.rs

1use core::{ffi::{CStr, c_char}, marker::PhantomData, mem::MaybeUninit, ops::AddAssign, pin::Pin, slice};
2use crate::{AmountReturned, LuaState, ffi::{self, luaL_Buffer}};
3
4/// Buffer that allows building Lua strings incrementally.
5///
6/// Construction of the string is done by calling the various `StringBuffer::add_*` methods to append text or data to the buffer.
7/// Once complete, the resulting string can be pushed onto the stack by calling [`StringBuffer::push_result`].
8///
9/// The buffer internally utilizes the stack in an unspecified way until completion,
10/// resulting in the [`LuaState`] being mutably borrowed until the `StringBuffer` is dropped
11/// *(you can still access the [`LuaState`] using [`StringBuffer::lua`])*.
12/// The buffer will however not pop any element that was pushed prior to itself.
13///
14/// # Creation
15///
16/// A few steps are necessary to properly create a `StringBuffer`:
17/// - It has to be initialized by the C API itself rather than by Rust (see [`LuaState::init_buffer`]).
18/// - Before being initialized, it has to be [pinned](https://doc.rust-lang.org/core/pin/index.html).
19///   *(The buffer is self-referential, thus moving it would quickly result in undefined behavior.)*
20///
21/// See below for some examples on how to create a buffer.
22///
23/// # Examples
24///
25/// Storing the buffer on the stack using [`pin!`](https://doc.rust-lang.org/core/pin/macro.pin.html) *(recommended)*:
26///
27/// ```
28/// use core::pin::pin;
29/// use low_luajit::{AmountReturned, LuaState, StringBuffer};
30///
31/// extern "C-unwind" fn example(mut lua: LuaState) -> AmountReturned {
32///     let buffer = pin!(StringBuffer::uninit());
33///     let mut buffer = lua.init_buffer(buffer);
34///
35///     // Use the buffer in some way...
36///     buffer.add_string("Hello");
37///     buffer.add_char(' ');
38///     buffer.add_string("world!");
39///
40///     buffer.push_result();
41///     lua.return_any(1)
42/// }
43///
44/// # let mut lua = unsafe { low_luajit::__test!() };
45/// lua.push_cfunction(example);
46/// lua.call(0, 1);
47/// assert_eq!(lua.to_string(-1), Some("Hello world!"));
48/// ```
49///
50/// Storing the buffer on the heap using a `Box`:
51///
52/// ```
53/// use low_luajit::{AmountReturned, LuaState, StringBuffer};
54///
55/// extern "C-unwind" fn example_box(mut lua: LuaState) -> AmountReturned {
56///     let mut buffer = Box::pin(StringBuffer::uninit());
57///     let mut buffer = lua.init_buffer(buffer.as_mut());
58///
59///     // Use the buffer in some way...
60///     buffer.add_cstring(c"foo");
61///     buffer.add_cstring(c"bar");
62///
63///     buffer.push_result();
64///     lua.return_any(1)
65/// }
66///
67/// # let mut lua = unsafe { low_luajit::__test!() };
68/// lua.push_cfunction(example_box);
69/// lua.call(0, 1);
70/// assert_eq!(lua.to_string(-1), Some("foobar"));
71/// ```
72///
73/// Unless `Box`'s features are required,
74/// [`pin!`](https://doc.rust-lang.org/core/pin/macro.pin.html) should be preferred as it tends to be cheaper to use.
75#[repr(transparent)]
76#[derive(Debug)]
77pub struct StringBuffer<'a>(luaL_Buffer, PhantomData<&'a mut LuaState>);
78
79impl StringBuffer<'_> {
80	/// Returns the `StringBuffer` in an unitialized state.
81	///
82	/// Before it can be utilized, the buffer has to be initialized by [`LuaState::init_buffer`].
83	#[inline]
84	#[must_use]
85	pub const fn uninit() -> MaybeUninit<Self> {
86		MaybeUninit::uninit()
87	}
88
89	/// Returns a mutable pointer to the wrapped [`luaL_Buffer`].
90	/// This can be used to pass this `StringBuffer` to raw [`ffi`] functions.
91	///
92	/// The caller must ensure that the `StringBuffer` outlives the pointer this function returns, or else it will end up dangling.
93	#[inline]
94	#[must_use]
95	pub fn as_mut_ptr(self: &mut Pin<&mut Self>) -> *mut luaL_Buffer {
96		(&raw const self.0).cast_mut()
97	}
98
99	/// Appends the given `byte` to the buffer.
100	#[inline]
101	#[allow(clippy::cast_possible_wrap)]
102	pub fn add_byte(self: &mut Pin<&mut Self>, byte: u8) {
103		// SAFETY: self.as_mut_ptr() is always valid.
104		unsafe { ffi::luaL_addchar(self.as_mut_ptr(), byte as c_char); }
105	}
106
107	/// Appends the given character `ch` to the buffer.
108	#[inline]
109	pub fn add_char(self: &mut Pin<&mut Self>, ch: char) {
110		if ch.is_ascii() {
111			self.add_byte(ch as u8);
112		} else {
113			self.add_string(ch.encode_utf8(&mut [0; 4]));
114		}
115	}
116
117	/// Appends a copy of the given `string` to the buffer.
118	///
119	/// As an alternative syntax, the `+=` operation can be used instead.
120	#[inline]
121	pub fn add_string(self: &mut Pin<&mut Self>, string: impl AsRef<[u8]>) {
122		self.add_byte_string(string.as_ref());
123	}
124
125	/// Appends a copy of the given `byte_string` to the buffer.
126	#[inline]
127	pub fn add_byte_string(self: &mut Pin<&mut Self>, byte_string: &[u8]) {
128		// SAFETY: self.as_mut_ptr() is always valid, the pointer remains valid for the duration of this function,
129		// and the length is correct as it's returned from the array itself.
130		unsafe { ffi::luaL_addlstring(self.as_mut_ptr(), byte_string.as_ptr().cast(), byte_string.len()); }
131	}
132
133	/// Appends a copy of the given `cstring` to the buffer.
134	#[inline]
135	pub fn add_cstring(self: &mut Pin<&mut Self>, cstring: &CStr) {
136		// SAFETY: self.as_mut_ptr() is always valid and the pointer either remains valid for the duration of this function.
137		unsafe { ffi::luaL_addstring(self.as_mut_ptr(), cstring.as_ptr()); }
138	}
139
140	/// Appends the value at the top of the stack to the buffer.
141	/// The value should be pushed onto the stack by the given function `f`.
142	///
143	/// Only strings or numbers are accepted. Anything else will be discarded.
144	///
145	/// # Panics
146	///
147	/// Panics if the amount of elements present in the stack after `f` is called does not increase by a single element.
148	///
149	/// While replacing the elements pushed by the `StringBuffer` itself *is* possible and won't cause a panic,
150	/// it's still considered a logic error and it will likely cause a Lua error once [`StringBuffer::push_result`] is called.
151	#[inline]
152	pub fn add_value(self: &mut Pin<&mut Self>, f: impl FnOnce(&mut LuaState)) {
153		// SAFETY: self.0.L is always valid and there should currently not be another accessible LuaState.
154		let mut lua = unsafe { LuaState::from_ptr(self.0.L) };
155		let top = lua.get_top();
156		f(&mut lua);
157		assert!(lua.get_top() - top == 1, "tried to append a value to a buffer by pushing {} elements instead of single element", lua.get_top() - top);
158
159		// SAFETY: self.as_mut_ptr() is always valid and the stack should always have a single extra element.
160		unsafe { ffi::luaL_addvalue(self.as_mut_ptr()); }
161	}
162
163	/// Appends the next `amount` bytes in the buffer area to the buffer.
164	///
165	/// This is done by initializing the bytes to `fill` and then returning a mutable slice to said bytes, to allow further modifications.
166	///
167	/// To instead simply mark the next `amount` of bytes as used, without initialization, see [`StringBuffer::add_size_unchecked`].
168	///
169	/// # Panics
170	///
171	/// Panics if `amount` is greater than the size of the buffer area (see [`StringBuffer::BUFFER_AREA_SIZE`]).
172	#[inline]
173	pub fn add_size<'a>(self: &'a mut Pin<&mut Self>, amount: usize, fill: u8) -> &'a mut [u8] {
174		assert!(
175			amount <= StringBuffer::BUFFER_AREA_SIZE,
176			"tried to append the next {amount} bytes in the buffer area to a buffer, but the size of the buffer area is only {} bytes",
177			StringBuffer::BUFFER_AREA_SIZE
178		);
179
180		// SAFETY: self.as_mut_ptr() is always valid, c_char can be casted as u8 (they're both 1 byte),
181		// luaL_prepbuffer should always return a non-null pointer valid for amount amount of values,
182		// and the buffer area will contain at least amount valid values.
183		unsafe {
184			let ptr = ffi::luaL_prepbuffer(self.as_mut_ptr()).cast::<u8>();
185			ptr.write_bytes(fill, amount);
186			ffi::luaL_addsize(self.as_mut_ptr(), amount);
187			slice::from_raw_parts_mut(ptr, amount)
188		}
189	}
190
191	/// Appends the next `amount` bytes in the buffer area to the buffer.
192	///
193	/// This is done by simply increasing the amount of the area marked as used by `amount`, without doing any checks.
194	///
195	/// For safer version of this function, see [`StringBuffer::add_size`].
196	///
197	/// # Safety
198	///
199	/// The next `amount` bytes in the buffer area must be initialized and not out of bounds.
200	///
201	/// See [`StringBuffer::flush_buffer`] for details on how to access and initialize the buffer area.
202	#[inline]
203	pub unsafe fn add_size_unchecked(self: &mut Pin<&mut Self>, amount: usize) {
204		// SAFETY: self.as_mut_ptr() is always valid and the caller must make sure to give a valid amount.
205		unsafe { ffi::luaL_addsize(self.as_mut_ptr(), amount); }
206	}
207
208	/// The size of the "buffer area" used internally by `StringBuffer`.
209	/// This size may vary across compilation targets.
210	///
211	/// To access the buffer area directly, see [`StringBuffer::flush_buffer`].
212	pub const BUFFER_AREA_SIZE: usize = ffi::LUAL_BUFFERSIZE;
213
214	/// Flushes the contents of the buffer onto the stack *(in an unspecified way)*,
215	/// returning a mutable reference to the array used internally by the buffer (the "buffer area").
216	///
217	/// It is unspecified whether any bytes in the array are initialized or not, some *may* contain remnants of the flushed data.
218	///
219	/// Any data written directly to the array will *not* be appended to the buffer unless [`StringBuffer::add_size_unchecked`] is called afterwards.
220	/// *(The data will otherwise be overwritten by the other methods or discarded.)*
221	///
222	/// The size of the array may vary across compilation targets.
223	#[inline]
224	pub fn flush_buffer<'a>(self: &'a mut Pin<&mut Self>) -> &'a mut [MaybeUninit<u8>; StringBuffer::BUFFER_AREA_SIZE] {
225		// SAFETY: self.as_mut_ptr() is always valid, luaL_prepbuffer should always return a non-null pointer for LUAL_BUFFERSIZE amount of values,
226		// and c_char can be casted as u8 (they're both 1 byte).
227		unsafe { &mut *ffi::luaL_prepbuffer(self.as_mut_ptr()).cast() }
228	}
229
230	/// Pushes the entire Lua string onto the stack as a single element, completing it's construction.
231	///
232	/// Unlike all other `StringBuffer` methods, this function leaves the stack in a well defined state:
233	/// only the finalized string remains, alongside any element present before the buffer was created.
234	///
235	/// After calling this function, the completed string can be returned to the caller by using [`LuaState::return_any`].
236	/// Alternatively, it *is* possible to just continue appending more data to the finalized string using the `StringBuffer::add_*` methods
237	/// (you will have to call this function again afterwards).
238	///
239	/// # Lua Errors
240	///
241	/// If any element that was previously pushed by the buffer (before calling this function) has been manually replaced
242	/// (via either [`StringBuffer::add_value`] or [`StringBuffer::lua`]) to an invalid value, this function will most likely error.
243	#[inline]
244	pub fn push_result(self: &mut Pin<&mut Self>) {
245		// SAFETY: self.as_mut_ptr() is always valid.
246		unsafe { ffi::luaL_pushresult(self.as_mut_ptr()); }
247	}
248
249	/// Finishes the currently called [`CFunction`], returning the completed string to the caller.
250	///
251	/// This function has the same caveats as [`StringBuffer::push_result`].
252	///
253	/// # Safety
254	///
255	/// The returned `AmountReturned` must not be invalidated.
256	/// *(There is currently no way to enforce this, hence the function's unsafeness.)*
257	///
258	/// This function's behavior may be changed in the future once a way to make this function safe is found.
259	///
260	/// [`CFunction`]: ./type.CFunction.html
261	#[inline]
262	pub unsafe fn return_result(mut self: Pin<&mut Self>) -> AmountReturned {
263		self.push_result();
264
265		// SAFETY: There should always be at least one element in the stack and the caller must not invalidate that.
266		unsafe { AmountReturned::unchecked(1) }
267	}
268
269	/// Temporarily grants access to the borrowed [`LuaState`] to the given function `f`,
270	/// returning whatever `f` returns.
271	///
272	/// As the stack might have grown in an unspecified way, you should not assume on the size or top elements of the stack.
273	///
274	/// # Panics
275	///
276	/// Panics if the amounts of elements present in the stack after `f` is called is not the same as the amount before `f` was called.
277	///
278	/// While replacing the elements pushed by the `StringBuffer` itself *is* possible and won't cause a panic,
279	/// it's still considered a logic error and it will likely cause a Lua error once [`StringBuffer::push_result`] is called.
280	#[inline]
281	pub fn lua<T>(self: &mut Pin<&mut Self>, f: impl FnOnce(&mut LuaState) -> T) -> T {
282		// SAFETY: self.0.L is always valid and there should currently not be another accessible LuaState.
283		let mut lua = unsafe { LuaState::from_ptr(self.0.L) };
284		let top = lua.get_top();
285		let result = f(&mut lua);
286
287		assert!(top == lua.get_top(), "temporarily accessed the Lua state as the stack held {top} elements, but left it with {} elements instead", lua.get_top());
288		result
289	}
290}
291
292impl<T: AsRef<[u8]>> AddAssign<T> for Pin<&mut StringBuffer<'_>> {
293	/// Behaves the same as [`StringBuffer::add_string`].
294	#[inline]
295	fn add_assign(&mut self, rhs: T) {
296		self.add_string(rhs);
297	}
298}
299
300impl<T: AsRef<[u8]>> Extend<T> for Pin<&mut StringBuffer<'_>> {
301	#[inline]
302	fn extend<U: IntoIterator<Item = T>>(&mut self, iter: U) {
303		for string in iter {
304			self.add_string(string);
305		}
306	}
307}
308
309/// [`StringBuffer`] initialization function.
310///
311/// *[See also the `StringBuffer` struct](struct.StringBuffer.html)*.
312impl LuaState {
313	/// Initializes the given `buffer`, returning a pinned mutable reference to it.
314	#[inline]
315	pub fn init_buffer<'a>(&'a mut self, buffer: Pin<&'a mut MaybeUninit<StringBuffer<'a>>>) -> Pin<&'a mut StringBuffer<'a>> {
316		// SAFETY: self.as_ptr() is always valid, the buffer is pinned, and getting a mutable reference to it does not move it.
317		unsafe {
318			ffi::luaL_buffinit(self.as_ptr(), buffer.as_ptr().cast_mut().cast());
319			buffer.map_unchecked_mut(|buffer| buffer.assume_init_mut())
320		}
321	}
322}