diff --git a/src/function.rs b/src/function.rs index d83eff1..97ad2ef 100644 --- a/src/function.rs +++ b/src/function.rs @@ -1,9 +1,10 @@ use std::ffi::CStr; -use crate::{FromLuaStack, LuaRef, PushToLuaStack, StackGuard, State}; +use crate::{AsLua, FromLuaStack, LuaRef, PushToLuaStack, StackGuard, State}; use mlua_sys as lua; +#[derive(Clone)] pub struct Function<'a> { state: &'a State, lref: LuaRef @@ -18,7 +19,7 @@ impl<'a> FromLuaStack<'a> for Function<'a> { } } -impl<'a> PushToLuaStack for Function<'a> { +impl<'a> PushToLuaStack<'a> for Function<'a> { unsafe fn push_to_lua_stack(&self, state: &State) -> crate::Result<()> { self.lref.push_to_lua_stack(state) } @@ -59,20 +60,26 @@ impl<'a> Function<'a> { } } +impl<'a> AsLua<'a> for Function<'a> { + fn as_lua(&self, lua: &'a State) -> crate::Result> { + Ok(crate::Value::Function(self.clone())) + } +} + pub trait PushToLuaStackMulti<'a> { fn len(&self) -> usize; - fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()>; + fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()>; } impl<'a, T> PushToLuaStackMulti<'a> for T where - T: PushToLuaStack, + T: PushToLuaStack<'a>, { fn len(&self) -> usize { 1 } - fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()> { + fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()> { unsafe { self.push_to_lua_stack(state)?; } @@ -109,14 +116,14 @@ impl<'a, T: FromLuaStack<'a>> FromLuaStackMulti<'a> for T { macro_rules! impl_function_arg_tuple { ( $count: expr, $first: tt, $( $name: tt ),+ ) => ( #[allow(non_snake_case)] - impl<'a, $first: PushToLuaStack, $($name: PushToLuaStack,)+> PushToLuaStackMulti<'a> for ($first, $($name,)+) { + impl<'a, $first: PushToLuaStack<'a>, $($name: PushToLuaStack<'a>,)+> PushToLuaStackMulti<'a> for ($first, $($name,)+) { fn len(&self) -> usize { // this will end up generating $count - 1 - 1 - 1... hopefully the compiler will // optimize that out $count } - fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()> { + fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()> { let ($first, $($name,)+) = self; unsafe { @@ -134,7 +141,13 @@ macro_rules! impl_function_arg_tuple { } fn results_from_lua_stack(state: &'a State) -> crate::Result { - Ok(unsafe { ( $first::from_lua_stack(state)?, $( $name::from_lua_stack(state)?, )+ ) }) + unsafe { + let ($( $name, )+ $first) = ( $( $name::from_lua_stack(state)?, )+ $first::from_lua_stack(state)? ); + + Ok( ($first, $( $name, )+) ) + } + //Ok(unsafe { ( $( $name::from_lua_stack(state)?, )+ $first::from_lua_stack(state)? ) }) + //Ok(unsafe { ( $first::from_lua_stack(state)?, $( $name::from_lua_stack(state)?, )+ ) }) } } @@ -144,12 +157,12 @@ macro_rules! impl_function_arg_tuple { // implements PushToLuaStackMulti and FromLuaStackMulti for a tuple with a single element ( $count: expr, $only: tt ) => { #[allow(non_snake_case)] - impl<'a, $only: PushToLuaStack> PushToLuaStackMulti<'a> for ($only,) { + impl<'a, $only: PushToLuaStack<'a>> PushToLuaStackMulti<'a> for ($only,) { fn len(&self) -> usize { 1 } - fn push_args_to_lua_stack(&self, state: &State) -> crate::Result<()> { + fn push_args_to_lua_stack(&self, state: &'a State) -> crate::Result<()> { let (a,) = self; unsafe { diff --git a/src/main.rs b/src/main.rs index 78ed43a..ca48ddf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{ffi::CStr, str::Utf8Error, sync::Arc}; +use std::{any::type_name, ffi::CStr, marker::PhantomData, mem, ptr, str::Utf8Error, sync::Arc}; use lua::{lua_typename, lua_type}; use mlua_sys as lua; @@ -18,65 +18,28 @@ use value::*; pub mod guard; use guard::*; -/* struct RustFn { - -} */ - -/* struct RustFnUpvalue { - -} */ - -/// -pub fn ptr_to_string(ptr: *const i8) -> std::result::Result { - let c = unsafe { CStr::from_ptr(ptr) }; - let s= c.to_str()?; - Ok(s.to_string()) -} +pub mod userdata; +use userdata::*; +pub mod util; +use util::*; fn main() -> Result<()> { let lua = State::new(); - lua.expose_libraries(&[StdLibrary::Debug]); + lua.expose_libraries(&[StdLibrary::Debug, StdLibrary::Package]); let globals = lua.globals()?; - let a = |lua: &State, (num,): (i32,)| -> Result { + let a = |_lua: &State, (num,): (i32,)| -> Result { println!("Rust got number from lua: {}", num); Ok(999) }; let f = lua.create_function(a)?; globals.set("native_test", f)?; - - //let tbl = lua.create_table()?; - - let vec2_add = lua.create_function(|lua: &State, (a, b): (Table, Table)| -> Result { - let ax: i32 = a.get("x")?; - let ay: i32 = a.get("y")?; - - let bx: i32 = b.get("x")?; - let by: i32 = b.get("y")?; - - let rx = ax + bx; - let ry = ay + by; - - let mt = lua.create_meta_table("Vec2")?; - mt.set("x", rx)?; - mt.set("y", ry)?; - Ok(mt) - })?; - - let mt = lua.create_meta_table("Vec2")?; - mt.set("x", 50)?; - mt.set("y", 50)?; - mt.set_meta("__add", vec2_add)?; - globals.set("pos1", mt)?; - - let mt = lua.create_meta_table("Vec2")?; - mt.set("x", 25)?; - mt.set("y", 25)?; - globals.set("pos2", mt)?; + let ud = lua.create_userdata("Vec2", Vec2 { x: 0.0, y: 0.0})?; + globals.set("Vec2", ud)?; let tbl = lua.create_table()?; tbl.set("x", 10)?; @@ -85,6 +48,8 @@ fn main() -> Result<()> { globals.set("X", tbl)?; lua.execute(r#" + require "util" + --[[function dump_table(tbl) for k, v in pairs(tbl) do if type(v) == "table" then @@ -123,28 +88,12 @@ fn main() -> Result<()> { local res = native_test(50) print("Lua got " .. res .. " back from rust!") - print("Pos1 is (" .. pos1.x .. ", " .. pos1.y .. ")") - print("Pos2 is (" .. pos2.x .. ", " .. pos2.y .. ")") - - local add_pos = pos1 + pos2 - print("Pos1 + pos2 is (" .. add_pos.x .. ", " .. add_pos.y .. ")") + print("Vec2: " .. dump_table(Vec2)) + print("Meta Vec2: " .. dump_table(getmetatable(Vec2))) + --local vec2 = Vec2.new(50, 50) + print("Vec2 is (" .. Vec2.x .. ", " .. Vec2.y .. ")") "#).unwrap(); - let num = globals.get::<_, i32>("cool_num")?; - assert_eq!(num, 50); - println!("Got number as 50!"); - - let num = globals.get::<_, Function>("say_number")?; - num.exec::<_, ()>(50)?; - - let num = globals.get::<_, Function>("multiply_print")?; - num.exec::<_, ()>((10, 5))?; - - let num = globals.get::<_, Function>("multiply_ret")?; - let num: i32 = num.exec::<_, i32>((10, 5))?; - assert_eq!(num, 50); - println!("Did math in lua and got 50!"); - unsafe { assert_eq!(lua::lua_gettop(lua.state_ptr()), 0); // ensure that nothing is left on the stack } @@ -165,10 +114,8 @@ impl LuaRef { /// Creates a reference to what is at the top of the stack. pub unsafe fn from_stack(state: &State) -> Result { let s = state.state_ptr(); - let t = lua::lua_gettop(s); let r = lua::luaL_ref(s, lua::LUA_REGISTRYINDEX); - let t = lua::lua_gettop(s); if r == lua::LUA_REFNIL { Err(Error::Nil) } else { @@ -177,7 +124,7 @@ impl LuaRef { } } -impl PushToLuaStack for LuaRef { +impl<'a> PushToLuaStack<'a> for LuaRef { unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { let s = state.state_ptr(); @@ -236,73 +183,65 @@ impl Error { let msg = format!("{}", self); let msg_c = msg.as_ptr() as *const i8; lua::luaL_error(lua, msg_c); - panic!("never gets here"); + unreachable!(); } } /// A result for use with lua functions type Result = core::result::Result; -pub trait PushToLuaStack { - unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()>; +pub trait PushToLuaStack<'a> { + unsafe fn push_to_lua_stack(&self, state: &'a State) -> Result<()>; } pub trait FromLuaStack<'a>: Sized { unsafe fn from_lua_stack(state: &'a State) -> Result; } -/* impl<'a> FromLuaStack<'a> for () { - unsafe fn from_lua_stack(state: &'a State) -> Result { - Ok(()) - } -} */ - /// Implements PushToLuaStack for a number -macro_rules! impl_push_to_lua_stack_number { +macro_rules! impl_as_lua_number { ($ty: ident) => { - impl PushToLuaStack for $ty { - unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { - state.ensure_stack(1)?; - lua::lua_pushnumber(state.state_ptr(), *self as f64); - Ok(()) + impl<'a> AsLua<'a> for $ty { + fn as_lua(&self, _lua: &'a State) -> crate::Result> { + Ok(Value::Number(*self as f64)) } } + + impl FromLua for $ty { + fn from_lua(_lua: &State, val: Value) -> crate::Result { + match val { + Value::Number(v) => Ok(v as $ty), + _ => { + Err(Error::UnexpectedType("Number".to_string(), val.type_name().to_string())) + }, + } + } + } + + impl<'a> PushToLuaStack<'a> for $ty { + unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> { + let v = self.as_lua(state)?; + v.push_to_lua_stack(state) + } + } + }; } -impl<'a> FromLuaStack<'a> for i32 { - unsafe fn from_lua_stack(state: &'a State) -> Result { - let s = state.state_ptr(); +impl_as_lua_number!(i8); +impl_as_lua_number!(i16); +impl_as_lua_number!(i32); +impl_as_lua_number!(i64); - if lua::lua_isnumber(s, -1) == 1 { - let v = lua::lua_tonumber(s, -1) as i32; - lua::lua_pop(s, 1); - Ok(v) - } else { - let lua_ty = lua_type(s, -1); - let typec = CStr::from_ptr(lua_typename(s, lua_ty)); - let type_str = typec.to_str() - .expect("Type has invalid bytes!"); +impl_as_lua_number!(u8); +impl_as_lua_number!(u16); +impl_as_lua_number!(u32); +impl_as_lua_number!(u64); - Err(Error::unexpected_type("Number", type_str)) - } - } -} +impl_as_lua_number!(f32); +impl_as_lua_number!(f64); -impl_push_to_lua_stack_number!(i8); -impl_push_to_lua_stack_number!(i16); -impl_push_to_lua_stack_number!(i32); -impl_push_to_lua_stack_number!(i64); - -impl_push_to_lua_stack_number!(u8); -impl_push_to_lua_stack_number!(u16); -impl_push_to_lua_stack_number!(u32); -impl_push_to_lua_stack_number!(u64); - -impl_push_to_lua_stack_number!(f32); -impl_push_to_lua_stack_number!(f64); - -impl PushToLuaStack for String { +impl<'a> PushToLuaStack<'a> for String { unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { state.ensure_stack(1)?; @@ -314,7 +253,19 @@ impl PushToLuaStack for String { } } -impl PushToLuaStack for &str { +impl<'a> FromLuaStack<'a> for String { + unsafe fn from_lua_stack(state: &'a State) -> Result { + let s = state.state_ptr(); + + let cstr = lua::lua_tostring(s, -1); + lua::lua_pop(s, 1); + + let cstr = CStr::from_ptr(cstr); + Ok(cstr.to_str().unwrap().to_string()) + } +} + +impl<'a> PushToLuaStack<'a> for &str { unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { state.ensure_stack(1)?; @@ -322,6 +273,54 @@ impl PushToLuaStack for &str { let cstr = s.as_ptr() as *const i8; lua::lua_pushstring(state.state_ptr(), cstr); + Ok(()) + } +} + +pub struct Vec2 { + x: f32, + y: f32, +} + +impl Userdata for Vec2 { + fn build<'a>(builder: &mut UserdataBuilder<'a, Vec2>) -> crate::Result<()> { + builder.name("Vec2") + .field_getter("x", |_, this| Ok(this.x)) + .field_getter("y", |_, this| Ok(this.y)); + + Ok(()) + } +} + +pub struct UserdataProxy(PhantomData); + +impl UserdataProxy { + pub fn new() -> Self { + Self(PhantomData) + } +} + +impl<'a, T: Userdata> AsLua<'a> for UserdataProxy { + fn as_lua(&self, lua: &'a State) -> crate::Result> { + todo!() + } +} + +impl Userdata for UserdataProxy { + fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> { + + let mut other = UserdataBuilder::::new(); + T::build(&mut other)?; + + builder.name(&other.name.unwrap()); + + for (lbl, getters) in other.field_getters.into_iter() { + let wrap = |state: &State, data: &Self| { + Ok(()) + }; + builder.field_getter(&lbl, wrap); + } + Ok(()) } } \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index 08c1562..918dd59 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,15 @@ use core::ffi; -use std::{ffi::{CString, CStr}, mem, ptr::{self, NonNull}}; +use std::{ffi::{CString, CStr}, mem, ptr::{self, NonNull}, str::Utf8Error}; use mlua_sys as lua; -use crate::{lua_error_guard, Error, FromLuaStack, FromLuaStackMulti, Function, LuaRef, PushToLuaStack, PushToLuaStackMulti, Result, Table}; +use crate::{ensure_type, lua_error_guard, AnyUserdata, Error, FromLuaStack, FromLuaStackMulti, Function, LuaRef, PushToLuaStack, PushToLuaStackMulti, Result, StackGuard, Table, Userdata, UserdataBuilder, Value}; + +pub fn ptr_to_string(ptr: *const i8) -> std::result::Result { + let c = unsafe { CStr::from_ptr(ptr) }; + let s= c.to_str()?; + Ok(s.to_string()) +} #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StdLibrary { @@ -167,7 +173,7 @@ impl State { unsafe { let s = self.state_ptr(); lua::lua_rawgeti(s, lua::LUA_REGISTRYINDEX, lua::LUA_RIDX_GLOBALS); - Table::with_ref(self, LuaRef::from_stack(self)?, None) + Table::with_ref(self, LuaRef::from_stack(self)?) } } @@ -180,11 +186,11 @@ impl State { let tyname = lua::lua_typename(s, ty); let tyname = CStr::from_ptr(tyname); let tyname = tyname.to_str().unwrap(); - println!("{}: {}", i, tyname); + println!("{}: {}", -(t + 1 - i), tyname); } } - pub fn create_function<'a, A, R, F>(&'a self, f: F) -> Result> + pub fn create_function<'a, A, R, F>(&self, f: F) -> Result where A: FromLuaStackMulti<'a>, R: PushToLuaStackMulti<'a>, @@ -268,4 +274,63 @@ impl State { } } + /// Create userdata + pub fn create_userdata(&self, name: &str, data: T) -> Result { + unsafe { + self.ensure_stack(2)?; + let _g = StackGuard::new(self); + let s = self.state_ptr(); + + let ptr = lua::lua_newuserdata(s, mem::size_of::()).cast::(); + ptr::write(ptr, data); + + let name_cstr = format!("{}\0", name); + let name_cstr = name_cstr.as_str(); + let name_cstr = name_cstr.as_ptr() as *const i8; + + // attempt to get the metatable + lua::lua_pushstring(s, name_cstr); + let ty = lua::lua_rawget(s, lua::LUA_REGISTRYINDEX); + + if ty == lua::LUA_TNIL { + lua::lua_pop(s, 1); // remove nil + + // if the metatable is not made yet, make it + let mt = self.create_userdata_metatable::()?; + let mt = mt.get_metatable().unwrap(); // the table 100% has a metatable + mt.push_to_lua_stack(self)?; + } else if ty != lua::LUA_TTABLE { + return Err(Error::RegistryConflict(name.to_string())); + } + + lua::lua_setmetatable(s, -2); + + AnyUserdata::from_lua_stack(self) + } + } + + pub(crate) fn create_userdata_metatable<'a, T: Userdata + 'static>(&'a self) -> Result> { + let mut builder = UserdataBuilder::::new(); + T::build(&mut builder)?; + + let getters = builder.field_getters; + let name = builder.name.unwrap(); + + let mt = self.create_meta_table(&name)?; + + if !getters.is_empty() { + let index_fn = self.create_function(move |lua: &State, (ud, key): (AnyUserdata, String)| { + if let Some(getter) = getters.get(&key) { + let r = getter(lua, Value::Userdata(ud))?; + Ok(r) + } else { + Ok(Value::Nil) + } + })?; + + mt.set_meta("__index", index_fn)?; + } + + Ok(mt) + } } \ No newline at end of file diff --git a/src/table.rs b/src/table.rs index e050537..3c1530f 100644 --- a/src/table.rs +++ b/src/table.rs @@ -3,7 +3,7 @@ use std::{ffi::CStr, ops::Deref, sync::Arc}; use mlua_sys as lua; -use crate::{FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State}; +use crate::{ensure_type, FromLuaStack, LuaRef, PushToLuaStack, Result, StackGuard, State}; pub(crate) struct MetaTableInfo { name: Option, @@ -13,8 +13,8 @@ pub(crate) struct MetaTableInfo { #[derive(Clone)] pub struct Table<'a> { state: &'a State, - lref: LuaRef, - meta: Option, // Some if this table is a metatable + pub(crate) lref: LuaRef, + pub(crate) meta: Option, // Some if this table is a metatable } impl<'a> Table<'a> { @@ -38,12 +38,17 @@ impl<'a> Table<'a> { pub fn new_meta_table(state: &'a State, name: &str) -> Result { let name_term = format!("{}\0", name); let name_term_c = name_term.as_str().as_ptr() as *const i8; - let name_term_arc = Arc::new(name_term_c); let (lref, meta_ref) = unsafe { let _g = StackGuard::new(state); state.ensure_stack(2)?; + // create the table, this will be retrieved from + // stack after the metatable is created + let s = state.state_ptr(); + lua::lua_newtable(s); + //let table_idx = lua::lua_gettop(s); + let s = state.state_ptr(); if lua::luaL_newmetatable(s, name_term_c) == 0 { // lua::luaL_getmetatable does not return the type that was @@ -55,11 +60,11 @@ impl<'a> Table<'a> { return Err(crate::Error::RegistryConflict(name.to_string())); } } + lua::lua_pushvalue(s, lua::lua_gettop(s)); + lua::lua_setmetatable(s, -3); // -3 here since the metatable was added twice let meta = LuaRef::from_stack(state)?; - - let s = state.state_ptr(); - lua::lua_newtable(s); + // retrieve the table created before the metatable (LuaRef::from_stack(state)?, meta) }; @@ -71,7 +76,7 @@ impl<'a> Table<'a> { } /// Construct a table with a lua reference to one. - pub fn with_ref(state: &'a State, lua_ref: LuaRef, name: Option) -> Result { + pub fn with_ref(state: &'a State, lua_ref: LuaRef) -> Result { let s = state.state_ptr(); unsafe { @@ -79,7 +84,7 @@ impl<'a> Table<'a> { lua_ref.push_to_lua_stack(state)?; if lua::lua_istable(s, -1) == 0 { - panic!("Index is not a table") + panic!("Provided reference is not a table") } } @@ -95,8 +100,8 @@ impl<'a> Table<'a> { /// This may trigger the `__newindex` metamethod, see [`Table::raw_set`] pub fn set(&self, key: K, val: V) -> Result<()> where - K: PushToLuaStack, - V: PushToLuaStack + K: PushToLuaStack<'a>, + V: PushToLuaStack<'a> { let s = self.state.state_ptr(); unsafe { @@ -126,9 +131,9 @@ impl<'a> Table<'a> { /// Get a value from the table. /// /// This may trigger the `__index` metamethod, see [`Table::raw_get`] - pub fn get(&'a self, key: K) -> Result + pub fn get(&self, key: K) -> Result where - K: PushToLuaStack, + K: PushToLuaStack<'a>, V: FromLuaStack<'a>, { let s = self.state.state_ptr(); @@ -167,8 +172,8 @@ impl<'a> Table<'a> { /// Set a key to a value in the table without calling any meta methods. pub fn raw_set(&self, key: K, val: V) -> Result<()> where - K: PushToLuaStack, - V: PushToLuaStack + K: PushToLuaStack<'a>, + V: PushToLuaStack<'a> { let s = self.state.state_ptr(); unsafe { @@ -189,7 +194,7 @@ impl<'a> Table<'a> { /// Get a value from the table without calling any meta methods. pub fn raw_get(&'a self, key: K) -> Result where - K: PushToLuaStack, + K: PushToLuaStack<'a>, V: FromLuaStack<'a>, { let s = self.state.state_ptr(); @@ -219,10 +224,14 @@ impl<'a> Table<'a> { } } + /// Set something in the metatable + /// + /// # Panics + /// Panics if this table is not a metatable pub fn set_meta(&self, key: K, val: V) -> Result<()> where - K: PushToLuaStack, - V: PushToLuaStack + K: PushToLuaStack<'a>, + V: PushToLuaStack<'a> { let mt = self.meta.as_ref() .expect("this table is not a meta table!"); @@ -232,7 +241,6 @@ impl<'a> Table<'a> { self.state.ensure_stack(3)?; let _g = StackGuard::new(self.state); - //lua::luaL_getmetatable(s, **cname); mt.push_to_lua_stack(self.state)?; key.push_to_lua_stack(self.state)?; val.push_to_lua_stack(self.state)?; @@ -241,9 +249,17 @@ impl<'a> Table<'a> { Ok(()) } + + /// Returns a handle to the metatable of this table + pub fn get_metatable(&self) -> Option
{ + self.meta.clone() + .map(|r| + Table::with_ref(self.state, r).unwrap() + ) + } } -impl<'a> PushToLuaStack for Table<'a> { +impl<'a> PushToLuaStack<'a> for Table<'a> { unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> { // no need to ensure stack, the LuaRef does it for us self.lref.push_to_lua_stack(state)?; @@ -254,19 +270,7 @@ impl<'a> PushToLuaStack for Table<'a> { impl<'a> FromLuaStack<'a> for Table<'a> { unsafe fn from_lua_stack(state: &'a State) -> Result { - let s = state.state_ptr(); - - let ty = lua::lua_type(s, -1); - if ty == lua::LUA_TTABLE { - let t = Table::with_ref(state, LuaRef::from_stack(state)?, None); - - t - } else { - let tyname = lua::lua_typename(s, ty); - let cstr = CStr::from_ptr(tyname); - let s = cstr.to_str() - .expect("Lua type has invalid bytes!"); - Err(crate::Error::UnexpectedType("Table".to_string(), s.to_string())) - } + ensure_type(state, lua::LUA_TTABLE, -1)?; + Table::with_ref(state, LuaRef::from_stack(state)?) } } \ No newline at end of file diff --git a/src/userdata.rs b/src/userdata.rs new file mode 100644 index 0000000..30d18db --- /dev/null +++ b/src/userdata.rs @@ -0,0 +1,164 @@ +use std::{collections::HashMap, marker::PhantomData, sync::Arc}; + +use crate::{ensure_type, AsLua, FromLua, FromLuaStack, LuaRef, PushToLuaStack, State, Table, Value}; + +use mlua_sys as lua; + +//pub type FieldSetter = fn(lua: &State, this: &T); +//pub type FieldGetter = fn(lua: &State, this: &T, val: &U); + +pub trait FieldSetter { + fn set_field(&self, val: Value); +} + +pub trait FieldGetter { + fn get_field(&self) -> Value; +} + +type UserdataFn<'a> = Box) -> crate::Result>>; + +pub struct UserdataBuilder<'a, T> { + pub(crate) name: Option, + pub(crate) field_getters: HashMap>, + _marker: PhantomData, +} + +impl<'a, T> UserdataBuilder<'a, T> { + pub fn new() -> Self { + Self { + name: None, + field_getters: HashMap::new(), + _marker: PhantomData, + } + } + + pub fn name(&mut self, name: &str) -> &mut Self { + self.name = Some(name.to_string()); + self + } + + pub fn field_getter(&mut self, name: &str, f: F) -> &mut Self + where + F: Fn(&'a State, &T) -> crate::Result + 'static, + R: AsLua<'a>, + { + let wrap = move |lua: &'a State, val: Value<'a>| { + let this = val.as_userdata().unwrap(); // if this panics, its a bug + let this = this.as_ref::()?; + f(lua, this).and_then(|r| r.as_lua(lua)) + }; + self.field_getters.insert(name.to_string(), Box::new(wrap)); + + self + } + + pub fn into_meta_table(self, state: &'a State, table: &Table<'a>) -> crate::Result<()> { + //let this: Self = unsafe { std::mem::transmute(self) }; + //let getters = *self.field_getters; + + /* let index_fn = state.create_function(move |lua: &'a State, (ud, key): (AnyUserdata<'a>, String)| { + let ud_ref = ud.as_ref::()?; + + if let Some(getter) = self.field_getters.get(&key) { + let r = getter(lua, ome(ud_ref), Value::Nil)?; + + let r = unsafe { std::mem::transmute(r) }; + + Ok(r) + } else { + //ud.get::<_, Value<'a>>(key) + Ok(Value::Nil) + } + })?; + + table.set("__index", index_fn)?; */ + + Ok(()) + } + + /* pub fn wrap_builder(this: &mut UserdataBuilder<'a, T>, other: UserdataBuilder<'a, U>, to_proxied: F) + where + F: Fn(&'a T) -> &'a U + 'a, + { + let to_proxied = Arc::new(to_proxied); + for (lbl, getter) in other.field_getters.into_iter() { + let to_proxied_cl = to_proxied.clone(); + let new_getter = move |lua: &'a State, this: Option<&T>, val: Value<'a>| { + let proxy = this.map(|t| to_proxied_cl(t)); + getter(lua, proxy, val) + }; + this.field_getters.insert(lbl, Box::new(new_getter)); + } + } */ + + /* pub fn into_userdata(self, state: &'a State, data: T) -> crate::Result> { + let name = self.name + .expect("No name was set for userdata!"); + + let getters = self.field_getters.clone(); + let index_fn = state.create_function(move |lua: &'a State, (ud, key): (AnyUserdata<'a>, String)| { + let ud_ref = ud.as_ref::()?; + + if let Some(getter) = getters.get(&key) { + getter(lua, Some(ud_ref), Value::Nil) + } else { + //ud.get::<_, Value<'a>>(key) + Ok(Value::Nil) + } + + //todo!() + })?; + + let mt = state.create_meta_table(&name)?; + mt.set("__index", index_fn)?; + + Ok(mt) + } */ +} + +pub trait Userdata: Sized { + fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()>; +} + +/// A handle to some userdata on the stack +#[derive(Clone)] +pub struct AnyUserdata<'a> { + lref: LuaRef, + state: &'a State, +} + +impl<'a> AnyUserdata<'a> { + pub fn from_ref(state: &'a State, lref: LuaRef) -> Self { + Self { + lref, + state, + } + } + + pub fn as_ref(&self) -> crate::Result<&'a T> { + unsafe { + let s = self.state.state_ptr(); + + self.lref.push_to_lua_stack(self.state)?; + let cptr = lua::lua_touserdata(s, -1); + Ok(cptr.cast::().as_ref().unwrap()) + } + } +} + +impl<'a> FromLuaStack<'a> for AnyUserdata<'a> { + unsafe fn from_lua_stack(state: &'a State) -> crate::Result { + ensure_type(state, lua::LUA_TUSERDATA, -1)?; + + Ok(AnyUserdata { + lref: LuaRef::from_stack(state)?, + state, + }) + } +} + +impl<'a> PushToLuaStack<'a> for AnyUserdata<'a> { + unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> { + self.lref.push_to_lua_stack(state) + } +} \ No newline at end of file diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..b7cbbe6 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,29 @@ +use std::ffi::CStr; + +use crate::State; + +use mlua_sys as lua; + +pub unsafe fn ensure_type(state: &State, typ: i32, idx: i32) -> crate::Result<()> { + let s = state.state_ptr(); + + let lua_type = lua::lua_type(s, idx); + + if lua_type == typ { + Ok(()) + } else { + let exp_tyname = lua::lua_typename(s, typ); + let exp_cstr = CStr::from_ptr(exp_tyname); + let exp_s = exp_cstr.to_str() + .expect("Lua type has invalid bytes!"); + + let tyname = lua::lua_typename(s, lua_type); + let cstr = CStr::from_ptr(tyname); + let s = cstr.to_str() + .expect("Lua type has invalid bytes!"); + + println!("ty = {}, typ = {}", lua_type, typ); + + Err(crate::Error::UnexpectedType(exp_s.to_string(), s.to_string())) + } +} \ No newline at end of file diff --git a/src/value.rs b/src/value.rs index c6e2905..940dc20 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,19 +1,54 @@ -use crate::{Function, Table, PushToLuaStack}; +use std::ffi::CStr; + +use crate::{AnyUserdata, FromLuaStack, Function, PushToLuaStack, State, Table}; use mlua_sys as lua; +#[derive(Clone)] pub enum Value<'a> { + Nil, Number(f64), String(String), Function(Function<'a>), Table(Table<'a>), + Userdata(AnyUserdata<'a>), } -impl<'a> PushToLuaStack for Value<'a> { +impl<'a> Value<'a> { + pub fn is_nil(&self) -> bool { + matches!(self, Value::Nil) + } + + pub fn type_name(&self) -> &'static str { + match self { + Value::Nil => "Nil", + Value::Number(_) => "Number", + Value::String(_) => "String", + Value::Function(_) => "Function", + Value::Table(_) => "Table", + Value::Userdata(_) => todo!(), + } + } + + pub fn as_userdata(&self) -> crate::Result<&AnyUserdata> { + match self { + Value::Userdata(ud) => Ok(ud), + _ => { + Err(crate::Error::UnexpectedType("Userdata".to_string(), self.type_name().to_string())) + } + } + } +} + +impl<'a> PushToLuaStack<'a> for Value<'a> { unsafe fn push_to_lua_stack(&self, state: &crate::State) -> crate::Result<()> { let s = state.state_ptr(); match self { + Value::Nil => { + state.ensure_stack(1)?; + lua::lua_pushnil(s); + } Value::Number(n) => { state.ensure_stack(1)?; lua::lua_pushnumber(s, *n); @@ -25,14 +60,91 @@ impl<'a> PushToLuaStack for Value<'a> { let cstr = s.as_ptr() as *const i8; lua::lua_pushstring(state.state_ptr(), cstr); } - Value::Function(f) => { - f.push_to_lua_stack(state)?; - }, - Value::Table(t) => { - t.push_to_lua_stack(state)?; - } + Value::Function(f) => f.push_to_lua_stack(state)?, + Value::Table(t) => t.push_to_lua_stack(state)?, + Value::Userdata(ud) => ud.push_to_lua_stack(state)?, } Ok(()) } +} + +impl<'a> FromLuaStack<'a> for Value<'a> { + unsafe fn from_lua_stack(state: &'a State) -> crate::Result { + + let s = state.state_ptr(); + let ty = lua::lua_type(s, -1); + + let val = match ty { + lua::LUA_TNIL => { + lua::lua_pop(s, 1); + + Ok(Value::Nil) + }, + lua::LUA_TNUMBER => { + let n = lua::lua_tonumber(s, -1); + lua::lua_pop(s, 1); + + Ok(Value::Number(n)) + } + lua::LUA_TSTRING => { + let cstr = lua::lua_tostring(s, -1); + lua::lua_pop(s, 1); + + let cstr = CStr::from_ptr(cstr); + let lua_str = cstr.to_str().unwrap().to_string(); + Ok(Value::String(lua_str)) + }, + lua::LUA_TFUNCTION => { + Function::from_lua_stack(state) + .map(|f| Value::Function(f)) + }, + lua::LUA_TTABLE => { + Table::from_lua_stack(state) + .map(|t| Value::Table(t)) + }, + _ => { + let s = lua::lua_typename(s, ty); + let s = CStr::from_ptr(s); + let s = s.to_str().unwrap(); + unimplemented!("Not yet able to get '{}' as a value from the stack", s); + } + }; + + val + } +} + +pub trait AsLua<'a> { + fn as_lua(&self, lua: &'a State) -> crate::Result>; +} + +pub trait FromLua: Sized { + fn from_lua(lua: &State, val: Value) -> crate::Result; +} + +/* impl<'a> AsLua<'a> for Value<'a> { + fn as_lua(&self, lua: &'a State) -> crate::Result> { + Ok(self.clone()) + } +} */ + +/* impl<'a, T: AsLua<'a>> PushToLuaStack<'a> for T { + unsafe fn push_to_lua_stack(&self, state: &'a State) -> crate::Result<()> { + let v = self.as_lua(state)?; + v.push_to_lua_stack(state) + } +} + */ +impl<'a, T: FromLua> FromLuaStack<'a> for T { + unsafe fn from_lua_stack(state: &'a State) -> crate::Result { + let v = Value::from_lua_stack(state)?; + T::from_lua(state, v) + } +} + +impl<'a> AsLua<'a> for () { + fn as_lua(&self, lua: &'a State) -> crate::Result> { + Ok(Value::Nil) + } } \ No newline at end of file diff --git a/util.lua b/util.lua new file mode 100755 index 0000000..585852e --- /dev/null +++ b/util.lua @@ -0,0 +1,50 @@ +--- +---Print a formatted string. Put `{}` where you want something to be formatted in its place. +--- +---```lua +---local yummy_food = "chips" +---printf("Lets eat {} together!", yummy_food) +---``` +--- +---The items provided in the variable arguments must implement `tostring`! +--- +---@param str string +---@param ... any +function printf(str, ...) + local fleft, fright = str:find("{}") + local formatted = "" + -- current vararg index + local arg = 1 + + while (fleft ~= nil and fright ~= nil) do + formatted = formatted .. str:sub(0, fleft - 1) + formatted = formatted .. tostring(select(arg, ...)) + + str = str:sub(fright + 1, str:len()) + + fleft, fright = str:find("{}") + arg = arg + 1 + end + + formatted = formatted .. str + print(formatted) +end + +--- +---Recursively dumps a table as a string. +--- +---@param obj table +---@return string +---@nodiscard +function dump_table(obj) + if type(obj) == 'table' then + local s = '{ ' + for k,v in pairs(obj) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. dump_table(v) .. ',' + end + return s .. '} ' + else + return tostring(obj) + end +end \ No newline at end of file