Further work on automatically generating lua type definitions
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
mod lua_device;
|
||||
mod lua_device_config;
|
||||
mod lua_type_definition;
|
||||
|
||||
use lua_device::impl_lua_device_macro;
|
||||
use lua_device_config::impl_lua_device_config_macro;
|
||||
use lua_type_definition::impl_lua_type_definition;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro_derive(LuaDevice, attributes(config))]
|
||||
#[proc_macro_derive(LuaDevice)]
|
||||
pub fn lua_device_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
@@ -18,3 +20,10 @@ pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::T
|
||||
|
||||
impl_lua_device_config_macro(&ast).into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(LuaTypeDefinition, attributes(device_config))]
|
||||
pub fn lua_type_definition_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
impl_lua_type_definition(&ast).into()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
@@ -12,6 +9,20 @@ pub fn impl_lua_device_macro(ast: &DeriveInput) -> TokenStream {
|
||||
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||
lua.globals().set(stringify!(#name), lua.create_proxy::<#name>()?)
|
||||
}
|
||||
|
||||
pub fn generate_lua_definition() -> String {
|
||||
// TODO: Do not hardcode the name of the config type
|
||||
let def = format!(
|
||||
r#"--- @class {0}
|
||||
{0} = {{}}
|
||||
--- @param config {0}Config
|
||||
--- @return WrappedDevice
|
||||
function {0}.new(config) end
|
||||
"#, stringify!(#name)
|
||||
);
|
||||
|
||||
def
|
||||
}
|
||||
}
|
||||
impl mlua::UserData for #name {
|
||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
@@ -27,19 +38,5 @@ pub fn impl_lua_device_macro(ast: &DeriveInput) -> TokenStream {
|
||||
}
|
||||
};
|
||||
|
||||
let def = format!(
|
||||
r#"--- @meta
|
||||
--- @class {name}
|
||||
{name} = {{}}
|
||||
--- @param config {name}Config
|
||||
--- @return Config
|
||||
function {name}.new(config) end"#
|
||||
);
|
||||
|
||||
File::create(format!("./definitions/generated/{name}.lua"))
|
||||
.unwrap()
|
||||
.write_all(def.as_bytes())
|
||||
.unwrap();
|
||||
|
||||
gen
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
use itertools::Itertools;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
@@ -26,7 +23,7 @@ mod kw {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Argument {
|
||||
pub enum Argument {
|
||||
Flatten {
|
||||
_keyword: kw::flatten,
|
||||
},
|
||||
@@ -110,8 +107,8 @@ impl Parse for Argument {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Args {
|
||||
args: Punctuated<Argument, Token![,]>,
|
||||
pub(crate) struct Args {
|
||||
pub(crate) args: Punctuated<Argument, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for Args {
|
||||
@@ -221,6 +218,21 @@ fn field_from_lua(field: &Field) -> TokenStream {
|
||||
temp.into()
|
||||
}
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice()
|
||||
{
|
||||
[] => value,
|
||||
[value] => value.to_owned(),
|
||||
_ => {
|
||||
return quote_spanned! {field.span() => compile_error!("Field contains duplicate 'from'")}
|
||||
}
|
||||
};
|
||||
|
||||
let value = match args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
Argument::With { expr, .. } => Some(quote! {
|
||||
{
|
||||
let temp = #value;
|
||||
@@ -235,7 +247,7 @@ fn field_from_lua(field: &Field) -> TokenStream {
|
||||
[] => value,
|
||||
[value] => value.to_owned(),
|
||||
_ => {
|
||||
return quote_spanned! {field.span() => compile_error!("Only one of either 'from' or 'with' is allowed")}
|
||||
return quote_spanned! {field.span() => compile_error!("Field contains duplicate 'with'")}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -279,15 +291,5 @@ pub fn impl_lua_device_config_macro(ast: &DeriveInput) -> TokenStream {
|
||||
}
|
||||
};
|
||||
|
||||
let mut def = format!("--- @meta\n--- @class {name}\n");
|
||||
for field in fields {
|
||||
def += &format!("--- @field {} any\n", field.ident.clone().unwrap())
|
||||
}
|
||||
|
||||
File::create(format!("./definitions/generated/{name}.lua"))
|
||||
.unwrap()
|
||||
.write_all(def.as_bytes())
|
||||
.unwrap();
|
||||
|
||||
impl_from_lua
|
||||
}
|
||||
|
||||
137
automation_macro/src/lua_type_definition.rs
Normal file
137
automation_macro/src/lua_type_definition.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use itertools::Itertools;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
AngleBracketedGenericArguments, Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed,
|
||||
PathArguments, Type, TypePath,
|
||||
};
|
||||
|
||||
use crate::lua_device_config::{Args, Argument};
|
||||
|
||||
fn field_definition(field: &Field) -> TokenStream {
|
||||
let (args, _): (Vec<_>, Vec<_>) = field
|
||||
.attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
if attr.path().is_ident("device_config") {
|
||||
Some(attr.parse_args::<Args>().map(|args| args.args))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.partition_result();
|
||||
let args: Vec<_> = args.into_iter().flatten().collect();
|
||||
|
||||
let field_name = if let Some(field_name) = args.iter().find_map(|arg| match arg {
|
||||
Argument::Rename { ident, .. } => Some(ident),
|
||||
_ => None,
|
||||
}) {
|
||||
field_name.value()
|
||||
} else {
|
||||
format!("{}", field.ident.clone().unwrap())
|
||||
};
|
||||
|
||||
let mut optional = args
|
||||
.iter()
|
||||
.filter(|arg| matches!(arg, Argument::Default { .. } | Argument::DefaultExpr { .. }))
|
||||
.count()
|
||||
>= 1;
|
||||
|
||||
if args
|
||||
.iter()
|
||||
.filter(|arg| matches!(arg, Argument::Flatten { .. }))
|
||||
.count()
|
||||
>= 1
|
||||
{
|
||||
let field_type = &field.ty;
|
||||
quote! {
|
||||
#field_type::generate_lua_fields().as_str()
|
||||
}
|
||||
} else {
|
||||
let path = if let Some(ty) = args.iter().find_map(|arg| match arg {
|
||||
Argument::From { ty, .. } => Some(ty),
|
||||
_ => None,
|
||||
}) {
|
||||
if let Type::Path(TypePath { path, .. }) = ty {
|
||||
path.clone()
|
||||
} else {
|
||||
todo!();
|
||||
}
|
||||
} else if let Type::Path(TypePath { path, .. }) = field.ty.clone() {
|
||||
path
|
||||
} else {
|
||||
todo!()
|
||||
};
|
||||
|
||||
let seg = path.segments.first().unwrap();
|
||||
let field_type = if seg.ident == "Option" {
|
||||
if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) =
|
||||
seg.arguments.clone()
|
||||
{
|
||||
optional = true;
|
||||
quote! { stringify!(#args) }
|
||||
} else {
|
||||
unreachable!("Option should always have angle brackets");
|
||||
}
|
||||
} else if seg.ident == "Vec" {
|
||||
if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) =
|
||||
seg.arguments.clone()
|
||||
{
|
||||
optional = true;
|
||||
quote! { stringify!(#args[]) }
|
||||
} else {
|
||||
unreachable!("Option should always have angle brackets");
|
||||
}
|
||||
} else {
|
||||
quote! { stringify!(#path).replace(" :: ", "_") }
|
||||
};
|
||||
|
||||
let mut format = "--- @field {} {}".to_string();
|
||||
if optional {
|
||||
format += "|nil";
|
||||
}
|
||||
format += "\n";
|
||||
|
||||
quote! {
|
||||
format!(#format, #field_name, #field_type).as_str()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_lua_type_definition(ast: &DeriveInput) -> TokenStream {
|
||||
let name = &ast.ident;
|
||||
let fields = if let Data::Struct(DataStruct {
|
||||
fields: Fields::Named(FieldsNamed { ref named, .. }),
|
||||
..
|
||||
}) = ast.data
|
||||
{
|
||||
named
|
||||
} else {
|
||||
return quote_spanned! {ast.span() => compile_error!("This macro only works on named structs")};
|
||||
};
|
||||
|
||||
let fields: Vec<_> = fields.iter().map(field_definition).collect();
|
||||
|
||||
let gen = quote! {
|
||||
impl #name {
|
||||
pub fn generate_lua_definition() -> String {
|
||||
let mut def = format!("--- @class {}\n", stringify!(#name));
|
||||
|
||||
def += #name::generate_lua_fields().as_str();
|
||||
|
||||
def
|
||||
}
|
||||
|
||||
pub fn generate_lua_fields() -> String {
|
||||
let mut def = String::new();
|
||||
|
||||
#(def += #fields;)*
|
||||
|
||||
def
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gen
|
||||
}
|
||||
Reference in New Issue
Block a user