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}