Skip to main content

low_luajit/lua/
module.rs

1use core::{ffi::CStr, mem, ptr};
2use crate::{AmountReturned, CFunction, LuaState, ffi::{self, lua_CFunction, luaL_Reg}, map_ptr};
3
4#[cfg(doc)]
5use crate::reg;
6
7/// Safe wrapper for [`luaL_Reg`], used in module creation.
8///
9/// This type is usually contained within a `&[Reg]`, these slices can be generated easily using the [`reg!`] macro or created manually
10/// using [`Reg::new`] and [`Reg::END`].
11///
12/// *[See also the `reg!` macro](macro.reg.html)*.
13#[repr(transparent)]
14#[derive(Debug, Clone, Copy)]
15pub struct Reg(luaL_Reg);
16
17impl Reg {
18	/// The terminator of a `Reg` slice. All slices must have the terminator as their last element.
19	///
20	/// [`reg!`] automatically appends the terminator to the generated slice.
21	pub const END: Self = Self(luaL_Reg { name: ptr::null(), func: None });
22
23	/// Manually creates a new `Reg` element intended to be inserted into a slice.
24	///
25	/// This function is internally called for you by [`reg!`].
26	#[inline]
27	#[must_use]
28	pub const fn new(name: &'static CStr, func: CFunction) -> Self {
29		Self(luaL_Reg {
30			name: name.as_ptr(), // SAFETY: same as CFunction::to_raw, just cannot call the method directly since it's a const function.
31			func: Some(unsafe { mem::transmute::<CFunction, lua_CFunction>(func) })
32		})
33	}
34
35	/// Wraps a pre-existing raw [`luaL_Reg`].
36	///
37	/// # Safety
38	///
39	/// - `name` must either be null or a valid string pointer.
40	/// - `func` must either be `None` or safe to call.
41	///
42	/// A non-modified [`luaL_Reg`] obtained from [`Reg::into_raw`] should always be safe to use.
43	#[inline]
44	#[must_use]
45	pub const unsafe fn from_raw(raw: luaL_Reg) -> Self {
46		Self(raw)
47	}
48
49	/// Returns the wrapped [`luaL_Reg`].
50	///
51	/// The raw [`luaL_Reg`] can be safely converted back into a `Reg` using [`Reg::from_raw`], as long as it is not modified.
52	#[inline]
53	#[must_use]
54	pub const fn into_raw(self) -> luaL_Reg {
55		self.0
56	}
57
58	/// Returns the stored function name.
59	///
60	/// [`Reg::END`] will return `None` instead.
61	#[inline]
62	#[must_use]
63	pub const fn name(&self) -> Option<&'static CStr> {
64		// SAFETY: self.0.name should always be a valid nul-terminated string pointer when not null.
65		map_ptr!(self.0.name, unsafe { CStr::from_ptr(self.0.name) })
66	}
67
68	/// Returns the stored function.
69	///
70	/// [`Reg::END`] will return `None` instead.
71	#[inline]
72	#[must_use]
73	pub const fn func(&self) -> Option<CFunction> {
74		// SAFETY: self.0.func should be safe as it comes from a CFunction, but CFunction::from_raw cannot be called since it's a const function.
75		match self.0.func {
76			Some(func) => Some(unsafe { mem::transmute::<lua_CFunction, CFunction>(func) }),
77			None => None,
78		}
79	}
80
81	/// Returns `true` if this `Reg` is equal to [`Reg::END`], `false` otherwise.
82	#[inline]
83	#[must_use]
84	pub const fn is_end(&self) -> bool {
85		self.0.name.is_null() && self.0.func.is_none()
86	}
87}
88
89/// Generates a list of named functions (`&[Reg]`) to be used in module creation.
90///
91/// The macro takes as input a list of the functions to put in the [`Reg`] slice. The list's elements can be:
92/// - A tuple comprising of the function's name (as a `CStr`) and the function itself (as a [`CFunction`]):
93/// ```
94/// # use low_luajit::{LuaState, AmountReturned, reg};
95/// # extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
96/// # extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
97/// # extern "C-unwind" fn baz(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
98/// # extern "C-unwind" fn example(lua: LuaState) -> AmountReturned {
99/// # const CONDITION: bool = true;
100/// lua.return_module(reg![
101///     (c"foo", foo), // Any (const) expression can be used inside the tuple!
102///     (c"bar", if CONDITION { bar } else { baz })
103/// ])
104/// # }
105/// ```
106/// - Just the function itself (as a [`CFunction`]), the function's name will be derived from the given identifier:
107/// ```
108/// # use low_luajit::{LuaState, AmountReturned, reg};
109/// # extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
110/// # extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
111/// # extern "C-unwind" fn baz(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
112/// # extern "C-unwind" fn example(mut lua: LuaState, condition: bool) -> AmountReturned {
113/// // Only identifiers can be used here.
114/// lua.register_module(c"my_global_module", reg![foo, bar, baz]);
115/// # AmountReturned::NONE }
116/// ```
117///
118/// *(All elements of the input list must be specified the same way.)*
119///
120/// The generated slice can be passed to various functions used in module creation:
121/// - [`LuaState::return_module`] returns to the caller a table containing the slice's functions.
122/// - [`LuaState::register_module`] sets as a new global a table containing the slice's functions.
123/// - [`LuaState::new_module`] pushes onto the stack a table containing the slice's functions.
124/// - *(Etc.)*
125#[macro_export]
126macro_rules! reg {
127	($(($name:expr, $func:expr)),* $(,)?) => {
128		const { &[$($crate::Reg::new($name, $func)),*, $crate::Reg::END] }
129	};
130	($($func:ident),* $(,)?) => {
131		// SAFETY: function names should not be able to contain nul terminators, and a nul terminator is added at the end.
132		$crate::reg![$((unsafe {core::ffi::CStr::from_bytes_with_nul_unchecked(concat!(stringify!($func), '\0').as_bytes())}, $func)),*]
133	}
134}
135
136/// Module related functions.
137impl LuaState {
138	/// Pushes onto the stack a table filled with all the functions contained in `list` and finishes the currently called [`CFunction`].
139	///
140	/// The returned [`AmountReturned`] will always be `1`, which will make Lua return the pushed table to the caller.
141	///
142	/// This function can be handy to quickly return your module's functions from your `luaopen_crate_name` function.
143	///
144	/// # Panics
145	///
146	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
147	/// *([`reg!`] automatically appends the terminator for you.)*
148	///
149	/// # Examples
150	///
151	/// ```
152	/// use low_luajit::{LuaState, AmountReturned, reg};
153	///
154	/// extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned {
155	///     // ...
156	/// # AmountReturned::NONE
157	/// }
158	///
159	/// extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned {
160	///     // ...
161	/// # AmountReturned::NONE
162	/// }
163	///
164	/// #[unsafe(no_mangle)]
165	/// extern "C-unwind" fn luaopen_crate_name(lua: LuaState) -> AmountReturned {
166	///     lua.return_module(reg![foo, bar])
167	/// }
168	/// ```
169	#[inline]
170	pub fn return_module(mut self, list: &[Reg]) -> AmountReturned {
171		self.new_module(list);
172
173		// SAFETY: There should always be at least one element in the stack.
174		unsafe { AmountReturned::unchecked(1) }
175	}
176
177	/// Pushes onto the stack a new table filled with all the functions contained in `list`.
178	/// This table is then set as the value of a global named `name` and of `package.loaded[name]`.
179	///
180	/// If `package.loaded[name]` already contains a table, said table will be used instead of creating a new table
181	/// (it will still be pushed onto the stack).
182	///
183	/// If `name` is `None`, this function will instead simply use the (pre-existing) top element as the table to fill.
184	/// (In this case the table will *not* be set as the value of neither a global nor of `package.loaded[name]`.)
185	///
186	/// All paths will result in the filled table being the top element.
187	///
188	/// # Panics
189	///
190	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
191	/// *([`reg!`] automatically appends the terminator for you.)*
192	///
193	/// # Lua Errors
194	///
195	/// Indexing most of the aformentioned tables may trigger the `__newindex` metamethod which could error.
196	#[inline]
197	pub fn register_module<'a>(&mut self, name: impl Into<Option<&'a CStr>>, list: &[Reg]) {
198		assert!(list.last().is_some_and(Reg::is_end), "tried to set a global module using an unterminated named function list");
199
200		// SAFETY: self.as_ptr() is always valid,
201		// name should always be a valid nul-terminated string pointer or a null pointer which luaL_register accepts,
202		// and list should always be valid and terminated.
203		unsafe { ffi::luaL_register(self.as_ptr(), name.into().map_or(ptr::null(), CStr::as_ptr), list.as_ptr().cast()); }
204	}
205
206	/// Creates a new table and pushes it onto the stack.
207	///
208	/// This table will be filled with all the functions contained in `list`.
209	///
210	/// To automatically return this table to the caller, see [`LuaState::return_module`].
211	///
212	/// For a function that only pushes an empty pre-allocated (for `list`) table to the stack, see [`LuaState::new_module_table`].
213	///
214	/// # Panics
215	///
216	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
217	/// *([`reg!`] automatically appends the terminator for you.)*
218	#[inline]
219	pub fn new_module(&mut self, list: &[Reg]) {
220		assert!(list.last().is_some_and(Reg::is_end), "tried to create a module using an unterminated named function list");
221
222		// SAEFTY: self.as_ptr() is always valid and list should always be valid and terminated.
223		unsafe { ffi::luaL_newlib(self.as_ptr(), list.as_ptr().cast()); }
224	}
225
226	/// Creates a new empty table and pushes it onto the stack.
227	///
228	/// This table will have as many elements as there are function in `list` pre-allocated.
229	///
230	/// To fill this empty pre-allocated table with `list`'s functions, see [`LuaState::set_cfunctions`].
231	///
232	/// For a function that both pushes a pre-allocated table to the stack and fills it with `list`'s functions, see [`LuaState::new_module`].
233	///
234	/// # Panics
235	///
236	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
237	/// *([`reg!`] automatically appends the terminator for you.)*
238	#[inline]
239	pub fn new_module_table(&mut self, list: &[Reg]) {
240		assert!(list.last().is_some_and(Reg::is_end), "tried to create a module's table using an unterminated named function list");
241
242		// SAEFTY: self.as_ptr() is always valid and list should always be valid and terminated.
243		unsafe { ffi::luaL_newlibtable(self.as_ptr(), list.as_ptr().cast()); }
244	}
245
246	/// Fills the table located at the top of the stack with `list`'s functions.
247	///
248	/// # Panics
249	///
250	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
251	/// *([`reg!`] automatically appends the terminator for you.)*
252	///
253	/// # Lua Errors
254	///
255	/// The top element may not be a table, and not every value can be used: if the value is `nil` this function will error.
256	///
257	/// Indexing the top element may also trigger the `__newindex` metamethod which could error.
258	#[inline]
259	pub fn set_cfunctions(&self, list: &[Reg]) {
260		// SAFETY: No elements are popped except the elements that are pushed by luaL_setfuncs itself.
261		unsafe { self.access_mut(|lua| lua.set_cclosures(list, 0)) }
262	}
263
264	/// Fills the table located at the top (before the `n` elements) of the stack with `list`'s functions,
265	/// while also associating `n` values to them (see [`LuaState::push_cclosure`]).
266	///
267	/// # Panics
268	///
269	/// Panics if the stack currently contains less than `n` values or if `list` does not have the [`Reg::END`] terminator at its end.
270	/// *([`reg!`] automatically appends the terminator for you.)*
271	///
272	/// # Lua Errors
273	///
274	/// The indexed value may not be a table, and not every value can be used: if the value is `nil` this function will error.
275	///
276	/// Indexing said value may also trigger the `__newindex` metamethod which could error.
277	#[inline]
278	pub fn set_cclosures(&mut self, list: &[Reg], n: u8) {
279		assert!(list.last().is_some_and(Reg::is_end), "tried to set a module's functions using an unterminated named function list");
280		assert!(i32::from(n) <= self.get_top(), "tried to set a module's functions with {n} upvalues but the stack currently holds {} values", self.get_top());
281
282		// SAEFTY: self.as_ptr() is always valid, list should always be valid and terminated and the stack contains enough values to pop.
283		unsafe { ffi::luaL_setfuncs(self.as_ptr(), list.as_ptr().cast(), i32::from(n)); }
284	}
285}