azalea_registry_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    Attribute, Ident, LitStr, Token, braced,
5    parse::{Parse, ParseStream, Result},
6    parse_macro_input,
7    punctuated::Punctuated,
8};
9
10struct Registry {
11    name: Ident,
12    items: Vec<RegistryItem>,
13    attrs: Vec<Attribute>,
14}
15
16struct RegistryItem {
17    attrs: Vec<Attribute>,
18    name: Ident,
19    id: String,
20}
21
22impl Parse for RegistryItem {
23    // Air => "minecraft:air"
24    fn parse(input: ParseStream) -> Result<Self> {
25        // parse annotations like #[default]
26        let attrs = input.call(Attribute::parse_outer).unwrap_or_default();
27
28        let name = input.parse()?;
29        input.parse::<Token![=>]>()?;
30        let id = input.parse::<LitStr>()?.value();
31        Ok(RegistryItem { attrs, name, id })
32    }
33}
34
35impl Parse for Registry {
36    fn parse(input: ParseStream) -> Result<Self> {
37        // enum Block {
38        //     Air => "minecraft:air",
39        //     Stone => "minecraft:stone"
40        // }
41
42        // this also includes docs
43        let attrs = input.call(Attribute::parse_outer).unwrap_or_default();
44
45        input.parse::<Token![enum]>()?;
46        let name = input.parse()?;
47        let content;
48        braced!(content in input);
49        let items: Punctuated<RegistryItem, _> =
50            content.parse_terminated(RegistryItem::parse, Token![,])?;
51
52        Ok(Registry {
53            name,
54            items: items.into_iter().collect(),
55            attrs,
56        })
57    }
58}
59
60#[proc_macro]
61pub fn registry(input: TokenStream) -> TokenStream {
62    let input = parse_macro_input!(input as Registry);
63    let name = input.name;
64    let mut generated = quote! {};
65
66    // enum Block {
67    //     Air = 0,
68    //     Stone,
69    // }
70    let mut enum_items = quote! {};
71    for (i, item) in input.items.iter().enumerate() {
72        let attrs = &item.attrs;
73        let name = &item.name;
74        let protocol_id = i as u32;
75        enum_items.extend(quote! {
76            #(#attrs)*
77            #name = #protocol_id,
78        });
79    }
80    let attributes = input.attrs;
81    generated.extend(quote! {
82        #(#attributes)*
83        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, azalea_buf::AzBuf, simdnbt::ToNbtTag, simdnbt::FromNbtTag)]
84        #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
85        #[repr(u32)]
86        pub enum #name {
87            #enum_items
88        }
89    });
90
91    let max_id = input.items.len() as u32;
92
93    let doc_0 = format!("Transmutes a u32 to a {name}.");
94    let doc_1 = format!("The `id` should be at most {max_id}.");
95
96    generated.extend(quote! {
97        impl #name {
98            #[doc = #doc_0]
99            ///
100            /// # Safety
101            #[doc = #doc_1]
102            #[inline]
103            pub unsafe fn from_u32_unchecked(id: u32) -> Self {
104                std::mem::transmute::<u32, #name>(id)
105            }
106
107            #[inline]
108            pub fn is_valid_id(id: u32) -> bool {
109                id <= #max_id
110            }
111        }
112        impl Registry for #name {
113            fn from_u32(value: u32) -> Option<Self> {
114                if Self::is_valid_id(value) {
115                    Some(unsafe { Self::from_u32_unchecked(value) })
116                } else {
117                    None
118                }
119            }
120            fn to_u32(&self) -> u32 {
121                *self as u32
122            }
123        }
124    });
125
126    let doc_0 = format!("Safely transmutes a u32 to a {name}.");
127
128    generated.extend(quote! {
129        impl TryFrom<u32> for #name {
130            type Error = ();
131
132            #[doc = #doc_0]
133            fn try_from(id: u32) -> Result<Self, Self::Error> {
134                if let Some(value) = Self::from_u32(id) {
135                    Ok(value)
136                } else {
137                    Err(())
138                }
139            }
140        }
141    });
142
143    // Display that uses registry ids
144    let mut display_items = quote! {};
145    let mut from_str_items = quote! {};
146    for item in &input.items {
147        let name = &item.name;
148        let id = &item.id;
149        display_items.extend(quote! {
150            Self::#name => write!(f, #id),
151        });
152        from_str_items.extend(quote! {
153            #id => Ok(Self::#name),
154        });
155    }
156    generated.extend(quote! {
157        impl std::fmt::Display for #name {
158            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159                match self {
160                    #display_items
161                }
162            }
163        }
164        impl std::str::FromStr for #name {
165            type Err = String;
166
167            fn from_str(s: &str) -> Result<Self, Self::Err> {
168                match s {
169                    #from_str_items
170                    _ => Err(format!("{s:?} is not a valid {name}", s = s, name = stringify!(#name))),
171                }
172            }
173        }
174    });
175
176    generated.into()
177}