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