use crate::{
impl_object, AbsoluteIndex, AsLua, LuaError, LuaRead, LuaState, Push, PushGuard, PushInto,
PushOneInto, Void,
};
use std::{error::Error, fmt, num::NonZeroI32};
#[derive(Debug)]
pub struct Object<L> {
guard: L,
index: AbsoluteIndex,
}
impl<L> Object<L> {
#[inline(always)]
pub(crate) fn new(guard: L, index: NonZeroI32) -> Self
where
L: AsLua,
{
Self {
index: AbsoluteIndex::new(index, guard.as_lua()),
guard,
}
}
#[inline(always)]
pub fn guard(&self) -> &L {
&self.guard
}
#[inline(always)]
pub fn into_guard(self) -> L {
self.guard
}
#[inline(always)]
pub fn index(&self) -> AbsoluteIndex {
self.index
}
#[inline(always)]
pub unsafe fn try_downcast<T>(self) -> Result<T, Self>
where
T: LuaRead<L>,
{
let Self { guard, index } = self;
T::lua_read_at_position(guard, index.0).map_err(|guard| Self { guard, index })
}
}
impl<L> AsLua for Object<L>
where
L: AsLua,
{
fn as_lua(&self) -> LuaState {
self.guard.as_lua()
}
}
impl<L> LuaRead<L> for Object<L>
where
L: AsLua,
{
fn lua_read_at_position(lua: L, index: NonZeroI32) -> Result<Self, L> {
Ok(Self::new(lua, index))
}
}
impl<L, K> Push<L> for Object<K>
where
L: AsLua,
{
type Err = Void;
fn push_to_lua(&self, lua: L) -> crate::PushResult<L, Self> {
unsafe {
crate::ffi::lua_pushvalue(lua.as_lua(), self.index.into());
Ok(PushGuard::new(lua, 1))
}
}
}
impl<L> crate::PushOne<L> for Object<L> where L: AsLua {}
impl<L, K> PushInto<L> for Object<K>
where
L: AsLua,
{
type Err = Void;
fn push_into_lua(self, lua: L) -> crate::PushIntoResult<L, Self> {
unsafe {
crate::ffi::lua_pushvalue(lua.as_lua(), self.index.into());
Ok(PushGuard::new(lua, 1))
}
}
}
impl<L> crate::PushOneInto<L> for Object<L> where L: AsLua {}
pub trait FromObject<L> {
unsafe fn check(lua: impl AsLua, index: NonZeroI32) -> bool;
unsafe fn from_obj(inner: Object<L>) -> Self;
fn try_from_obj(inner: Object<L>) -> Result<Self, Object<L>>
where
Self: Sized,
L: AsLua,
{
if unsafe { Self::check(inner.guard(), inner.index().0) } {
Ok(unsafe { Self::from_obj(inner) })
} else {
Err(inner)
}
}
}
pub trait Index<L>: AsRef<Object<L>>
where
L: AsLua,
{
#[track_caller]
#[inline(always)]
fn get<'lua, K, R>(&'lua self, key: K) -> Option<R>
where
L: 'lua,
K: PushOneInto<LuaState>,
K::Err: Into<Void>,
R: LuaRead<PushGuard<&'lua L>>,
{
self.try_get(key).ok()
}
#[track_caller]
#[inline]
fn try_get<'lua, K, R>(&'lua self, key: K) -> Result<R, LuaError>
where
L: 'lua,
K: PushOneInto<LuaState>,
K::Err: Into<Void>,
R: LuaRead<PushGuard<&'lua L>>,
{
let Object { guard, index } = self.as_ref();
unsafe { imp::try_get(guard, *index, key).map_err(|(_, e)| e) }
}
#[track_caller]
#[inline(always)]
fn into_get<K, R>(self, key: K) -> Result<R, Self>
where
Self: AsLua + Sized,
K: PushOneInto<LuaState>,
K::Err: Into<Void>,
R: LuaRead<PushGuard<Self>>,
{
self.try_into_get(key).map_err(|(this, _)| this)
}
#[track_caller]
#[inline]
fn try_into_get<K, R>(self, key: K) -> Result<R, (Self, LuaError)>
where
Self: AsLua + Sized,
K: PushOneInto<LuaState>,
K::Err: Into<Void>,
R: LuaRead<PushGuard<Self>>,
{
let this_index = self.as_ref().index;
unsafe { imp::try_get(self, this_index, key) }
}
#[track_caller]
#[inline]
fn call_method<'lua, A, R>(
&'lua self,
name: &str,
args: A,
) -> Result<R, MethodCallError<A::Err>>
where
L: 'lua,
Self: Push<LuaState>,
Self::Err: Into<Void>,
A: PushInto<LuaState>,
R: LuaRead<PushGuard<Callable<PushGuard<&'lua L>>>>,
{
use MethodCallError::{LuaError, NoSuchMethod, PushError};
self.get::<_, Callable<_>>(name)
.ok_or(NoSuchMethod)?
.into_call_with((self, args))
.map_err(|e| match e {
CallError::LuaError(e) => LuaError(e),
CallError::PushError(e) => PushError(e.other().first()),
})
}
}
#[derive(Debug)]
pub enum MethodCallError<E> {
NoSuchMethod,
LuaError(LuaError),
PushError(E),
}
impl<E> From<CallError<E>> for MethodCallError<E> {
fn from(e: CallError<E>) -> Self {
match e {
CallError::PushError(e) => Self::PushError(e),
CallError::LuaError(e) => Self::LuaError(e),
}
}
}
impl<E> fmt::Display for MethodCallError<E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::NoSuchMethod => f.write_str("Method not found"),
Self::LuaError(lua_error) => write!(f, "Lua error: {}", lua_error),
Self::PushError(err) => {
write!(f, "Error while pushing arguments: {}", err)
}
}
}
}
impl<E> Error for MethodCallError<E>
where
E: Error,
{
fn description(&self) -> &str {
match self {
Self::NoSuchMethod => "Method not found",
Self::LuaError(_) => "Lua error",
Self::PushError(_) => "Error while pushing arguments",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
Self::NoSuchMethod => None,
Self::LuaError(lua_error) => Some(lua_error),
Self::PushError(err) => Some(err),
}
}
}
#[derive(Debug)]
pub struct Indexable<L> {
inner: Object<L>,
}
impl_object! { Indexable,
check(lua, index) {
imp::is_indexable(&lua, index)
}
impl Index,
}
pub trait NewIndex<L>: AsRef<Object<L>>
where
L: AsLua,
{
#[track_caller]
#[inline(always)]
fn set<K, V>(&self, key: K, value: V)
where
K: PushOneInto<LuaState>,
K::Err: Into<Void>,
V: PushOneInto<LuaState>,
V::Err: Into<Void>,
{
if let Err(e) = self.try_set(key, value) {
panic!("Setting value failed: {}", e)
}
}
#[track_caller]
#[inline]
fn try_set<K, V>(&self, key: K, value: V) -> Result<(), LuaError>
where
K: PushOneInto<LuaState>,
K::Err: Into<Void>,
V: PushOneInto<LuaState>,
V::Err: Into<Void>,
{
let Object { guard, index } = self.as_ref();
unsafe { imp::try_checked_set(guard, *index, key, value) }.map_err(|e| match e {
Ok(_) => unreachable!("Void is uninstantiatable"),
Err(e) => e,
})
}
#[track_caller]
#[inline(always)]
fn checked_set<K, V>(&self, key: K, value: V) -> Result<(), CheckedSetError<K::Err, V::Err>>
where
K: PushOneInto<LuaState>,
V: PushOneInto<LuaState>,
{
self.try_checked_set(key, value)
.map_err(|e| e.unwrap_or_else(|e| panic!("Setting value failed: {}", e)))
}
#[track_caller]
#[inline(always)]
fn try_checked_set<K, V>(
&self,
key: K,
value: V,
) -> Result<(), TryCheckedSetError<K::Err, V::Err>>
where
K: PushOneInto<LuaState>,
V: PushOneInto<LuaState>,
{
let Object { guard, index } = self.as_ref();
unsafe { imp::try_checked_set(guard, *index, key, value) }
}
}
pub type TryCheckedSetError<K, V> = Result<CheckedSetError<K, V>, LuaError>;
#[derive(Debug, Copy, Clone)]
pub enum CheckedSetError<K, V> {
KeyPushError(K),
ValuePushError(V),
}
#[derive(Debug)]
pub struct IndexableRW<L> {
inner: Object<L>,
}
impl_object! { IndexableRW,
check(lua, index) {
imp::is_rw_indexable(&lua, index)
}
impl Index,
impl NewIndex,
}
pub trait Call<L>: AsRef<Object<L>>
where
L: AsLua,
{
#[track_caller]
#[inline]
fn call<'lua, R>(&'lua self) -> Result<R, LuaError>
where
L: 'lua,
R: LuaRead<PushGuard<&'lua L>>,
{
Ok(self.call_with(())?)
}
#[track_caller]
#[inline]
fn call_with<'lua, A, R>(&'lua self, args: A) -> Result<R, CallError<A::Err>>
where
L: 'lua,
A: PushInto<LuaState>,
R: LuaRead<PushGuard<&'lua L>>,
{
let Object { guard, index } = self.as_ref();
imp::call(guard, *index, args)
}
#[track_caller]
#[inline]
fn into_call<R>(self) -> Result<R, LuaError>
where
Self: AsLua + Sized,
R: LuaRead<PushGuard<Self>>,
{
Ok(self.into_call_with(())?)
}
#[track_caller]
#[inline]
fn into_call_with<A, R>(self, args: A) -> Result<R, CallError<A::Err>>
where
Self: AsLua + Sized,
A: PushInto<LuaState>,
R: LuaRead<PushGuard<Self>>,
{
let index = self.as_ref().index;
imp::call(self, index, args)
}
}
#[derive(Debug)]
pub enum CallError<E> {
LuaError(LuaError),
PushError(E),
}
impl<E> CallError<E> {
pub fn map<F, R>(self, f: F) -> CallError<R>
where
F: FnOnce(E) -> R,
{
match self {
CallError::LuaError(e) => CallError::LuaError(e),
CallError::PushError(e) => CallError::PushError(f(e)),
}
}
}
impl<E> From<LuaError> for CallError<E> {
fn from(e: LuaError) -> Self {
Self::LuaError(e)
}
}
impl<E> From<CallError<E>> for LuaError
where
E: Into<Void>,
{
fn from(e: CallError<E>) -> Self {
match e {
CallError::LuaError(le) => le,
CallError::PushError(_) => {
unreachable!("no way to create instance of Void")
}
}
}
}
impl<E> fmt::Display for CallError<E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::LuaError(lua_error) => write!(f, "Lua error: {}", lua_error),
Self::PushError(err) => {
write!(f, "Error while pushing arguments: {}", err)
}
}
}
}
impl<E> Error for CallError<E>
where
E: Error,
{
fn description(&self) -> &str {
match self {
Self::LuaError(_) => "Lua error",
Self::PushError(_) => "error while pushing arguments",
}
}
fn cause(&self) -> Option<&dyn Error> {
match self {
Self::LuaError(lua_error) => Some(lua_error),
Self::PushError(err) => Some(err),
}
}
}
#[derive(Debug)]
pub struct Callable<L> {
inner: Object<L>,
}
impl_object! { Callable,
check(lua, index) {
imp::is_callable(&lua, index)
}
impl Call,
}
mod imp {
use super::{CallError, CheckedSetError, TryCheckedSetError};
use crate::{
c_ptr, ffi, nzi32, AbsoluteIndex, AsLua, LuaError, LuaRead, LuaState, PushGuard, PushInto,
PushOneInto, ToString, Void,
};
use std::num::NonZeroI32;
#[track_caller]
pub(super) unsafe fn try_get<T, K, R>(
this: T,
this_index: AbsoluteIndex,
key: K,
) -> Result<R, (T, LuaError)>
where
T: AsLua,
K: PushOneInto<LuaState>,
K::Err: Into<Void>,
R: LuaRead<PushGuard<T>>,
{
let raw_lua = this.as_lua();
let this_index = this_index.into();
if ffi::lua_istable(raw_lua, this_index)
&& !ffi::luaL_hasmetafield(raw_lua, this_index, c_ptr!("__index"))
{
raw_lua.push_one(key).assert_one_and_forget();
ffi::lua_rawget(raw_lua, this_index);
} else {
raw_lua.push_one(key).assert_one_and_forget();
let index_ref = ffi::luaL_ref(raw_lua, ffi::LUA_REGISTRYINDEX);
ffi::lua_pushvalue(raw_lua, this_index);
let table_ref = ffi::luaL_ref(raw_lua, ffi::LUA_REGISTRYINDEX);
let res = raw_lua.pcall(|l| {
let raw_lua = l.as_lua();
ffi::lua_rawgeti(raw_lua, ffi::LUA_REGISTRYINDEX, table_ref);
ffi::lua_rawgeti(raw_lua, ffi::LUA_REGISTRYINDEX, index_ref);
ffi::lua_gettable(raw_lua, -2);
ffi::luaL_ref(raw_lua, ffi::LUA_REGISTRYINDEX)
});
let value_ref = match res {
Ok(value_ref) => value_ref,
Err(e) => return Err((this, e)),
};
ffi::lua_rawgeti(raw_lua, ffi::LUA_REGISTRYINDEX, value_ref);
ffi::luaL_unref(raw_lua, ffi::LUA_REGISTRYINDEX, value_ref);
ffi::luaL_unref(raw_lua, ffi::LUA_REGISTRYINDEX, index_ref);
ffi::luaL_unref(raw_lua, ffi::LUA_REGISTRYINDEX, table_ref);
}
R::lua_read_at_position(PushGuard::new(this, 1), nzi32!(-1)).map_err(|g| {
let e = LuaError::wrong_type_returned::<R, _>(raw_lua, 1);
(g.into_inner(), e)
})
}
#[track_caller]
pub(super) unsafe fn try_checked_set<T, K, V>(
this: T,
this_index: AbsoluteIndex,
key: K,
value: V,
) -> Result<(), TryCheckedSetError<K::Err, V::Err>>
where
T: AsLua,
K: PushOneInto<LuaState>,
V: PushOneInto<LuaState>,
{
let raw_lua = this.as_lua();
let this_index = this_index.into();
if ffi::lua_istable(raw_lua, this_index)
&& !ffi::luaL_hasmetafield(raw_lua, this_index, c_ptr!("__index"))
&& !ffi::luaL_hasmetafield(raw_lua, this_index, c_ptr!("__newindex"))
{
raw_lua
.try_push_one(key)
.map_err(|(e, _)| Ok(CheckedSetError::KeyPushError(e)))?
.assert_one_and_forget();
raw_lua
.try_push_one(value)
.map_err(|(e, _)| Ok(CheckedSetError::ValuePushError(e)))?
.assert_one_and_forget();
ffi::lua_rawset(raw_lua, this_index);
} else {
raw_lua
.try_push_one(value)
.map_err(|(e, _)| Ok(CheckedSetError::ValuePushError(e)))?
.assert_one_and_forget();
let value_ref = ffi::luaL_ref(raw_lua, ffi::LUA_REGISTRYINDEX);
raw_lua
.try_push_one(key)
.map_err(|(e, _)| Ok(CheckedSetError::KeyPushError(e)))?
.assert_one_and_forget();
let index_ref = ffi::luaL_ref(raw_lua, ffi::LUA_REGISTRYINDEX);
ffi::lua_pushvalue(raw_lua, this_index);
let table_ref = ffi::luaL_ref(raw_lua, ffi::LUA_REGISTRYINDEX);
raw_lua
.pcall(|l| {
let raw_lua = l.as_lua();
ffi::lua_rawgeti(raw_lua, ffi::LUA_REGISTRYINDEX, table_ref);
ffi::lua_rawgeti(raw_lua, ffi::LUA_REGISTRYINDEX, index_ref);
ffi::lua_rawgeti(raw_lua, ffi::LUA_REGISTRYINDEX, value_ref);
ffi::lua_settable(raw_lua, -3);
})
.map_err(Err)?;
ffi::luaL_unref(raw_lua, ffi::LUA_REGISTRYINDEX, value_ref);
ffi::luaL_unref(raw_lua, ffi::LUA_REGISTRYINDEX, index_ref);
ffi::luaL_unref(raw_lua, ffi::LUA_REGISTRYINDEX, table_ref);
}
Ok(())
}
#[track_caller]
#[inline]
pub(super) fn call<T, A, R>(
this: T,
index: AbsoluteIndex,
args: A,
) -> Result<R, CallError<A::Err>>
where
T: AsLua,
A: PushInto<LuaState>,
R: LuaRead<PushGuard<T>>,
{
let raw_lua = this.as_lua();
let (pcall_return_value, pushed_value) = unsafe {
let old_top = ffi::lua_gettop(raw_lua);
ffi::lua_pushvalue(raw_lua, index.into());
let num_pushed = match this.as_lua().try_push(args) {
Ok(g) => g.forget_internal(),
Err((err, _)) => return Err(CallError::PushError(err)),
};
let pcall_return_value = ffi::lua_pcall(raw_lua, num_pushed, ffi::LUA_MULTRET, 0);
let n_results = ffi::lua_gettop(raw_lua) - old_top;
(pcall_return_value, PushGuard::new(this, n_results))
};
match pcall_return_value {
ffi::LUA_ERRMEM => panic!("lua_pcall returned LUA_ERRMEM"),
ffi::LUA_ERRRUN => {
let error_msg = ToString::lua_read(pushed_value)
.ok()
.expect("can't find error message at the top of the Lua stack");
return Err(LuaError::ExecutionError(error_msg.into()).into());
}
0 => {}
_ => panic!(
"Unknown error code returned by lua_pcall: {}",
pcall_return_value
),
}
let n_results = pushed_value.size;
LuaRead::lua_read_at_maybe_zero_position(pushed_value, -n_results)
.map_err(|lua| LuaError::wrong_type_returned::<R, _>(lua, n_results).into())
}
#[inline(always)]
pub(super) fn is_callable(lua: impl AsLua, index: NonZeroI32) -> bool {
let raw_lua = lua.as_lua();
let i = index.into();
unsafe {
ffi::lua_isfunction(raw_lua, i) || ffi::luaL_hasmetafield(raw_lua, i, c_ptr!("__call"))
}
}
#[inline(always)]
pub(super) fn is_indexable(lua: impl AsLua, index: NonZeroI32) -> bool {
let raw_lua = lua.as_lua();
let i = index.into();
unsafe {
ffi::lua_istable(raw_lua, i) || ffi::luaL_hasmetafield(raw_lua, i, c_ptr!("__index"))
}
}
#[inline(always)]
pub(super) fn is_rw_indexable(lua: impl AsLua, index: NonZeroI32) -> bool {
let raw_lua = lua.as_lua();
let i = index.into();
unsafe {
ffi::lua_istable(raw_lua, i)
|| ffi::luaL_hasmetafield(raw_lua, i, c_ptr!("__index"))
&& ffi::luaL_hasmetafield(raw_lua, i, c_ptr!("__newindex"))
}
}
}
#[macro_export]
macro_rules! impl_object {
(
$this:ident,
check($lua:ident, $index:ident) { $($check:tt)* }
$( impl $trait:ident, )*
) => {
impl<L> $crate::object::FromObject<L> for $this<L>
where
L: $crate::AsLua,
{
#[inline(always)]
unsafe fn check($lua: impl $crate::AsLua, $index: ::std::num::NonZeroI32) -> bool {
$($check)*
}
#[inline(always)]
unsafe fn from_obj(inner: $crate::object::Object<L>) -> Self {
Self { inner }
}
}
impl<L> $crate::AsLua for $this<L>
where
L: $crate::AsLua,
{
#[inline(always)]
fn as_lua(&self) -> $crate::LuaState {
self.inner.as_lua()
}
}
impl<L> ::std::convert::AsRef<$crate::object::Object<L>> for $this<L> {
#[inline(always)]
fn as_ref(&self) -> &$crate::object::Object<L> {
&self.inner
}
}
impl<L> ::std::convert::From<$this<L>> for $crate::object::Object<L>
where
L: $crate::AsLua,
{
#[inline(always)]
fn from(o: $this<L>) -> Self {
o.inner
}
}
impl<L> ::std::convert::TryFrom<$crate::object::Object<L>> for $this<L>
where
L: $crate::AsLua,
{
type Error = $crate::object::Object<L>;
#[inline(always)]
fn try_from(o: $crate::object::Object<L>) -> ::std::result::Result<Self, Self::Error> {
Self::try_from_obj(o)
}
}
$(
impl<L> $trait<L> for $this<L>
where
L: $crate::AsLua,
{}
)*
impl<L> $crate::LuaRead<L> for $this<L>
where
L: $crate::AsLua,
{
#[inline(always)]
fn lua_read_at_position(
lua: L,
index: ::std::num::NonZeroI32,
) -> ::std::result::Result<Self, L> {
::std::convert::TryFrom::try_from($crate::object::Object::new(lua, index))
.map_err($crate::object::Object::into_guard)
}
}
impl<L, T> $crate::Push<L> for $this<T>
where
L: $crate::AsLua,
{
type Err = $crate::Void;
#[inline(always)]
fn push_to_lua(&self, lua: L) -> $crate::PushResult<L, Self> {
unsafe {
$crate::ffi::lua_pushvalue(lua.as_lua(), self.as_ref().index().into());
Ok(PushGuard::new(lua, 1))
}
}
}
impl<L, T> $crate::PushOne<L> for $this<T>
where
L: $crate::AsLua,
{}
}
}