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}