Groundwork for userdata
Still need a way to add methods and functions
This commit is contained in:
parent
71199bc905
commit
2b03b55014
|
@ -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<crate::Value<'a>> {
|
||||
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<Self> {
|
||||
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 {
|
||||
|
|
229
src/main.rs
229
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,29 +18,19 @@ use value::*;
|
|||
pub mod guard;
|
||||
use guard::*;
|
||||
|
||||
/* struct RustFn {
|
||||
|
||||
} */
|
||||
|
||||
/* struct RustFnUpvalue {
|
||||
|
||||
} */
|
||||
|
||||
///
|
||||
pub fn ptr_to_string(ptr: *const i8) -> std::result::Result<String, Utf8Error> {
|
||||
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<i32> {
|
||||
let a = |_lua: &State, (num,): (i32,)| -> Result<i32> {
|
||||
println!("Rust got number from lua: {}", num);
|
||||
Ok(999)
|
||||
};
|
||||
|
@ -48,35 +38,8 @@ fn main() -> Result<()> {
|
|||
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<Table> {
|
||||
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<Self> {
|
||||
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,89 @@ 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<T> = core::result::Result<T, Error>;
|
||||
|
||||
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<Self>;
|
||||
}
|
||||
|
||||
/* impl<'a> FromLuaStack<'a> for () {
|
||||
unsafe fn from_lua_stack(state: &'a State) -> Result<Self> {
|
||||
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<Value<'a>> {
|
||||
Ok(Value::Number(*self as f64))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for $ty {
|
||||
fn from_lua(_lua: &State, val: Value) -> crate::Result<Self> {
|
||||
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 {
|
||||
impl_as_lua_number!(i8);
|
||||
impl_as_lua_number!(i16);
|
||||
impl_as_lua_number!(i32);
|
||||
impl_as_lua_number!(i64);
|
||||
|
||||
impl_as_lua_number!(u8);
|
||||
impl_as_lua_number!(u16);
|
||||
impl_as_lua_number!(u32);
|
||||
impl_as_lua_number!(u64);
|
||||
|
||||
impl_as_lua_number!(f32);
|
||||
impl_as_lua_number!(f64);
|
||||
|
||||
impl<'a> PushToLuaStack<'a> for String {
|
||||
unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> {
|
||||
state.ensure_stack(1)?;
|
||||
|
||||
let s = format!("{}\0", self);
|
||||
let cstr = s.as_ptr() as *const i8;
|
||||
lua::lua_pushstring(state.state_ptr(), cstr);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromLuaStack<'a> for String {
|
||||
unsafe fn from_lua_stack(state: &'a State) -> Result<Self> {
|
||||
let s = state.state_ptr();
|
||||
|
||||
if lua::lua_isnumber(s, -1) == 1 {
|
||||
let v = lua::lua_tonumber(s, -1) as i32;
|
||||
let cstr = lua::lua_tostring(s, -1);
|
||||
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!");
|
||||
|
||||
Err(Error::unexpected_type("Number", type_str))
|
||||
}
|
||||
let cstr = CStr::from_ptr(cstr);
|
||||
Ok(cstr.to_str().unwrap().to_string())
|
||||
}
|
||||
}
|
||||
|
||||
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 &str {
|
||||
unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> {
|
||||
state.ensure_stack(1)?;
|
||||
|
||||
|
@ -314,13 +277,49 @@ impl PushToLuaStack for String {
|
|||
}
|
||||
}
|
||||
|
||||
impl PushToLuaStack for &str {
|
||||
unsafe fn push_to_lua_stack(&self, state: &State) -> Result<()> {
|
||||
state.ensure_stack(1)?;
|
||||
pub struct Vec2 {
|
||||
x: f32,
|
||||
y: f32,
|
||||
}
|
||||
|
||||
let s = format!("{}\0", self);
|
||||
let cstr = s.as_ptr() as *const i8;
|
||||
lua::lua_pushstring(state.state_ptr(), cstr);
|
||||
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<T: Userdata>(PhantomData<T>);
|
||||
|
||||
impl<T: Userdata> UserdataProxy<T> {
|
||||
pub fn new() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Userdata> AsLua<'a> for UserdataProxy<T> {
|
||||
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Userdata> Userdata for UserdataProxy<T> {
|
||||
fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> {
|
||||
|
||||
let mut other = UserdataBuilder::<T>::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(())
|
||||
}
|
||||
|
|
75
src/state.rs
75
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<String, Utf8Error> {
|
||||
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<Function<'a>>
|
||||
pub fn create_function<'a, A, R, F>(&self, f: F) -> Result<Function>
|
||||
where
|
||||
A: FromLuaStackMulti<'a>,
|
||||
R: PushToLuaStackMulti<'a>,
|
||||
|
@ -268,4 +274,63 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create userdata
|
||||
pub fn create_userdata<T: Userdata + 'static>(&self, name: &str, data: T) -> Result<AnyUserdata> {
|
||||
unsafe {
|
||||
self.ensure_stack(2)?;
|
||||
let _g = StackGuard::new(self);
|
||||
let s = self.state_ptr();
|
||||
|
||||
let ptr = lua::lua_newuserdata(s, mem::size_of::<T>()).cast::<T>();
|
||||
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::<T>()?;
|
||||
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<Table<'a>> {
|
||||
let mut builder = UserdataBuilder::<T>::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)
|
||||
}
|
||||
}
|
72
src/table.rs
72
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<String>,
|
||||
|
@ -13,8 +13,8 @@ pub(crate) struct MetaTableInfo {
|
|||
#[derive(Clone)]
|
||||
pub struct Table<'a> {
|
||||
state: &'a State,
|
||||
lref: LuaRef,
|
||||
meta: Option<LuaRef>, // Some if this table is a metatable
|
||||
pub(crate) lref: LuaRef,
|
||||
pub(crate) meta: Option<LuaRef>, // 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<Self> {
|
||||
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<String>) -> Result<Self> {
|
||||
pub fn with_ref(state: &'a State, lua_ref: LuaRef) -> Result<Self> {
|
||||
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<K, V>(&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<K, V>(&'a self, key: K) -> Result<V>
|
||||
pub fn get<K, V>(&self, key: K) -> Result<V>
|
||||
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<K, V>(&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<K, V>(&'a self, key: K) -> Result<V>
|
||||
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<K, V>(&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<Table> {
|
||||
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<Self> {
|
||||
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)?)
|
||||
}
|
||||
}
|
|
@ -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<T> = fn(lua: &State, this: &T);
|
||||
//pub type FieldGetter<T, U> = 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<dyn Fn(&'a State, Value<'a>) -> crate::Result<Value<'a>>>;
|
||||
|
||||
pub struct UserdataBuilder<'a, T> {
|
||||
pub(crate) name: Option<String>,
|
||||
pub(crate) field_getters: HashMap<String, UserdataFn<'a>>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
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<F, R>(&mut self, name: &str, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&'a State, &T) -> crate::Result<R> + '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::<T>()?;
|
||||
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::<T>()?;
|
||||
|
||||
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<U, F>(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<Table<'a>> {
|
||||
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::<T>()?;
|
||||
|
||||
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<T>(&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::<T>().as_ref().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromLuaStack<'a> for AnyUserdata<'a> {
|
||||
unsafe fn from_lua_stack(state: &'a State) -> crate::Result<Self> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
128
src/value.rs
128
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<Self> {
|
||||
|
||||
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<Value<'a>>;
|
||||
}
|
||||
|
||||
pub trait FromLua: Sized {
|
||||
fn from_lua(lua: &State, val: Value) -> crate::Result<Self>;
|
||||
}
|
||||
|
||||
/* impl<'a> AsLua<'a> for Value<'a> {
|
||||
fn as_lua(&self, lua: &'a State) -> crate::Result<Value<'a>> {
|
||||
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<Self> {
|
||||
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<Value<'a>> {
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue