/* * Copyright 2024 Luca Fulchir * * Licensed under the Apache License, Version 2.0 with LLVM exception (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License and of the exception at * * http://www.apache.org/licenses/LICENSE-2.0 * https://spdx.org/licenses/LLVM-exception.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ use ::proc_macro::TokenStream; use ::quote::quote; use ::syn::{ parse::{ParseBuffer, ParseStream, Parser}, parse_macro_input, DeriveInput, }; /// Use as #[repository(MyBaseRepo)] /// Will setup a `base` field that is the given base repo /// /// e.g.: `#[repository(::bok::RepositoryEmpty)]` #[proc_macro_attribute] pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); match &mut ast.data { syn::Data::Struct(ref mut struct_data) => { match &mut struct_data.fields { syn::Fields::Named(fields) => { let base = proc_macro2::TokenStream::from(attrs); fields.named.push( syn::Field::parse_named .parse2(quote! { base: #base }) .unwrap(), ); } _ => (), } quote! { #ast } .into() } _ => panic!("`repository` has to be used with a struct"), } } /// Use as #[derive(Repository)] /// adds extension capabilities to a repo #[proc_macro_derive(Repository)] pub fn derive_repository(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident.clone(); let elements = match input.data { ::syn::Data::Struct(s) => match s.fields { syn::Fields::Named(n) => n.named, _ => panic!("only named supported"), }, _ => panic!("`Repository` has to be used on a struct"), }; let base_type = elements .iter() .find_map(|field| { if let Some(name) = &field.ident { if name != "base" { None } else { let t = &field.ty; Some(quote! {#t}) } } else { None } }) .expect("can't find base value. Add #[repository] to your struct"); let expanded = quote! { impl ::bok::Repository for #name {} impl ::std::ops::Deref for #name { type Target = #base_type; fn deref(&self) -> &#base_type { &self.base } } }; TokenStream::from(expanded) } /// Use on a function as #[to_code] /// will create a new function, same name with `_code` that /// returns the ::proc_macro2::TokenStream of the function /// /// e.g.: /// ``` /// impl MyPackage { /// #[package(::bok_macro::to_code)] /// fn build() { /// ... /// } /// } /// let p = MyPackage::default(); /// let tokens : ::proc_macro2::TokenStream = p.build_code() /// ``` #[proc_macro_attribute] pub fn to_code(_attrs: TokenStream, input: TokenStream) -> TokenStream { let ast_orig: ::syn::ItemFn = parse_macro_input!(input as ::syn::ItemFn); let mut ast_new = ast_orig.clone(); let new_fn_name = quote::format_ident!("{}", ast_orig.sig.ident.to_string() + "_code"); ast_new.sig.ident = quote::format_ident!("{}", new_fn_name); use ::quote::ToTokens; let fn_block = ast_new.block.to_token_stream(); //let quote_macro = ::quote::format_ident!("quote"); let asdf = quote! { #ast_orig fn #new_fn_name () -> ::proc_macro2::TokenStream { //::quote::#quote_macro! { ::quote::quote! #fn_block //} } }; asdf.into() /* let fn_block_pb = ::syn::punctuated::Punctuated::< ::syn::Expr, ::syn::Token![,], >::parse_terminated .parse2(fn_block) .unwrap(); */ /* let fn_block_pb = ::syn::parse::Parser::parse2( //::syn::punctuated::Punctuated::<::syn::Expr, ::syn::Token![,]>::parse_terminated, |input: ParseStream<'_>| ::syn::Result::Ok({ input.clone() }), fn_block, ) .unwrap(); */ /* let fn_block_pb = ::syn::punctuated::Punctuated::< //::syn::parse::ParseBuffer<'_>, proc_macro2::TokenStream, ::syn::Token![,], >::parse_terminated .parse2(fn_block) .unwrap(); */ /* let wtf: ::syn::parse::ParseBuffer<'_> = parse_macro_input!(fn_block_pb as ::syn::parse::ParseBuffer).into(); */ /* match ::syn::Block::parse_within(&fn_block) { //match ::syn::Block::parse_within(&wtf) { Ok(_) => { // } Err(_) => panic!("can't parse code block"), } */ /* use ::quote::ToTokens; ast_new.into_token_stream().into() */ } /// Use as #[package(MyBasePackage)] /// Will setup a `base` field that is the given base package /// /// e.g.: `#[package(::bok::PkgEmpty)]` #[proc_macro_attribute] pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); match &mut ast.data { syn::Data::Struct(ref mut struct_data) => { match &mut struct_data.fields { syn::Fields::Named(fields) => { let base = proc_macro2::TokenStream::from(attrs); fields.named.push( syn::Field::parse_named .parse2(quote! { base: #base }) .unwrap(), ); } _ => (), } quote! { #ast } .into() } _ => panic!("`package` has to be used with a struct"), } } /// #[derive(Package)] /// adds: /// * Builder pattern to a package /// * deref for builder towards `.base` /// * getters/setters for all package fields /// * deref for package #[proc_macro_derive(Package)] pub fn derive_package(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident.clone(); let name_builder = quote::format_ident!("{name}Builder"); let name_builder2 = name_builder.clone(); let elements = match input.data { ::syn::Data::Struct(s) => match s.fields { syn::Fields::Named(n) => n.named, _ => panic!("only named supported"), }, _ => panic!("only struct allowed"), }; let all_fields = elements.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() != "base" { return Some(&field.ident); } } None }); let all_fields2 = all_fields.clone(); let all_fields3 = all_fields.clone(); let all_fields_mut = elements.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() != "base" { return Some(quote::format_ident!("{}_mut", id.to_string())); } } None }); let base_type = elements .iter() .find_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "base" { return Some(&field.ty); } } None }) .expect("expected a base type"); let all_types = elements.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() != "base" { return Some(&field.ty); } } None }); let all_types2 = all_types.clone(); let non_opt_fields = elements.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "base" { return None; } } match &field.ty { syn::Type::Path(pth) => { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { None } else { let id = &field.ident; Some(quote! {#id}) } } t => Some(quote! {#t}), } }); let opt_fields = elements.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "base" { return None; } } match &field.ty { syn::Type::Path(pth) => { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { let id = &field.ident; Some(quote! {#id}) } else { None } } _ => None, } }); let non_opt_types = elements.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "base" { return None; } } match &field.ty { syn::Type::Path(pth) => { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { None } else { let t = &field.ty; Some(quote! {#t}) } } _ => None, } }); let opt_types = elements.iter().filter_map(|field| { if let Some(id) = field.ident.clone() { if id.to_string() == "base" { return None; } } if let ::syn::Type::Path(pth) = &field.ty { let first_path = pth.path.segments.first().unwrap(); if first_path.ident == "Option" { if let syn::PathArguments::AngleBracketed( syn::AngleBracketedGenericArguments { args, .. }, ) = &first_path.arguments { if let Some(syn::GenericArgument::Type(syn::Type::Path( p, ))) = args.first() { let id = &p.path.segments.first().unwrap().ident; return Some(quote! {#id}); } } } } None }); let non_opt_fields2 = non_opt_fields.clone(); let non_opt_fields3 = non_opt_fields.clone(); let non_opt_fields4 = non_opt_fields.clone(); let opt_fields2 = opt_fields.clone(); let opt_fields3 = opt_fields.clone(); let opt_types2 = opt_types.clone(); let non_opt_types2 = non_opt_types.clone(); let expanded = quote! { impl ::bok::Pkg for #name { fn base(&self) -> Option<&impl ::bok::Pkg> { Some(&self.base) } } // FIXME: proper formatting of all functions impl ::core::fmt::Display for #name { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) ->::core::fmt::Result { use ::bok::Pkg; let pkg = self.package_code(); pkg.fmt(f) } } impl #name { pub fn builder() -> #name_builder { #name_builder { #(#all_fields : ::std::option::Option::None,)* } } #(pub fn #all_fields2(&self) -> &#all_types { &self.#all_fields2 })* #(pub fn #all_fields_mut(&mut self) -> &#all_types2 { &mut self.#all_fields3 })* } pub struct #name_builder { #(#non_opt_fields: ::std::option::Option<#non_opt_types>,)* #(#opt_fields: ::std::option::Option<#opt_types>,)* } /* impl ::std::ops::Deref for #name_builder { type Target = #base_type; fn deref(&self) -> &#base_type { &self.base } } */ impl #name_builder2 { pub fn build(&mut self) -> Result<#name, ::std::boxed::Box> { #(if self.#non_opt_fields2.is_none() { return ::std::result::Result::Err("unset field".into()); })* Ok( #name { // FIXME: user must be able to override. Trait? base: #base_type::default(), #(#non_opt_fields3 : self.#non_opt_fields3.clone().unwrap(),)* #(#opt_fields2 : self.#opt_fields2.clone(),)* } ) } #(pub fn #non_opt_fields4 (&mut self, val : #non_opt_types2) -> &mut Self { self.#non_opt_fields4 = Some(val); self })* #(pub fn #opt_fields3 (&mut self, val : #opt_types2) -> &mut Self { self.#opt_fields3 = Some(val); self })* } }; TokenStream::from(expanded) }