#![allow(non_snake_case)]
use luna_core::runtime::Value;
use luna_core::version::LuaVersion;
use luna_core::vm::{LuaError, Vm};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
#[repr(transparent)]
pub struct LuaState {
vm: Vm,
}
pub const LUA_OK: c_int = 0;
pub const LUA_ERRRUN: c_int = 2;
pub const LUA_ERRSYNTAX: c_int = 3;
pub const LUA_ERRMEM: c_int = 4;
pub const LUA_TNONE: c_int = -1;
pub const LUA_TNIL: c_int = 0;
pub const LUA_TBOOLEAN: c_int = 1;
pub const LUA_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3;
pub const LUA_TSTRING: c_int = 4;
pub const LUA_TTABLE: c_int = 5;
pub const LUA_TFUNCTION: c_int = 6;
pub const LUA_TUSERDATA: c_int = 7;
pub const LUA_TTHREAD: c_int = 8;
pub type LuaCFunction = extern "C" fn(*mut LuaState) -> c_int;
fn abs_index(vm: &Vm, idx: c_int) -> Option<usize> {
let len = vm.capi_stack.len() as c_int;
let abs = if idx > 0 {
idx
} else if idx < 0 {
len + idx + 1
} else {
return None; };
if abs < 1 || abs > len {
None
} else {
Some((abs - 1) as usize)
}
}
fn get_at(vm: &Vm, idx: c_int) -> Option<Value> {
abs_index(vm, idx).map(|i| vm.capi_stack[i])
}
unsafe fn vm_mut<'a>(L: *mut LuaState) -> &'a mut Vm {
debug_assert!(!L.is_null(), "null lua_State*");
unsafe { &mut (*L).vm }
}
fn type_tag(v: Value) -> c_int {
match v {
Value::Nil => LUA_TNIL,
Value::Bool(_) => LUA_TBOOLEAN,
Value::Int(_) | Value::Float(_) => LUA_TNUMBER,
Value::Str(_) => LUA_TSTRING,
Value::Table(_) => LUA_TTABLE,
Value::Closure(_) | Value::Native(_) => LUA_TFUNCTION,
Value::Userdata(_) => LUA_TUSERDATA,
Value::Coro(_) => LUA_TTHREAD,
Value::LightUserdata(_) => LUA_TLIGHTUSERDATA,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn luaL_newstate() -> *mut LuaState {
let mut vm = Vm::new_minimal(LuaVersion::Lua55);
crate::install_default_jit(&mut vm);
let l = Box::new(LuaState { vm });
Box::into_raw(l)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_close(L: *mut LuaState) {
if L.is_null() {
return;
}
let _ = unsafe { Box::from_raw(L) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaL_openlibs(L: *mut LuaState) {
let vm = unsafe { vm_mut(L) };
vm.open_all_libs();
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn luaL_loadstring(L: *mut LuaState, src: *const c_char) -> c_int {
if L.is_null() || src.is_null() {
return LUA_ERRSYNTAX;
}
let vm = unsafe { vm_mut(L) };
let src_bytes = unsafe { CStr::from_ptr(src).to_bytes() };
match vm.load(src_bytes, b"=(load)") {
Ok(cl) => {
vm.capi_stack.push(Value::Closure(cl));
LUA_OK
}
Err(e) => {
let msg = format!("{e}");
let v = Value::Str(vm.heap.intern(msg.as_bytes()));
vm.capi_stack.push(v);
LUA_ERRSYNTAX
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pcall(
L: *mut LuaState,
nargs: c_int,
nresults: c_int,
_msgh: c_int,
) -> c_int {
let vm = unsafe { vm_mut(L) };
let needed = (nargs + 1) as usize;
if vm.capi_stack.len() < needed {
let v = Value::Str(vm.heap.intern(b"not enough values on stack"));
vm.capi_stack.push(v);
return LUA_ERRRUN;
}
let func_idx = vm.capi_stack.len() - needed;
let args: Vec<Value> = vm.capi_stack[func_idx + 1..].to_vec();
let f = vm.capi_stack[func_idx];
vm.capi_stack.truncate(func_idx);
match vm.call_value(f, &args) {
Ok(mut results) => {
if nresults >= 0 {
results.resize(nresults as usize, Value::Nil);
}
for v in results {
vm.capi_stack.push(v);
}
LUA_OK
}
Err(e) => {
let err_val = match e.0 {
Value::Str(_) => e.0,
_ => {
let rendered = vm.error_text(&e);
Value::Str(vm.heap.intern(rendered.as_bytes()))
}
};
vm.capi_stack.push(err_val);
LUA_ERRRUN
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_getglobal(L: *mut LuaState, name: *const c_char) -> c_int {
if name.is_null() {
return LUA_TNONE;
}
let vm = unsafe { vm_mut(L) };
let name_bytes = unsafe { CStr::from_ptr(name).to_bytes() };
let key = Value::Str(vm.heap.intern(name_bytes));
let v = vm.globals().get(key);
vm.capi_stack.push(v);
type_tag(v)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_setglobal(L: *mut LuaState, name: *const c_char) {
if name.is_null() {
return;
}
let vm = unsafe { vm_mut(L) };
let v = vm.capi_stack.pop().unwrap_or(Value::Nil);
let name_str = unsafe { CStr::from_ptr(name).to_str().unwrap_or("?") };
let _ = vm.set_global(name_str, v); }
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushnil(L: *mut LuaState) {
let vm = unsafe { vm_mut(L) };
vm.capi_stack.push(Value::Nil);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushboolean(L: *mut LuaState, b: c_int) {
let vm = unsafe { vm_mut(L) };
vm.capi_stack.push(Value::Bool(b != 0));
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushinteger(L: *mut LuaState, n: i64) {
let vm = unsafe { vm_mut(L) };
vm.capi_stack.push(Value::Int(n));
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushnumber(L: *mut LuaState, n: f64) {
let vm = unsafe { vm_mut(L) };
vm.capi_stack.push(Value::Float(n));
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushstring(L: *mut LuaState, str: *const c_char) -> *const c_char {
if str.is_null() {
unsafe { lua_pushnil(L) };
return std::ptr::null();
}
let vm = unsafe { vm_mut(L) };
let bytes = unsafe { CStr::from_ptr(str).to_bytes() };
let interned = vm.heap.intern(bytes);
vm.capi_stack.push(Value::Str(interned));
unsafe { lua_tostring(L, -1) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_tointeger(L: *mut LuaState, idx: c_int) -> i64 {
let vm = unsafe { vm_mut(L) };
match get_at(vm, idx) {
Some(Value::Int(i)) => i,
Some(Value::Float(f)) => f as i64,
Some(Value::Bool(true)) => 1,
Some(Value::Bool(false)) => 0,
Some(Value::Str(st)) => std::str::from_utf8(st.as_bytes())
.ok()
.and_then(|s| s.trim().parse::<i64>().ok())
.unwrap_or(0),
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_tonumber(L: *mut LuaState, idx: c_int) -> f64 {
let vm = unsafe { vm_mut(L) };
match get_at(vm, idx) {
Some(Value::Int(i)) => i as f64,
Some(Value::Float(f)) => f,
Some(Value::Bool(true)) => 1.0,
Some(Value::Bool(false)) => 0.0,
Some(Value::Str(st)) => std::str::from_utf8(st.as_bytes())
.ok()
.and_then(|s| s.trim().parse::<f64>().ok())
.unwrap_or(0.0),
_ => 0.0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_toboolean(L: *mut LuaState, idx: c_int) -> c_int {
let vm = unsafe { vm_mut(L) };
match get_at(vm, idx) {
Some(Value::Nil) | None => 0,
Some(Value::Bool(false)) => 0,
_ => 1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_tostring(L: *mut LuaState, idx: c_int) -> *const c_char {
let vm = unsafe { vm_mut(L) };
let bytes: Vec<u8> = match get_at(vm, idx) {
Some(Value::Str(st)) => st.as_bytes().to_vec(),
Some(Value::Int(i)) => i.to_string().into_bytes(),
Some(Value::Float(f)) => f.to_string().into_bytes(),
Some(Value::Nil) | None => return std::ptr::null(),
Some(Value::Bool(true)) => b"true".to_vec(),
Some(Value::Bool(false)) => b"false".to_vec(),
_ => return std::ptr::null(),
};
let c = CString::new(bytes).unwrap_or_else(|_| CString::new("?").unwrap());
let p = c.as_ptr();
vm.capi_cstr_pin = Some(c);
p
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_type(L: *mut LuaState, idx: c_int) -> c_int {
let vm = unsafe { vm_mut(L) };
get_at(vm, idx).map_or(LUA_TNONE, type_tag)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isnil(L: *mut LuaState, idx: c_int) -> c_int {
(unsafe { lua_type(L, idx) } == LUA_TNIL) as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isnumber(L: *mut LuaState, idx: c_int) -> c_int {
let vm = unsafe { vm_mut(L) };
matches!(get_at(vm, idx), Some(Value::Int(_)) | Some(Value::Float(_))) as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isinteger(L: *mut LuaState, idx: c_int) -> c_int {
let vm = unsafe { vm_mut(L) };
matches!(get_at(vm, idx), Some(Value::Int(_))) as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isstring(L: *mut LuaState, idx: c_int) -> c_int {
let vm = unsafe { vm_mut(L) };
matches!(
get_at(vm, idx),
Some(Value::Str(_)) | Some(Value::Int(_)) | Some(Value::Float(_))
) as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isboolean(L: *mut LuaState, idx: c_int) -> c_int {
let vm = unsafe { vm_mut(L) };
matches!(get_at(vm, idx), Some(Value::Bool(_))) as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_isfunction(L: *mut LuaState, idx: c_int) -> c_int {
let vm = unsafe { vm_mut(L) };
matches!(
get_at(vm, idx),
Some(Value::Closure(_)) | Some(Value::Native(_))
) as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_gettop(L: *mut LuaState) -> c_int {
let vm = unsafe { vm_mut(L) };
vm.capi_stack.len() as c_int
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_settop(L: *mut LuaState, idx: c_int) {
let vm = unsafe { vm_mut(L) };
let new_len = if idx >= 0 {
idx as usize
} else {
(vm.capi_stack.len() as c_int + idx + 1).max(0) as usize
};
if new_len < vm.capi_stack.len() {
vm.capi_stack.truncate(new_len);
} else {
vm.capi_stack.resize(new_len, Value::Nil);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pop(L: *mut LuaState, n: c_int) {
unsafe { lua_settop(L, -n - 1) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushvalue(L: *mut LuaState, idx: c_int) {
let vm = unsafe { vm_mut(L) };
if let Some(v) = get_at(vm, idx) {
vm.capi_stack.push(v);
} else {
vm.capi_stack.push(Value::Nil);
}
}
fn capi_trampoline(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let cf_value = vm.running_native_upvalue(0);
let cf: LuaCFunction = match cf_value {
Value::LightUserdata(p) => unsafe { std::mem::transmute::<*const (), LuaCFunction>(p) },
_ => {
let s = Value::Str(vm.heap.intern(b"missing C function pointer upvalue"));
return Err(LuaError(s));
}
};
let baseline = vm.capi_stack.len();
for i in 0..nargs {
let v = vm.nat_arg(fs, nargs, i);
vm.capi_stack.push(v);
}
let vm_ptr: *mut Vm = vm as *mut Vm;
let nret = cf(vm_ptr as *mut LuaState) as usize;
let vm = unsafe { &mut *vm_ptr };
let stack_len = vm.capi_stack.len();
if stack_len < baseline + nret {
let s = Value::Str(
vm.heap
.intern(b"C function returned more values than were pushed"),
);
return Err(LuaError(s));
}
let results_start = stack_len - nret;
let results: Vec<Value> = vm.capi_stack[results_start..].to_vec();
vm.capi_stack.truncate(baseline);
Ok(vm.nat_return(fs, &results))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_pushcfunction(L: *mut LuaState, f: LuaCFunction) {
let vm = unsafe { vm_mut(L) };
let cf_ptr = f as *const ();
let trampoline: luna_core::runtime::value::NativeFn = capi_trampoline;
let f_val = vm.native_with(trampoline, Box::new([Value::LightUserdata(cf_ptr)]));
vm.capi_stack.push(f_val);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_register(L: *mut LuaState, name: *const c_char, f: LuaCFunction) {
unsafe { lua_pushcfunction(L, f) };
unsafe { lua_setglobal(L, name) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lua_version(L: *mut LuaState) -> c_int {
let vm = unsafe { vm_mut(L) };
match vm.version() {
LuaVersion::Lua51 => 501,
LuaVersion::Lua52 => 502,
LuaVersion::Lua53 => 503,
LuaVersion::Lua54 => 504,
LuaVersion::MacroLua => 504,
LuaVersion::Lua55 => 505,
}
}