From 5d3fde94816c8423d7ab762b006bf5773a641e19 Mon Sep 17 00:00:00 2001 From: Luca Fulchir Date: Sat, 16 Nov 2024 12:52:55 +0100 Subject: [PATCH] bok-macro: use sub modules Signed-off-by: Luca Fulchir --- bok-macro/src/lib.rs | 577 +---------------------------------------- bok-macro/src/pkgs.rs | 541 ++++++++++++++++++++++++++++++++++++++ bok-macro/src/repos.rs | 87 +++++++ 3 files changed, 639 insertions(+), 566 deletions(-) create mode 100644 bok-macro/src/pkgs.rs create mode 100644 bok-macro/src/repos.rs diff --git a/bok-macro/src/lib.rs b/bok-macro/src/lib.rs index 5c90283..06b6f71 100644 --- a/bok-macro/src/lib.rs +++ b/bok-macro/src/lib.rs @@ -16,10 +16,9 @@ */ use ::proc_macro::TokenStream; -use ::quote::quote; -use ::syn::{ - parse::Parser, parse_macro_input, DeriveInput, Fields, ItemStruct, -}; + +mod pkgs; +mod repos; /// Use as #[::bok::repository(MyBaseRepo)] /// Will setup a `base` field that is the given base repo @@ -29,47 +28,7 @@ use ::syn::{ #[::macro_magic::import_tokens_attr] #[proc_macro_attribute] pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream { - let base = parse_macro_input!(attrs as ItemStruct); - let local = parse_macro_input!(input as ItemStruct); - - let Fields::Named(local_fields) = local.fields else { - use ::syn::spanned::Spanned; - return ::syn::Error::new( - local.fields.span(), - "unnamed fields are not supported", - ) - .to_compile_error() - .into(); - }; - - let Fields::Named(base_fields) = base.fields else { - use ::syn::spanned::Spanned; - return ::syn::Error::new( - base.fields.span(), - "unnamed fields are not supported", - ) - .to_compile_error() - .into(); - }; - let local_fields_it = local_fields.named.iter(); - let base_fields_extra = base_fields.named.iter(); - let attrs = local.attrs; - let generics = local.generics; - let ident = local.ident; - let vis = local.vis; - quote! { - #(#attrs) - * - #[::macro_magic::export_tokens] - #[derive(::bok::Repository, Debug)] - #vis struct #ident<#generics> { - #(#base_fields_extra), - * - #(#local_fields_it), - * - } - } - .into() + crate::repos::repository(attrs, input) } /// Unless you know what you are doing, use `#[::bok::repository(MyBaseRepo)]` @@ -85,22 +44,7 @@ pub fn repository(attrs: TokenStream, input: TokenStream) -> TokenStream { /// 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 expanded = quote! { - impl ::bok::Repository for #name { - fn name(&self) -> ::bok::RepoName { - (module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into() - } - fn path(&self) -> ::bok::Path<::bok::RepoName> { - (module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into() - } - } - }; - - TokenStream::from(expanded) + crate::repos::derive_repository(input) } /// Use on a function as `#[::bok::pkg_fn_to_code]` @@ -122,38 +66,8 @@ pub fn derive_repository(input: TokenStream) -> TokenStream { /// let tokens : ::proc_macro2::TokenStream = p.build_code() /// ``` #[proc_macro_attribute] -pub 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 fn pkg_fn_to_code(attrs: TokenStream, input: TokenStream) -> TokenStream { + crate::pkgs::pkg_fn_to_code(attrs, input) } /// Use as #[::bok::package(MyBasePackage)] @@ -167,34 +81,7 @@ pub fn pkg_fn_to_code(_attrs: TokenStream, input: TokenStream) -> TokenStream { /// ``` #[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(), - ); - 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"), - } + crate::pkgs::package(attrs, input) } /// Use as #[::bok::impl_package] @@ -210,106 +97,8 @@ pub fn package(attrs: TokenStream, input: TokenStream) -> TokenStream { /// } /// ``` #[proc_macro_attribute] -pub 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 fn impl_package(attrs: TokenStream, input: TokenStream) -> TokenStream { + crate::pkgs::impl_package(attrs, input) } /// Unless you know what you are doing, use `#[::bok::package(MyBasePackage)]` @@ -327,349 +116,5 @@ pub fn impl_package(_attrs: TokenStream, input: TokenStream) -> TokenStream { /// * deref for package #[proc_macro_derive(Package)] pub 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) + crate::pkgs::derive_package(input) } diff --git a/bok-macro/src/pkgs.rs b/bok-macro/src/pkgs.rs new file mode 100644 index 0000000..c991a33 --- /dev/null +++ b/bok-macro/src/pkgs.rs @@ -0,0 +1,541 @@ +/* + * 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) +} diff --git a/bok-macro/src/repos.rs b/bok-macro/src/repos.rs new file mode 100644 index 0000000..ff8eb80 --- /dev/null +++ b/bok-macro/src/repos.rs @@ -0,0 +1,87 @@ +/* + * 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_macro_input, DeriveInput, Fields, ItemStruct}; + +pub(crate) fn repository( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream { + let base = parse_macro_input!(attrs as ItemStruct); + let local = parse_macro_input!(input as ItemStruct); + + let Fields::Named(local_fields) = local.fields else { + use ::syn::spanned::Spanned; + return ::syn::Error::new( + local.fields.span(), + "unnamed fields are not supported", + ) + .to_compile_error() + .into(); + }; + + let Fields::Named(base_fields) = base.fields else { + use ::syn::spanned::Spanned; + return ::syn::Error::new( + base.fields.span(), + "unnamed fields are not supported", + ) + .to_compile_error() + .into(); + }; + let local_fields_it = local_fields.named.iter(); + let base_fields_extra = base_fields.named.iter(); + let attrs = local.attrs; + let generics = local.generics; + let ident = local.ident; + let vis = local.vis; + quote! { + #(#attrs) + * + #[::macro_magic::export_tokens] + #[derive(::bok::Repository, Debug)] + #vis struct #ident<#generics> { + #(#base_fields_extra), + * + , + #(#local_fields_it), + * + } + } + .into() +} + +pub(crate) fn derive_repository(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident.clone(); + + let expanded = quote! { + impl ::bok::Repository for #name { + fn name(&self) -> ::bok::RepoName { + (module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into() + } + fn path(&self) -> ::bok::Path<::bok::RepoName> { + (module_path!().to_owned() + "::" + ::std::stringify!(#name)).as_str().into() + } + } + }; + + TokenStream::from(expanded) +}