#![recursion_limit = "256"]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
use proc_macro::TokenStream;
use syn::{
punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields,
FieldsNamed, FieldsUnnamed, Ident, Lifetime, Type, WhereClause, WherePredicate,
};
#[proc_macro_derive(SystemData)]
pub fn system_data(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
let gen = impl_system_data(&ast);
gen.into()
}
fn impl_system_data(ast: &DeriveInput) -> proc_macro2::TokenStream {
let name = &ast.ident;
let mut generics = ast.generics.clone();
let (fetch_return, tys) = gen_from_body(&ast.data, name);
let tys = &tys;
let def_fetch_lt = ast
.generics
.lifetimes()
.next()
.expect("There has to be at least one lifetime");
let impl_fetch_lt = &def_fetch_lt.lifetime;
{
let where_clause = generics.make_where_clause();
constrain_system_data_types(where_clause, impl_fetch_lt, tys);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
impl #impl_generics
shred::SystemData< #impl_fetch_lt >
for #name #ty_generics #where_clause
{
fn setup(world: &mut shred::World) {
#(
<#tys as shred::SystemData> :: setup(world);
)*
}
fn fetch(world: & #impl_fetch_lt shred::World) -> Self {
#fetch_return
}
fn reads() -> Vec<shred::ResourceId> {
let mut r = Vec::new();
#( {
let mut reads = <#tys as shred::SystemData> :: reads();
r.append(&mut reads);
} )*
r
}
fn writes() -> Vec<shred::ResourceId> {
let mut r = Vec::new();
#( {
let mut writes = <#tys as shred::SystemData> :: writes();
r.append(&mut writes);
} )*
r
}
}
}
}
fn collect_field_types(fields: &Punctuated<Field, Comma>) -> Vec<Type> {
fields.iter().map(|x| x.ty.clone()).collect()
}
fn gen_identifiers(fields: &Punctuated<Field, Comma>) -> Vec<Ident> {
fields.iter().map(|x| x.ident.clone().unwrap()).collect()
}
fn constrain_system_data_types(clause: &mut WhereClause, fetch_lt: &Lifetime, tys: &[Type]) {
for ty in tys.iter() {
let where_predicate: WherePredicate = parse_quote!(#ty : shred::SystemData< #fetch_lt >);
clause.predicates.push(where_predicate);
}
}
fn gen_from_body(ast: &Data, name: &Ident) -> (proc_macro2::TokenStream, Vec<Type>) {
enum DataType {
Struct,
Tuple,
}
let (body, fields) = match *ast {
Data::Struct(DataStruct {
fields: Fields::Named(FieldsNamed { named: ref x, .. }),
..
}) => (DataType::Struct, x),
Data::Struct(DataStruct {
fields: Fields::Unnamed(FieldsUnnamed { unnamed: ref x, .. }),
..
}) => (DataType::Tuple, x),
_ => panic!("Enums are not supported"),
};
let tys = collect_field_types(fields);
let fetch_return = match body {
DataType::Struct => {
let identifiers = gen_identifiers(fields);
quote! {
#name {
#( #identifiers: shred::SystemData::fetch(world) ),*
}
}
}
DataType::Tuple => {
let count = tys.len();
let fetch = vec![quote! { shred::SystemData::fetch(world) }; count];
quote! {
#name ( #( #fetch ),* )
}
}
};
(fetch_return, tys)
}