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, condition: bool) -> AmountReturned {
99/// lua.return_module(reg![
100///     (c"foo", foo), // Any expression can be used inside the tuple!
101///     (c"bar", if condition { bar } else { baz })
102/// ])
103/// # }
104/// ```
105/// - Just the function itself (as a [`CFunction`]), the function's name will be derived from the given identifier:
106/// ```
107/// # use low_luajit::{LuaState, AmountReturned, reg};
108/// # extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
109/// # extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
110/// # extern "C-unwind" fn baz(lua: LuaState) -> AmountReturned { AmountReturned::NONE }
111/// # extern "C-unwind" fn example(mut lua: LuaState, condition: bool) -> AmountReturned {
112/// // Only identifiers (no expressions) can be used here.
113/// lua.register_module(c"my_global_module", reg![foo, bar, baz]);
114/// # AmountReturned::NONE }
115/// ```
116///
117/// *(All elements of the input list must be specified the same way.)*
118///
119/// The generated slice can be passed to various functions used in module creation:
120/// - [`LuaState::return_module`] returns to the caller a table containing the slice's functions.
121/// - [`LuaState::register_module`] sets as a new global a table containing the slice's functions.
122/// - [`LuaState::new_module`] pushes onto the stack a table containing the slice's functions.
123/// - *(Etc.)*
124#[macro_export]
125macro_rules! reg {
126	($(($name:expr, $func:expr)),* $(,)?) => {
127		&[$($crate::Reg::new($name, $func)),*, $crate::Reg::END]
128	};
129	($($func:ident),* $(,)?) => {
130		// SAFETY: function names should not be able to contain nul terminators, and a nul terminator is added at the end.
131		$crate::reg![$((unsafe {core::ffi::CStr::from_bytes_with_nul_unchecked(concat!(stringify!($func), '\0').as_bytes())}, $func)),*]
132	}
133}
134
135/// Module related functions.
136impl LuaState {
137	/// Pushes onto the stack a table filled with all the functions contained in `list` and finishes the currently called [`CFunction`].
138	///
139	/// The returned [`AmountReturned`] will always be `1`, which will make Lua return the pushed table to the caller.
140	///
141	/// This function can be handy to quickly return your module's functions from your `luaopen_crate_name` function.
142	///
143	/// # Panics
144	///
145	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
146	/// *([`reg!`] automatically appends the terminator for you.)*
147	///
148	/// # Examples
149	///
150	/// ```
151	/// # use low_luajit::{LuaState, AmountReturned, reg};
152	/// extern "C-unwind" fn foo(lua: LuaState) -> AmountReturned {
153	///     // ...
154	/// # AmountReturned::NONE
155	/// }
156	///
157	/// extern "C-unwind" fn bar(lua: LuaState) -> AmountReturned {
158	///     // ...
159	/// # AmountReturned::NONE
160	/// }
161	///
162	/// #[unsafe(no_mangle)]
163	/// extern "C-unwind" fn luaopen_crate_name(lua: LuaState) -> AmountReturned {
164	///     lua.return_module(reg![foo, bar])
165	/// }
166	/// ```
167	#[inline]
168	pub fn return_module(mut self, list: &[Reg]) -> AmountReturned {
169		self.new_module(list);
170
171		// SAFETY: There should always be at least one element in the stack.
172		unsafe { AmountReturned::unchecked(1) }
173	}
174
175	/// Pushes onto the stack a new table filled with all the functions contained in `list`.
176	/// This table is then set as the value of a global named `name` and of `package.loaded[name]`.
177	///
178	/// If `package.loaded[name]` already contains a table, said table will be used instead of creating a new table
179	/// (it will still be pushed onto the stack).
180	///
181	/// If `name` is `None`, this function will instead simply use the (pre-existing) top element as the table to fill.
182	/// (In this case the table will *not* be set as the value of neither a global nor of `package.loaded[name]`.)
183	///
184	/// All paths will result in the filled table being the top element.
185	///
186	/// # Panics
187	///
188	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
189	/// *([`reg!`] automatically appends the terminator for you.)*
190	///
191	/// # Lua Errors
192	///
193	/// Indexing most of the aformentioned tables may trigger the `__newindex` metamethod which could error.
194	#[inline]
195	pub fn register_module<'a>(&mut self, name: impl Into<Option<&'a CStr>>, list: &[Reg]) {
196		assert!(list.last().is_some_and(Reg::is_end), "tried to set a global module using an unterminated named function list");
197
198		// SAFETY: self.0 is always valid,
199		// name should always be a valid nul-terminated string pointer or a null pointer which luaL_register accepts,
200		// and list should always be valid and terminated.
201		unsafe { ffi::luaL_register(self.0, name.into().map_or(ptr::null(), CStr::as_ptr), list.as_ptr().cast()); }
202	}
203
204	/// Creates a new table and pushes it onto the stack.
205	///
206	/// This table will be filled with all the functions contained in `list`.
207	///
208	/// To automatically return this table to the caller, see [`LuaState::return_module`].
209	///
210	/// For a function that only pushes an empty pre-allocated (for `list`) table to the stack, see [`LuaState::new_module_table`].
211	///
212	/// # Panics
213	///
214	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
215	/// *([`reg!`] automatically appends the terminator for you.)*
216	#[inline]
217	pub fn new_module(&mut self, list: &[Reg]) {
218		assert!(list.last().is_some_and(Reg::is_end), "tried to create a module using an unterminated named function list");
219
220		// SAEFTY: self.0 is always valid and list should always be valid and terminated.
221		unsafe { ffi::luaL_newlib(self.0, list.as_ptr().cast()); }
222	}
223
224	/// Creates a new empty table and pushes it onto the stack.
225	///
226	/// This table will have as many elements as there are function in `list` pre-allocated.
227	///
228	// To fill this empty pre-allocated table with `list`'s functions, see [`LuaState::set_functions`]. (TODO)
229	///
230	/// For a function that both pushes a pre-allocated table to the stack and fills it with `list`'s functions, see [`LuaState::new_module`].
231	///
232	/// # Panics
233	///
234	/// Panics if `list` does not have the [`Reg::END`] terminator at its end.
235	/// *([`reg!`] automatically appends the terminator for you.)*
236	#[inline]
237	pub fn new_module_table(&mut self, list: &[Reg]) {
238		assert!(list.last().is_some_and(Reg::is_end), "tried to create a module's table using an unterminated named function list");
239
240		// SAEFTY: self.0 is always valid and list should always be valid and terminated.
241		unsafe { ffi::luaL_newlibtable(self.0, list.as_ptr().cast()); }
242	}
243}