#[cfg(not(any(feature = "Lua5_3", feature = "Lua5_4")))]
compile_error!("no feature for supported Lua version");
use super::{cmach::cmach_lua_t, *};
use sandkiste::prelude::*;
use std::borrow::{Cow, Cow::Borrowed, Cow::Owned};
use std::error::Error;
use std::ffi::{c_int, c_void, CString};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::num::TryFromIntError;
use std::rc::Rc;
use std::slice;
use std::str;
fn abort_on_unwind<F: FnOnce() -> R, R>(func: F) -> R {
struct AbortOnDrop;
impl Drop for AbortOnDrop {
fn drop(&mut self) {
std::process::abort();
}
}
let guard = AbortOnDrop;
let result = func();
std::mem::forget(guard);
result
}
#[derive(Debug)]
pub struct LuaMachine<'a> {
c_machine: *mut cmach_lua_t,
phantom: PhantomData<fn(&'a ()) -> &'a ()>,
}
impl<'a> PartialEq for LuaMachine<'a> {
fn eq(&self, other: &Self) -> bool {
self.c_machine == other.c_machine
}
}
impl<'a> Eq for LuaMachine<'a> {}
impl<'a> Hash for LuaMachine<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.c_machine.hash(state);
}
}
impl<'a> Machine<'a> for LuaMachine<'a> {
type Datum<'b, 'c> = LuaDatum<'a, 'b, 'c>
where
Self: 'b;
type Function<'b> = LuaFunction<'a, 'b>
where
Self: 'b;
}
pub type LuaReferenceKey = c_int;
#[derive(Debug)]
struct LuaInnerReference<'a, 'b> {
machine: &'b LuaMachine<'a>,
key: LuaReferenceKey,
identity: *const c_void,
}
impl<'a, 'b> PartialEq for LuaInnerReference<'a, 'b> {
fn eq(&self, other: &Self) -> bool {
self.identity == other.identity
}
}
impl<'a, 'b> Eq for LuaInnerReference<'a, 'b> {}
impl<'a, 'b> Hash for LuaInnerReference<'a, 'b> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.identity.hash(state);
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LuaReference<'a, 'b>(Rc<LuaInnerReference<'a, 'b>>);
impl<'a, 'b> LuaReference<'a, 'b> {
fn new(inner_ref: LuaInnerReference<'a, 'b>) -> Self {
LuaReference(Rc::new(inner_ref))
}
}
impl<'a, 'b> AsRef<LuaInnerReference<'a, 'b>> for LuaReference<'a, 'b> {
fn as_ref(&self) -> &LuaInnerReference<'a, 'b> {
self.0.as_ref()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LuaFunction<'a, 'b> {
lua_ref: LuaReference<'a, 'b>,
chunk_name: Option<String>,
}
#[derive(Clone, Debug)]
enum LuaType<'a, 'b, 'c> {
Nil,
Boolean(bool),
Function(LuaFunction<'a, 'b>),
Float(cmach::lua_Number),
Integer(cmach::lua_Integer),
String(Cow<'c, str>),
Binary(Cow<'c, [u8]>),
Table(LuaReference<'a, 'b>),
Userdata(LuaReference<'a, 'b>),
Thread(LuaReference<'a, 'b>),
}
#[derive(Clone, Debug)]
pub struct LuaDatum<'a, 'b, 'c>(LuaType<'a, 'b, 'c>);
impl<'a, 'b, 'c, T> From<Option<T>> for LuaDatum<'a, 'b, 'c>
where
T: Into<Self>,
{
fn from(value: Option<T>) -> Self {
match value {
None => LuaDatum::null(),
Some(v) => v.into(),
}
}
}
impl<'a, 'b, 'c> Nullable for LuaDatum<'a, 'b, 'c> {
fn null() -> Self {
LuaDatum(LuaType::Nil)
}
fn is_null(&self) -> bool {
match self.0 {
LuaType::Nil => true,
_ => false,
}
}
}
impl<'a, 'b, 'c> From<bool> for LuaDatum<'a, 'b, 'c> {
fn from(value: bool) -> Self {
LuaDatum(LuaType::Boolean(value))
}
}
impl<'a, 'b, 'c> MaybeBoolean for LuaDatum<'a, 'b, 'c> {
fn try_as_bool(&self) -> Result<bool, TypeMismatch> {
match self.0 {
LuaType::Boolean(false) => Ok(false),
LuaType::Boolean(true) => Ok(true),
_ => Err(TypeMismatch::with_static_msg("boolean expected")),
}
}
}
impl<'a, 'b, 'c> From<LuaFunction<'a, 'b>> for LuaDatum<'a, 'b, 'c> {
fn from(value: LuaFunction<'a, 'b>) -> Self {
LuaDatum(LuaType::Function(value))
}
}
impl<'a, 'b, 'c> MaybeFunction for LuaDatum<'a, 'b, 'c> {
type Function = LuaFunction<'a, 'b>;
type FunctionRef<'d> = &'d LuaFunction<'a, 'b>
where
Self: 'd;
fn try_as_function(&self) -> Result<&LuaFunction<'a, 'b>, TypeMismatch> {
match self.0 {
LuaType::Function(ref f) => Ok(f),
_ => Err(TypeMismatch::with_static_msg("function expected")),
}
}
}
impl<'a, 'b, 'c> TryFrom<LuaDatum<'a, 'b, 'c>> for LuaReference<'a, 'b> {
type Error = DatumConversionFailure<LuaDatum<'a, 'b, 'c>>;
fn try_from(value: LuaDatum<'a, 'b, 'c>) -> Result<Self, Self::Error> {
match value.0 {
LuaType::Function(f) => Ok(f.lua_ref),
LuaType::Table(r) => Ok(r),
LuaType::Userdata(r) => Ok(r),
LuaType::Thread(r) => Ok(r),
_ => Err(DatumConversionFailure::with_static_msg(
value,
"reference type expected",
)),
}
}
}
impl<'a, 'b, 'c> MaybeOpaque for LuaDatum<'a, 'b, 'c> {
type Opaque = LuaReference<'a, 'b>;
type OpaqueRef<'d> = &'d LuaReference<'a, 'b>
where
Self: 'd;
fn is_opaque(&self) -> bool {
match self.0 {
LuaType::Function(_) => true,
LuaType::Table(_) => true,
LuaType::Userdata(_) => true,
LuaType::Thread(_) => true,
_ => false,
}
}
fn try_as_opaque(&self) -> Result<&LuaReference<'a, 'b>, TypeMismatch> {
match self.0 {
LuaType::Function(ref f) => Ok(&f.lua_ref),
LuaType::Table(ref r) => Ok(r),
LuaType::Userdata(ref r) => Ok(r),
LuaType::Thread(ref r) => Ok(r),
_ => Err(TypeMismatch::with_static_msg("reference type expected")),
}
}
}
impl<'a, 'b, 'c> From<String> for LuaDatum<'a, 'b, 'c> {
fn from(value: String) -> LuaDatum<'a, 'b, 'c> {
LuaDatum(LuaType::String(Owned(value)))
}
}
impl<'a, 'b, 'c> From<&'c str> for LuaDatum<'a, 'b, 'c> {
fn from(value: &'c str) -> LuaDatum<'a, 'b, 'c> {
LuaDatum(LuaType::String(Borrowed(value)))
}
}
impl<'a, 'b, 'c> TryFrom<LuaDatum<'a, 'b, 'c>> for String {
type Error = DatumConversionFailure<LuaDatum<'a, 'b, 'c>>;
fn try_from(value: LuaDatum<'a, 'b, 'c>) -> Result<Self, Self::Error> {
match value.0 {
LuaType::String(s) => Ok(s.into_owned()),
LuaType::Binary(_) => Err(DatumConversionFailure::with_static_msg(
value,
"Lua string contains invalid UTF-8 sequence",
)),
_ => Err(DatumConversionFailure::with_static_msg(
value,
"string expected",
)),
}
}
}
impl<'a, 'b, 'c> MaybeString<'c> for LuaDatum<'a, 'b, 'c> {
fn try_as_str(&self) -> Result<&str, TypeMismatch> {
match self.0 {
LuaType::String(ref s) => Ok(s),
LuaType::Binary(_) => Err(TypeMismatch::with_static_msg(
"Lua string contains invalid UTF-8 sequence",
)),
_ => Err(TypeMismatch::with_static_msg("string expected")),
}
}
}
impl<'a, 'b, 'c> From<Vec<u8>> for LuaDatum<'a, 'b, 'c> {
fn from(value: Vec<u8>) -> LuaDatum<'a, 'b, 'c> {
match String::from_utf8(value) {
Ok(s) => LuaDatum(LuaType::String(Owned(s))),
Err(err) => LuaDatum(LuaType::Binary(Owned(err.into_bytes()))),
}
}
}
impl<'a, 'b, 'c> From<&'c [u8]> for LuaDatum<'a, 'b, 'c> {
fn from(value: &'c [u8]) -> LuaDatum<'a, 'b, 'c> {
match str::from_utf8(value) {
Ok(s) => LuaDatum(LuaType::String(Borrowed(s))),
Err(_) => LuaDatum(LuaType::Binary(Borrowed(value))),
}
}
}
impl<'a, 'b, 'c> TryFrom<LuaDatum<'a, 'b, 'c>> for Vec<u8> {
type Error = DatumConversionFailure<LuaDatum<'a, 'b, 'c>>;
fn try_from(value: LuaDatum<'a, 'b, 'c>) -> Result<Self, Self::Error> {
match value.0 {
LuaType::String(s) => Ok(s.into_owned().into()),
LuaType::Binary(b) => Ok(b.into_owned()),
_ => Err(DatumConversionFailure::with_static_msg(
value,
"binary string expected",
)),
}
}
}
impl<'a, 'b, 'c> MaybeBinary<'c> for LuaDatum<'a, 'b, 'c> {
fn try_as_bin(&self) -> Result<&[u8], TypeMismatch> {
match self.0 {
LuaType::String(ref s) => Ok(s.as_bytes()),
LuaType::Binary(ref b) => Ok(b),
_ => Err(TypeMismatch::with_static_msg("binary string expected")),
}
}
}
impl<'a, 'b, 'c> From<f64> for LuaDatum<'a, 'b, 'c> {
fn from(value: f64) -> Self {
LuaDatum(LuaType::Float(value))
}
}
impl<'a, 'b, 'c> From<f32> for LuaDatum<'a, 'b, 'c> {
fn from(value: f32) -> Self {
LuaDatum(LuaType::Float(value as f64))
}
}
impl<'a, 'b, 'c> MaybeFloat for LuaDatum<'a, 'b, 'c> {
fn try_as_f64(&self) -> Result<f64, TypeMismatch> {
match self.0 {
LuaType::Float(value) => Ok(value as f64),
LuaType::Integer(value) => Ok(value as f64),
_ => Err(TypeMismatch::with_static_msg("float (or integer) expected")),
}
}
}
impl<'a, 'b, 'c> TryFrom<usize> for LuaDatum<'a, 'b, 'c> {
type Error = TryFromIntError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Ok(LuaDatum(LuaType::Integer(value.try_into()?)))
}
}
impl<'a, 'b, 'c> TryFrom<isize> for LuaDatum<'a, 'b, 'c> {
type Error = TryFromIntError;
fn try_from(value: isize) -> Result<Self, Self::Error> {
Ok(LuaDatum(LuaType::Integer(value.try_into()?)))
}
}
impl<'a, 'b, 'c> TryFrom<u64> for LuaDatum<'a, 'b, 'c> {
type Error = TryFromIntError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
Ok(LuaDatum(LuaType::Integer(value.try_into()?)))
}
}
impl<'a, 'b, 'c> TryFrom<i64> for LuaDatum<'a, 'b, 'c> {
type Error = TryFromIntError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Ok(LuaDatum(LuaType::Integer(value.try_into()?)))
}
}
impl<'a, 'b, 'c> TryFrom<u32> for LuaDatum<'a, 'b, 'c> {
type Error = TryFromIntError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(LuaDatum(LuaType::Integer(value.try_into()?)))
}
}
impl<'a, 'b, 'c> From<i32> for LuaDatum<'a, 'b, 'c> {
fn from(value: i32) -> Self {
LuaDatum(LuaType::Integer(value.into()))
}
}
impl<'a, 'b, 'c> From<u16> for LuaDatum<'a, 'b, 'c> {
fn from(value: u16) -> Self {
LuaDatum(LuaType::Integer(value.into()))
}
}
impl<'a, 'b, 'c> From<i16> for LuaDatum<'a, 'b, 'c> {
fn from(value: i16) -> Self {
LuaDatum(LuaType::Integer(value.into()))
}
}
impl<'a, 'b, 'c> From<u8> for LuaDatum<'a, 'b, 'c> {
fn from(value: u8) -> Self {
LuaDatum(LuaType::Integer(value.into()))
}
}
impl<'a, 'b, 'c> From<i8> for LuaDatum<'a, 'b, 'c> {
fn from(value: i8) -> Self {
LuaDatum(LuaType::Integer(value.into()))
}
}
impl<'a, 'b, 'c> MaybeInteger for LuaDatum<'a, 'b, 'c> {
fn try_as_i64(&self) -> Result<i64, TypeMismatch> {
match self.0 {
LuaType::Integer(value) => value
.try_into()
.map_err(|_| TypeMismatch::with_static_msg("Lua integer does not fit into i64")),
LuaType::Float(_) => Err(TypeMismatch::with_static_msg(
"integer expected, but got float",
)),
_ => Err(TypeMismatch::with_static_msg("integer expected")),
}
}
}
impl<'a, 'b, 'c> MaybeArray for LuaDatum<'a, 'b, 'c> {
type Element<'d> = LuaDatum<'a, 'b, 'd>;
fn try_array(&self) -> Result<(), TypeMismatch> {
match self.0 {
LuaType::Table(_) => Ok(()),
_ => Err(TypeMismatch::with_static_msg("array (table) expected")),
}
}
fn array_len(&self) -> Result<usize, MachineError> {
match self.0 {
LuaType::Table(ref lua_ref) => {
let machine = lua_ref.as_ref().machine;
let l = machine.lua_state();
unsafe {
machine.push_reference(lua_ref);
machine.pop_error_with_backtrace(cmach::cmach_lua_len(l))?;
let result = machine.pop_datum()?;
Ok(result.try_as_usize()?)
}
}
_ => Err(self.try_array().unwrap_err())?,
}
}
fn array_get(&self, index: usize) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
match self.0 {
LuaType::Table(ref lua_ref) => {
let machine = lua_ref.as_ref().machine;
let l = machine.lua_state();
unsafe {
let index = cmach::lua_Integer::try_from(index)
.ok()
.and_then(|x| x.checked_add(1))
.ok_or_else(|| {
MachineError::new()
.set_message_str("lua_Integer too small to store array index")
})?;
machine.push_reference(lua_ref);
cmach::lua_pushinteger(l, index);
machine.pop_error_with_backtrace(cmach::cmach_lua_gettable(l))?;
Ok(machine.pop_datum()?)
}
}
_ => Err(self.try_array().unwrap_err())?,
}
}
fn array_set<'d>(
&self,
index: usize,
element: LuaDatum<'a, 'b, 'd>,
) -> Result<(), MachineError> {
match self.0 {
LuaType::Table(ref lua_ref) => {
let machine = lua_ref.as_ref().machine;
let l = machine.lua_state();
unsafe {
let index = cmach::lua_Integer::try_from(index)
.ok()
.and_then(|x| x.checked_add(1))
.ok_or_else(|| {
MachineError::new()
.set_message_str("lua_Integer too small to store array index")
})?;
machine.push_reference(lua_ref);
cmach::lua_pushinteger(l, index);
match machine.push_datum(&element) {
Ok(_) => (),
Err(err) => {
cmach::lua_pop(l, 2);
Err(err)?
}
}
machine.pop_error_with_backtrace(cmach::cmach_lua_settable(l))?;
Ok(())
}
}
_ => Err(self.try_array().unwrap_err())?,
}
}
fn array_push<'d>(&self, element: LuaDatum<'a, 'b, 'd>) -> Result<(), MachineError> {
self.array_set(self.array_len()?, element)
}
fn array_truncate(&self, len: usize) -> Result<(), MachineError> {
for index in (len..self.array_len()?).rev() {
self.array_set(index, LuaDatum::null())?;
}
Ok(())
}
}
impl<'a, 'b, 'c> MaybeStringMap for LuaDatum<'a, 'b, 'c> {
type Value<'d> = LuaDatum<'a, 'b, 'd>;
fn try_string_map(&self) -> Result<(), TypeMismatch> {
match self.0 {
LuaType::Table(_) => Ok(()),
_ => Err(TypeMismatch::with_static_msg("string map (table) expected")),
}
}
fn string_map_get(&self, key: &str) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
match self.0 {
LuaType::Table(ref lua_ref) => {
let machine = lua_ref.as_ref().machine;
let l = machine.lua_state();
unsafe {
machine.push_reference(lua_ref);
match machine.push_str(key) {
Ok(_) => (),
Err(err) => {
cmach::lua_pop(l, 1);
Err(err)?
}
}
machine.pop_error_with_backtrace(cmach::cmach_lua_gettable(l))?;
Ok(machine.pop_datum()?)
}
}
_ => Err(self.try_string_map().unwrap_err())?,
}
}
fn string_map_set<'d, 'e>(
&self,
key: &'d str,
value: LuaDatum<'a, 'b, 'e>,
) -> Result<(), MachineError> {
match self.0 {
LuaType::Table(ref lua_ref) => {
let machine = lua_ref.as_ref().machine;
let l = machine.lua_state();
unsafe {
machine.push_reference(lua_ref);
match machine.push_str(key) {
Ok(_) => (),
Err(err) => {
cmach::lua_pop(l, 1);
Err(err)?
}
}
match machine.push_datum(&value) {
Ok(_) => (),
Err(err) => {
cmach::lua_pop(l, 2);
Err(err)?
}
}
machine.pop_error_with_backtrace(cmach::cmach_lua_settable(l))?;
Ok(())
}
}
_ => Err(self.try_string_map().unwrap_err())?,
}
}
}
impl<'a> HasArray<'a> for LuaMachine<'a> {
fn new_empty_array<'b>(&'b self) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
self.new_table()
}
}
impl<'a> HasStringMap<'a> for LuaMachine<'a> {
fn new_empty_string_map<'b>(&'b self) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
self.new_table()
}
}
pub struct LuaModule<'a, 'b> {
machine: &'b LuaMachine<'a>,
table: LuaDatum<'a, 'b, 'static>,
}
impl<'a> HasModules<'a> for LuaMachine<'a> {
type Module<'b> = LuaModule<'a, 'b>
where
'a: 'b;
fn module<'b>(&'b self, name: &str) -> Result<LuaModule<'a, 'b>, MachineError> {
let mut table = self.get(name)?;
if table.is_null() {
table = self.new_empty_string_map()?;
self.set(name, table.clone())?;
}
Ok(LuaModule {
machine: self,
table,
})
}
}
impl<'a, 'b> Module<'a, 'b> for LuaModule<'a, 'b> {
type Machine = LuaMachine<'a>;
fn module(&self, name: &str) -> Result<Self, MachineError> {
let mut table = self.table.string_map_get(name)?;
if table.is_null() {
table = self.machine.new_empty_string_map()?;
self.table.string_map_set(name, table.clone())?;
}
Ok(LuaModule {
machine: self.machine,
table,
})
}
fn get(
&self,
name: &str,
) -> Result<<Self::Machine as Machine<'a>>::Datum<'b, 'static>, MachineError> {
Ok(self.table.string_map_get(name)?)
}
fn set<'c>(
&self,
name: &str,
datum: <Self::Machine as Machine<'a>>::Datum<'b, 'c>,
) -> Result<(), MachineError> {
self.table.string_map_set(name, datum)?;
Ok(())
}
}
impl<'a, 'b> Drop for LuaInnerReference<'a, 'b> {
fn drop(&mut self) {
unsafe {
cmach::luaL_unref(
self.machine.lua_state(),
cmach::LUA_REGISTRYINDEX.try_into().unwrap(),
self.key,
)
};
}
}
impl<'a> Drop for LuaMachine<'a> {
fn drop(&mut self) {
unsafe {
cmach::cmach_lua_free(self.c_machine);
};
}
}
impl<'a> Globals<'a> for LuaMachine<'a> {
fn get<'b>(&'b self, name: &str) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
self.assert_empty_stack();
unsafe {
let l = self.lua_state();
self.push_str(name)?;
self.pop_error_with_backtrace(cmach::cmach_lua_getglobal(l))?;
self.pop_datum()
}
}
fn set<'b, 'c>(&'b self, name: &str, value: LuaDatum<'a, 'b, 'c>) -> Result<(), MachineError> {
self.assert_empty_stack();
unsafe {
let l = self.lua_state();
self.push_str(name)?;
match self.push_datum(&value) {
Ok(()) => (),
Err(err) => {
cmach::lua_pop(l, 1);
Err(err)?;
}
}
self.pop_error_with_backtrace(cmach::cmach_lua_setglobal(l))?;
Ok(())
}
}
}
impl<'a> LuaMachine<'a> {
pub fn new() -> Self {
unsafe {
let c_machine: *mut cmach_lua_t = cmach::cmach_lua_new();
if c_machine.is_null() {
panic!("memory allocation error in cmach_lua_new");
}
LuaMachine {
c_machine,
phantom: PhantomData,
}
}
}
pub fn lua_state(&self) -> *mut cmach::lua_State {
unsafe { (*self.c_machine).L }
}
pub fn assert_empty_stack(&self) {
unsafe {
let l = self.lua_state();
assert_eq!(cmach::lua_gettop(l), 0, "unexpected values on Lua stack");
}
}
pub fn set_execution_limit(&self, instr: u64) {
unsafe {
let instr = c_int::try_from(instr).unwrap_or(c_int::MAX);
cmach::cmach_lua_execlimit(self.c_machine, instr);
}
}
pub fn set_memory_limit(&self, bytes: usize) {
unsafe {
(*self.c_machine).mem_limit = bytes;
}
}
pub fn load_stdlib(&self) -> Result<(), MachineError> {
self.assert_empty_stack();
unsafe {
let l = self.lua_state();
self.pop_error_with_backtrace(cmach::cmach_lua_openlibs(l))?;
Ok(())
}
}
pub fn load_stdlib_sealed(&self) -> Result<(), MachineError> {
self.assert_empty_stack();
unsafe {
let l = self.lua_state();
self.pop_error_with_backtrace(cmach::cmach_lua_openlibs_sealed(l))?;
Ok(())
}
}
pub fn seal(&self) -> Result<(), MachineError> {
self.assert_empty_stack();
unsafe {
let l = self.lua_state();
self.pop_error_with_backtrace(cmach::cmach_lua_seal(l))?;
Ok(())
}
}
pub fn new_table<'b>(&'b self) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
self.assert_empty_stack();
unsafe {
let l = self.lua_state();
self.pop_error_with_backtrace(cmach::cmach_lua_newtable(l))?;
let result = self.extract_datum(-1);
cmach::lua_pop(l, 1);
result
}
}
pub fn get_reference_key<'b>(&'b self, lua_ref: &LuaReference<'a, 'b>) -> LuaReferenceKey {
let inner_ref = lua_ref.as_ref();
assert_eq!(
self, inner_ref.machine,
"reference used on wrong Lua machine"
);
inner_ref.key
}
pub unsafe fn pop_error_message(&self) -> String {
let l = self.lua_state();
let message = if cmach::cmach_lua_touserstr(l) == cmach::LUA_OK.try_into().unwrap() {
let udata = cmach::lua_touserdata(l, -1);
let len = cmach::lua_rawlen(l, -1);
String::from_utf8_lossy(slice::from_raw_parts(udata as *const u8, len as usize))
.into_owned()
} else {
"(unknown)".to_string()
};
cmach::lua_pop(l, 1);
message
}
pub unsafe fn pop_error(&self, status: c_int) -> Result<(), MachineError> {
if status == cmach::LUA_OK.try_into().unwrap() {
Ok(())
} else {
let is_execlimit = cmach::cmach_lua_is_execlimit(self.c_machine) != 0;
let message = self.pop_error_message();
Err(MachineError::new()
.set_kind(if status == cmach::LUA_ERRMEM.try_into().unwrap() {
MachineErrorKind::Memory
} else if is_execlimit {
MachineErrorKind::ExecLimit
} else if status == cmach::LUA_ERRSYNTAX.try_into().unwrap() {
MachineErrorKind::Syntax
} else if status == cmach::LUA_ERRRUN.try_into().unwrap() {
MachineErrorKind::Runtime
} else {
MachineErrorKind::Unknown
})
.set_message(message)
.set_message_incl_chunk_name()
.set_message_incl_pos())
}
}
pub unsafe fn pop_error_with_backtrace(&self, status: c_int) -> Result<(), MachineError> {
if status == cmach::LUA_ERRRUN.try_into().unwrap() {
let l = self.lua_state();
cmach::lua_rawgeti(l, -1, 1);
let is_execlimit = cmach::cmach_lua_is_execlimit(self.c_machine) != 0;
let message = self.pop_error_message();
cmach::lua_rawgeti(l, -1, 2);
let machine_backtrace = self.pop_error_message();
cmach::lua_rawgeti(l, -1, 3);
let chunk_name = self.pop_error_message();
cmach::lua_rawgeti(l, -1, 4);
let line = usize::try_from(cmach::lua_tointeger(l, -1)).ok();
cmach::lua_pop(l, 2); Err(MachineError::new()
.set_kind(if is_execlimit {
MachineErrorKind::ExecLimit
} else {
MachineErrorKind::Runtime
})
.set_message(message)
.set_machine_backtrace(machine_backtrace)
.set_message_incl_chunk_name()
.set_chunk_name(chunk_name)
.set_message_incl_pos()
.set_line_opt(line))
} else {
self.pop_error(status)
}
}
pub unsafe fn extract_datum<'b>(
&'b self,
index: c_int,
) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
let l = self.lua_state();
let type_id = cmach::lua_type(l, index);
let lua_type = if type_id == cmach::LUA_TNIL.try_into().unwrap() {
LuaType::Nil
} else if type_id == cmach::LUA_TBOOLEAN.try_into().unwrap() {
LuaType::Boolean(cmach::lua_toboolean(l, index) != 0)
} else if type_id == cmach::LUA_TNUMBER.try_into().unwrap() {
if cmach::lua_isinteger(l, index) != 0 {
LuaType::Integer(cmach::lua_tointeger(l, index))
} else {
LuaType::Float(cmach::lua_tonumber(l, index))
}
} else if type_id == cmach::LUA_TSTRING.try_into().unwrap() {
cmach::lua_pushvalue(l, index);
self.pop_error_with_backtrace(cmach::cmach_lua_touserstr(l))?;
let udata = cmach::lua_touserdata(l, -1);
let len = cmach::lua_rawlen(l, -1);
let vec = slice::from_raw_parts(udata as *const u8, len as usize).to_vec();
cmach::lua_pop(l, 1);
match String::from_utf8(vec) {
Ok(s) => LuaType::String(Owned(s)),
Err(err) => LuaType::Binary(Owned(err.into_bytes())),
}
} else {
let identity = cmach::lua_topointer(l, index);
cmach::lua_pushvalue(l, index);
self.pop_error_with_backtrace(cmach::cmach_lua_ref(l))?;
let key = cmach::lua_tointeger(l, -1) as LuaReferenceKey;
cmach::lua_pop(l, 1);
let inner_ref = LuaInnerReference {
machine: &self,
key,
identity,
};
let lua_ref = LuaReference::new(inner_ref);
if type_id == cmach::LUA_TFUNCTION.try_into().unwrap() {
LuaType::Function(LuaFunction {
lua_ref,
chunk_name: None,
})
} else if type_id == cmach::LUA_TTABLE.try_into().unwrap() {
LuaType::Table(lua_ref)
} else if type_id == cmach::LUA_TUSERDATA.try_into().unwrap() {
LuaType::Userdata(lua_ref)
} else if type_id == cmach::LUA_TTHREAD.try_into().unwrap() {
LuaType::Thread(lua_ref)
} else {
panic!("unexpected type {} on Lua stack", type_id)
}
};
Ok(LuaDatum(lua_type))
}
pub unsafe fn pop_datum<'b>(&'b self) -> Result<LuaDatum<'a, 'b, 'static>, MachineError> {
let result = self.extract_datum(-1);
cmach::lua_pop(self.lua_state(), 1);
result
}
pub unsafe fn push_reference<'b>(&'b self, lua_ref: &LuaReference<'a, 'b>) {
cmach::lua_rawgeti(
self.lua_state(),
cmach::LUA_REGISTRYINDEX.try_into().unwrap(),
self.get_reference_key(lua_ref) as cmach::lua_Integer,
);
}
pub unsafe fn push_bin(&self, binary: &[u8]) -> Result<(), MachineError> {
self.pop_error_with_backtrace(cmach::cmach_lua_pushlstring(
self.lua_state(),
binary.as_ptr() as *const _,
cmach::lua_Integer::try_from(binary.len()).map_err(|_| {
MachineError::new().set_message_str("lua_Integer too small to store length")
})?,
))
}
pub unsafe fn push_str(&self, string: &str) -> Result<(), MachineError> {
self.push_bin(string.as_ref())
}
pub unsafe fn push_datum<'b, 'c>(
&'b self,
datum: &LuaDatum<'a, 'b, 'c>,
) -> Result<(), MachineError> {
let l = self.lua_state();
match datum.0 {
LuaType::Nil => cmach::lua_pushnil(l),
LuaType::Boolean(b) => cmach::lua_pushboolean(l, if b { 1 } else { 0 }),
LuaType::Function(ref f) => self.push_reference(&f.lua_ref),
LuaType::Float(x) => cmach::lua_pushnumber(l, x),
LuaType::Integer(i) => cmach::lua_pushinteger(l, i),
LuaType::String(ref s) => self.push_str(s)?,
LuaType::Binary(ref b) => self.push_bin(b)?,
LuaType::Table(ref lua_ref) => self.push_reference(lua_ref),
LuaType::Userdata(ref lua_ref) => self.push_reference(lua_ref),
LuaType::Thread(ref lua_ref) => self.push_reference(lua_ref),
};
Ok(())
}
}
impl<'a, 'b> Function for LuaFunction<'a, 'b> {
type Datum<'c> = LuaDatum<'a, 'b, 'c>;
fn chunk_name(&self) -> Option<&str> {
self.chunk_name.as_deref()
}
fn call<'c, A>(&self, args: A) -> Result<Vec<LuaDatum<'a, 'b, 'static>>, MachineError>
where
A: IntoIterator<Item = LuaDatum<'a, 'b, 'c>>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
let args = args.into_iter();
unsafe {
let machine = self.lua_ref.as_ref().machine;
machine.assert_empty_stack();
let l = machine.lua_state();
let old_top = cmach::lua_gettop(l);
let res = (|| {
cmach::lua_pushcfunction(l, Some(cmach::cmach_lua_errmsgh));
let base_top = old_top + 1;
machine.push_reference(&self.lua_ref);
let argc = c_int::try_from(args.len()).map_err(|_| {
MachineError::new()
.set_message_str("C integer too small to store argument count")
})?;
let mut remaining_argc = argc;
if cmach::lua_checkstack(l, argc) == 0 {
Err(MachineError::new()
.set_message_str("Lua stack exhausted while preparing function arguments")
.set_chunk_name_opt(self.chunk_name.clone()))?;
}
for arg in args {
if remaining_argc == 0 {
panic!("ExactSizeIterator for arguments provided more items than reported");
}
remaining_argc -= 1;
machine.push_datum(&arg)?;
}
if remaining_argc > 0 {
panic!("ExactSizeIterator for arguments provided less items than reported");
}
machine.pop_error_with_backtrace(cmach::lua_pcall(
l,
argc,
cmach::LUA_MULTRET as c_int,
old_top + 1,
))?;
if cmach::lua_checkstack(l, 10) == 0 {
Err(MachineError::new()
.set_message_str("Lua stack exhausted after calling function")
.set_chunk_name_opt(self.chunk_name.clone()))?;
}
let new_top = cmach::lua_gettop(l);
let mut retvals = Vec::with_capacity((new_top - base_top) as usize);
for index in base_top..new_top {
retvals.push(machine.extract_datum(index + 1)?);
}
Ok(retvals)
})();
cmach::lua_settop(l, old_top);
res
}
}
}
impl<'a> Compile<'a, String> for LuaMachine<'a> {
fn compile(
&self,
chunk_name: Option<String>,
code: String,
) -> Result<LuaFunction<'a, '_>, MachineError> {
self.assert_empty_stack();
let codelen = code.len();
let code = CString::new(code).map_err(|err| {
MachineError::new()
.set_kind(MachineErrorKind::Syntax)
.set_message(err.to_string())
})?;
let lua_name: Option<CString> = chunk_name.as_ref().map(|name| {
let mut lua_name = String::with_capacity(name.len() + 2);
lua_name.push('=');
lua_name.push_str(name);
CString::new(lua_name)
.unwrap_or_else(|_| CString::new("(invalid name)".to_string()).unwrap())
});
let inner_ref = unsafe {
let l = self.lua_state();
self.pop_error(cmach::luaL_loadbufferx(
self.lua_state(),
code.as_ptr(),
codelen
.try_into()
.expect("could not convert integer of code length"),
match &lua_name {
None => code.as_ptr(),
Some(n) => n.as_ptr(),
},
b"t\0".as_ptr() as *const _,
))?;
let identity = cmach::lua_topointer(l, -1);
self.pop_error(cmach::cmach_lua_ref(l))?;
let key = cmach::lua_tointeger(l, -1) as LuaReferenceKey;
cmach::lua_pop(l, 1);
LuaInnerReference {
machine: &self,
key,
identity,
}
};
Ok(LuaFunction {
lua_ref: LuaReference::new(inner_ref),
chunk_name,
})
}
}
impl<'a, 'b> Compile<'a, &'b str> for LuaMachine<'a> {
fn compile(
&self,
chunk_name: Option<String>,
code: &'b str,
) -> Result<LuaFunction<'a, '_>, MachineError> {
self.compile(chunk_name, code.to_string())
}
}
unsafe extern "C" fn callback_trampoline<F>(context: *mut c_void, nargs: c_int) -> c_int
where
F: FnMut(c_int) -> c_int,
{
let closure = &mut *(context as *mut F);
abort_on_unwind(|| closure(nargs))
}
fn get_callback_trampoline<F>(_closure: &F) -> cmach::cmach_callback_fptr
where
F: FnMut(c_int) -> c_int,
{
Some(callback_trampoline::<F>)
}
unsafe extern "C" fn release_trampoline<F>(context: *mut c_void)
where
F: FnMut(c_int) -> c_int,
{
abort_on_unwind(|| drop(Box::from_raw(context as *mut F)))
}
fn get_release_trampoline<F>(_closure: &F) -> cmach::cmach_callback_release_fptr
where
F: FnMut(c_int) -> c_int,
{
Some(release_trampoline::<F>)
}
impl<'a> Callback<'a> for LuaMachine<'a> {
fn callback<'b, 'c, F, R>(&'b self, func: F) -> Result<LuaDatum<'a, 'b, 'static>, MachineError>
where
R: IntoIterator<Item = Self::Datum<'b, 'c>>,
<R as IntoIterator>::IntoIter: ExactSizeIterator,
F: 'a + Fn(Vec<LuaDatum<'a, 'b, 'static>>) -> Result<R, Box<dyn Error>>,
{
self.assert_empty_stack();
let l = self.lua_state();
let inner_closure = move |nargs| -> Result<c_int, MachineError> {
unsafe {
if cmach::lua_checkstack(l, 10) == 0 {
Err(MachineError::new().set_message_str("Lua stack exhausted"))?;
}
let mut args = Vec::with_capacity(nargs as usize);
for i in 0..nargs {
args.push(self.extract_datum(i - nargs)?);
}
cmach::lua_pop(l, nargs);
match func(args) {
Ok(retvals) => {
let retvals = retvals.into_iter();
let retvalc = c_int::try_from(retvals.len()).map_err(|_| {
MachineError::new()
.set_message_str("C integer too small to store return value count")
})?;
let mut remaining_retvalc = retvalc;
let space = retvalc.checked_add(10).ok_or_else(|| {
MachineError::new().set_message_str("return value count too high")
})?;
if cmach::lua_checkstack(l, space) == 0 {
Err(MachineError::new().set_message_str("Lua stack exhausted"))?;
}
for retval in retvals {
if remaining_retvalc == 0 {
panic!("ExactSizeIterator for arguments provided more items than reported");
}
remaining_retvalc -= 1;
self.push_datum(&retval)?;
}
if remaining_retvalc > 0 {
panic!(
"ExactSizeIterator for arguments provided less items than reported"
);
}
Ok(retvalc)
}
Err(err) => {
self.push_str(&format!("callback returned error: {}", err))?;
Ok(-1)
}
}
}
};
let closure = move |nargs| match inner_closure(nargs) {
Ok(retvals) => retvals,
Err(err) => {
let errstr = err.to_string();
unsafe {
let (errstrptr, errlen) = match cmach::lua_Integer::try_from(errstr.len()) {
Ok(errlen) => (errstr.as_ptr(), errlen),
Err(_) => {
const TOOLONG: &'static str = "error message too long";
(TOOLONG.as_ptr(), TOOLONG.len() as cmach::lua_Integer)
}
};
cmach::cmach_lua_pushlstring(l, errstrptr as *const _, errlen);
}
-1
}
};
let callback_trampoline = get_callback_trampoline(&closure);
let release_trampoline = get_release_trampoline(&closure);
let closure_ptr = Box::into_raw(Box::new(closure));
unsafe {
self.pop_error_with_backtrace(cmach::cmach_lua_pushclosure(
l,
closure_ptr as *mut c_void,
callback_trampoline,
release_trampoline,
))?;
Ok(self.pop_datum()?)
}
}
}