tora_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::ToTokens;
3use syn::parse::Parse;
4use syn::spanned::Spanned;
5use syn::{
6    parse_macro_input, parse_quote, Attribute, Error, Fields, ItemEnum, ItemStruct, LitInt, Type,
7};
8
9mod derive_impl;
10
11fn get_list_attr_or_default<T>(key: &str, default: T, attributes: &[Attribute]) -> T
12where
13    T: Parse,
14{
15    for attribute in attributes {
16        if attribute.meta.path().is_ident(key) {
17            return attribute
18                .meta
19                .require_list()
20                .unwrap()
21                .parse_args::<T>()
22                .unwrap();
23        }
24    }
25    default
26}
27
28fn derive_empty_item_error<T>(tokens: T) -> TokenStream
29where
30    T: ToTokens,
31{
32    Error::new_spanned(tokens, "This macro cannot be derived on empty items")
33        .into_compile_error()
34        .into()
35}
36
37/// The `ReadEnum` macro generates a `FromReader` implementation for enums.
38///
39/// For structs, use [ReadStruct].
40///
41/// # Attributes
42///
43/// ## `type_variant_id($ty)`
44///
45/// This attribute tells the macro what type represents the enum variant ID.
46///
47/// The enum variant ID is the number written to notify the reader of the variant they should expect
48/// to receive.
49///
50/// ```
51/// use tora_derive::ReadEnum;
52///
53/// #[derive(ReadEnum)]
54/// enum Packet {
55///     PlayerJoin, // 0
56///     PlayerQuit // 1
57/// }
58/// ```
59///
60/// By default, this macro assumes [u8].
61///
62/// In the case that the enum deriving this macro contains more than [u8::MAX] variants, the user
63/// will be required to specify this attribute manually.
64///
65/// # Usage
66///
67/// ```
68/// use tora_derive::ReadEnum;
69///
70/// #[derive(ReadEnum)]
71/// #[type_variant_id(u32)]
72/// enum Packet {
73///     Variant1,
74///     Variant2,
75/// }
76/// ```
77///
78/// # Generated code
79///
80/// ```
81/// use std::io;
82/// use std::io::{ErrorKind, Read};
83///
84/// use tora::read::{ToraRead, FromReader};
85///
86/// enum Packet {
87///     Variant1,
88///     Variant2,
89/// }
90///
91/// impl FromReader for Packet {
92///     fn from_reader<R>(r: &mut R) -> io::Result<Self>
93///     where R: Read
94///     {
95///         let id = r.reads::<u32>()?;
96///         Ok(match id {
97///             0 => Self::Variant1,
98///             1 => Self::Variant2,
99///             _ => return Err(io::Error::new(ErrorKind::InvalidInput, "Invalid packet ID"))
100///         })
101///     }
102/// }
103/// ```
104#[proc_macro_derive(ReadEnum, attributes(type_variant_id))]
105pub fn derive_read_enum(tokens: TokenStream) -> TokenStream {
106    let item = parse_macro_input!(tokens as ItemEnum);
107
108    if item.variants.is_empty() {
109        return derive_empty_item_error(item);
110    }
111
112    let path = get_list_attr_or_default("type_variant_id", parse_quote!(u8), &item.attrs);
113    derive_impl::impl_read_enum(item.ident, path, item.variants.into_iter()).into()
114}
115
116/// The `ReadStruct` derive macro generates a `FromReader` implementation for structs.
117///
118/// For enums, use [ReadEnum].
119///
120/// # Usage
121///
122/// ```
123/// use tora_derive::ReadStruct;
124///
125/// #[derive(ReadStruct)]
126/// struct Packet {
127///     message: String,
128/// }
129/// ```
130///
131/// # Generated code
132///
133/// ```
134/// use std::io;
135/// use std::io::Read;
136///
137/// use tora::read::{ToraRead, FromReader};
138///
139/// struct Packet {
140///     message: String,
141/// }
142///
143/// impl FromReader for Packet {
144///     fn from_reader<R>(r: &mut R) -> io::Result<Self>
145///     where R: Read
146///     {
147///         Ok(Self { message: r.reads()? })
148///     }
149/// }
150/// ```
151#[proc_macro_derive(ReadStruct)]
152pub fn derive_read_struct(tokens: TokenStream) -> TokenStream {
153    let item = parse_macro_input!(tokens as ItemStruct);
154
155    if item.fields.is_empty() {
156        return derive_empty_item_error(item);
157    }
158
159    match item.fields {
160        Fields::Named(f) => derive_impl::impl_read_struct_named(
161            item.ident,
162            f.named.into_iter().map(|f| f.ident.unwrap()),
163        ),
164        Fields::Unnamed(f) => {
165            derive_impl::impl_read_struct_tuple(item.ident, f.unnamed.into_iter().map(|f| f.ty))
166        }
167        Fields::Unit => return derive_empty_item_error(item),
168    }
169    .into()
170}
171
172/// The `WriteStruct` derive macro generates a `SerializeIo` implementation for structs.
173///
174/// # Usage
175///
176/// ```
177/// use tora_derive::WriteStruct;
178///
179/// #[derive(WriteStruct)]
180/// struct Packet {
181///     message: String,
182/// }
183/// ```
184///
185/// # Generated code
186///
187/// ```
188/// use std::io;
189/// use std::io::Write;
190///
191/// use tora::write::{ToraWrite, SerializeIo};
192///
193/// struct Packet {
194///     message: String,
195/// }
196///
197/// impl SerializeIo for Packet {
198///     fn serialize<W>(&self, w: &mut W) -> io::Result<()>
199///     where W: Write
200///     {
201///         w.writes(&self.message)
202///     }
203/// }
204/// ```
205#[proc_macro_derive(WriteStruct)]
206pub fn derive_write_struct(tokens: TokenStream) -> TokenStream {
207    let item = parse_macro_input!(tokens as ItemStruct);
208
209    if item.fields.is_empty() {
210        return derive_empty_item_error(item);
211    }
212    let types = item.fields.into_iter().enumerate().map(|(i, f)| {
213        f.ident
214            .as_ref()
215            .map(|i| i.to_token_stream())
216            .unwrap_or_else(|| LitInt::new(&i.to_string(), f.span()).to_token_stream())
217    });
218    derive_impl::impl_write_struct(item.ident, types).into()
219}
220
221/// The `WriteEnum` derive macro generates a `SerializeIo` implementation for enums.
222///
223/// Opposite of the `ReadEnum` macro.
224///
225/// # Attributes
226///
227/// ## `type_variant_id($ty)`
228///
229/// This attribute tells the macro what type represents the enum variant ID.
230///
231/// The enum variant ID is the number written to notify the reader of the variant they should expect
232/// to receive.
233///
234/// ```
235/// use tora_derive::WriteEnum;
236///
237/// #[derive(WriteEnum)]
238/// enum Packet {
239///     PlayerJoin, // 0
240///     PlayerQuit // 1
241/// }
242/// ```
243///
244/// By default, this macro assumes [u8].
245///
246/// In the case that the enum deriving this macro contains more than [u8::MAX] variants, the user
247/// will be required to specify this attribute manually.
248#[proc_macro_derive(WriteEnum, attributes(type_variant_id))]
249pub fn derive_write_enum(tokens: TokenStream) -> TokenStream {
250    let item = parse_macro_input!(tokens as ItemEnum);
251
252    if item.variants.is_empty() {
253        return derive_empty_item_error(item);
254    }
255
256    let ty: Type = get_list_attr_or_default("type_variant_id", parse_quote!(u8), &item.attrs);
257    derive_impl::impl_write_enum(item.ident, ty, item.variants.into_iter()).into()
258}