426 lines
13 KiB
Rust
426 lines
13 KiB
Rust
/*
|
|
* Copyright 2024 Luca Fulchir <luca.fulchir@runesauth.com>
|
|
*
|
|
* 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<dyn ::std::error::Error>> {
|
|
#(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)
|
|
}
|