use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Output)]
pub fn output(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input);
match data {
syn::Data::Struct(struct_data) => {
let builder_ident = format_ident!("{ident}Builder");
// Get all field identifiers in the struct
// Each field is assumed to have a unique identifier (Rust should enforce this?)
let field_idents = struct_data
.fields
.iter()
.map(|field| field.ident.clone().unwrap()); // TODO: handle tuple structs
let types = struct_data.fields.iter().map(|field| field.ty.clone());
let ident_type_mappings = field_idents.clone().zip(types.clone());
// In the builder struct definition, map each field from `field: T` to `field: Option<T>`
let optional_mappings = ident_type_mappings
.clone()
.map(|(field_ident, ty)| quote! { #field_ident: Option<#ty> });
// Functions to handle incoming data
// In the future these functions will include logic for immediate output to stdout (human output),
// just lazily setting (machine output), or something else (maybe test handler?)
// TODO: investigate setting collections, e.g. have the setter extend rather than set the collection so that
// it can be immmediately output rather than to buffer all items in collection
let setter_functions = ident_type_mappings.clone().map(|(field_ident, ty)| {
quote! { fn #field_ident(&mut self, value: #ty) -> &mut Self {
assert!(self.#field_ident.is_none());
self.#field_ident = Some(value);
self
}}
});
// Simple getter functions to inspect the value set in the builder
let getter_functions = field_idents
.clone()
.map(|field_ident| format_ident!("get_{field_ident}"))
.zip(field_idents.clone())
.zip(types.clone())
.map(|((function_ident, field_ident), ty)| {
quote! { fn #function_ident(&self) -> &Option<#ty> {
&self.#field_ident
}}
});
// Construct the builder implementation and return the token stream
quote! {
struct #builder_ident {
#(#optional_mappings),*
}
impl #builder_ident {
const fn new() -> Self {
Self {
#(#field_idents: None),*
}
}
#(#setter_functions)*
#(#getter_functions)*
}
impl #ident {
pub const fn new() -> #builder_ident {
#builder_ident::new()
}
}
}
.into()
}
_ => todo!("Handle deriving non-structs"),
}
}