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}