azalea_registry_macros/
lib.rs1use 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 fn parse(input: ParseStream) -> Result<Self> {
25 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 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 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 #[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 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}