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#[macro_export]
38macro_rules! error {
39 ($lua:ident, $($arg:tt)*) => {
40 $crate::error_with_message($lua, &std::format!($($arg)*))
41 }
42}
43
44pub fn upvalue_index<P: Into<c_int>>(i: P) -> c_int {
46 LUA_REGISTRYINDEX - i.into()
47}
48
49pub fn abs_index(lua: *mut LuaState, index: c_int) -> c_int {
52 (api().lua_absindex)(lua, index)
53}
54
55pub fn pop(lua: *mut LuaState, count: c_int) {
57 (api().lua_settop)(lua, -count - 1);
58}
59
60pub fn push_value(lua: *mut LuaState, index: c_int) {
62 (api().lua_pushvalue)(lua, index);
63}
64
65pub fn push_nil(lua: *mut LuaState) {
67 (api().lua_pushnil)(lua);
68}
69
70pub fn push_str(lua: *mut LuaState, value: &str) {
73 unsafe { (api().lua_pushlstring)(lua, transmute(value.as_ptr()), value.len()) };
74}
75
76pub fn push_fn(lua: *mut LuaState, value: LuaFunction, up: c_int) {
82 (api().lua_pushcclosure)(lua, value, up);
83}
84
85pub 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
96pub fn create_table(lua: *mut LuaState, elements: c_int, fields: c_int) {
101 (api().lua_createtable)(lua, elements, fields);
102}
103
104pub 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
129pub 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
140pub fn set_functions(lua: *mut LuaState, entries: &[FunctionEntry], upvalues: c_int) {
144 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 unsafe { (api().aux_setfuncs)(lua, table.as_ptr(), upvalues) };
164}
165
166pub fn is_none_or_nil(lua: *mut LuaState, index: c_int) -> bool {
169 (api().lua_type)(lua, index) <= 0
170}
171
172pub 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
185pub 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
193pub 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
207pub 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
217pub fn set_metatable(lua: *mut LuaState, index: c_int) {
220 (api().lua_setmetatable)(lua, index);
221}
222
223pub 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
232pub 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
243pub 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
252pub fn error(lua: *mut LuaState) -> ! {
254 (api().lua_error)(lua);
255 unreachable!();
256}
257
258pub trait UserData: 'static {
260 fn type_name() -> &'static str;
262}
263
264pub trait Closure: UserData {
266 fn call(&mut self, lua: *mut LuaState) -> c_int;
267}
268
269pub trait Object: UserData {
271 fn methods() -> &'static [MethodEntry<Self>];
273}
274
275pub struct MethodEntry<T: ?Sized> {
277 pub name: &'static str,
278
279 pub function: Method<T>,
297}
298
299pub type Method<T> = fn(&mut T, *mut LuaState) -> c_int;
300
301pub struct FunctionEntry<'name> {
303 pub name: &'name str,
304 pub function: Option<LuaFunction>,
305}
306
307pub 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 pub fn from_lua(lua: *mut LuaState, index: c_int) -> &'static Self {
327 let ud = (api().lua_touserdata)(lua, index);
329
330 if ud.is_null() {
331 error!(lua, "expect a userdata at #{}", index);
332 }
333
334 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 if r#type == "locenv" || r#type.contains('.') {
350 error!(lua, "expect a module context at #{}", index);
351 }
352
353 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 pub fn module_name(&self) -> &str {
362 self.module_name.as_ref()
363 }
364
365 pub fn working_directory(&self) -> &Path {
367 self.working_directory.as_ref()
368 }
369
370 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 pub extern "C" fn finalize(lua: *mut LuaState) -> c_int {
399 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 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 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 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 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}