1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00
azalea/azalea-registry/azalea-registry-macros/src/lib.rs
2025-02-22 23:01:54 +00:00

171 lines
4.6 KiB
Rust
Executable file

use proc_macro::TokenStream;
use quote::quote;
use syn::{
Attribute, Ident, LitStr, Token, braced,
parse::{Parse, ParseStream, Result},
parse_macro_input,
punctuated::Punctuated,
};
struct RegistryItem {
name: Ident,
id: String,
}
struct Registry {
name: Ident,
items: Vec<RegistryItem>,
attributes: Vec<Attribute>,
}
impl Parse for RegistryItem {
// Air => "minecraft:air"
fn parse(input: ParseStream) -> Result<Self> {
let name = input.parse()?;
input.parse::<Token![=>]>()?;
let id = input.parse::<LitStr>()?.value();
Ok(RegistryItem { name, id })
}
}
impl Parse for Registry {
fn parse(input: ParseStream) -> Result<Self> {
// enum Block {
// Air => "minecraft:air",
// Stone => "minecraft:stone"
// }
// this also includes docs
let attributes = input.call(Attribute::parse_outer).unwrap_or_default();
input.parse::<Token![enum]>()?;
let name = input.parse()?;
let content;
braced!(content in input);
let items: Punctuated<RegistryItem, _> =
content.parse_terminated(RegistryItem::parse, Token![,])?;
Ok(Registry {
name,
items: items.into_iter().collect(),
attributes,
})
}
}
#[proc_macro]
pub fn registry(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Registry);
let name = input.name;
let mut generated = quote! {};
// enum Block {
// Air = 0,
// Stone,
// }
let mut enum_items = quote! {};
for (i, item) in input.items.iter().enumerate() {
let name = &item.name;
let protocol_id = i as u32;
enum_items.extend(quote! {
#name = #protocol_id,
});
}
let attributes = input.attributes;
generated.extend(quote! {
#(#attributes)*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, azalea_buf::AzBuf, simdnbt::ToNbtTag, simdnbt::FromNbtTag)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[repr(u32)]
pub enum #name {
#enum_items
}
});
let max_id = input.items.len() as u32;
let doc_0 = format!("Transmutes a u32 to a {name}.");
let doc_1 = format!("The `id` should be at most {max_id}.");
generated.extend(quote! {
impl #name {
#[doc = #doc_0]
///
/// # Safety
#[doc = #doc_1]
#[inline]
pub unsafe fn from_u32_unchecked(id: u32) -> Self {
std::mem::transmute::<u32, #name>(id)
}
#[inline]
pub fn is_valid_id(id: u32) -> bool {
id <= #max_id
}
}
impl Registry for #name {
fn from_u32(value: u32) -> Option<Self> {
if Self::is_valid_id(value) {
Some(unsafe { Self::from_u32_unchecked(value) })
} else {
None
}
}
fn to_u32(&self) -> u32 {
*self as u32
}
}
});
let doc_0 = format!("Safely transmutes a u32 to a {name}.");
generated.extend(quote! {
impl TryFrom<u32> for #name {
type Error = ();
#[doc = #doc_0]
fn try_from(id: u32) -> Result<Self, Self::Error> {
if let Some(value) = Self::from_u32(id) {
Ok(value)
} else {
Err(())
}
}
}
});
// Display that uses registry ids
let mut display_items = quote! {};
let mut from_str_items = quote! {};
for item in &input.items {
let name = &item.name;
let id = &item.id;
display_items.extend(quote! {
Self::#name => write!(f, #id),
});
from_str_items.extend(quote! {
#id => Ok(Self::#name),
});
}
generated.extend(quote! {
impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#display_items
}
}
}
impl std::str::FromStr for #name {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#from_str_items
_ => Err(format!("{s:?} is not a valid {name}", s = s, name = stringify!(#name))),
}
}
}
});
generated.into()
}