extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use proc_macro_error::*;
use quote::quote;
use std::collections::HashMap;
const DERIVE_NAME: &'static str = "Arraygen";
const DECL_FN_NAME: &'static str = "gen_array";
const INCLUDE_FIELD: &'static str = "in_array";
#[proc_macro_error]
#[proc_macro_derive(Arraygen, attributes(gen_array, in_array))]
pub fn arraygen(input: TokenStream) -> TokenStream {
ImplContext::new(input, DERIVE_NAME).transform_ast()
}
struct ImplContext {
ast: syn::DeriveInput,
derive_name: &'static str,
}
impl ImplContext {
fn new(input: TokenStream, derive_name: &'static str) -> ImplContext {
ImplContext {
ast: syn::parse(input).expect("Could not parse AST."),
derive_name,
}
}
fn transform_ast(&self) -> TokenStream {
let mut methods = self
.ast
.attrs
.clone()
.into_iter()
.flat_map(|attr| {
let attribute = attr.clone();
attr.path.segments.into_iter().filter_map(move |segment| {
if segment.ident == DECL_FN_NAME {
Some((attribute.clone(), segment.ident.span()))
} else {
None
}
})
})
.map(|(attr, span)| parse_declared_method(attr.tokens, span))
.fold(HashMap::new(), |mut acc, method| {
if acc.contains_key(&method.name) {
abort!(
method.name.span(),
"{} found two or more methods declared with the name '{}'.",
DECL_FN_NAME,
method.name
)
} else {
acc.insert(method.name.clone(), method);
acc
}
});
match self.ast.data {
syn::Data::Struct(ref class) => read_fields(&class.fields, &mut methods),
_ => abort!(
self.ast.ident.span(),
"The type '{}' is not a struct but tries to derive '{}' which can only be used on structs.",
self.ast.ident,
self.derive_name
),
}
if methods.len() == 0 {
emit_warning!(
self.ast.ident.span(),
"The type '{}' derives '{}' but does not contain any '{}' attribute, so '{}' does nothing.",
self.ast.ident,
self.derive_name,
DECL_FN_NAME,
self.derive_name
);
return quote! {}.into();
}
let methods =
methods
.into_iter()
.fold(Vec::<TokenTree>::new(), |mut acc, (name, method)| {
if method.fields.len() == 0 {
emit_warning!(
method.name.span(),
"Method '{}' returns an empty array.",
name
);
}
acc.extend(make_method_tokens(&method));
acc
});
let (ty, generics) = (&self.ast.ident, &self.ast.generics);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let tokens = quote! {
impl #impl_generics #ty #ty_generics
#where_clause
{
#(#methods)
*
}
};
tokens.into()
}
}
#[derive(Debug)]
struct DeclaredFunction {
name: syn::Ident,
vis: proc_macro2::TokenStream,
ty: proc_macro2::TokenStream,
body: proc_macro2::TokenStream,
is_mut: bool,
is_ref: bool,
fields: Vec<syn::Ident>,
}
enum FunctionParsing {
Begin,
ExpectingFnOrPubCrate,
ExpectingFn,
ExpectingName,
ExpectingColon,
ExpectingArrowEnd,
ExpectingType,
Error,
}
fn parse_declared_method(
tokens: proc_macro2::TokenStream,
gen_array_span: proc_macro2::Span,
) -> DeclaredFunction {
let mut search_element = FunctionParsing::Begin;
let mut name: Option<syn::Ident> = None;
let mut ty: Vec<TokenTree> = vec![];
let mut vis: Vec<TokenTree> = vec![];
let mut body: Vec<TokenTree> = vec![];
for token in tokens.into_iter() {
match token {
TokenTree::Group(group) => {
for token in group.stream().into_iter() {
if let FunctionParsing::ExpectingType = search_element {
ty.push(token.clone());
continue;
}
match token {
TokenTree::Ident(ref ident) => match search_element {
FunctionParsing::Begin => match ident.to_string().as_ref() {
"pub" => {
vis.push(token.clone());
search_element = FunctionParsing::ExpectingFnOrPubCrate;
}
"fn" => {
body.push(token.clone());
search_element = FunctionParsing::ExpectingName;
}
_ => search_element = FunctionParsing::Error,
},
FunctionParsing::ExpectingFnOrPubCrate
| FunctionParsing::ExpectingFn => match ident.to_string().as_ref() {
"fn" => {
body.push(token.clone());
search_element = FunctionParsing::ExpectingName;
}
_ => search_element = FunctionParsing::Error,
},
FunctionParsing::ExpectingName => {
name = Some(ident.clone());
body.push(token.clone());
search_element = FunctionParsing::ExpectingColon;
}
_ => search_element = FunctionParsing::Error,
},
TokenTree::Group(_) => match search_element {
FunctionParsing::ExpectingFnOrPubCrate => {
vis.push(token.clone());
search_element = FunctionParsing::ExpectingFn;
}
_ => search_element = FunctionParsing::Error,
},
TokenTree::Punct(ref punct) => match search_element {
FunctionParsing::ExpectingArrowEnd => {
if punct.to_string() == ">" {
search_element = FunctionParsing::ExpectingType;
} else {
search_element = FunctionParsing::Error;
}
}
FunctionParsing::ExpectingColon => {
if punct.to_string() == ":" {
search_element = FunctionParsing::ExpectingType;
} else if punct.to_string() == "-" {
search_element = FunctionParsing::ExpectingArrowEnd;
} else {
search_element = FunctionParsing::Error
}
}
_ => search_element = FunctionParsing::Error,
},
_ => search_element = FunctionParsing::Error,
}
}
}
_ => search_element = FunctionParsing::Error,
}
}
if ty.len() == 0 {
search_element = FunctionParsing::Error;
}
let is_ref = ty.len() >= 1 && ty[0].to_string() == "&";
let is_mut = is_ref && ty.len() >= 2 && ty[1].to_string() == "mut";
let decl_fn = if let Some(name) = name {
Some(DeclaredFunction {
name,
vis: vis.into_iter().collect(),
ty: ty.into_iter().collect(),
body: body.into_iter().collect(),
is_mut,
is_ref,
fields: vec![],
})
} else {
None
};
match search_element {
FunctionParsing::ExpectingType => {
if let Some(decl_fn) = decl_fn {
return decl_fn;
}
}
FunctionParsing::Error => {
if let Some(decl_fn) = decl_fn {
abort!(decl_fn.name.span(), "'{}' tried to declare a method '{}', but the return type syntax was wrong.", DECL_FN_NAME, decl_fn.name;
help = "Correct syntax is {}", decl_fn_correct_syntax(&decl_fn););
} else {
abort!(gen_array_span, "'{}' was used with the wrong syntax.", DECL_FN_NAME;
help = "Correct syntax is {}", decl_fn_correct_syntax_without_name());
}
}
_ => {}
}
abort!(
gen_array_span,
"Bug on '{}', contact with the maintainer of {} crate.",
DECL_FN_NAME,
DERIVE_NAME
);
}
fn read_fields(fields: &syn::Fields, methods: &mut HashMap<syn::Ident, DeclaredFunction>) {
for field in fields.iter() {
if field.attrs.is_empty() {
continue;
}
if let Some(ref ident) = field.ident {
for attr in field.attrs.iter() {
let segments: Vec<_> = attr
.path
.segments
.iter()
.filter_map(|segment| {
if segment.ident == INCLUDE_FIELD {
Some(segment.ident.clone())
} else {
None
}
})
.collect();
let include_ident = match segments.len() {
0 => continue,
1 => &segments[0],
_ => abort!(
segments[0].span(),
"Wrong syntax, used multiple '{}' in same attribute.",
INCLUDE_FIELD
),
};
let mut error = false;
let mut correct_fns = vec![];
let mut need_comma = false;
for token in attr.tokens.clone() {
match token {
TokenTree::Group(group) => {
for token in group.stream().into_iter() {
match token {
TokenTree::Ident(name) => {
if need_comma {
error = true;
}
match methods.get_mut(&name) {
Some(ref mut method) => {
let has_this_field_already = method.fields.iter().any(|field| field == ident);
if has_this_field_already {
abort!(include_ident.span(), "Field '{}' is already included in method '{}', no need to repeat it.", ident, name;
help = "Remove the repeated entries.");
}
method.fields.push(ident.clone());
need_comma = true;
}
None => error = true,
}
correct_fns.push(name.clone());
}
TokenTree::Punct(punct) => {
if need_comma && punct.to_string() == "," {
need_comma = false;
} else {
error = true;
}
}
_ => error = true,
}
}
}
_ => error = true,
}
}
if error {
if correct_fns.len() > 0 {
for correct_fn in &correct_fns {
if let None = methods.get_mut(&correct_fn) {
abort!(correct_fn.span(), "Method '{}' was not declared with the attribute '{}' at struct level.", correct_fn, DECL_FN_NAME);
}
}
let correct_fns = correct_fns
.iter()
.map(|ident| ident.to_string())
.collect::<Vec<String>>()
.join(", ");
abort!(include_ident.span(), "'{}' shouldn't contain those tokens.", INCLUDE_FIELD;
help = "Correct syntax is {}", include_field_correct_syntax(&correct_fns));
} else {
abort!(include_ident.span(), "'{}' was used with the wrong syntax.", INCLUDE_FIELD;
help = "Correct syntax is {}", include_field_correct_syntax_without_name());
}
}
}
}
}
}
fn make_method_tokens(props: &DeclaredFunction) -> proc_macro2::TokenStream {
let field_idents = &props.fields;
let count = field_idents.len();
let return_type = &props.ty;
let vis = &props.vis;
let body = &props.body;
let refa = if props.is_ref {
if props.is_mut {
quote! {&mut}
} else {
quote! {&}
}
} else {
quote! {}
};
let muta = if props.is_mut {
quote! {mut}
} else {
quote! {}
};
quote! {
#[inline(always)]
#vis #body (& #muta self) -> [#return_type; #count] {
[#(#refa self.#field_idents),*]
}
}
}
fn decl_fn_correct_syntax_without_name() -> String {
format!("#[{}(fn your_function_name: YourReturnType)]", DECL_FN_NAME)
}
fn decl_fn_correct_syntax(decl_fn: &DeclaredFunction) -> String {
let vis = &decl_fn.vis;
let body = &decl_fn.body;
let signature = quote! {
#vis #body: YourReturnType
};
format!("#[{}({})]", DECL_FN_NAME, signature.to_string())
}
fn include_field_correct_syntax_without_name() -> String {
format!("#[{}(your_generated_function_name)]", INCLUDE_FIELD)
}
fn include_field_correct_syntax(correct_fns: &String) -> String {
format!("#[{}({})]", INCLUDE_FIELD, correct_fns)
}