/* * 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::Parser, parse_macro_input, DeriveInput}; pub(crate) fn pkg_fn_to_code( _attrs: TokenStream, input: TokenStream, ) -> TokenStream { let mut ast_orig: ::syn::ItemFn = parse_macro_input!(input as ::syn::ItemFn); let mut ast_new = ast_orig.clone(); ast_new.block = ast_orig.block.clone(); // Add a prefix and suffix statement // where we declare and return the result let prefix_ret: ::syn::Stmt = ::syn::parse_quote! { let mut ret : ::core::result::Result<(),()> = ::core::result::Result::Err(()); }; let suffix_ret: ::syn::Stmt = ::syn::parse_quote! { return ret; }; ast_orig.block.stmts.insert(0, prefix_ret); ast_orig.block.stmts.push(suffix_ret); 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 asdf = quote! { #ast_orig fn #new_fn_name (&self) -> ::proc_macro2::TokenStream { ::quote::quote! #fn_block } }; asdf.into() } pub(crate) 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(), ); fields.named.push( syn::Field::parse_named .parse2(quote! { version: ::bok::Version }) .unwrap(), ); } _ => (), } quote! { #[derive(::bok::Package,::std::fmt::Debug, Clone)] #ast } .into() } _ => panic!("`package` has to be used with a struct"), } } pub(crate) fn impl_package( _attrs: TokenStream, input: TokenStream, ) -> TokenStream { let mut ast = parse_macro_input!(input as ::syn::ItemImpl); let name_pkg = match &*ast.self_ty { ::syn::Type::Path(tp) => match tp.path.get_ident() { Some(id) => id.clone(), _ => panic!("impl_package expected path ident"), }, _ => panic!("impl_package expected path"), }; let name: ::syn::ImplItem = ::syn::parse_quote! { fn name(&self) -> ::bok::PkgName { (module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into() } }; let path: ::syn::ImplItem = ::syn::parse_quote! { fn path(&self) -> ::bok::Path<::bok::PkgName> { (module_path!().to_owned() + "::" + ::std::stringify!(#name_pkg)).as_str().into() } }; let version: ::syn::ImplItem = ::syn::parse_quote! { fn version(&self) -> ::bok::Version { self.version.clone() } }; let base: ::syn::ImplItem = ::syn::parse_quote! { fn base(&self) -> ::core::option::Option<&dyn ::bok::Pkg> { Some(&self.base) } }; let base_mut: ::syn::ImplItem = ::syn::parse_quote! { fn base_mut(&mut self) -> ::core::option::Option<&mut dyn ::bok::Pkg> { Some(&mut self.base) } }; let any: ::syn::ImplItem = ::syn::parse_quote! { fn as_any(&self) -> &dyn ::std::any::Any { self } }; let any_mut: ::syn::ImplItem = ::syn::parse_quote! { fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { self } }; let hash: ::syn::ImplItem = ::syn::parse_quote! { fn hash(&self) -> ::bok::Hash { use ::sha3::{Digest}; let mut hasher = ::sha3::Sha3_256::new(); let mut pkg_vars = String::new(); use ::std::fmt::Write; write!(&mut pkg_vars, "{:?}", self, ).ok(); hasher.update(&pkg_vars); hasher.update(self.to_string()); let hash = hasher.finalize(); ::bok::Hash::Sha3(hash) } }; let hash_code: ::syn::ImplItem = ::syn::parse_quote! { fn hash_code(&self) -> ::bok::Hash { use ::sha3::{Digest}; let mut hasher = ::sha3::Sha3_256::new(); hasher.update(self.to_string()); let hash = hasher.finalize(); ::bok::Hash::Sha3(hash) } }; for mut impl_item in ast.items.iter_mut() { if let ::syn::ImplItem::Fn(ref mut fn_impl) = &mut impl_item { if fn_impl.sig.ident.to_string() == "dependencies_set" { continue; } let fn_with_code: ::syn::ImplItemFn = ::syn::parse_quote! { #[::bok::pkg_fn_to_code] #fn_impl }; *fn_impl = fn_with_code; } else { panic!("wtf"); } } ast.items.push(name); ast.items.push(path); ast.items.push(version); ast.items.push(base); ast.items.push(base_mut); ast.items.push(any); ast.items.push(any_mut); ast.items.push(hash); ast.items.push(hash_code); quote! { #ast } .into() } pub(crate) fn derive_package(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let mut input_nobase = input.clone(); let name = input.ident.clone(); let name_builder = quote::format_ident!("{name}Builder"); let name_builder2 = name_builder.clone(); let name_builder3 = 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" && id.to_string() != "version" { 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" && id.to_string() != "version" { 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" && id.to_string() != "version" { 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" || id.to_string() == "version" { 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" || id.to_string() == "version" { 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" || id.to_string() == "version" { 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" || id.to_string() == "version" { 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 non_opt_fields5 = 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(); { // remove the `base` field from the struct let ::syn::Data::Struct(ref mut ds) = input_nobase.data else { panic!("::bok::package expected a struct"); }; let ::syn::Fields::Named(ref mut old_nf) = ds.fields else { panic!("::bok::package expected a named field"); }; let mut new_nf = ::syn::punctuated::Punctuated::< ::syn::Field, ::syn::token::Comma, >::new(); for f in old_nf.named.iter() { if let Some(ref id) = f.ident { if id.to_string() == "base" { continue; } } new_nf.push(f.clone()); } old_nf.named = new_nf; } let pkg_struct = quote! { #input_nobase }; let mut pkg_struct_str = String::new(); { use ::core::fmt::Write; write!(&mut pkg_struct_str, "{}", pkg_struct) .expect("can't write package struct into string"); } let expanded = quote! { impl ::core::fmt::Display for #name { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) ->::core::fmt::Result { <#name as ::bok::PkgCode>::fmt(self, f) } } impl ::bok::PkgCode for #name { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) ->::core::fmt::Result { // FIXME: this sounds convoluted. // first we turn everything into a string, then we // parse the string into tokenstream again to format it // reason: the `prettyplease` works on `::syn::File` // and I can't find an easy translation // `TokenStream2 -> TokenStream` use ::bok::Pkg; // don't add a method if it is just the base Trait implementation fn maybe_add_fn(out: &mut String, name: &str, fn_str: &str) -> ::core::fmt::Result { let str_base = "self . base () . unwrap () . ".to_owned() + name + " ()"; if fn_str.trim() != str_base { use ::core::fmt::Write; write!(out, "\ fn {}(&self) -> Result<(), ()> {{\n\ {}\n\ }}\n", name, fn_str, ) } else { ::core::fmt::Result::Ok(()) } }; use ::core::fmt::Write; let pkg_empty = ::bok::PkgEmpty::default(); let mut pkg_string = String::new(); write!(&mut pkg_string, "#[::bok::package({})]\n\ {}", ::std::stringify!(#base_type), #pkg_struct_str)?; write!(&mut pkg_string, "#[::bok::impl_package]\n\ impl ::bok::Pkg for {} {{\n", ::std::stringify!(#name) )?; maybe_add_fn(&mut pkg_string, "prepare", &self.prepare_code().to_string())?; maybe_add_fn(&mut pkg_string, "configure", &self.configure_code().to_string())?; maybe_add_fn(&mut pkg_string, "build", &self.build_code().to_string())?; maybe_add_fn(&mut pkg_string, "check", &self.check_code().to_string())?; maybe_add_fn(&mut pkg_string, "install", &self.install_code().to_string())?; write!(&mut pkg_string, "}}\n")?; let re_parsed = ::syn::parse_file(&pkg_string).unwrap(); let formatted = prettyplease::unparse(&re_parsed); f.write_str(&formatted) } } impl #name { pub fn as_pkg(&self) -> &dyn ::bok::Pkg { self } pub fn as_pkg_mut(&mut self) -> &mut dyn ::bok::Pkg { self } pub fn builder() -> #name_builder { #name_builder::default() } #(pub fn #all_fields2(&self) -> &#all_types { &self.#all_fields2 })* #(pub fn #all_fields_mut(&mut self) -> &#all_types2 { &mut self.#all_fields3 })* } #[derive(::std::default::Default, ::std::fmt::Debug)] 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 ::bok::PkgBuilder for #name_builder2 { fn name(&self) -> ::bok::PkgName { use ::bok::Pkg; #name::default().name() } fn path(&self) -> ::bok::Path<::bok::PkgName> { use ::bok::Pkg; #name::default().path() } fn version(&self) -> ::bok::Version { use ::bok::Pkg; #name::default().version() } fn default_unused(&mut self) -> &mut dyn ::bok::PkgBuilder { let def = #name::default(); #(if self.#non_opt_fields5.is_none() { self.#non_opt_fields5 = Some(def.#non_opt_fields5); })* self } fn build(&mut self) -> Result, ::std::boxed::Box> { #(if self.#non_opt_fields2.is_none() { return ::std::result::Result::Err("unset field".into()); })* Ok( Box::new(#name { // FIXME: user must be able to override. Trait? base: #base_type::default(), version: #name::default().version, #(#non_opt_fields3 : self.#non_opt_fields3.clone().unwrap(),)* #(#opt_fields2 : self.#opt_fields2.clone(),)* }) ) } } impl #name_builder3 { pub fn as_any(&self) -> &dyn ::std::any::Any { self } pub fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { self } pub fn as_builder(&self) -> &dyn ::bok::PkgBuilder { self } pub fn as_builder_mut(&mut self) -> &mut dyn ::bok::PkgBuilder { self } #(pub fn #non_opt_fields4 (&mut self, val : #non_opt_types2) -> &mut Self { if self.#non_opt_fields4 != None && self.#non_opt_fields4 != Some(val) { panic!("Package \"{}\": mandatory attribute set multiple times: \"{}\"", ::std::stringify!(#name), ::std::stringify!(#non_opt_fields4)); } self.#non_opt_fields4 = Some(val); self })* #(pub fn #opt_fields3 (&mut self, val : #opt_types2) -> &mut Self { if self.#opt_fields3 != None && self.#opt_fields3 != Some(val) { panic!("Package \"{}\": optional attribute set multiple times: \"{}\"", ::std::stringify!(#name), ::std::stringify!(#opt_fields3)); } self.#opt_fields3 = Some(val); self })* } }; TokenStream::from(expanded) }