Further work on automatically generating lua type definitions
All checks were successful
Build and deploy automation_rs / Build automation_rs (push) Successful in 6m25s
Build and deploy automation_rs / Build Docker image (push) Successful in 1m24s
Build and deploy automation_rs / Deploy Docker container (push) Has been skipped

This commit is contained in:
2024-04-30 02:08:29 +02:00
parent 2f494a7dd6
commit 11d5d5db4d
25 changed files with 510 additions and 101 deletions

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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
}

View 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
}