Skip to main content

azalea_block_macros/
lib.rs

1//! An internal crate used by `azalea_block`.
2
3mod property;
4mod utils;
5
6use std::{collections::HashMap, fmt::Write};
7
8use proc_macro::TokenStream;
9use proc_macro2::TokenTree;
10use quote::quote;
11use syn::{
12    Expr, Ident, Token, braced,
13    parse::{Parse, ParseStream, Result},
14    parse_macro_input,
15};
16use utils::{combinations_of, to_pascal_case};
17
18use crate::property::{
19    generate::{
20        generate_properties_code, get_property_value_type, get_property_variant_types,
21        make_property_struct_names_to_names,
22    },
23    parse::{PropertyDefinition, PropertyWithNameAndDefault, parse_property_definitions},
24};
25
26// must be the same as the type in `azalea-block/src/lib.rs`
27type BlockStateIntegerRepr = u16;
28
29/// ```ignore
30/// grass_block => BlockBehavior::default(), {
31///   "snowy": false,
32/// },
33/// ```
34struct BlockDefinition {
35    name: Ident,
36    behavior: Expr,
37    properties_and_defaults: Vec<PropertyWithNameAndDefault>,
38}
39
40struct BlockDefinitions {
41    blocks: Vec<BlockDefinition>,
42}
43
44impl Parse for BlockDefinition {
45    fn parse(input: ParseStream) -> Result<Self> {
46        // acacia_button => BlockBehavior::new().strength(0.5, 0.5), {
47        //     "face": Face::Wall,
48        //     "facing": FacingCardinal::North,
49        //     "powered": Powered(false),
50        // },
51        let name = input.parse()?;
52        input.parse::<Token![=>]>()?;
53        let behavior = input.parse()?;
54
55        input.parse::<Token![,]>()?;
56        let content;
57        braced!(content in input);
58
59        let mut properties_and_defaults = Vec::new();
60
61        // read the things comma-separated
62        let property_and_default_punctuated =
63            content.parse_terminated(PropertyWithNameAndDefault::parse, Token![,])?;
64
65        for property_and_default in property_and_default_punctuated {
66            properties_and_defaults.push(property_and_default);
67        }
68
69        Ok(BlockDefinition {
70            name,
71            behavior,
72            properties_and_defaults,
73        })
74    }
75}
76
77impl Parse for BlockDefinitions {
78    fn parse(input: ParseStream) -> Result<Self> {
79        let mut blocks = Vec::new();
80
81        let block_definitions_punctuated =
82            input.parse_terminated(BlockDefinition::parse, Token![,])?;
83        for block_definition in block_definitions_punctuated {
84            blocks.push(block_definition);
85        }
86
87        Ok(BlockDefinitions { blocks })
88    }
89}
90
91struct MakeBlockStates {
92    pub properties: Vec<PropertyDefinition>,
93    pub blocks: BlockDefinitions,
94}
95impl Parse for MakeBlockStates {
96    fn parse(input: ParseStream) -> Result<Self> {
97        // Properties => { ... } Blocks => { ... }
98        let properties_ident = input.parse::<Ident>()?;
99        assert_eq!(properties_ident.to_string(), "Properties");
100        input.parse::<Token![=>]>()?;
101        let content;
102        braced!(content in input);
103        let properties = parse_property_definitions(&content)?;
104
105        input.parse::<Token![,]>()?;
106
107        let blocks_ident = input.parse::<Ident>()?;
108        assert_eq!(blocks_ident.to_string(), "Blocks");
109        input.parse::<Token![=>]>()?;
110        let content;
111        braced!(content in input);
112        let blocks = content.parse()?;
113
114        Ok(MakeBlockStates { properties, blocks })
115    }
116}
117
118struct PropertyVariantData {
119    pub block_state_ids: Vec<BlockStateIntegerRepr>,
120    pub kind: PropertyKind,
121    pub ident: Ident,
122    pub index: usize,
123}
124
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126enum PropertyKind {
127    Enum,
128    Bool,
129}
130
131#[derive(Clone, Debug)]
132struct PropertyVariantMeta {
133    pub ident: Ident,
134    pub index: usize,
135}
136
137#[proc_macro]
138pub fn make_block_states(input: TokenStream) -> TokenStream {
139    let input = parse_macro_input!(input as MakeBlockStates);
140
141    let mut properties_map = HashMap::new();
142    for property in &input.properties {
143        let property_value_type = get_property_value_type(&property.data);
144        let property_variant_types = get_property_variant_types(&property.data);
145        properties_map.insert(property_value_type.to_string(), property_variant_types);
146    }
147
148    let property_struct_names_to_names = make_property_struct_names_to_names(&input.properties);
149
150    let mut block_state_enum_variants = quote! {};
151    let mut block_structs = quote! {};
152
153    let mut from_state_to_block_match = quote! {};
154    let mut from_kind_to_block_match = quote! {};
155    let mut from_kind_to_state_match = quote! {};
156    let mut from_kind_to_states_match = quote! {};
157
158    let mut from_state_to_kind_table = quote! {};
159
160    // keys are enum names like Waterlogged
161    let mut properties_to_state_ids = HashMap::<String, Vec<PropertyVariantData>>::new();
162
163    let last_block_kind_id = u32::try_from(input.blocks.blocks.len() - 1).unwrap();
164
165    let mut state_id: BlockStateIntegerRepr = 0;
166    for (block_kind_id, block) in input.blocks.blocks.iter().enumerate() {
167        let block_property_names = &block
168            .properties_and_defaults
169            .iter()
170            .map(|p| p.property_value_type.to_string())
171            .collect::<Vec<_>>();
172        let mut block_properties_vec = Vec::new();
173        for property_name in block_property_names {
174            let property_variants = properties_map
175                .get(property_name)
176                .unwrap_or_else(|| panic!("Property '{property_name}' not found"))
177                .clone();
178            block_properties_vec.push(property_variants);
179        }
180
181        let mut properties_with_name: Vec<PropertyWithNameAndDefault> =
182            Vec::with_capacity(block.properties_and_defaults.len());
183        // Used to determine the index of the property so we can optionally add a number
184        // to it
185        let mut previous_names: Vec<String> = Vec::new();
186        for property in &block.properties_and_defaults {
187            let index: Option<usize> = if block
188                .properties_and_defaults
189                .iter()
190                .filter(|p| p.name == property.name)
191                .count()
192                > 1
193            {
194                Some(
195                    previous_names
196                        .iter()
197                        .filter(|&p| p == &property.name)
198                        .count(),
199                )
200            } else {
201                None
202            };
203
204            let mut property_name = property_struct_names_to_names
205                .get(&property.name)
206                .cloned()
207                .unwrap_or_else(|| property.name.clone());
208            previous_names.push(property_name.clone());
209            if let Some(index) = index {
210                // property_name.push_str(&format!("_{}", &index.to_string()));
211                write!(property_name, "_{index}").unwrap();
212            }
213            properties_with_name.push(PropertyWithNameAndDefault {
214                name_ident: name_to_ident(&property_name),
215                name: property_name,
216                property_type: property.property_type.clone(),
217                property_value_type: property.property_value_type.clone(),
218                kind: property.kind,
219                default: property.default.clone(),
220            });
221        }
222        drop(previous_names);
223
224        //     pub face: properties::Face,
225        //     pub facing: properties::Facing,
226        //     pub powered: properties::Powered,
227        // or
228        //     pub has_bottle_0: HasBottle,
229        //     pub has_bottle_1: HasBottle,
230        //     pub has_bottle_2: HasBottle,
231        let mut block_struct_fields = quote! {};
232        for PropertyWithNameAndDefault {
233            property_value_type,
234            name_ident,
235            kind,
236            ..
237        } in &properties_with_name
238        {
239            block_struct_fields.extend(match kind {
240                PropertyKind::Enum => {
241                    quote! { pub #name_ident: properties::#property_value_type, }
242                }
243                PropertyKind::Bool => {
244                    quote! { pub #name_ident: #property_value_type, }
245                }
246            });
247        }
248
249        let block_name_pascal_case = Ident::new(
250            &to_pascal_case(&block.name.to_string()),
251            proc_macro2::Span::call_site(),
252        );
253        let block_struct_name = Ident::new(
254            &block_name_pascal_case.to_string(),
255            proc_macro2::Span::call_site(),
256        );
257
258        let first_state_id = state_id;
259        let mut default_state_id = None;
260
261        // if there's no properties, then the block is just a single state
262        if block_properties_vec.is_empty() {
263            block_state_enum_variants.extend(quote! {
264                #block_name_pascal_case,
265            });
266            default_state_id = Some(state_id);
267            state_id += 1;
268        }
269        for combination in combinations_of(&block_properties_vec) {
270            let mut is_default = true;
271
272            // 	face: properties::Face::Floor,
273            // 	facing: properties::Facing::North,
274            // 	powered: properties::Powered::True,
275            let mut from_block_to_state_combination_match_inner = quote! {};
276            for i in 0..properties_with_name.len() {
277                let property = &properties_with_name[i];
278                let property_name_ident = &property.name_ident;
279                let property_value_name_ident = &property.property_type;
280                let variant = &combination[i];
281                let variant_ident = variant.ident.clone();
282
283                // property.default is a TokenStream, so we have to parse it like this
284                let property_default_ident = property
285                    .default
286                    .clone()
287                    .into_iter()
288                    .last()
289                    .and_then(|tt| match tt {
290                        TokenTree::Ident(ident) => Some(ident),
291                        _ => None,
292                    })
293                    .unwrap();
294                if variant.ident != property_default_ident {
295                    is_default = false;
296                }
297
298                let property_variant = match property.kind {
299                    PropertyKind::Enum => {
300                        quote! { properties::#property_value_name_ident::#variant_ident }
301                    }
302                    PropertyKind::Bool => {
303                        quote! { #variant_ident }
304                    }
305                };
306
307                from_block_to_state_combination_match_inner.extend(quote! {
308                    #property_name_ident: #property_variant,
309                });
310
311                // add to properties_to_state_ids
312                let property_variants = properties_to_state_ids
313                    .entry(property_value_name_ident.to_string())
314                    .or_default();
315                let property_variant_data = property_variants
316                    .iter_mut()
317                    .find(|v| v.ident == variant_ident);
318                if let Some(property_variant_data) = property_variant_data {
319                    property_variant_data.block_state_ids.push(state_id);
320                } else {
321                    property_variants.push(PropertyVariantData {
322                        block_state_ids: vec![state_id],
323                        ident: variant_ident,
324                        index: variant.index,
325                        kind: property.kind,
326                    });
327                }
328            }
329
330            if is_default {
331                default_state_id = Some(state_id);
332            }
333
334            state_id += 1;
335        }
336
337        let Some(default_state_id) = default_state_id else {
338            let defaults = properties_with_name
339                .iter()
340                .map(|p| match p.default.clone().into_iter().last().unwrap() {
341                    TokenTree::Ident(i) => i.to_string(),
342                    _ => {
343                        panic!()
344                    }
345                })
346                .collect::<Vec<_>>();
347            panic!(
348                "Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}"
349            )
350        };
351
352        // 7035..=7058 => {
353        //     let b = b - 7035;
354        //     &AcaciaButtonBlock {
355        //         powered: properties::Powered::from((b / 1) % 2),
356        //         facing: properties::Facing::from((b / 2) % 4),
357        //         face: properties::Face::from((b / 8) % 3),
358        //     }
359        // }
360        let mut from_state_to_block_inner = quote! {};
361        let mut division: BlockStateIntegerRepr = 1;
362        for i in (0..properties_with_name.len()).rev() {
363            let PropertyWithNameAndDefault {
364                property_type: property_struct_name_ident,
365                name_ident: property_name_ident,
366                property_value_type,
367                ..
368            } = &properties_with_name[i];
369
370            let property_variants = &block_properties_vec[i];
371            let property_variants_count = property_variants.len() as crate::BlockStateIntegerRepr;
372            let conversion_code = {
373                if &property_value_type.to_string() == "bool" {
374                    assert_eq!(property_variants_count, 2);
375                    // this is not a mistake, it starts with true for some reason
376                    quote! {(b / #division) % #property_variants_count == 0}
377                } else {
378                    quote! {properties::#property_struct_name_ident::from((b / #division) % #property_variants_count)}
379                }
380            };
381            from_state_to_block_inner.extend(quote! {
382                #property_name_ident: #conversion_code,
383            });
384
385            division *= property_variants_count;
386        }
387
388        let mut as_block_state_inner = quote! { #first_state_id };
389        let mut factor: BlockStateIntegerRepr = 1;
390        for i in (0..properties_with_name.len()).rev() {
391            let PropertyWithNameAndDefault {
392                name_ident: property_name_ident,
393                property_value_type,
394                ..
395            } = &properties_with_name[i];
396
397            let property_variants = &block_properties_vec[i];
398            let property_variants_count = property_variants.len() as crate::BlockStateIntegerRepr;
399            if &property_value_type.to_string() == "bool" {
400                // this is not a mistake, it starts with true for some reason, so invert it to
401                // make `true be 0`
402                as_block_state_inner.extend(
403                    quote! { + (!self.#property_name_ident as BlockStateIntegerRepr) * #factor},
404                );
405            } else {
406                as_block_state_inner.extend(
407                    quote! { + (self.#property_name_ident as BlockStateIntegerRepr) * #factor},
408                );
409            };
410
411            factor *= property_variants_count;
412        }
413
414        let last_state_id = state_id - 1;
415        from_state_to_block_match.extend(if first_state_id == last_state_id {
416            quote! {
417                #first_state_id => {
418                    Box::new(#block_struct_name { #from_state_to_block_inner })
419                },
420            }
421        } else {
422            quote! {
423                #first_state_id..=#last_state_id => {
424                    let b = b - #first_state_id;
425                    Box::new(#block_struct_name { #from_state_to_block_inner })
426                },
427            }
428        });
429
430        from_kind_to_block_match.extend(quote! {
431            BlockKind::#block_name_pascal_case => Box::new(#block_struct_name::default()),
432        });
433        from_kind_to_state_match.extend(quote! {
434            BlockKind::#block_name_pascal_case => BlockState::new_const(#default_state_id),
435        });
436        from_kind_to_states_match.extend(quote! {
437            BlockKind::#block_name_pascal_case => BlockStates::from(#first_state_id..=#last_state_id),
438        });
439        for _ in first_state_id..=last_state_id {
440            let block_kind_id = block_kind_id as u32;
441            from_state_to_kind_table.extend(quote! { #block_kind_id, });
442        }
443
444        let mut property_map_inner = quote! {};
445        let mut get_property_match_inner = quote! {};
446        let mut set_property_match_inner = quote! {};
447
448        for PropertyWithNameAndDefault {
449            name,
450            name_ident,
451            kind,
452            ..
453        } in &properties_with_name
454        {
455            let variant_name_tokens = match kind {
456                PropertyKind::Enum => quote! { self.#name_ident.to_static_str() },
457                PropertyKind::Bool => quote! { if self.#name_ident { "true" } else { "false" } },
458            };
459            property_map_inner.extend(quote! {
460                map.insert(#name, #variant_name_tokens);
461            });
462            get_property_match_inner.extend(quote! {
463                #name => Some(#variant_name_tokens),
464            });
465
466            set_property_match_inner.extend(match kind {
467                PropertyKind::Enum => quote! { #name => self.#name_ident = new_value.parse()?, },
468                PropertyKind::Bool => {
469                    quote! { #name => self.#name_ident = new_value.parse::<bool>().map_err(|_| InvalidPropertyError)?, }
470                }
471            });
472        }
473        let set_property = if set_property_match_inner.is_empty() {
474            quote! {
475                Err(InvalidPropertyError)
476            }
477        } else {
478            quote! {
479                match name {
480                    #set_property_match_inner
481                    _ => return Err(InvalidPropertyError),
482                }
483                Ok(())
484            }
485        };
486
487        let mut block_default_fields = quote! {};
488        for PropertyWithNameAndDefault {
489            name_ident,
490            default: property_default,
491            ..
492        } in properties_with_name
493        {
494            block_default_fields.extend(quote! { #name_ident: #property_default, });
495        }
496
497        let block_behavior = &block.behavior;
498        let block_id = block.name.to_string();
499
500        let as_block_state = quote! { BlockState::new_const(#as_block_state_inner) };
501
502        let mut block_struct = quote! {
503            #[derive(Clone, Copy, Debug, PartialEq)]
504            pub struct #block_struct_name
505        };
506        if block_struct_fields.is_empty() {
507            block_struct.extend(quote! {;});
508        } else {
509            block_struct.extend(quote! { { #block_struct_fields } });
510        }
511
512        block_struct.extend(quote! {
513            impl BlockTrait for #block_struct_name {
514                fn behavior(&self) -> BlockBehavior {
515                    #block_behavior
516                }
517                fn id(&self) -> &'static str {
518                    #block_id
519                }
520                fn as_block_state(&self) -> BlockState {
521                    #as_block_state
522                }
523                fn as_block_kind(&self) -> BlockKind {
524                    BlockKind::#block_name_pascal_case
525                }
526
527                fn property_map(&self) -> std::collections::HashMap<&'static str, &'static str> {
528                    let mut map = std::collections::HashMap::new();
529                    #property_map_inner
530                    map
531                }
532                fn get_property(&self, name: &str) -> Option<&'static str> {
533                    match name {
534                        #get_property_match_inner
535                        _ => None,
536                    }
537                }
538                fn set_property(&mut self, name: &str, new_value: &str) -> Result<(), InvalidPropertyError> {
539                    #set_property
540                }
541            }
542
543            impl From<#block_struct_name> for BlockState {
544                fn from(b: #block_struct_name) -> Self {
545                    b.as_block_state()
546                }
547            }
548
549            impl Default for #block_struct_name {
550                fn default() -> Self {
551                    Self {
552                        #block_default_fields
553                    }
554                }
555            }
556        });
557
558        block_structs.extend(block_struct);
559    }
560
561    let last_state_id = state_id - 1;
562    let mut generated = quote! {
563        impl BlockState {
564            /// The highest possible block state ID.
565            pub const MAX_STATE: BlockStateIntegerRepr = #last_state_id;
566
567            /// Get a property from this block state, or `None` if the block can't have the property.
568            ///
569            /// ```
570            /// fn is_waterlogged(block_state: azalea_block::BlockState) -> bool {
571            ///     block_state.property::<azalea_block::properties::Waterlogged>().unwrap_or_default()
572            /// }
573            /// ```
574            pub fn property<P: Property>(self) -> Option<P::Value> {
575                P::try_from_block_state(self)
576            }
577
578            pub fn as_block_kind(self) -> BlockKind {
579                static TABLE: &[u32] = &[
580                    #from_state_to_kind_table
581                ];
582                const _: () = assert!(BlockKind::is_valid_id(#last_block_kind_id));
583
584                // SAFETY: the table was constructed from trusted values, and
585                // we just checked that the highest one must be valid
586                unsafe { BlockKind::from_u32(TABLE[self.id() as usize]).unwrap_unchecked() }
587            }
588        }
589    };
590
591    let properties_code =
592        generate_properties_code(&input.properties, &properties_to_state_ids, last_state_id);
593
594    generated.extend(quote! {
595        pub mod properties {
596            use super::*;
597
598            #properties_code
599        }
600
601        pub mod blocks {
602            use super::*;
603            use azalea_registry::builtin::BlockKind;
604
605            #block_structs
606
607            impl From<BlockState> for Box<dyn BlockTrait> {
608                fn from(block_state: BlockState) -> Self {
609                    let b = block_state.id();
610                    match b {
611                        #from_state_to_block_match
612                        _ => panic!("Invalid block state: {}", b),
613                    }
614                }
615            }
616            impl From<BlockKind> for Box<dyn BlockTrait> {
617                fn from(block: BlockKind) -> Self {
618                    match block {
619                        #from_kind_to_block_match
620                        _ => unreachable!("There should always be a block struct for every BlockKind variant")
621                    }
622                }
623            }
624            impl From<BlockKind> for BlockState {
625                fn from(block: BlockKind) -> Self {
626                    match block {
627                        #from_kind_to_state_match
628                        _ => unreachable!("There should always be a block state for every BlockKind variant")
629                    }
630                }
631            }
632            impl From<BlockKind> for BlockStates {
633                fn from(block: BlockKind) -> Self {
634                    match block {
635                        #from_kind_to_states_match
636                        _ => unreachable!("There should always be a block state for every BlockKind variant")
637                    }
638                }
639            }
640        }
641    });
642
643    generated.into()
644}
645
646/// Convert a name to a Rust identifier, replacing some Rust keywords with
647/// alternatives (e.g. `type` -> `kind`).
648fn name_to_ident(name: &str) -> Ident {
649    let ident_str = match name {
650        "type" => "kind",
651        _ => name,
652    };
653    Ident::new(ident_str, proc_macro2::Span::call_site())
654}