1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use Index;
use {ffi, CheckLua, Error, IntoLua, Result, State};

use functions;
use functions::Function;

use ffi::AsCStr;

use std::ffi::CString;
use std::os::raw;
use std::rc::Rc;
use std::{mem, ops, ptr};

/// Type to implement Lua userdata, which is an arbitrary block of memory managed by Lua
pub trait UserData {
    /// Name of the metatable
    const METATABLE: &'static str;

    /// Called only once to register the metamethods of the type.
    fn register(meta: &mut MetaTable) {}
}

/// Trait to read userdatums from Lua
pub trait FromLua {
    /// Get a reference
    unsafe fn from_lua(state: &State, idx: Index) -> Result<&Self>;
}

/// Trait to read userdatums from Lua that allow mutation
pub trait FromLuaMut {
    /// Get a mutable reference
    unsafe fn from_lua_mut(state: &mut State, idx: Index) -> Result<&mut Self>;
}

impl<T: UserData> CheckLua for T {
    unsafe fn check(state: &State, idx: Index) -> bool {
        let meta = CString::new(T::METATABLE).unwrap();
        !ffi::luaL_checkudata(state.pointer, idx.as_absolute(), meta.as_ptr() as _).is_null()
    }
}

impl<T: UserData> FromLua for T {
    unsafe fn from_lua(state: &State, idx: Index) -> Result<&Self> {
        if ffi::lua_isuserdata(state.pointer, idx.as_absolute()) == 0 {
            return Err(Error::Type);
        }

        let meta = CString::new(T::METATABLE).unwrap();
        let pointer = ffi::luaL_checkudata(state.pointer, idx.as_absolute(), meta.as_ptr() as _);

        if pointer.is_null() {
            return Err(Error::Type);
        }

        Ok(mem::transmute(pointer as *const T))
    }
}

impl<T: UserData> FromLuaMut for T {
    unsafe fn from_lua_mut(state: &mut State, idx: Index) -> Result<&mut Self> {
        if ffi::lua_isuserdata(state.pointer, idx.as_absolute()) == 0 {
            return Err(Error::Type);
        }

        let meta = CString::new(T::METATABLE).unwrap();
        let pointer = ffi::luaL_checkudata(state.pointer, idx.as_absolute(), meta.as_ptr() as _);

        if pointer.is_null() {
            return Err(Error::Type);
        }

        Ok(mem::transmute(pointer as *mut T))
    }
}

#[doc(hidden)]
pub struct LuaUserDataWrapper<T>(pub T);

/// Type used to register userdata metamethods from the `LuaUserData` trait.
pub struct MetaTable(*mut ffi::lua_State);

macro_rules! impl_meta {
    (pub enum $name:ident { $( $variant:ident => $meta:expr ,)+ }) => {
        /// Metamethods. All metamethods are supported except for `__gc`, which is implemented by the `Drop` trat.
        #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
        pub enum MetaMethod {
            $($variant,)+
        }
        impl MetaMethod {
            #[inline]
            fn as_cstr(&self) -> &str {
                match *self {
                    $(MetaMethod::$variant => concat!($meta, "\0"),)+
                }
            }
        }
    }
}

impl_meta! {
    pub enum MetaMethod {
        ToString => "__tostring",
        Index => "__index",
        NewIndex => "__newindex",
        Add => "__add",
        Sub => "__sub",
        Mul => "__mul",
        Div => "__div",
        Mod => "__mod",
        Pow => "__pow",
        Unm => "__unm",
        Concat => "__concat",
        Len => "__len",
        Eq => "__eq",
        Lt => "__lt",
        Le => "__le",
        Call => "__call",
    }
}

impl MetaTable {
    /// Register a metamethod
    pub fn set<F>(&mut self, method: MetaMethod)
    where
        F: Function,
    {
        unsafe {
            ffi::lua_pushstring(self.0, method.as_cstr().as_ptr() as _);
            ffi::lua_pushcfunction(self.0, Some(functions::function::<F>));
            ffi::lua_settable(self.0, -3);
        }
    }
}

impl<D> IntoLua for LuaUserDataWrapper<D>
where
    D: UserData,
{
    unsafe fn into_lua(self, state: &mut State) {
        let data = self.0;
        let ptr = ffi::lua_newuserdata(state.pointer, mem::size_of::<D>()) as *mut D;

        let table_name = CString::new(D::METATABLE).unwrap();
        if ffi::luaL_newmetatable(state.pointer, table_name.as_ptr() as _) == 1 {
            ffi::lua_pushstring(state.pointer, "__gc\0".as_ptr() as _);
            ffi::lua_pushcfunction(state.pointer, Some(__gc::<D>));
            ffi::lua_settable(state.pointer, -3);

            let mut meta = MetaTable(state.pointer);
            D::register(&mut meta);
        }
        ffi::lua_setmetatable(state.pointer, -2);

        ptr::copy(&data, ptr, 1);
        mem::forget(data);

        unsafe extern "C" fn __gc<D>(state: *mut ffi::lua_State) -> raw::c_int {
            ptr::drop_in_place(ffi::lua_touserdata(state, -1) as *mut D);
            0
        }
    }
}