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
143    for property in &input.properties {
144        let property_value_type = get_property_value_type(&property.data);
145        let property_variant_types = get_property_variant_types(&property.data);
146        properties_map.insert(property_value_type.to_string(), property_variant_types);
147    }
148
149    let property_struct_names_to_names = make_property_struct_names_to_names(&input.properties);
150
151    let mut block_state_enum_variants = quote! {};
152    let mut block_structs = quote! {};
153    let mut block_statics = quote! {};
154
155    let mut from_state_to_block_match = quote! {};
156    let mut from_kind_to_block_match = quote! {};
157    let mut from_kind_to_state_match = quote! {};
158    let mut from_kind_to_states_match = quote! {};
159
160    let mut from_state_to_kind_table = quote! {};
161
162    // keys are enum names like Waterlogged
163    let mut properties_to_state_ids = HashMap::<String, Vec<PropertyVariantData>>::new();
164
165    let last_block_kind_id = u32::try_from(input.blocks.blocks.len() - 1).unwrap();
166
167    let mut state_id: BlockStateIntegerRepr = 0;
168    for (block_kind_id, block) in input.blocks.blocks.iter().enumerate() {
169        let block_property_names = &block
170            .properties_and_defaults
171            .iter()
172            .map(|p| p.property_value_type.to_string())
173            .collect::<Vec<_>>();
174        let mut block_properties_vec = Vec::new();
175        for property_name in block_property_names {
176            let property_variants = properties_map
177                .get(property_name)
178                .unwrap_or_else(|| panic!("Property '{property_name}' not found"))
179                .clone();
180            block_properties_vec.push(property_variants);
181        }
182
183        let mut properties_with_name: Vec<PropertyWithNameAndDefault> =
184            Vec::with_capacity(block.properties_and_defaults.len());
185        // Used to determine the index of the property so we can optionally add
186        // a number to it
187        let mut previous_names: Vec<String> = Vec::new();
188        for property in &block.properties_and_defaults {
189            let index: Option<usize> = if block
190                .properties_and_defaults
191                .iter()
192                .filter(|p| p.name == property.name)
193                .count()
194                > 1
195            {
196                Some(
197                    previous_names
198                        .iter()
199                        .filter(|&p| p == &property.name)
200                        .count(),
201                )
202            } else {
203                None
204            };
205
206            let mut property_name = property_struct_names_to_names
207                .get(&property.name)
208                .cloned()
209                .unwrap_or_else(|| property.name.clone());
210            previous_names.push(property_name.clone());
211            if let Some(index) = index {
212                // property_name.push_str(&format!("_{}", &index.to_string()));
213                write!(property_name, "_{index}").unwrap();
214            }
215            properties_with_name.push(PropertyWithNameAndDefault {
216                name_ident: name_to_ident(&property_name),
217                name: property_name,
218                property_type: property.property_type.clone(),
219                property_value_type: property.property_value_type.clone(),
220                kind: property.kind,
221                default: property.default.clone(),
222            });
223        }
224        drop(previous_names);
225
226        //     pub face: properties::Face,
227        //     pub facing: properties::Facing,
228        //     pub powered: properties::Powered,
229        // or
230        //     pub has_bottle_0: HasBottle,
231        //     pub has_bottle_1: HasBottle,
232        //     pub has_bottle_2: HasBottle,
233        let mut block_struct_fields = quote! {};
234        for PropertyWithNameAndDefault {
235            property_value_type,
236            name_ident,
237            kind,
238            ..
239        } in &properties_with_name
240        {
241            block_struct_fields.extend(match kind {
242                PropertyKind::Enum => {
243                    quote! { pub #name_ident: properties::#property_value_type, }
244                }
245                PropertyKind::Bool => {
246                    quote! { pub #name_ident: #property_value_type, }
247                }
248            });
249        }
250
251        let block_name_pascal_case = Ident::new(
252            &to_pascal_case(&block.name.to_string()),
253            proc_macro2::Span::call_site(),
254        );
255        let block_struct_name = Ident::new(
256            &block_name_pascal_case.to_string(),
257            proc_macro2::Span::call_site(),
258        );
259
260        let first_state_id = state_id;
261        let mut default_state_id = None;
262
263        // if there's no properties, then the block is just a single state
264        if block_properties_vec.is_empty() {
265            block_state_enum_variants.extend(quote! {
266                #block_name_pascal_case,
267            });
268            default_state_id = Some(state_id);
269
270            let static_name = Ident::new(
271                &format!("__BLOCK_STATE_{}_{}", block_struct_name, state_id).to_uppercase(),
272                proc_macro2::Span::call_site(),
273            );
274
275            block_statics.extend(quote! {
276                static #static_name: #block_struct_name = #block_struct_name {};
277            });
278
279            from_state_to_block_match.extend(quote! { &#static_name, });
280
281            state_id += 1;
282        }
283        for combination in combinations_of(&block_properties_vec) {
284            let mut is_default = true;
285
286            // 	face: properties::Face::Floor,
287            // 	facing: properties::Facing::North,
288            // 	powered: properties::Powered::True,
289            let mut from_block_to_state_combination_match_inner = quote! {};
290            for i in 0..properties_with_name.len() {
291                let property = &properties_with_name[i];
292                let property_name_ident = &property.name_ident;
293                let property_value_name_ident = &property.property_type;
294                let variant = &combination[i];
295                let variant_ident = variant.ident.clone();
296
297                // property.default is a TokenStream, so we have to parse it
298                // like this
299                let property_default_ident = property
300                    .default
301                    .clone()
302                    .into_iter()
303                    .last()
304                    .and_then(|tt| match tt {
305                        TokenTree::Ident(ident) => Some(ident),
306                        _ => None,
307                    })
308                    .unwrap();
309                if variant.ident != property_default_ident {
310                    is_default = false;
311                }
312
313                let property_variant = match property.kind {
314                    PropertyKind::Enum => {
315                        quote! { properties::#property_value_name_ident::#variant_ident }
316                    }
317                    PropertyKind::Bool => {
318                        quote! { #variant_ident }
319                    }
320                };
321
322                from_block_to_state_combination_match_inner.extend(quote! {
323                    #property_name_ident: #property_variant,
324                });
325
326                // add to properties_to_state_ids
327                let property_variants = properties_to_state_ids
328                    .entry(property_value_name_ident.to_string())
329                    .or_default();
330                let property_variant_data = property_variants
331                    .iter_mut()
332                    .find(|v| v.ident == variant_ident);
333                if let Some(property_variant_data) = property_variant_data {
334                    property_variant_data.block_state_ids.push(state_id);
335                } else {
336                    property_variants.push(PropertyVariantData {
337                        block_state_ids: vec![state_id],
338                        ident: variant_ident,
339                        index: variant.index,
340                        kind: property.kind,
341                    });
342                }
343            }
344
345            if is_default {
346                default_state_id = Some(state_id);
347            }
348
349            let static_name = Ident::new(
350                &format!("__BLOCK_STATE_{}_{}", block_struct_name, state_id).to_uppercase(),
351                proc_macro2::Span::call_site(),
352            );
353
354            block_statics.extend(quote! {
355                static #static_name: #block_struct_name = #block_struct_name {
356                    #from_block_to_state_combination_match_inner
357                };
358            });
359
360            from_state_to_block_match.extend(quote! { &#static_name, });
361
362            state_id += 1;
363        }
364
365        let Some(default_state_id) = default_state_id else {
366            let defaults = properties_with_name
367                .iter()
368                .map(|p| match p.default.clone().into_iter().last().unwrap() {
369                    TokenTree::Ident(i) => i.to_string(),
370                    _ => {
371                        panic!()
372                    }
373                })
374                .collect::<Vec<_>>();
375            panic!(
376                "Couldn't get default state id for {block_name_pascal_case}, combinations={block_properties_vec:?}, defaults={defaults:?}"
377            )
378        };
379
380        // 7035..=7058 => {
381        //     let b = b - 7035;
382        //     &AcaciaButtonBlock {
383        //         powered: properties::Powered::from((b / 1) % 2),
384        //         facing: properties::Facing::from((b / 2) % 4),
385        //         face: properties::Face::from((b / 8) % 3),
386        //     }
387        // }
388        let mut from_state_to_block_inner = quote! {};
389
390        let mut division: BlockStateIntegerRepr = 1;
391        for i in (0..properties_with_name.len()).rev() {
392            let PropertyWithNameAndDefault {
393                property_type: property_struct_name_ident,
394                name_ident: property_name_ident,
395                property_value_type,
396                ..
397            } = &properties_with_name[i];
398
399            let property_variants = &block_properties_vec[i];
400            let property_variants_count = property_variants.len() as crate::BlockStateIntegerRepr;
401            let conversion_code = {
402                if &property_value_type.to_string() == "bool" {
403                    assert_eq!(property_variants_count, 2);
404                    // this is not a mistake, it starts with true for some
405                    // reason
406                    quote! {(b / #division) % #property_variants_count == 0}
407                } else {
408                    quote! {properties::#property_struct_name_ident::from((b / #division) % #property_variants_count)}
409                }
410            };
411            from_state_to_block_inner.extend(quote! {
412                #property_name_ident: #conversion_code,
413            });
414
415            division *= property_variants_count;
416        }
417
418        let mut as_block_state_inner = quote! { #first_state_id };
419        let mut factor: BlockStateIntegerRepr = 1;
420        for i in (0..properties_with_name.len()).rev() {
421            let PropertyWithNameAndDefault {
422                name_ident: property_name_ident,
423                property_value_type,
424                ..
425            } = &properties_with_name[i];
426
427            let property_variants = &block_properties_vec[i];
428            let property_variants_count = property_variants.len() as crate::BlockStateIntegerRepr;
429            if &property_value_type.to_string() == "bool" {
430                // this is not a mistake, it starts with true for some reason,
431                // so invert it to make `true be 0`
432                as_block_state_inner.extend(
433                    quote! { + (!self.#property_name_ident as BlockStateIntegerRepr) * #factor},
434                );
435            } else {
436                as_block_state_inner.extend(
437                    quote! { + (self.#property_name_ident as BlockStateIntegerRepr) * #factor},
438                );
439            };
440
441            factor *= property_variants_count;
442        }
443
444        let last_state_id = state_id - 1;
445
446        from_kind_to_block_match.extend(quote! {
447            BlockKind::#block_name_pascal_case => Box::new(#block_struct_name::default()),
448        });
449        from_kind_to_state_match.extend(quote! {
450            BlockKind::#block_name_pascal_case => BlockState::new_const(#default_state_id),
451        });
452        from_kind_to_states_match.extend(quote! {
453            BlockKind::#block_name_pascal_case => BlockStates::from(#first_state_id..=#last_state_id),
454        });
455        for _ in first_state_id..=last_state_id {
456            let block_kind_id = block_kind_id as u32;
457            from_state_to_kind_table.extend(quote! { #block_kind_id, });
458        }
459
460        let mut property_map_inner = quote! {};
461        let mut get_property_match_inner = quote! {};
462        let mut set_property_match_inner = quote! {};
463
464        for PropertyWithNameAndDefault {
465            name,
466            name_ident,
467            kind,
468            ..
469        } in &properties_with_name
470        {
471            let variant_name_tokens = match kind {
472                PropertyKind::Enum => quote! { self.#name_ident.to_static_str() },
473                PropertyKind::Bool => quote! { if self.#name_ident { "true" } else { "false" } },
474            };
475            property_map_inner.extend(quote! {
476                map.insert(#name, #variant_name_tokens);
477            });
478            get_property_match_inner.extend(quote! {
479                #name => Some(#variant_name_tokens),
480            });
481
482            set_property_match_inner.extend(match kind {
483                PropertyKind::Enum => quote! { #name => self.#name_ident = new_value.parse()?, },
484                PropertyKind::Bool => {
485                    quote! { #name => self.#name_ident = new_value.parse::<bool>().map_err(|_| InvalidPropertyError)?, }
486                }
487            });
488        }
489        let set_property = if set_property_match_inner.is_empty() {
490            quote! {
491                Err(InvalidPropertyError)
492            }
493        } else {
494            quote! {
495                match name {
496                    #set_property_match_inner
497                    _ => return Err(InvalidPropertyError),
498                }
499                Ok(())
500            }
501        };
502
503        let mut block_default_fields = quote! {};
504        for PropertyWithNameAndDefault {
505            name_ident,
506            default: property_default,
507            ..
508        } in properties_with_name
509        {
510            block_default_fields.extend(quote! { #name_ident: #property_default, });
511        }
512
513        let block_behavior = &block.behavior;
514        let block_id = block.name.to_string();
515
516        let as_block_state = quote! { BlockState::new_const(#as_block_state_inner) };
517
518        let mut block_struct = quote! {
519            #[derive(Clone, Copy, Debug, PartialEq)]
520            pub struct #block_struct_name
521        };
522        if block_struct_fields.is_empty() {
523            block_struct.extend(quote! {;});
524        } else {
525            block_struct.extend(quote! { { #block_struct_fields } });
526        }
527
528        block_struct.extend(quote! {
529            impl BlockTrait for #block_struct_name {
530                fn boxed(&self) -> Box<dyn BlockTrait>{
531                    Box::new(*self)
532                }
533                fn behavior(&self) -> BlockBehavior {
534                    #block_behavior
535                }
536                fn id(&self) -> &'static str {
537                    #block_id
538                }
539                fn as_block_state(&self) -> BlockState {
540                    #as_block_state
541                }
542                fn as_block_kind(&self) -> BlockKind {
543                    BlockKind::#block_name_pascal_case
544                }
545
546
547                fn property_map(&self) -> std::collections::HashMap<&'static str, &'static str> {
548                    let mut map = std::collections::HashMap::new();
549                    #property_map_inner
550                    map
551                }
552                fn get_property(&self, name: &str) -> Option<&'static str> {
553                    match name {
554                        #get_property_match_inner
555                        _ => None,
556                    }
557                }
558                fn set_property(&mut self, name: &str, new_value: &str) -> Result<(), InvalidPropertyError> {
559                    #set_property
560                }
561            }
562
563            impl From<#block_struct_name> for BlockState {
564                fn from(b: #block_struct_name) -> Self {
565                    b.as_block_state()
566                }
567            }
568
569            impl Default for #block_struct_name {
570                fn default() -> Self {
571                    Self {
572                        #block_default_fields
573                    }
574                }
575            }
576        });
577
578        block_structs.extend(block_struct);
579    }
580
581    let last_state_id = state_id - 1;
582
583    let mut generated = quote! {
584        impl BlockState {
585            /// The highest possible block state ID.
586            pub const MAX_STATE: BlockStateIntegerRepr = #last_state_id;
587
588            /// Get a property from this block state, or `None` if the block can't have the property.
589            ///
590            /// ```
591            /// fn is_waterlogged(block_state: azalea_block::BlockState) -> bool {
592            ///     block_state.property::<azalea_block::properties::Waterlogged>().unwrap_or_default()
593            /// }
594            /// ```
595            pub fn property<P: Property>(self) -> Option<P::Value> {
596                P::try_from_block_state(self)
597            }
598
599            pub fn as_block_kind(self) -> BlockKind {
600                static TABLE: &[u32] = &[
601                    #from_state_to_kind_table
602                ];
603                const _: () = assert!(BlockKind::is_valid_id(#last_block_kind_id));
604
605                // SAFETY: the table was constructed from trusted values, and
606                // we just checked that the highest one must be valid
607                unsafe { BlockKind::from_u32(TABLE[self.id() as usize]).unwrap_unchecked() }
608            }
609        }
610    };
611
612    let properties_code =
613        generate_properties_code(&input.properties, &properties_to_state_ids, last_state_id);
614
615    generated.extend(quote! {
616        pub mod properties {
617            use super::*;
618
619            #properties_code
620        }
621
622        pub mod blocks {
623            use super::*;
624            use azalea_registry::builtin::BlockKind;
625
626            #block_structs
627
628            #block_statics
629
630            static BLOCK_STATE_TABLE: &[&'static dyn BlockTrait; BlockState::MAX_STATE as usize + 1] = &[
631                #from_state_to_block_match
632            ];
633
634
635            /// Convert a [`BlockState`] to a [`&'static dyn BlockTrait`].
636            #[inline]
637            #[must_use]
638            pub const fn blockstate_to_blocktrait(state: BlockState) -> &'static dyn BlockTrait {
639                BLOCK_STATE_TABLE[state.id() as usize]
640            }
641
642            impl From<BlockState> for &'static dyn BlockTrait {
643                #[inline]
644                fn from(block_state: BlockState) -> Self {
645                    blockstate_to_blocktrait(block_state)
646                }
647            }
648
649            impl From<BlockKind> for Box<dyn BlockTrait> {
650                fn from(block: BlockKind) -> Self {
651                    match block {
652                        #from_kind_to_block_match
653                        _ => unreachable!("There should always be a block struct for every BlockKind variant")
654                    }
655                }
656            }
657            impl From<BlockKind> for BlockState {
658                fn from(block: BlockKind) -> Self {
659                    match block {
660                        #from_kind_to_state_match
661                        _ => unreachable!("There should always be a block state for every BlockKind variant")
662                    }
663                }
664            }
665            impl From<BlockKind> for BlockStates {
666                fn from(block: BlockKind) -> Self {
667                    match block {
668                        #from_kind_to_states_match
669                        _ => unreachable!("There should always be a block state for every BlockKind variant")
670                    }
671                }
672            }
673        }
674    });
675
676    generated.into()
677}
678
679/// Convert a name to a Rust identifier, replacing some Rust keywords with
680/// alternatives (e.g. `type` -> `kind`).
681fn name_to_ident(name: &str) -> Ident {
682    let ident_str = match name {
683        "type" => "kind",
684        _ => name,
685    };
686    Ident::new(ident_str, proc_macro2::Span::call_site())
687}