prc_rs_derive/
lib.rs

1extern crate proc_macro;
2use crate::proc_macro::TokenStream;
3use quote::quote;
4use syn::parse::{Parse, ParseStream};
5use syn::punctuated::Punctuated;
6use syn::token::Comma;
7use syn::token::Eq;
8
9use syn::Lit;
10use syn::Path;
11use syn::{Attribute, Data, DeriveInput, Field, Fields, Ident, Result as SynResult};
12
13const NAMED_STRUCT_ONLY_ERR: &str = "Derive macro only implemented for named structs";
14
15const INVALID_ATTR_NAME: &str = "Invalid struct attribute. Accepted name is only 'path'";
16const INVALID_ATTR_COUNT: &str =
17    "Invalid struct attributes. Only use 'path' attribute in struct once";
18
19const INVALID_FIELD_ATTR_NAME: &str =
20    "Invalid field attribute. Accepted attribute names are 'name', and 'hash'";
21const INVALID_FIELD_ATTR_COUNT: &str =
22    "Invalid field attributes. Only use 'name' or 'hash' attribute in field once";
23
24#[proc_macro_derive(Prc, attributes(prc))]
25pub fn prc_derive(input: TokenStream) -> TokenStream {
26    match derive_or_error(input) {
27        Err(err) => err.to_compile_error().into(),
28        Ok(result) => result,
29    }
30}
31
32fn derive_or_error(input: TokenStream) -> SynResult<TokenStream> {
33    let input: DeriveInput = syn::parse(input)?;
34    let ident = input.ident;
35
36    let attrs = parse_struct_attributes(&input.attrs)?;
37
38    match input.data {
39        Data::Struct(data_struct) => match &data_struct.fields {
40            Fields::Named(fields) => derive_named_struct(ident, &attrs, &fields.named),
41            Fields::Unnamed(..) => panic!("{}", NAMED_STRUCT_ONLY_ERR),
42            Fields::Unit => panic!("{}", NAMED_STRUCT_ONLY_ERR),
43        },
44        _ => panic!("{}", NAMED_STRUCT_ONLY_ERR),
45    }
46}
47
48#[derive(Default)]
49struct MainAttributes {
50    path: Option<Path>,
51}
52
53enum MainAttribute {
54    Path(Path),
55}
56
57enum FieldAttribute {
58    // in case we want something more general later, add this
59    // From(Tokens),
60    Name(Lit),
61    Hash(Lit),
62}
63
64fn parse_struct_attributes(attrs: &[Attribute]) -> SynResult<MainAttributes> {
65    let mut attributes = MainAttributes::default();
66
67    attrs
68        .iter()
69        .filter(|attr| attr.path.is_ident("prc"))
70        .try_for_each(|attr| {
71            let attr_kind: MainAttribute = attr.parse_args()?;
72            match attr_kind {
73                MainAttribute::Path(path) => {
74                    if attributes.path.is_some() {
75                        panic!("{}", INVALID_ATTR_COUNT);
76                    } else {
77                        attributes.path = Some(path);
78                    }
79                }
80            }
81
82            SynResult::Ok(())
83        })?;
84
85    Ok(attributes)
86}
87
88impl Parse for MainAttribute {
89    fn parse(input: ParseStream) -> SynResult<Self> {
90        let key: Ident = input.parse()?;
91        let struct_attr = match key.to_string().as_ref() {
92            "path" => {
93                let _eq: Eq = input.parse()?;
94                MainAttribute::Path(input.parse()?)
95            }
96            _ => panic!("{}", INVALID_ATTR_NAME),
97        };
98
99        SynResult::Ok(struct_attr)
100    }
101}
102
103impl Parse for FieldAttribute {
104    fn parse(input: ParseStream) -> SynResult<Self> {
105        let key: Ident = input.parse()?;
106        match key.to_string().as_ref() {
107            "name" => {
108                let _eq: Eq = input.parse()?;
109                Ok(FieldAttribute::Name(input.parse()?))
110            }
111            "hash" => {
112                let _eq: Eq = input.parse()?;
113                Ok(FieldAttribute::Hash(input.parse()?))
114            }
115            // "from" => {}
116            _ => Err(input.error(INVALID_FIELD_ATTR_NAME)),
117        }
118    }
119}
120
121fn derive_named_struct(
122    ident: Ident,
123    attrs: &MainAttributes,
124    fields: &Punctuated<Field, Comma>,
125) -> SynResult<TokenStream> {
126    let path = attrs
127        .path
128        .as_ref()
129        .map(|some_path| quote!(#some_path))
130        .unwrap_or(quote!(::prc));
131
132    let names = fields
133        .iter()
134        .map(|field| {
135            let attrs = field
136                .attrs
137                .iter()
138                .filter(|attr| attr.path.is_ident("prc"))
139                .map(|attr| attr.parse_args())
140                .collect::<Result<Vec<_>, _>>()?;
141
142            if attrs.len() > 1 {
143                panic!("{}", INVALID_FIELD_ATTR_COUNT);
144            }
145
146            let ident = field.ident.as_ref().unwrap();
147            let ident_string = ident.to_string();
148
149            let hash = match attrs.get(0) {
150                Some(FieldAttribute::Hash(hash)) => quote!(#path::hash40::Hash40(#hash)),
151                Some(FieldAttribute::Name(name)) => quote!(#path::hash40::hash40(#name)),
152                None => quote!(#path::hash40::hash40(#ident_string)),
153            };
154
155            Ok((ident, hash))
156        })
157        .collect::<SynResult<Vec<_>>>()?;
158
159    let struct_names = names.iter().map(|name| name.0);
160    let hashes = names.iter().map(|name| &name.1);
161
162    Ok(quote! {
163        impl Prc for #ident {
164            fn read_param<R: ::std::io::Read + ::std::io::Seek>(reader: &mut R, offsets: #path::prc_trait::FileOffsets) -> #path::prc_trait::Result<Self> {
165                let data = #path::prc_trait::StructData::from_stream(reader)?;
166                Ok(Self {
167                    #(
168                        #struct_names: Prc::read_from_struct(reader, #hashes, offsets, data)?,
169                    )*
170                })
171            }
172        }
173    }
174    .into())
175}