use proc_macro2::{TokenStream};
use syn::{ItemStruct, Field, Ident, Result, Token, punctuated::Punctuated, Type, Visibility, Expr, Path, Lit};
use syn::parse::{Parse, ParseStream};
use quote::{quote, ToTokens};
use super::{Generate, RoopertAttribute, RoopertAttributeType, GetterAttribute, SetterAttribute};
use super::parse::{is_getter_attribute, is_setter_attribute, is_roopert_attribute, single_path_segment};
#[cfg_attr(feature="verbose", derive(Debug))]
enum AccessorAutoRule {
All,
Private,
No,
}
#[derive(Clone)]
struct FieldMetadata {
ty: Type,
ident: Ident,
}
impl FieldMetadata {
fn from_named_field(field: &Field) -> Self {
Self {
ty: field.ty.clone(),
ident: field.ident.clone().unwrap(),
}
}
}
impl AccessorAutoRule {
fn needs_accessor(&self, field: &Field) -> bool {
match self {
AccessorAutoRule::All => true,
AccessorAutoRule::Private => field.vis == Visibility::Inherited,
AccessorAutoRule::No => false,
}
}
fn from_assignment_str(value: &str, input: ParseStream, ctx: &str) -> Result<AccessorAutoRule> {
match value {
"all" => Ok(AccessorAutoRule::All),
"private" => Ok(AccessorAutoRule::Private),
"no" => Ok(AccessorAutoRule::No),
_ => Err(input.error(format!("Unrecognised right hand side of assignment in #[roopert(accesssor, ..., {} = {}]", ctx, value)))
}
}
}
#[cfg_attr(feature="verbose", derive(Debug))]
pub struct AccessorsAttribute {
getter_rule: AccessorAutoRule,
setter_rule: AccessorAutoRule,
}
impl AccessorsAttribute {
fn rule_from_expr(assignee: &Ident, expr: &Expr, input: ParseStream) -> Result<AccessorAutoRule> {
match expr {
Expr::Path(var) =>
AccessorAutoRule::from_assignment_str(&single_path_segment(&var.path, input, accessor_path_err_rule)?.to_string().to_lowercase(), input, &assignee.to_string()),
Expr::Lit(literal) => {
match &literal.lit {
Lit::Str(lit_str) =>
AccessorAutoRule::from_assignment_str(&lit_str.value().to_lowercase(), input, &assignee.to_string()),
_ => Err(input.error(format!("Unsupported literal type in right hand side of assignment in #[roopert(accessor, ..., {} = ???]", assignee.to_string())))
}
},
_ => Err(input.error(format!("Unrecognised right hand side of assignment in #[roopert(accessor, ..., {} = ???)]", assignee.to_string())))
}
}
}
impl Parse for AccessorsAttribute {
fn parse(input: ParseStream) -> Result<Self> {
let mut get_rule = None;
let mut set_rule = None;
let params = Punctuated::<Expr, Token![,]>::parse_terminated(input)?;
for p in params.iter() {
match p {
Expr::Assign(assign) => {
if let Expr::Path(var) = &*assign.left {
let ident = single_path_segment(&var.path, input, accessor_path_err_left)?;
match &ident.to_string().to_lowercase() as &str {
"get" => {
get_rule = Some(Self::rule_from_expr(&ident, &*assign.right, input)?);
Ok(())
},
"set" => {
set_rule = Some(Self::rule_from_expr(&ident, &*assign.right, input)?);
Ok(())
}
_ => Err(input.error(format!("Unrecognised assignment {} in #[roopert(accessor, ...)]", ident.to_string())))
}
} else {
Err(input.error("Unsupported left hand side of assignment in #[roopert(accessor, ..., ??? = ...]"))
}
},
_ => Err(input.error(format!("Unrecognised attribute parameter {} in #[roopert(accessor, ...)]", p.to_token_stream())))
}?;
}
Ok(Self{
getter_rule: get_rule.unwrap_or(AccessorAutoRule::No),
setter_rule: set_rule.unwrap_or(AccessorAutoRule::No),
})
}
}
impl Generate for AccessorsAttribute {
fn generate(&mut self, input: TokenStream) -> core::result::Result<TokenStream, String> {
let mut target_struct: ItemStruct = syn::parse(input.into()).map_err(|_| "Only named structs objects can have roopert accessors".to_string())?;
let target_struct_ident = &target_struct.ident.clone();
let mut getters: Vec<(FieldMetadata, GetterAttribute)> = Vec::new();
let mut setters: Vec<(FieldMetadata, SetterAttribute)> = Vec::new();
for field in target_struct.fields.iter_mut() {
let mut setter_found = false;
let mut getter_found = false;
let field_meta = FieldMetadata::from_named_field(field);
let mut new_attributes = Vec::with_capacity(field.attrs.len());
for attr in &field.attrs {
let is_getter_path = is_getter_attribute(attr);
let is_setter_path = is_setter_attribute(attr);
let is_roopert_path = is_roopert_attribute(attr);
if is_getter_path {
let getter = attr.parse_args::<GetterAttribute>().map_err(|_| "Malformed roopert #[get] attribute".to_string())?;
getter_found = true;
getters.push((field_meta.clone(), getter));
} else if is_setter_path {
let setter = attr.parse_args::<SetterAttribute>().map_err(|_| "Malformed roopert #[set] attribute".to_string())?;
setter_found = true;
setters.push((field_meta.clone(), setter));
} else if is_roopert_path {
let parsed_attr = attr.parse_args::<RoopertAttribute>().map_err(|e| format!("Malformed #[roopert(...)] attribute: {}", e))?;
match parsed_attr.attr {
RoopertAttributeType::Get(getter) => {
getter_found = true;
getters.push((
field_meta.clone(), getter
))
},
RoopertAttributeType::Set(setter) => {
setter_found = true;
setters.push((
field_meta.clone(), setter
))
},
_ => {
new_attributes.push(attr.clone()); continue;
}
}
} else {
new_attributes.push(attr.clone()); }
}
field.attrs = new_attributes;
if !setter_found && self.setter_rule.needs_accessor(field) {
setters.push((field_meta.clone(), SetterAttribute::with_accessor_defaults()));
}
if !getter_found && self.getter_rule.needs_accessor(field) {
getters.push((field_meta.clone(), GetterAttribute::with_accessor_defaults()));
}
}
let mut getter_tokens = Vec::new();
for (meta, attr) in getters {
getter_tokens.push(attr.impl_get_fn(&meta.ident, &meta.ty));
}
let mut setter_tokens = Vec::new();
for (meta, attr) in setters {
setter_tokens.push(attr.impl_set_fn(&meta.ident, &meta.ty));
}
let (impl_generics, ty_generics, where_clause) = target_struct.generics.split_for_impl();
Ok(quote!{
#target_struct
impl #impl_generics #target_struct_ident #ty_generics #where_clause {
#(#getter_tokens)*
#(#setter_tokens)*
}
})
}
fn auto_append(&self) -> bool {false}
}
fn accessor_path_err_left(path: &Path) -> String {
format!("Unsupported path in left hand side of assignment in attribute #[roopert(accessors, ... = {})]", path.to_token_stream())
}
fn accessor_path_err_rule(path: &Path) -> String {
format!("Unsupported path in right hand side of assignment in attribute #[roopert(accessors, ... = {})]", path.to_token_stream())
}