From 4c1a1588c575ca666451c88b7b19453a3d0fd82f Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 28 Feb 2024 18:39:15 -0500 Subject: [PATCH] Add optional support for teal --- Cargo.toml | 4 + elua-derive/Cargo.toml | 18 ++ elua-derive/src/lib.rs | 114 ++++++++++ elua-derive/tests/from_github.rs | 6 + src/lib.rs | 5 + src/state.rs | 2 +- src/teal/fn_desc.rs | 69 ++++++ src/teal/mod.rs | 197 +++++++++++++++++ src/teal/record.rs | 60 ++++++ src/teal/userdata.rs | 360 +++++++++++++++++++++++++++++++ src/teal/walker.rs | 93 ++++++++ src/tests.rs | 4 +- src/userdata/borrow.rs | 6 +- src/userdata/borrow_mut.rs | 6 +- src/userdata/mod.rs | 2 +- src/userdata/proxy.rs | 6 +- 16 files changed, 935 insertions(+), 17 deletions(-) mode change 100755 => 100644 Cargo.toml create mode 100644 elua-derive/Cargo.toml create mode 100644 elua-derive/src/lib.rs create mode 100644 elua-derive/tests/from_github.rs create mode 100644 src/teal/fn_desc.rs create mode 100644 src/teal/mod.rs create mode 100644 src/teal/record.rs create mode 100644 src/teal/userdata.rs create mode 100644 src/teal/walker.rs diff --git a/Cargo.toml b/Cargo.toml old mode 100755 new mode 100644 index 3ab5a8c..fdf966f --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,7 @@ edition = "2021" [dependencies] mlua-sys = { version = "0.5.0", features = ["lua54"] } thiserror = "1.0.56" +elua-derive = { path = "./elua-derive", optional = true} + +[features] +teal = ["elua-derive"] diff --git a/elua-derive/Cargo.toml b/elua-derive/Cargo.toml new file mode 100644 index 0000000..5923407 --- /dev/null +++ b/elua-derive/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "elua-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytes = "1.5.0" +proc-macro2 = "1.0.78" +quote = "1.0.35" +reqwest = { version = "0.11.24", features = ["blocking"] } +syn = "2.0.51" +tempdir = "0.3.7" +zip = "0.6.6" diff --git a/elua-derive/src/lib.rs b/elua-derive/src/lib.rs new file mode 100644 index 0000000..33be297 --- /dev/null +++ b/elua-derive/src/lib.rs @@ -0,0 +1,114 @@ +use std::{fs::{self, File}, io::Read}; + +use proc_macro::TokenStream; +use quote::quote; + +use syn::{parenthesized, parse_macro_input, token, Ident, LitStr}; +use tempdir::TempDir; + +const DEFAULT_TEAL_VERSION: &str = "0.15.3"; + +enum CompilerSource { + Github, + Path, +} + +impl syn::parse::Parse for CompilerSource { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident: Ident = input.parse()?; + + let ident_str = ident.to_string().to_lowercase(); + let ident_str = ident_str.as_str(); + + match ident_str { + "github" => Ok(CompilerSource::Github), + "path" => Ok(CompilerSource::Path), + _ => Err(syn::Error::new_spanned(ident, "unknown teal download source")) + } + } +} + +struct EmbedInput { + source: CompilerSource, + path: String, +} + +impl syn::parse::Parse for EmbedInput { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let src: CompilerSource = input.parse()?; + + let path = if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + let path: LitStr = content.parse()?; + path.value() + } else { + DEFAULT_TEAL_VERSION.to_string() + }; + + Ok(EmbedInput { + source: src, + path, + }) + } +} + +#[proc_macro] +pub fn embed_compiler(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as EmbedInput); + + let tl_lua = match input.source { + CompilerSource::Github => { + let path = if input.path.starts_with("v") { + input.path[1..].to_string() + } else { input.path }; + + download_from_github(path) + }, + CompilerSource::Path => { + fs::read_to_string(input.path) + .expect("Failed to read tl.lua file from provided path") + }, + }; + + let tl_loader_str = format!( + "local tl = (function()\n{}\nend)()\ntl.loader()\n", + tl_lua + ); + let teal_compiler = quote! { + |require: &str| { + format!("{}\n return require '{}' ", #tl_loader_str, require) + } + }; + + teal_compiler.into() +} + +fn download_teal(url: String, inner_folder: String) -> String { + let resp = reqwest::blocking::get(url).expect("failed to download teal from url"); + + let bytes = resp.bytes().expect("failed to get bytes from request"); + + let dir = TempDir::new("tl").expect("failed to create tempdir"); + let dir = dir.path().join("tl.tar.gz"); + fs::write(&dir, bytes).expect("failed to write teal zip file to tempdir"); + + let file = File::open(dir).expect("Unable to open teal zip file!"); + let mut arch = zip::ZipArchive::new(&file) + .expect("failure to read teal zip file"); + + let mut teal_lua = arch.by_name(&format!("{}/tl.lua", inner_folder)) + .expect("failed to find tl.lua file inside zip archive"); + + let mut contents = Vec::with_capacity(10000); + teal_lua.read_to_end(&mut contents).expect("Failed to read zip file into buffer"); + + String::from_utf8(contents).expect("Failure to retrieve tl.lua as utf8 string!") +} + +fn download_from_github(version: String) -> String { + let url = format!("https://github.com/teal-language/tl/archive/v{}.zip", version); + let inner = format!("tl-{version}"); + + download_teal(url, inner) +} \ No newline at end of file diff --git a/elua-derive/tests/from_github.rs b/elua-derive/tests/from_github.rs new file mode 100644 index 0000000..e1d578e --- /dev/null +++ b/elua-derive/tests/from_github.rs @@ -0,0 +1,6 @@ +use elua_derive::embed_compiler; + +#[test] +fn from_github() { + let compiler = embed_compiler!(Github); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 0ddc591..dd428d2 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,11 @@ pub use table_iter::*; pub mod table_pairs; pub use table_pairs::*; +#[cfg(feature = "teal")] +pub mod teal; +#[cfg(feature = "teal")] +pub use teal::*; + #[cfg(test)] pub mod tests; diff --git a/src/state.rs b/src/state.rs index 4171a44..bb2a783 100755 --- a/src/state.rs +++ b/src/state.rs @@ -499,7 +499,7 @@ impl State { pub(crate) fn create_userdata_metatable<'a, T: Userdata + 'static>(&'a self) -> Result> { let mut builder = UserdataBuilder::::new(); - T::build(self, &mut builder)?; + T::build(&mut builder); let getters = builder.field_getters; let setters = builder.field_setters; diff --git a/src/teal/fn_desc.rs b/src/teal/fn_desc.rs new file mode 100644 index 0000000..7c7ca6b --- /dev/null +++ b/src/teal/fn_desc.rs @@ -0,0 +1,69 @@ +use crate::{GenerateTealDefinition, Type}; + +#[derive(Clone)] +pub struct FuncArg { + pub name: Option, + pub ty: Type +} + +/// A struct that stores type information about a function +#[derive(Clone)] +pub struct FunctionDesc { + /// The name of the function + pub name: String, + /// A boolean indicating if this is a part of userdata + pub is_field: bool, + /// The arguments of the function + pub args: Vec, + /// The return type of the function, `None` if it returns nothing + pub return_ty: Option, +} + +impl GenerateTealDefinition for FunctionDesc { + fn generate(&self, ctx: &mut crate::GenerateContext) { + let args: String = self.args.iter() + .map(|t| match &t.name { + Some(name) => { + // include the argument name if one is set + format!("{}: {}", name, t.ty.name) + }, + None => t.ty.name.clone() + }) + .collect::>() + .join(", "); + + let ret = match &self.return_ty { + Some(ty) => format!(": {}", ty.name), + None => String::new() + }; + + let content = format!("function({args}){ret}"); + if self.is_field { + ctx.write(&self.name) + .write(": ") + .write(&content) + .write_new_line(); + } else { + ctx.write(&content); + } + } +} + +/// Represents a Field on a Userdata. +#[derive(Clone)] +pub struct FieldDesc { + /// The name of the field + pub name: String, + /// The type of the field + pub ty: Type, + /// A boolean indicating if there is a setter for this field. + pub can_set: bool, +} + +impl GenerateTealDefinition for FieldDesc { + fn generate(&self, ctx: &mut crate::GenerateContext) { + ctx.write(&self.name) + .write(": ") + .writeln(&self.ty.name); + } +} \ No newline at end of file diff --git a/src/teal/mod.rs b/src/teal/mod.rs new file mode 100644 index 0000000..3d537da --- /dev/null +++ b/src/teal/mod.rs @@ -0,0 +1,197 @@ +pub mod record; +pub use record::*; + +pub mod fn_desc; +pub use fn_desc::*; + +pub mod walker; +pub use walker::*; + +pub mod userdata; +pub use userdata::*; + +use crate::{Table, Value, Function, AnyUserdata, ValueVec}; + +#[derive(Default)] +pub struct GenerateContext { + /// The indentation level of current context. + pub level: u32, + /// The current record that things are being generated for. + pub record: Option, + pub content: String, +} + +impl GenerateContext { + /// Writes `s` to the end of the current generating content. + /// + /// If this write is happening on a new line, it will be indented + pub fn write(&mut self, s: &str) -> &mut Self { + if self.content.ends_with("\n") { + self.write_indent(); + } + + self.content = format!("{}{}", self.content, s); + + self + } + + /// Writes `s` directly to the end of the current generating content. There is no check for + /// indentation + pub fn write_raw(&mut self, s: &str) -> &mut Self { + if self.content.ends_with("\n") { + self.write_indent(); + } + + self.content = format!("{}{}", self.content, s); + + + self + } + + /// Writes `s` followed by a new line directly to the end of the current generating content. + /// + /// If this write is happening on a new line, it will be indented + pub fn writeln(&mut self, s: &str) -> &mut Self { + if self.content.ends_with("\n") { + self.write_indent(); + } + + self.content = format!("{}{}\n", self.content, s); + + self + } + + /// Writes a new line to the generating content. + pub fn write_new_line(&mut self) -> &mut Self { + self.content = format!("{}\n", self.content); + + self + } + + /// Writes the indent to the generating content that is valid for this level. + pub fn write_indent(&mut self) -> &mut Self { + let level = self.level * 4; // 4 spaces per indentation + let indent = " ".repeat(level as _); + self.content = format!("{}{}", self.content, indent); + + self + } + + /// Writes `s` on its own line, will insert a new line if its *not already on its own line*. + pub fn write_on_ln(&mut self, s: &str) -> &mut Self { + if self.content.ends_with("\n") { + self.write(s); + } else { + self.write_new_line() + .write(s); + } + + self + } + + /// Increases the indentation level by 1. + pub fn inc_level(&mut self) -> &mut Self { + self.level += 1; + self + } + + /// Decreases the indentation level by 1. + pub fn dec_level(&mut self) -> &mut Self { + self.level -= 1; + self + } +} + +pub trait GenerateTealDefinition { + fn generate(&self, ctx: &mut GenerateContext); +} + +pub trait LuaTypeName { + fn get_lua_name() -> String; +} + +// implements LuaTypeName for types that have the exact same name as the Rust type. +macro_rules! impl_type_name_simple { + ($name: literal, $t: ident) => { + impl LuaTypeName for $t { + fn get_lua_name() -> String { + $name.to_string() + } + } + }; + ($name:literal, $t:ident <$( $generic: tt ),+>) => { + impl<$($generic: LuaTypeName,)+ > LuaTypeName for $t <$( $generic ),+ >{ + fn get_lua_name() -> String { + let generic_name = { + let v = vec![$( $generic::get_lua_name(), )+]; + v.join(", ") + }; + format!("{}<{}>", $name, generic_name) + } + } + }; + ($lua_name: literal, $name:tt, $($lts:lifetime),+) => { + impl<$($lts,)+ > LuaTypeName for $name <$( $lts ),+ >{ + fn get_lua_name() -> String { + $lua_name.to_string() + } + } + } +} + +impl_type_name_simple!("Boolean", bool); +impl_type_name_simple!("Number", f32); +impl_type_name_simple!("Number", f64); +impl_type_name_simple!("Number", u8); +impl_type_name_simple!("Number", u16); +impl_type_name_simple!("Number", u32); +impl_type_name_simple!("Number", u64); +impl_type_name_simple!("Number", usize); + +impl_type_name_simple!("Number", i8); +impl_type_name_simple!("Number", i16); +impl_type_name_simple!("Number", i32); +impl_type_name_simple!("Number", i64); +impl_type_name_simple!("Number", isize); + +impl_type_name_simple!("string", String); + +impl_type_name_simple!("T | nil", Option); + +impl_type_name_simple!("T", Table, 'a); +impl_type_name_simple!("any", Value, 'a); +impl_type_name_simple!("any", ValueVec, 'a); +impl_type_name_simple!("function(any)", Function, 'a); +impl_type_name_simple!("T", AnyUserdata, 'a); + +impl LuaTypeName for () { + fn get_lua_name() -> String { + "()".to_string() + } +} + +/// Tuple used to implement LuaTypeName for tuples that are used in function arguments +macro_rules! impl_type_name_tuples { + ( $count: expr, $first: tt, $( $name: tt ),+ ) => { + impl<$first: LuaTypeName, $($name: LuaTypeName,)+> LuaTypeName for ($first, $($name),+) { + fn get_lua_name() -> String { + let tuple_names = { + let v = vec![$first::get_lua_name(), $( $name::get_lua_name(), )+]; + v.join(", ") + }; + format!("({})", tuple_names) + } + } + + impl_type_name_tuples!(count - 1, $( $name ),+); + }; + ( $count: expr, $last: tt ) => { + impl<$last: LuaTypeName> LuaTypeName for ($last,) { + fn get_lua_name() -> String { + $last::get_lua_name() + } + } + }; +} + +impl_type_name_tuples! { 16, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16 } \ No newline at end of file diff --git a/src/teal/record.rs b/src/teal/record.rs new file mode 100644 index 0000000..a1796b4 --- /dev/null +++ b/src/teal/record.rs @@ -0,0 +1,60 @@ +use std::ops::{Deref, DerefMut}; + +use crate::{FieldDesc, FunctionDesc, GenerateTealDefinition, LuaTypeName}; + +pub struct Record { + pub(crate) ty: Type, + pub fields: Vec, + pub functions: Vec, +} + +impl Deref for Record { + type Target = Type; + + fn deref(&self) -> &Self::Target { + &self.ty + } +} + +impl DerefMut for Record { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.ty + } +} + +impl GenerateTealDefinition for Record { + fn generate(&self, ctx: &mut crate::GenerateContext) { + ctx.record = Some(self.ty.clone()); + ctx.write("record ") + .writeln(&self.ty.name) + .inc_level(); + + for field in self.fields.iter() { + field.generate(ctx); + } + + ctx.write_new_line(); + + for func in self.functions.iter() { + func.generate(ctx); + } + + ctx.dec_level() + .write_on_ln("end"); + } +} + +/// A struct that only stores only the type of a Record; so the name, and generics. +#[derive(Clone)] +pub struct Type { + /// The name of the type, including generics, i.e., `LuaBuffer` + pub name: String, +} + +impl Type { + pub fn new() -> Self { + Self { + name: T::get_lua_name(), + } + } +} \ No newline at end of file diff --git a/src/teal/userdata.rs b/src/teal/userdata.rs new file mode 100644 index 0000000..12781d4 --- /dev/null +++ b/src/teal/userdata.rs @@ -0,0 +1,360 @@ +use std::{collections::HashMap, mem, ops::{Deref, DerefMut}}; + +use crate::{AsLua, FieldDesc, FromLua, FromLuaVec, FuncArg, FunctionDesc, LuaTypeName, State, Type, Userdata, UserdataBuilder}; + +enum MutCow<'a, T> { + Borrowed(&'a mut T), + Owned(T), +} + +impl<'a, T> Deref for MutCow<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + MutCow::Borrowed(v) => v, + MutCow::Owned(v) => &v, + } + } +} + +impl<'a, T> DerefMut for MutCow<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + MutCow::Borrowed(v) =>v, + MutCow::Owned(v) => v, + } + } +} + +pub trait TealUserdata: Sized { + fn name() -> String; + + fn build<'a>(builder: &mut TealUserdataBuilder<'a, Self>); +} + +pub struct TealUserdataBuilder<'a, T> +where + // Userdata is implemented for all types that implement TealUserdata with the + // generic impl below. + T: Userdata +{ + inner: MutCow<'a, UserdataBuilder<'a, T>>, + pub(crate) fields: HashMap, + pub(crate) funcs: HashMap, + func_arg_names: Option>, +} + +impl<'a, T: Userdata> From<&'a mut UserdataBuilder<'a, T>> for TealUserdataBuilder<'a, T> { + fn from(value: &'a mut UserdataBuilder<'a, T>) -> Self { + Self { + inner: MutCow::Borrowed(value), + funcs: Default::default(), + fields: Default::default(), + func_arg_names: None, + } + } +} + +pub(crate) fn function_desc<'a, A, R>(func_arg_names: &mut Option>, fn_name: &str) -> FunctionDesc +where + A: FromLuaVec<'a> + LuaTypeName, + R: AsLua<'a> + LuaTypeName, +{ + let arg_ty = A::get_lua_name(); + let args: Vec = if A::value_num().unwrap_or(0) > 1 { + // if the Args are multiple types, the type name was created as a tuple. + // to get each type of them, the surrounding parenthesis are removed, + // and then the string is split by the commas. + let args_split = arg_ty[1..arg_ty.len()-1] + .split(","); + + if let Some(names) = func_arg_names.take() { + let args_num = A::value_num().unwrap_or(0); + debug_assert_eq!(names.len(), args_num, + "Invalid number of argument names was provided! (Expected {}, received {})", + args_num, names.len()); + + args_split.zip(names.into_iter()) + .map(|(ty, name)| FuncArg { + name: Some(name.to_string()), + ty: Type { + name: ty.trim().to_string() + } + }) + .collect() + } else { + args_split + .map(|s| FuncArg { name: None, ty: Type { name: s.trim().to_string() } }) + .collect() + } + } else { + if &arg_ty == "()" { + todo!("Dont insert any arg"); + } else { + if let Some(mut names) = func_arg_names.take() { + debug_assert_eq!(names.len(), 1, + "Invalid number of argument names was provided! (Expected {}, received {})", + 1, names.len()); + + vec![ + FuncArg { + name: names.pop(), + ty: Type { + name: arg_ty + } + } + ] + } else { + vec![ + FuncArg { + name: None, + ty: Type { + name: arg_ty + } + } + ] + } + } + }; + + let ret_name = R::get_lua_name(); + let ret_ty = match ret_name.as_str() { + "()" => None, + _ => Some(Type { name: ret_name }) + }; + + let func_desc = FunctionDesc { + name: fn_name.to_string(), + is_field: true, + args, + return_ty: ret_ty, + }; + + func_desc +} + +impl<'a, T: Userdata> TealUserdataBuilder<'a, T> { + pub(crate) fn new() -> Self { + Self { + inner: MutCow::Owned(UserdataBuilder::new()), + fields: Default::default(), + funcs: Default::default(), + func_arg_names: None, + } + } + + pub fn field_getter(&mut self, name: &str, f: F) -> &mut Self + where + F: Fn(&'a State, &T) -> crate::Result + 'static, + R: AsLua<'a> + LuaTypeName, + T: TealUserdata + 'static + { + let tname = R::get_lua_name(); + // maybe field_setter was ran first + if !self.fields.contains_key(&tname) { + let desc = FieldDesc { + name: name.to_string(), + ty: Type { + name: tname.clone(), + }, + can_set: false, + }; + + self.fields.insert(tname, desc); + } + + self.inner.field_getter(name, f); + self + } + + pub fn field_setter(&mut self, name: &str, f: F) -> &mut Self + where + F: Fn(&'a State, &mut T, V) -> crate::Result<()> + 'static, + V: FromLua<'a> + LuaTypeName, + T: TealUserdata + 'static + { + let tname = V::get_lua_name(); + if let Some(field) = self.fields.get_mut(&tname) { + field.can_set = true; + } else { + let desc = FieldDesc { + name: name.to_string(), + ty: Type { + name: tname.clone(), + }, + can_set: true, + }; + + self.fields.insert(tname, desc); + } + + self.inner.field_setter(name, f); + self + } + + pub fn function(&mut self, name: &str, f: F) -> &mut Self + where + F: Fn(&'a State, A) -> crate::Result + 'static, + A: FromLuaVec<'a> + LuaTypeName, + R: AsLua<'a> + LuaTypeName, + { + let func_desc = function_desc::(&mut self.func_arg_names, name); + self.funcs.insert(name.to_string(), func_desc); + + self.inner.function(name, f); + self + } + + pub fn method(&mut self, name: &str, f: F) -> &mut Self + where + F: Fn(&'a State, &T, A) -> crate::Result + 'static, + A: FromLuaVec<'a> + LuaTypeName, + R: AsLua<'a> + LuaTypeName, + T: TealUserdata + 'static + { + let mut func_desc = function_desc::(&mut self.func_arg_names, name); + let mut tmp = vec![FuncArg { + name: Some("self".to_string()), + ty: Type { + name: T::get_lua_name() + } + }]; + // this is probably the fastest way to prepend to a Vec + tmp.append(&mut func_desc.args); + func_desc.args = tmp; + self.funcs.insert(name.to_string(), func_desc); + + self.inner.method(name, f); + self + } + + pub fn method_mut(&mut self, name: &str, f: F) -> &mut Self + where + F: Fn(&'a State, &mut T, A) -> crate::Result + 'static, + A: FromLuaVec<'a> + LuaTypeName, + R: AsLua<'a> + LuaTypeName, + T: TealUserdata + 'static + { + let mut func_desc = function_desc::(&mut self.func_arg_names, name); + let mut tmp = vec![FuncArg { + name: Some("self".to_string()), + ty: Type { + name: T::get_lua_name() + } + }]; + // this is probably the fastest way to prepend to a Vec + tmp.append(&mut func_desc.args); + func_desc.args = tmp; + self.funcs.insert(name.to_string(), func_desc); + + self.inner.method_mut(name, f); + self + } + + pub fn meta_method(&mut self, name: N, f: F) -> &mut Self + where + N: AsRef, + F: Fn(&'a State, &T, A) -> crate::Result + 'static, + A: FromLuaVec<'a> + LuaTypeName, + R: AsLua<'a> + LuaTypeName, + T: TealUserdata + 'static + { + let mut func_desc = function_desc::(&mut self.func_arg_names, name.as_ref()); + let mut tmp = vec![FuncArg { + name: Some("self".to_string()), + ty: Type { + name: T::get_lua_name() + } + }]; + // this is probably the fastest way to prepend to a Vec + tmp.append(&mut func_desc.args); + func_desc.args = tmp; + self.funcs.insert(name.as_ref().to_string(), func_desc); + + self.inner.meta_method(name, f); + self + } + + pub fn set_arg_names(&mut self, names: &[&str]) -> &mut Self { + debug_assert!(self.func_arg_names.is_none(), + "Invalid attempt to set argument names for function. \ + There are arguments that are currently pending to be set on a function"); + + self.func_arg_names = Some(names.iter().map(|n| n.to_string()).collect()); + + self + } +} + +impl Userdata for T { + fn name() -> String { + ::name() + } + + fn build<'a>(builder: &mut UserdataBuilder<'a, Self>) { + let builder = unsafe { mem::transmute::<_, &mut UserdataBuilder<'_, Self>>(builder) }; + let mut teal_build = TealUserdataBuilder::from(builder); + ::build(&mut teal_build); + } +} + +impl LuaTypeName for T { + fn get_lua_name() -> String { + T::name() + } +} + +#[cfg(test)] +pub(crate) mod tests { + use crate::TealUserdata; + + pub(crate) struct Vec3 { + x: f32, + y: f32, + z: f32, + } + + impl TealUserdata for Vec3 { + fn name() -> String { + "Vec3".to_string() + } + + fn build<'a, 'b>(builder: &'b mut crate::TealUserdataBuilder<'a, Self>) { + builder + .field_getter("x", |_, this| Ok(this.x)) + .field_setter("x", |_, this, v| { + this.x = v; + Ok(()) + }) + .field_getter("y", |_, this| Ok(this.y)) + .field_setter("y", |_, this, v| { + this.y = v; + Ok(()) + }) + .field_getter("z", |_, this| Ok(this.z)) + .field_setter("z", |_, this, v| { + this.z = v; + Ok(()) + }) + .set_arg_names(&["x", "y", "z"]) + .function("new", |_, (x, y, z)| { + Ok(Vec3 { + x, + y, + z + }) + }) + .set_arg_names(&["scalar"]) + .method("do_something", |_, this, scalar: f32| { + Ok(this.x * scalar) + }); + + } + } + + #[test] + fn simple_teal_ud() { + + } +} \ No newline at end of file diff --git a/src/teal/walker.rs b/src/teal/walker.rs new file mode 100644 index 0000000..18e199e --- /dev/null +++ b/src/teal/walker.rs @@ -0,0 +1,93 @@ +use crate::{function_desc, AsLua, FieldDesc, FromLuaVec, FunctionDesc, GenerateContext, GenerateTealDefinition, LuaTypeName, Record, TealUserdata, TealUserdataBuilder, Type}; + +#[derive(Default)] +pub struct TypeWalker { + records: Vec, + global_fields: Vec, + global_funcs: Vec, +} + +impl TypeWalker { + pub fn new() -> Self { + Self::default() + } + + pub fn walk_type(&mut self) { + let mut ud = TealUserdataBuilder::::new(); + T::build(&mut ud); + + let fields = ud.fields.values().cloned().collect(); + let functions = ud.funcs.values().cloned().collect(); + + let record = Record { + ty: Type { name: T::get_lua_name() }, + fields, + functions, + }; + + self.records.push(record); + } + + pub fn add_global_field(&mut self, name: &str) { + let field = FieldDesc { + name: name.to_string(), + ty: Type::new::(), + can_set: false, + }; + + self.global_fields.push(field); + } + + pub fn add_global_func<'a, Args, Ret>(&mut self, name: &str, arg_names: &[&str]) + where + Args: FromLuaVec<'a> + LuaTypeName, + Ret: AsLua<'a> + LuaTypeName + { + let arg_names: Vec<_> = arg_names.iter().map(|s| s.to_string()).collect(); + let mut desc = function_desc::(&mut Some(arg_names), name); + desc.is_field = true; + + self.global_funcs.push(desc); + } + + /// Creates the content of a `.d.tl` file. + pub fn to_type_def(&self) -> String { + let mut gen_ctx = GenerateContext::default(); + + for field in self.global_fields.iter() { + field.generate(&mut gen_ctx); + } + if !self.global_fields.is_empty() { + gen_ctx.write_new_line(); + } + + for funcs in self.global_funcs.iter() { + funcs.generate(&mut gen_ctx); + } + if !self.global_funcs.is_empty() { + gen_ctx.write_new_line(); + } + + for record in self.records.iter() { + record.generate(&mut gen_ctx); + } + + gen_ctx.content + } +} + +#[cfg(test)] +mod tests { + use crate::TypeWalker; + use crate::teal::userdata::tests::Vec3; + + #[test] + fn vec3_type_walk() { + let mut walker = TypeWalker::new(); + walker.walk_type::(); + walker.add_global_field::("multiplier"); + walker.add_global_func::<(f32, f64, usize), i32>("do_cool_math", &["a", "b", "z"]); + + println!("{}", walker.to_type_def()); + } +} \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs index 769028f..c895bef 100755 --- a/src/tests.rs +++ b/src/tests.rs @@ -8,7 +8,7 @@ pub struct Vec2 { } impl Userdata for Vec2 { - fn build<'a>(_state: &State, builder: &mut UserdataBuilder<'a, Vec2>) -> crate::Result<()> { + fn build<'a>(builder: &mut UserdataBuilder<'a, Vec2>) { builder .field_getter("x", |_, this| Ok(this.x)) .field_getter("y", |_, this| Ok(this.y)) @@ -58,8 +58,6 @@ impl Userdata for Vec2 { y: ly + ry, }) }); - - Ok(()) } fn name() -> String { diff --git a/src/userdata/borrow.rs b/src/userdata/borrow.rs index d08d1e6..e151c86 100755 --- a/src/userdata/borrow.rs +++ b/src/userdata/borrow.rs @@ -47,9 +47,9 @@ impl<'a, T: Userdata> From> for UserdataRef<'static, T> { } impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> { - fn build<'b>(state: &State, builder: &mut UserdataBuilder<'b, Self>) -> crate::Result<()> { + fn build<'b>(builder: &mut UserdataBuilder<'b, Self>) { let mut other = UserdataBuilder::::new(); - T::build(state, &mut other)?; + T::build(&mut other); builder.expand_with(other); @@ -75,8 +75,6 @@ impl<'a, T: Userdata + 'static> Userdata for UserdataRef<'a, T> { if builder.wrapped_getter_mut.set(Box::new(mut_getter)).is_err() { panic!("Somehow the wrapped mutable getter has already been set"); } - - Ok(()) } fn name() -> String { diff --git a/src/userdata/borrow_mut.rs b/src/userdata/borrow_mut.rs index 9d438eb..bc53a07 100755 --- a/src/userdata/borrow_mut.rs +++ b/src/userdata/borrow_mut.rs @@ -56,9 +56,9 @@ impl<'a, T: Userdata> From> for UserdataRefMut<'static, T> { } impl<'a, T: Userdata + 'static> Userdata for UserdataRefMut<'a, T> { - fn build<'b>(state: &State, builder: &mut UserdataBuilder<'b, Self>) -> crate::Result<()> { + fn build<'b>(builder: &mut UserdataBuilder<'b, Self>) { let mut other = UserdataBuilder::::new(); - T::build(state, &mut other)?; + T::build(&mut other); builder.expand_with(other); @@ -91,8 +91,6 @@ impl<'a, T: Userdata + 'static> Userdata for UserdataRefMut<'a, T> { if builder.wrapped_getter_mut.set(Box::new(getter_mut)).is_err() { panic!("Somehow the wrapped mutable getter has already been set"); } - - Ok(()) } fn name() -> String { diff --git a/src/userdata/mod.rs b/src/userdata/mod.rs index 30e33e9..7f00f95 100755 --- a/src/userdata/mod.rs +++ b/src/userdata/mod.rs @@ -107,7 +107,7 @@ impl<'a> PushToLuaStack<'a> for MetaMethod { pub trait Userdata: Sized { fn name() -> String; - fn build<'a>(state: &State, builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()>; + fn build<'a>(builder: &mut UserdataBuilder<'a, Self>); } impl<'a, T: Userdata + 'static> AsLua<'a> for T { diff --git a/src/userdata/proxy.rs b/src/userdata/proxy.rs index 83dc259..1d4c03a 100755 --- a/src/userdata/proxy.rs +++ b/src/userdata/proxy.rs @@ -11,14 +11,12 @@ impl UserdataProxy { } impl Userdata for UserdataProxy { - fn build<'a>(state: &State, builder: &mut UserdataBuilder<'a, Self>) -> crate::Result<()> { + fn build<'a>(builder: &mut UserdataBuilder<'a, Self>){ let mut other = UserdataBuilder::::new(); - T::build(state, &mut other)?; + T::build(&mut other); // only the functions need to be added since they're the only thing usable from a proxy builder.functions = other.functions; - - Ok(()) } fn name() -> String {