locenv/
lib.rs

1use self::api::{ApiTable, BootstrapContext, LuaFunction, LuaReg, LuaState};
2use std::collections::LinkedList;
3use std::ffi::{c_void, CStr, CString};
4use std::mem::{size_of, transmute};
5use std::os::raw::{c_int, c_uint};
6use std::path::{Path, PathBuf};
7use std::ptr::{null, null_mut};
8use std::unreachable;
9
10pub mod api;
11
12pub const LUAI_IS32INT: bool = (c_uint::MAX >> 30) >= 3;
13pub const LUAI_MAXSTACK: c_int = if LUAI_IS32INT { 1000000 } else { 15000 };
14pub const LUA_REGISTRYINDEX: c_int = -LUAI_MAXSTACK - 1000;
15
16pub const LUA_TNIL: c_int = 0;
17pub const LUA_TBOOLEAN: c_int = 1;
18pub const LUA_TLIGHTUSERDATA: c_int = 2;
19pub const LUA_TNUMBER: c_int = 3;
20pub const LUA_TSTRING: c_int = 4;
21pub const LUA_TTABLE: c_int = 5;
22pub const LUA_TFUNCTION: c_int = 6;
23pub const LUA_TUSERDATA: c_int = 7;
24pub const LUA_TTHREAD: c_int = 8;
25
26pub static mut API_TABLE: *const ApiTable = null();
27
28/// A helper macro that combine `error_with_message` and `format` together.
29///
30/// # Examples
31///
32/// ```no_run
33/// # let lua: *mut locenv::api::LuaState = std::ptr::null_mut();
34/// # let e = "abc";
35/// locenv::error!(lua, "Something went wrong: {}", e);
36/// ```
37#[macro_export]
38macro_rules! error {
39    ($lua:ident, $($arg:tt)*) => {
40        $crate::error_with_message($lua, &std::format!($($arg)*))
41    }
42}
43
44/// Returns the pseudo-index that represents the i-th upvalue of the running function. i must be in the range [1,256].
45pub fn upvalue_index<P: Into<c_int>>(i: P) -> c_int {
46    LUA_REGISTRYINDEX - i.into()
47}
48
49/// Converts the acceptable index `index` into an equivalent absolute index (that is, one that does
50/// not depend on the stack size).
51pub fn abs_index(lua: *mut LuaState, index: c_int) -> c_int {
52    (api().lua_absindex)(lua, index)
53}
54
55/// Pops `count` elements from the stack.
56pub fn pop(lua: *mut LuaState, count: c_int) {
57    (api().lua_settop)(lua, -count - 1);
58}
59
60/// Pushes a copy of the element at the given index onto the stack.
61pub fn push_value(lua: *mut LuaState, index: c_int) {
62    (api().lua_pushvalue)(lua, index);
63}
64
65/// Pushes a nil value onto the stack.
66pub fn push_nil(lua: *mut LuaState) {
67    (api().lua_pushnil)(lua);
68}
69
70/// Pushes a string onto the stack. The string can contain any binary data, including
71/// embedded zeros.
72pub fn push_str(lua: *mut LuaState, value: &str) {
73    unsafe { (api().lua_pushlstring)(lua, transmute(value.as_ptr()), value.len()) };
74}
75
76/// Pushes a new function onto the stack.
77///
78/// This function receives a Rust function and pushes onto the stack a Lua value of type function
79/// that, when called, invokes the corresponding Rust function. The parameter `up` tells how many
80/// upvalues this function will have.
81pub fn push_fn(lua: *mut LuaState, value: LuaFunction, up: c_int) {
82    (api().lua_pushcclosure)(lua, value, up);
83}
84
85/// Pushes a new closure onto the stack.
86///
87/// The closure will be owned by the [`Context`] at the specified `index`.
88pub fn push_closure<T: Closure>(lua: *mut LuaState, context: c_int, value: T) {
89    let context = abs_index(lua, context);
90
91    push_value(lua, context);
92    create_userdata(lua, context, value, |_, _, _| {});
93    push_fn(lua, execute_closure::<T>, 2);
94}
95
96/// Creates a new empty table and pushes it onto the stack. Parameter `elements` is a hint for how many
97/// elements the table will have as a sequence; parameter `fields` is a hint for how many other elements the
98/// table will have. Lua may use these hints to preallocate memory for the new table. This preallocation may
99/// help performance when you know in advance how many elements the table will have.
100pub fn create_table(lua: *mut LuaState, elements: c_int, fields: c_int) {
101    (api().lua_createtable)(lua, elements, fields);
102}
103
104/// This function creates and pushes on the stack a new full userdata, with Rust object associated
105/// Lua values.
106///
107/// The userdata will be owned by the [`Context`] at the specified `index`.
108pub fn new_userdata<T: Object>(lua: *mut LuaState, context: c_int, value: T) {
109    create_userdata(lua, context, value, |lua, context, _| {
110        let methods = T::methods();
111
112        if methods.is_empty() {
113            return;
114        }
115
116        create_table(lua, 0, methods.len() as _);
117
118        for method in T::methods() {
119            push_value(lua, context);
120            (api().lua_pushlightuserdata)(lua, unsafe { transmute(method.function) });
121            push_fn(lua, invoke_method::<T>, 2);
122            set_field(lua, -2, method.name);
123        }
124
125        set_field(lua, -2, "__index");
126    });
127}
128
129/// Does the equivalent to t[key] = v, where t is the value at the given `index` and v is the value
130/// on the top of the stack.
131///
132/// This function pops the value from the stack. As in Lua, this function may trigger a metamethod
133/// for the "newindex" event.
134pub fn set_field(lua: *mut LuaState, index: c_int, key: &str) {
135    let key = CString::new(key).unwrap();
136
137    unsafe { (api().lua_setfield)(lua, index, key.as_ptr()) };
138}
139
140/// Registers all functions in the `entries` into the table on the top of the stack. When `upvalues` is not zero,
141/// all functions are created with `upvalues` upvalues, initialized with copies of the `upvalues` values previously
142/// pushed on the stack on top of the library table. These values are popped from the stack after the registration.
143pub fn set_functions(lua: *mut LuaState, entries: &[FunctionEntry], upvalues: c_int) {
144    // Build a table of LuaReg.
145    let mut table: Vec<LuaReg> = Vec::new();
146    let mut names: LinkedList<CString> = LinkedList::new();
147
148    for e in entries {
149        names.push_back(CString::new(e.name).unwrap());
150
151        table.push(LuaReg {
152            name: names.back().unwrap().as_ptr(),
153            func: e.function,
154        });
155    }
156
157    table.push(LuaReg {
158        name: null(),
159        func: None,
160    });
161
162    // Register.
163    unsafe { (api().aux_setfuncs)(lua, table.as_ptr(), upvalues) };
164}
165
166/// Returns `true` if the given `index` is not valid or if the value at this `index` is nil, and
167/// `false` otherwise.
168pub fn is_none_or_nil(lua: *mut LuaState, index: c_int) -> bool {
169    (api().lua_type)(lua, index) <= 0
170}
171
172/// If the function argument `arg` is a string, returns this string. If this argument is absent or
173/// is nil, returns [`None`]. Otherwise, raises an error.
174///
175/// This function uses [`to_string`] to get its result, so all conversions and caveats of that
176/// function apply here.
177pub fn opt_string(lua: *mut LuaState, arg: c_int) -> Option<String> {
178    if is_none_or_nil(lua, arg) {
179        None
180    } else {
181        Some(check_string(lua, arg))
182    }
183}
184
185/// Checks whether the function argument `arg` is a string and returns this string.
186pub fn check_string(lua: *mut LuaState, arg: c_int) -> String {
187    let data = unsafe { (api().aux_checklstring)(lua, arg, null_mut()) };
188    let raw = unsafe { CStr::from_ptr(data) };
189
190    raw.to_str().unwrap().into()
191}
192
193/// Converts the Lua value at the given `index` to a string.
194///
195/// The Lua value must be a string or a number; otherwise, the function returns [`None`]. If the value is a number,
196/// then this function also changes the actual value in the stack to a string.
197pub fn to_string(lua: *mut LuaState, index: c_int) -> Option<String> {
198    let value = unsafe { (api().lua_tolstring)(lua, index, null_mut()) };
199
200    if value.is_null() {
201        return None;
202    }
203
204    unsafe { Some(CStr::from_ptr(value).to_str().unwrap().into()) }
205}
206
207/// Pushes onto the stack the value t[key], where t is the value at the given `index`. As in Lua, this function may
208/// trigger a metamethod for the "index" event.
209///
210/// Returns the type of the pushed value.
211pub fn get_field(lua: *mut LuaState, index: c_int, key: &str) -> c_int {
212    let key = CString::new(key).unwrap();
213
214    unsafe { (api().lua_getfield)(lua, index, key.as_ptr()) }
215}
216
217/// Pops a table or nil from the stack and sets that value as the new metatable for the value at the
218/// given `index` (nil means no metatable).
219pub fn set_metatable(lua: *mut LuaState, index: c_int) {
220    (api().lua_setmetatable)(lua, index);
221}
222
223/// Raises a type error for the argument `arg` of the function that called it, using a standard message; `expect` is
224/// a "name" for the expected type.
225pub fn type_error(lua: *mut LuaState, arg: c_int, expect: &str) -> ! {
226    let expect = CString::new(expect).unwrap();
227
228    unsafe { (api().aux_typeerror)(lua, arg, expect.as_ptr()) };
229    unreachable!();
230}
231
232/// Raises an error reporting a problem with argument `arg` of the function that called it, using a standard message
233/// that includes `comment` as a comment:
234///
235/// `bad argument #arg to 'funcname' (comment)`
236pub fn argument_error(lua: *mut LuaState, arg: c_int, comment: &str) -> ! {
237    let comment = CString::new(comment).unwrap();
238
239    unsafe { (api().aux_argerror)(lua, arg, comment.as_ptr()) };
240    unreachable!();
241}
242
243/// Raises a Lua error with the specified message.
244pub fn error_with_message(lua: *mut LuaState, message: &str) -> ! {
245    let format = CString::new("%s").unwrap();
246    let message = CString::new(message).unwrap();
247
248    unsafe { (api().aux_error)(lua, format.as_ptr(), message.as_ptr()) };
249    unreachable!();
250}
251
252/// Raises a Lua error, using the value on the top of the stack as the error object.
253pub fn error(lua: *mut LuaState) -> ! {
254    (api().lua_error)(lua);
255    unreachable!();
256}
257
258/// A trait to allow Rust object to be able to get collected by Lua GC.
259pub trait UserData: 'static {
260    /// Gets a unique name for this type within this module.
261    fn type_name() -> &'static str;
262}
263
264/// A trait for implement Lua closure.
265pub trait Closure: UserData {
266    fn call(&mut self, lua: *mut LuaState) -> c_int;
267}
268
269/// A trait for implement Lua object.
270pub trait Object: UserData {
271    /// Gets a set of available methods.
272    fn methods() -> &'static [MethodEntry<Self>];
273}
274
275/// Represents a method of a Lua object.
276pub struct MethodEntry<T: ?Sized> {
277    pub name: &'static str,
278
279    /// A pointer to function for this method.
280    ///
281    /// Please note that the first argument for the method is on the **second** index, not the first index.
282    /// Let say the user invoke your method as the following:
283    ///
284    /// ```notrust
285    /// v:method('abc')
286    /// ```
287    ///
288    /// Within this function you can get 'abc' with:
289    ///
290    /// ```no_run
291    /// # let lua: *mut locenv::api::LuaState = std::ptr::null_mut();
292    /// locenv::check_string(lua, 2);
293    /// ```
294    ///
295    /// Notice the index is `2`, not `1`.
296    pub function: Method<T>,
297}
298
299pub type Method<T> = fn(&mut T, *mut LuaState) -> c_int;
300
301/// Represents a function to add to a Lua table.
302pub struct FunctionEntry<'name> {
303    pub name: &'name str,
304    pub function: Option<LuaFunction>,
305}
306
307/// Represents the execution context of the current function.
308pub struct Context {
309    locenv: *const c_void,
310    module_name: String,
311    working_directory: PathBuf,
312}
313
314impl Context {
315    pub unsafe fn new(bootstrap: *const BootstrapContext) -> Self {
316        Self {
317            locenv: (*bootstrap).locenv,
318            module_name: CStr::from_ptr((*bootstrap).name).to_str().unwrap().into(),
319            working_directory: CStr::from_ptr((*bootstrap).name).to_str().unwrap().into(),
320        }
321    }
322
323    /// Gets a reference to the context from Lua stack at the specified index.
324    ///
325    /// **The returned reference is valid as long as the value at the specified index alive**.
326    pub fn from_lua(lua: *mut LuaState, index: c_int) -> &'static Self {
327        // Get userdata.
328        let ud = (api().lua_touserdata)(lua, index);
329
330        if ud.is_null() {
331            error!(lua, "expect a userdata at #{}", index);
332        }
333
334        // Get type name.
335        if (api().lua_getmetatable)(lua, index) == 0 {
336            error!(lua, "expect a module context at #{}", index);
337        }
338
339        if get_field(lua, -1, "__name") != LUA_TSTRING {
340            pop(lua, 2);
341            error!(lua, "invalid metatable for the value at #{}", index);
342        }
343
344        let r#type = to_string(lua, -1).unwrap();
345
346        pop(lua, 2);
347
348        // Check if it is a Context.
349        if r#type == "locenv" || r#type.contains('.') {
350            error!(lua, "expect a module context at #{}", index);
351        }
352
353        // Dereference.
354        let context: *mut Self = null_mut();
355
356        unsafe { ud.copy_to_nonoverlapping(transmute(&context), size_of::<*mut Self>()) };
357        unsafe { &*context }
358    }
359
360    /// Gets name of the current module.
361    pub fn module_name(&self) -> &str {
362        self.module_name.as_ref()
363    }
364
365    /// Gets a full path to the directory where the current Lua script is working on.
366    pub fn working_directory(&self) -> &Path {
367        self.working_directory.as_ref()
368    }
369
370    /// Gets a full path where to store configurations for the current module. The returned value is in the following form:
371    ///
372    /// `$LOCENV_DATA/config/<module>`
373    pub fn configurations_path(&self) -> PathBuf {
374        let name = CString::new(self.module_name.as_str()).unwrap();
375        let mut size: u32 = 256;
376
377        loop {
378            let mut buffer: Vec<u8> = Vec::with_capacity(size as _);
379            let result = unsafe {
380                (api().module_configurations_path)(
381                    self.locenv,
382                    name.as_ptr(),
383                    buffer.as_mut_ptr() as *mut _,
384                    size,
385                )
386            };
387
388            if result <= size {
389                unsafe { buffer.set_len((result - 1) as _) };
390                return unsafe { PathBuf::from(String::from_utf8_unchecked(buffer)) };
391            }
392
393            size *= 2;
394        }
395    }
396
397    /// A finalizer for [`Context`]. This method is used by #\[loader\] attribute.
398    pub extern "C" fn finalize(lua: *mut LuaState) -> c_int {
399        // Get a pointer to context.
400        let table = unsafe { (api().aux_checklstring)(lua, upvalue_index(1), null_mut()) };
401        let ud = unsafe { (api().aux_checkudata)(lua, 1, table) };
402        let raw: *mut Self = null_mut();
403
404        unsafe { ud.copy_to_nonoverlapping(transmute(&raw), size_of::<*mut Self>()) };
405
406        // Destroy.
407        unsafe { Box::from_raw(raw) };
408
409        0
410    }
411
412    fn get_userdata<T: UserData>(&self, lua: *mut LuaState, index: c_int) -> *mut T {
413        let table = self.get_type_name::<T>();
414        let table = CString::new(table).unwrap();
415        let ud = unsafe { (api().aux_checkudata)(lua, index, table.as_ptr()) };
416        let raw: *mut T = null_mut();
417
418        unsafe { ud.copy_to_nonoverlapping(transmute(&raw), size_of::<*mut T>()) };
419
420        raw
421    }
422
423    fn get_type_name<T: UserData>(&self) -> String {
424        format!("{}.{}", self.module_name, T::type_name())
425    }
426}
427
428fn create_userdata<T, S>(lua: *mut LuaState, context: c_int, value: T, setup: S)
429where
430    T: UserData,
431    S: FnOnce(*mut LuaState, c_int, &T),
432{
433    // Get table name.
434    let context = abs_index(lua, context);
435    let table = Context::from_lua(lua, context).get_type_name::<T>();
436    let table = CString::new(table).unwrap();
437
438    // Push the userdata.
439    let boxed = Box::into_raw(Box::new(value));
440    let size = size_of::<*mut T>();
441    let up = (api().lua_newuserdatauv)(lua, size, 1);
442
443    unsafe { up.copy_from_nonoverlapping(transmute(&boxed), size) };
444
445    // Associate the userdata with metatable.
446    if unsafe { (api().aux_newmetatable)(lua, table.as_ptr()) } == 1 {
447        push_value(lua, context);
448        push_fn(lua, free_userdata::<T>, 1);
449        set_field(lua, -2, "__gc");
450        unsafe { setup(lua, context, &*boxed) };
451    }
452
453    set_metatable(lua, -2);
454}
455
456extern "C" fn execute_closure<T: Closure>(lua: *mut LuaState) -> c_int {
457    let context = Context::from_lua(lua, upvalue_index(1));
458    let closure = context.get_userdata::<T>(lua, upvalue_index(2));
459
460    unsafe { (*closure).call(lua) }
461}
462
463extern "C" fn invoke_method<T: Object>(lua: *mut LuaState) -> c_int {
464    let context = Context::from_lua(lua, upvalue_index(1));
465    let method = (api().lua_touserdata)(lua, upvalue_index(2));
466    let method: Method<T> = unsafe { transmute(method) };
467    let data = context.get_userdata::<T>(lua, 1);
468
469    unsafe { method(&mut *data, lua) }
470}
471
472extern "C" fn free_userdata<T: UserData>(lua: *mut LuaState) -> c_int {
473    let context = Context::from_lua(lua, upvalue_index(1));
474    unsafe { Box::from_raw(context.get_userdata::<T>(lua, 1)) };
475    0
476}
477
478fn api() -> &'static ApiTable {
479    unsafe { &*API_TABLE }
480}