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