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}