Skip to main content

scratch_test_model_proc/
lib.rs

1#![doc = include_str!("../../../README.md")]
2//! ## scratch-test-model-proc
3mod parse_impls;
4mod valid_attributes;
5
6use proc_macro2::TokenStream as TokenStream2;
7use valid_attributes::{BlockLevelAttribute, my_attributes};
8
9use proc_macro::TokenStream;
10use quote::quote;
11use syn::parse_macro_input;
12
13struct BlockKindEnumSpec {
14    vis: syn::Visibility,
15    name: syn::Ident,
16    variants: Vec<BlockVariantSpec>,
17    #[allow(unused)]
18    default_location: Option<ParameterLocation>,
19}
20struct BlockVariantSpec {
21    /// The opcode of the block, likely snakecase
22    opcode: String,
23    name: syn::Ident,
24    parameters: Vec<BlockParameterSpec>,
25    parse_via: Option<syn::Ident>,
26}
27
28enum VariantLevelAttributes {
29    Opcode(syn::LitStr),
30    ParseVia(syn::Ident),
31}
32enum FieldLevelAttributes {
33    JsonName(syn::LitStr),
34    Location(ParameterLocation),
35    // Optional(bool),
36}
37
38struct BlockParameterSpec {
39    /// The name the parameter uses in the JSON representation, likely uppercase
40    json_name: String,
41    location: ParameterLocation,
42    // optional: bool,
43    #[allow(unused)]
44    vis: syn::Visibility,
45    name: syn::Ident,
46    #[allow(unused)]
47    ty: syn::Type,
48}
49
50#[derive(Debug, Clone, Copy)]
51enum ParameterLocation {
52    Inputs,
53    Fields,
54}
55
56impl quote::ToTokens for BlockKindEnumSpec {
57    fn to_tokens(&self, tokens: &mut TokenStream2) {
58        let unit_name = syn::Ident::new(
59            &format!("{}Unit", self.name),
60            proc_macro2::Span::call_site(),
61        );
62        let name = &self.name;
63        let variants = self
64            .variants
65            .iter()
66            .map(|v| v.name.clone())
67            .collect::<Vec<_>>();
68        let opcodes: Vec<_> = self.variants.iter().map(|v| v.opcode.clone()).collect();
69        let opcode_comments = opcodes.iter().map(|o| format!("`{o}` is the json-side opcode"));
70        let opcode_names = opcodes.iter();
71        let vis = &self.vis;
72
73
74        // unit version
75        tokens.extend(quote! {
76            #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
77            #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
78            #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
79            #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80            #vis enum #unit_name {
81                #( 
82                    #[doc = #opcode_comments]
83                    #[cfg_attr(feature = "serde", serde(rename = #opcode_names))]
84                    #variants
85                ,)*
86            }
87            impl crate::_exports::AsOpcodeUnit for #name {
88                type OpcodeUnit = #unit_name;
89
90                fn opcode(&self) -> Self::OpcodeUnit {
91                    match self {
92                        #( Self::#variants{..} => Self::OpcodeUnit::#variants, )*
93                    }
94                }
95            }
96            impl #unit_name {
97                /// Method with the same functionality as
98                /// [`AsOpcodeName::opcode_name`](crate::_exports::AsOpcodeName::opcode_name),
99                /// but this can be used in a `const`-context.
100                pub const fn k_opcode_name(&self) -> &'static str {
101                    match self {
102                        #( Self::#variants => #opcodes, )*
103                    }
104                }
105            }
106            impl crate::_exports::AsOpcodeName for #unit_name {
107                fn opcode_name(&self) -> &'static str {
108                    match self {
109                        #( Self::#variants => #opcodes, )*
110                    }
111                }
112            }
113
114            impl std::fmt::Display for #unit_name {
115                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116                    f.write_str(crate::_exports::AsOpcodeName::opcode_name(&self))
117                }
118            }
119        });
120
121        let variants = self.variants.iter().map(|variant| {
122            if let Some(parse_via_func) = variant.parse_via.as_ref() {
123                quote! {
124                    #parse_via_func(all_target_blocks, opcode, inputs, fields, mutation)
125                }
126            } else {
127
128            let attribute_defs = variant.parameters.iter().map(|p| {
129                let ident = &p.name;
130                let key = &p.json_name;
131                match p.location {
132                    ParameterLocation::Inputs => quote! {
133                        let #ident = crate::_exports::helper_attr_access(all_target_blocks, inputs, #key)?;
134                    },
135                    ParameterLocation::Fields => quote! {
136                        let #ident = crate::_exports::helper_attr_access(all_target_blocks, fields, #key)?;
137                    },
138                }
139            });
140            let attribute_names = variant.parameters.iter().map(|p| &p.name);
141
142            let var_name = &variant.name;
143
144            quote! {
145                #(#attribute_defs;)*
146                Ok(Self::#var_name { #(#attribute_names,)* })
147            }}
148        });
149
150        tokens.extend(quote!( impl crate::_exports::ParseJsonBlock for #name {
151            fn parse_json_block<'a, 'b>(
152                all_target_blocks: crate::_exports::JsonBlocks<'a>,
153                opcode: &str,
154                inputs: &'b crate::_exports::BlockProperties<'a>,
155                fields: &'b crate::_exports::BlockProperties<'a>,
156                mutation: Option<&'b crate::_exports::BlockProperties<'a>>
157            ) -> Result<Self, crate::_exports::BlockKindError<'a>>
158            where
159                Self: Sized {
160                match opcode {
161                    #(#opcodes => {#variants})*
162                    opcode => Err(crate::_exports::BlockKindError::UnknownOpcode(opcode.into()))
163                }
164            }
165        } ));
166
167        // do for all attributes
168        //
169        let attribute_types = self.variants.iter()
170            .flat_map(|variant| variant.parameters.iter().map(|p| &p.ty)).map(|ty| {
171                quote! { #ty: crate::_exports::DoForAttrs<'a, S> }
172            });
173
174        let variants = self.variants.iter().map(|variant| {
175            let params = variant.parameters.iter().map(|param| {
176                let name = &param.name;
177                quote! { crate::_exports::DoForAttrs::<'a, S>::do_for_attrs(
178                    #name,
179                    inputs_that_are_passed_to_all_attributes, 
180                    outputs_received_by_all_attributes,
181                )?; 
182                }
183            });
184            let names = variant.parameters.iter().map(|param| &param.name);
185            let var_name = &variant.name;
186            quote! {
187                Self::#var_name{ #(#names),* } => { #(#params)* }
188            }
189        });
190
191        tokens.extend(quote!(
192            impl<'a, S: crate::_exports::DoForAttrsStrategy<'a>> crate::_exports::DoForAttrs<'a, S> for #name 
193                where
194                    #(#attribute_types),*
195            {
196                fn do_for_attrs(
197                    &'a self,
198                    inputs_that_are_passed_to_all_attributes: &S::Inputs,
199                    outputs_received_by_all_attributes: &mut S::Outputs
200                ) -> Result<(), S::Error> {
201                    match self {
202                        #(#variants)*
203                    }
204                    Ok(())
205                }
206            }
207    ));
208    }
209}
210
211#[proc_macro_derive(Block, attributes(block))]
212pub fn derive_block(input: TokenStream) -> TokenStream {
213    let data = parse_macro_input!(input as BlockKindEnumSpec);
214
215    quote! {
216        #data
217    }
218    .into()
219}