low_luajit/lua/type/table.rs
1use core::{ffi::CStr, marker::PhantomData, ops::Not};
2use crate::{LuaState, ffi, map_ptr};
3
4macro_rules! iterator_impl {
5 ($iterator:ident, $assert_err:literal; $(impla {$($impl_fn:item)*};)? $($iter_fn:item)+) => {
6 impl $iterator<'_> {
7 /// Constructs a new iterator over the table at the given `index`.
8 ///
9 /// # Panics
10 ///
11 /// Panics if the element at the given `index` is not a table.
12 #[inline]
13 pub fn new(lua: &LuaState, index: i32) -> Self {
14 let iterator = Self { lua: lua.as_ptr(), _marker: PhantomData };
15
16 let mut lua = iterator.lua();
17 lua.push_value(index);
18 lua.push_nil();
19 lua.push_nil();
20 assert!(lua.is_table(-3), $assert_err, index);
21
22 iterator
23 }
24
25 /// Grants special access to the Lua state for the iterator. Do not expose to the public API!
26 const fn lua(&self) -> LuaState {
27 // SAFETY: self.lua is always valid and the originating LuaState should be borrowed.
28 // While usually this would not be safe enough (as the user can still *read* from the state),
29 // this iterator does not interact with elements it has not pushed itself and said elements are all referenced in a table.
30 unsafe { LuaState::from_ptr(self.lua) }
31 }
32
33 $($($impl_fn)*)?
34 }
35
36 impl Iterator for $iterator<'_> {
37 type Item = (i32, i32);
38
39 $($iter_fn)+
40 }
41
42 impl Drop for $iterator<'_> {
43 #[inline]
44 fn drop(&mut self) {
45 // SAFETY: The stack should always hold at least 3 elements until now.
46 unsafe { self.lua().pop_unchecked(3); }
47 }
48 }
49 };
50}
51
52/// Iterator over all key-value pairs of a specified table.
53///
54/// This iterator internally calls [`LuaState::next`] for you,
55/// returning for each iteration the indices to the key and value, respectively.
56/// (Currently, the returned indices are always `-2` and `-1`, this may change in the future.)
57///
58/// Once created, three elements will be pushed onto the stack *(the table, the key, and the value)*.
59/// Each iteration overwrites the previous key-value pair's elements with the new pair's.
60/// All three elements will be automatically popped once the iterator is *dropped*.
61///
62/// After traversing the entire table, the iterator can be reused to reiterate over the entire table again and again if needed.
63///
64/// Multiple `Pairs` (and [`IPairs`]) may exist simultaneously for nested iterations,
65/// but the iterators should be used and dropped in the order in which they were created:
66/// doing otherwise is considered a logic error and will result in unexpected behavior *(not UB)*.
67///
68/// Finishing the loop without properly dropping the iterator is also considered a logic error (as the elements would not be popped).
69///
70/// Lastly, getting the key's value using a function that can replace the number stored in the stack with a string
71/// (like [`LuaState::to_string`]) may cause a logic error if said replacement occurs.
72#[repr(transparent)]
73#[derive(Debug)]
74#[must_use = "iterators are lazy and do nothing unless consumed"]
75pub struct Pairs<'a> {
76 lua: *mut ffi::lua_State,
77 _marker: PhantomData<&'a LuaState>,
78}
79
80iterator_impl!(
81 Pairs,
82 "cannot iterate over all key-value pairs of the element at index {} as it is not a table";
83
84 #[inline]
85 fn next(&mut self) -> Option<Self::Item> {
86 let mut lua = self.lua();
87 // SAFETY: The stack should always hold at least 3 elements until this Pairs is dropped.
88 unsafe { lua.pop_unchecked(1); }
89
90 // SAFETY: The element at index -2 should always be a table.
91 if unsafe { lua.next_unchecked(-2) } {
92 Some((-2, -1))
93 } else {
94 lua.push_nil();
95 lua.push_nil();
96 None
97 }
98 }
99);
100
101/// Iterator over the indexed key-value pairs of a specified table.
102///
103/// This iterator returns for each iteration the indices to the key and value, respectively.
104/// (Currently, the returned indices are always `-2` and `-1`, this may change in the future.)
105/// Just like Lua's `ipairs` function, it starts at key `1` and increments it for each iteration;
106/// stopping once the key does not contain a value in the table.
107///
108/// Once created, three elements will be pushed onto the stack *(the table, the value, and the key)*.
109/// Each iteration overwrites the previous key-value pair's elements with the new pair's.
110/// All three elements will be automatically popped once the iterator is *dropped*.
111///
112/// Calling `next()` after the iterator ended will still cause it to increment the key and read the next key-value pair,
113/// which could be used to skip over gaps.
114///
115/// Multiple `IPairs` (and [`Pairs`]) may exist simultaneously for nested iterations,
116/// but the iterators should be used and dropped in the order in which they were created:
117/// doing otherwise is considered a logic error and will result in unexpected behavior *(not UB)*.
118///
119/// Finishing the loop without properly dropping the iterator is also considered a logic error (as the elements would not be popped).
120#[repr(transparent)]
121#[derive(Debug)]
122#[must_use = "iterators are lazy and do nothing unless consumed"]
123pub struct IPairs<'a> {
124 lua: *mut ffi::lua_State,
125 _marker: PhantomData<&'a LuaState>,
126}
127
128iterator_impl!(
129 IPairs,
130 "cannot iterate over the indexed key-value pairs of the element at index {} as it is not a table";
131
132 #[inline]
133 #[allow(clippy::cast_possible_truncation)]
134 fn next(&mut self) -> Option<Self::Item> {
135 let mut lua = self.lua();
136 let i = lua.to_integer(-2) + 1;
137 // SAFETY: The stack should always hold at least 3 elements until this IPairs is dropped.
138 unsafe { lua.pop_unchecked(2); }
139
140 lua.push_integer(i);
141 // SAFETY: lua.as_ptr() is always valid and the element at index -2 should always be a table.
142 unsafe { ffi::lua_rawgeti(lua.as_ptr(), -2, i as i32); }
143 lua.is_nil(-1).not().then_some((-2, -1))
144 }
145
146 #[inline]
147 fn size_hint(&self) -> (usize, Option<usize>) {
148 (0, Some(self.lua().object_length(-3)))
149 }
150);
151
152impl IPairs<'_> {
153 /// Returns the index that the iterator has last read.
154 ///
155 /// If the iterator has not done its first iteration yet, `0` will be returned.
156 #[inline]
157 #[must_use]
158 pub fn current_index(&self) -> usize {
159 self.lua().to_integer(-2).cast_unsigned()
160 }
161}
162
163/// Table related functions.
164impl LuaState {
165 /// Returns `true` if the value at the given acceptable `index` is a table, `false` otherwise.
166 #[inline]
167 #[must_use]
168 pub fn is_table(&self, index: i32) -> bool {
169 // SAFETY: self.as_ptr() is always valid.
170 unsafe { ffi::lua_istable(self.as_ptr(), index) != 0 }
171 }
172
173 /// Creates a new empty table and pushes it onto the stack.
174 ///
175 /// To pre-allocate elements when creating a new table, see [`LuaState::create_table`].
176 #[inline]
177 pub fn new_table(&mut self) {
178 // SAFETY: self.as_ptr() is always valid.
179 unsafe { ffi::lua_newtable(self.as_ptr()); }
180 }
181
182 /// Creates a new empty table and pushes it onto the stack.
183 ///
184 /// The table will have `n_arr` array elements and `n_rec` non-array elements pre-allocated.
185 ///
186 /// To create a new table without pre-allocating, see [`LuaState::new_table`].
187 ///
188 /// # Lua Errors
189 ///
190 /// Calling this function way too many times may trigger a stack overflow.
191 ///
192 /// Incredibly high values for `n_arr` or `n_rec` may instantly trigger a table overflow.
193 #[inline]
194 pub fn create_table(&mut self, n_arr: i32, n_rec: i32) {
195 // SAFETY: self.as_ptr() is always valid.
196 unsafe { ffi::lua_createtable(self.as_ptr(), n_arr, n_rec); }
197 }
198
199 /// Does the equivalent of the Lua code `t[key]`, where `t` is the value located at the given valid `index`.
200 ///
201 /// The result is pushed onto the stack (even if `nil`).
202 ///
203 /// For a function that does the same but allows any value instead of `key`, see [`LuaState::get_table`].
204 ///
205 /// # Lua Errors
206 ///
207 /// Not every value `t` can be indexed: for example, if `t` is `nil` this function will error.
208 ///
209 /// Indexing `t` may also trigger the `__index` metamethod which could error.
210 #[inline]
211 pub fn get_field(&mut self, index: i32, key: &CStr) {
212 // SAFETY: self.as_ptr() is always valid and key's pointer is valid for the duration of this function.
213 unsafe { ffi::lua_getfield(self.as_ptr(), index, key.as_ptr()); }
214 }
215
216 /// Does the equivalent of the Lua code `t[key] = v`, where `t` is the value located at the given valid `index`
217 /// and `v` is the value at the top of the stack (which is popped).
218 ///
219 /// For a function that does the same but allows any value instead of `key`, see [`LuaState::set_table`].
220 ///
221 /// # Panics
222 ///
223 /// Panics if the stack is empty.
224 ///
225 /// # Lua Errors
226 ///
227 /// Not every value `t` can be indexed: for example, if `t` is `nil` this function will error.
228 ///
229 /// Indexing `t` may also trigger the `__newindex` metamethod which could error.
230 #[inline]
231 pub fn set_field(&mut self, index: i32, key: &CStr) {
232 assert!(self.get_top() > 0, "tried to set field {key:?} for the table at index {index} despite having an empty stack");
233
234 // SAFETY: self.as_ptr() is always valid, key's pointer is valid for the duration of this function, and the stack is not empty.
235 unsafe { ffi::lua_setfield(self.as_ptr(), index, key.as_ptr()); }
236 }
237
238 /// Gets the global named `name` and pushes it onto the stack (even if `nil`).
239 ///
240 /// # Lua Errors
241 ///
242 /// Indexing the global environment may trigger the `__index` metamethod which could error.
243 #[inline]
244 pub fn get_global(&mut self, name: &CStr) {
245 // SAFETY: self.as_ptr() is always valid and name's pointer is valid for the duration of this function.
246 unsafe { ffi::lua_getglobal(self.as_ptr(), name.as_ptr()); }
247 }
248
249 /// Pops the top element and sets it as the value of the global named `name`.
250 ///
251 /// # Panics
252 ///
253 /// Panics if the stack is empty.
254 ///
255 /// # Lua Errors
256 ///
257 /// Indexing the global environment may trigger the `__newindex` metamethod which could error.
258 #[inline]
259 pub fn set_global(&mut self, name: &CStr) {
260 assert!(self.get_top() > 0, "tried to set global {name:?} despite having an empty stack");
261
262 // SAFETY: self.as_ptr() and name's pointer are always valid and the stack is not empty.
263 unsafe { ffi::lua_setglobal(self.as_ptr(), name.as_ptr()); }
264 }
265
266 /// Does the equivalent of the Lua code `t[k]`, where `t` is the value located at the given valid `index`
267 /// and `k` is the value at the top of the stack (which is popped).
268 ///
269 /// The result is pushed onto the stack, replacing `k` (even if `nil`).
270 ///
271 /// # Panics
272 ///
273 /// Panics if the stack is empty.
274 ///
275 /// # Lua Errors
276 ///
277 /// Not every value `t` can be indexed: for example, if `t` is `nil` this function will error.
278 ///
279 /// Indexing `t` may also trigger the `__index` metamethod which could error.
280 #[inline]
281 pub fn get_table(&mut self, index: i32) {
282 assert!(self.get_top() > 0, "tried to get a value from the table at index {index} despite having an empty stack");
283
284 // SAFETY: self.as_ptr() is always valid and the stack is not empty.
285 unsafe { ffi::lua_gettable(self.as_ptr(), index); }
286 }
287
288 /// Does the equivalent of the Lua code `t[k] = v`, where `t` is the value located at the given valid `index`
289 /// and `v` and `k` are the values at the top of the stack (which are popped from the stack in said order).
290 ///
291 /// # Panics
292 ///
293 /// Panics if the stack does not contain both `v` and `k` (thus contains less than 2 elements).
294 ///
295 /// # Lua Errors
296 ///
297 /// Not every value `t` or `k` can be used: if `t` or `k` are `nil` this function will error.
298 ///
299 /// Indexing `t` may also trigger the `__newindex` metamethod which could error.
300 #[inline]
301 pub fn set_table(&mut self, index: i32) {
302 assert!(2 <= self.get_top(), "tried to set a value for the table at index {index} despite not having `v` and `k` in the stack");
303
304 // SAFETY: self.as_ptr() is always valid and v and k are in the stack.
305 unsafe { ffi::lua_settable(self.as_ptr(), index); }
306 }
307
308 /// Accesses a table similarly to [`LuaState::get_table`], but without triggering any metamethod (see [`LuaState::get_table`]).
309 ///
310 /// # Panics
311 ///
312 /// Panics if the element at the given valid `index` is not a table or if the stack is empty.
313 #[inline]
314 pub fn raw_get(&mut self, index: i32) {
315 assert!(self.is_table(index), "cannot raw get a value from the element at index {index} as it is not a table");
316 assert!(self.get_top() > 0, "tried to raw get a value from the table at index {index} despite having an empty stack");
317
318 // SAFETY: self.as_ptr() is always valid, the element specified is a table and the stack is not empty.
319 unsafe { ffi::lua_rawget(self.as_ptr(), index); }
320 }
321
322 /// Accesses a table similarly to [`LuaState::set_table`], but without triggering any metamethod (see [`LuaState::set_table`]).
323 ///
324 /// # Panics
325 ///
326 /// Panics if the element at the given valid `index` is not a table or if the stack is empty.
327 ///
328 /// # Lua errors
329 ///
330 /// Not every value `k` can be used: if `k` is `nil` this function will error.
331 #[inline]
332 pub fn raw_set(&mut self, index: i32) {
333 assert!(self.is_table(index), "cannot raw set a value for the element at index {index} as it is not a table");
334 assert!(2 <= self.get_top(), "tried to raw set a value for the table at index {index} despite not having `v` and `k` in the stack");
335
336 // SAFETY: self.as_ptr() is always valid, the element specified is a table and v and k are in the stack.
337 unsafe { ffi::lua_rawset(self.as_ptr(), index); }
338 }
339
340 /// Does the equivalent of the Lua code `t[i]`, where `t` is the value located at the given valid `index`.
341 ///
342 /// The result is pushed onto the stack.
343 ///
344 /// # Panics
345 ///
346 /// Panics if the element at the given valid `index` is not a table.
347 #[inline]
348 pub fn raw_geti(&mut self, index: i32, i: i32) {
349 assert!(self.is_table(index), "cannot raw get an element from the element at index {index} as it is not a table");
350
351 // SAFETY: self.as_ptr() is always valid and the element specified is a table.
352 unsafe { ffi::lua_rawgeti(self.as_ptr(), index, i); }
353 }
354
355 /// Does the equivalent of the Lua code `t[i] = v`, where `t` is the value located at the given valid `index`
356 /// and `v` is the value at the top of the stack (which is popped).
357 ///
358 /// # Panics
359 ///
360 /// Panics if the element at the given valid `index` is not a table or if the stack is empty.
361 #[inline]
362 pub fn raw_seti(&mut self, index: i32, i: i32) {
363 assert!(self.is_table(index), "cannot raw set a value for the element at index {index} as it is not a table");
364 assert!(self.get_top() > 0, "tried to raw set a value for the table at index {index} despite having an empty stack");
365
366 // SAFETY: self.as_ptr() is always valid, the element specified is a table and the stack is not empty.
367 unsafe { ffi::lua_rawseti(self.as_ptr(), index, i); }
368 }
369
370 /// Pops the last element of the stack, treating it as the key,
371 /// and pushes onto the stack the *next* key-value pair from the table at the given `index`, returning `true`.
372 ///
373 /// If there is no next key-value pair, then nothing is pushed and `false` is returned instead.
374 ///
375 /// This function can be used to traverse all elements of a table, see the example below.
376 ///
377 /// # Panics
378 ///
379 /// Panics if the element at the given `index` is not a table.
380 ///
381 /// # Lua Errors
382 ///
383 /// If the given key is neither `nil` nor present in the table, this function will error.
384 ///
385 /// # Examples
386 ///
387 /// ```
388 /// # use low_luajit::{LuaState, AmountReturned};
389 /// # extern "C-unwind" fn example(mut lua: LuaState) -> AmountReturned {
390 /// # let table_index = 1;
391 /// // Assume the table is located in the stack at index t.
392 /// lua.push_nil(); // Push the first (dummy) key.
393 /// while lua.next(table_index) { // Get the next key-value pair.
394 /// // (The key is located at index -2, while the value is located at index -1.)
395 /// // Use the key-value pair in some way...
396 /// lua.pop(1); // Pop the value, so that the key can be used to get the next key-value pair.
397 /// }
398 /// # AmountReturned::NONE }
399 /// ```
400 #[inline]
401 pub fn next(&mut self, index: i32) -> bool {
402 assert!(self.is_table(index), "cannot get the next key-value pair of the element at index {index} as it is not a table");
403
404 // SAFETY: The element at the given index is a table.
405 unsafe { self.next_unchecked(index) }
406 }
407
408 /// Behaves exactly as [`LuaState::next`], but without doing any checks.
409 ///
410 /// # Safety
411 ///
412 /// The element at the given `index` must be a table.
413 #[inline]
414 pub unsafe fn next_unchecked(&mut self, index: i32) -> bool {
415 // SAFETY: The caller must ensure the element at the given index is a table.
416 unsafe { ffi::lua_next(self.as_ptr(), index) != 0 }
417 }
418
419 /// Returns an iterator over all key-value pairs of the table at the given `index`,
420 /// mimicking the behavior of Lua's `pairs` function.
421 ///
422 /// For each iteration, the indices of the current key-value pair are returned.
423 ///
424 /// *[See also the `Pairs` struct](struct.Pairs.html)*.
425 ///
426 /// # Panics
427 ///
428 /// Panics if the element at the given `index` is not a table.
429 ///
430 /// # Examples
431 ///
432 /// Printing the Lua standard library:
433 ///
434 /// ```
435 /// use low_luajit::LuaState;
436 ///
437 /// # let mut lua = unsafe { low_luajit::__test!(luaL_openlibs) };
438 /// for (k, v) in lua.pairs(LuaState::GLOBALS_INDEX) {
439 /// println!("{}: {}", lua.display(k), lua.display(v));
440 ///
441 /// if lua.is_table(v) {
442 /// for (k, v) in lua.pairs(v) {
443 /// println!("\t{}: {}", lua.display(k), lua.display(v));
444 /// }
445 /// }
446 /// }
447 /// ```
448 #[inline]
449 pub fn pairs(&self, index: i32) -> Pairs<'_> {
450 Pairs::new(self, index)
451 }
452
453 /// Returns an iterator over the indexed key-value pairs of the table at the given `index`,
454 /// mimicking the behavior of Lua's `ipairs` function.
455 ///
456 /// For each iteration, the indices of the current key-value pair are returned.
457 ///
458 /// *[See also the `IPairs` struct](struct.IPairs.html)*.
459 ///
460 /// # Panics
461 ///
462 /// Panics if the element at the given `index` is not a table.
463 #[inline]
464 pub fn ipairs(&self, index: i32) -> IPairs<'_> {
465 IPairs::new(self, index)
466 }
467
468 /// Gets the metatable of the value at the given acceptable `index` and pushes it onto the stack, returning `true`.
469 ///
470 /// If `index` is invalid or the value has no metatable, nothing is pushed and the function returns `false`.
471 #[inline]
472 pub fn get_metatable(&mut self, index: i32) -> bool {
473 // SAFETY: self.as_ptr() is always valid.
474 unsafe { ffi::lua_getmetatable(self.as_ptr(), index) != 0 }
475 }
476
477 /// Pops the top elemenet and sets it as the metatable of the value at the given acceptable `index`.
478 ///
479 /// This function always returns `true`.
480 ///
481 /// # Panics
482 ///
483 /// Panics if the top element is not a table or if the stack is empty.
484 #[inline]
485 pub fn set_metatable(&mut self, index: i32) -> bool {
486 assert!(self.get_top() > 0, "tried to set the metatable for the element at index {index} despite having an empty stack");
487 assert!(self.is_table(-1), "cannot set the metatable for the element at index {index}, as the given metatable is not a table");
488
489 // SAFETY: self.as_ptr() is always valid, the stack is not empty and the top element is a table.
490 unsafe { ffi::lua_setmetatable(self.as_ptr(), index) != 0 }
491 }
492
493 /// Gets the environment table of the value at the given `index` and pushes it onto the stack.
494 ///
495 /// If `index` is invalid or the value has no environment table, `nil` is pushed onto the stack.
496 #[inline]
497 pub fn get_fenv(&mut self, index: i32) {
498 // SAFETY: self.as_ptr() is always valid.
499 unsafe { ffi::lua_getfenv(self.as_ptr(), index); }
500 }
501
502 /// Pops a table from the stack and sets it as the environment table of the value at the given `index`, returning `true`.
503 ///
504 /// Only functions, userdata and threads may have an environment table.
505 ///
506 /// If `index` is invalid or the value's type is not one listed above, the stack is still popped but the function returns `false`.
507 ///
508 /// # Panics
509 ///
510 /// Panics if the top element is not a table or if the stack is empty.
511 #[inline]
512 pub fn set_fenv(&mut self, index: i32) -> bool {
513 assert!(self.get_top() > 0, "tried to set the environment table for the element at index {index} despite having an empty stack");
514 assert!(self.is_table(-1), "cannot set the environment table for the element at index {index}, as the given environment table is not a table");
515
516 // SAFETY: self.as_ptr() is always valid, the stack is not empty and the top element is a table.
517 unsafe { ffi::lua_setfenv(self.as_ptr(), index) != 0 }
518 }
519
520 /// Calls the metamethod `method` from the metatable of the element at the given `index`.
521 ///
522 /// The metamethod will receive the element itself as its only argument and its result will be pushed onto the stack, returning `true`.
523 ///
524 /// If there is no metatable or no metamethod, nothing is pushed and this function returns `false` instead.
525 ///
526 /// # Lua Errors
527 ///
528 /// The called metamethod may error.
529 #[inline]
530 pub fn call_meta(&mut self, index: i32, method: &CStr) -> bool {
531 // SAFETY: self.as_ptr() is always valid and method's pointer is valid for the duration of this function.
532 unsafe { ffi::luaL_callmeta(self.as_ptr(), index, method.as_ptr()) != 0 }
533 }
534
535 /// Searches for a table located at `path` within the table located at the given `index`, creating it if it doesn't exist.
536 ///
537 /// `path` should be a list of keys separated by dots, these keys will be traversed starting from the given table.
538 /// Any missing table (`nil`) will be created while traversing by calling [`LuaState::create_table`] using `size_hint` for `n_rec`
539 /// *(only for the final table, any other table created will use `1` instead; `n_arr` is always `0`)*.
540 ///
541 /// # Panics
542 ///
543 /// Panics if the element at the given `index` is not a table.
544 ///
545 /// # Errors
546 ///
547 /// If any key in `path` leads to a value that is neither a table nor `nil`,
548 /// this function will return `Err` with a substring of `path` containing the non-traversed keys.
549 ///
550 /// # Examples
551 ///
552 /// ```
553 /// use low_luajit::LuaState;
554 ///
555 /// # let mut lua = unsafe { low_luajit::__test!(luaL_openlibs) };
556 /// assert_eq!(lua.find_table(LuaState::GLOBALS_INDEX, c"math", 0), Ok(()));
557 /// assert_eq!(lua.find_table(LuaState::GLOBALS_INDEX, c"new.tables", 123), Ok(()));
558 /// assert_eq!(lua.find_table(LuaState::GLOBALS_INDEX, c"math.pi", 0), Err(c"pi")); // Not a table!
559 /// ```
560 #[inline]
561 pub fn find_table<'a>(&mut self, index: i32, path: &'a CStr, size_hint: i32) -> Result<(), &'a CStr> {
562 assert!(self.is_table(index), "cannot find a table within the element at index {index} as it is not a table");
563
564 // SAFETY: self.as_ptr() and path are always valid and luaL_findtable returns valid nul-terminated string pointers when not null.
565 let fname = unsafe { ffi::luaL_findtable(self.as_ptr(), index, path.as_ptr(), size_hint) };
566 map_ptr!(fname, unsafe { CStr::from_ptr(fname) }).map_or(Ok(()), Err)
567 }
568}