Skip to main content

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}