mlcg_derive/
lib.rs

1#![feature(try_blocks)]
2use std::path::PathBuf;
3
4use quote::{format_ident, quote};
5
6#[proc_macro_derive(Eval)]
7pub fn derive_eval_for_self(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
8    let input = syn::parse_macro_input!(tokens as syn::DeriveInput);
9    let name = &input.ident;
10    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
11    let expanded = quote::quote! {
12        impl #impl_generics Eval<#name #ty_generics> for #name #ty_generics #where_clause {
13            #[inline]
14            fn eval(self) -> #name #ty_generics {
15                self
16            }
17        }
18    };
19    expanded.into()
20}
21
22#[proc_macro]
23pub fn commands(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
24    use std::fs;
25    let expanded: Result<_, Box<dyn std::error::Error>> = try {
26        let path = PathBuf::from(&std::env::var("CARGO_MANIFEST_DIR")?).join(
27            tokens
28                .to_string()
29                .trim_start_matches("\"")
30                .trim_end_matches("\""),
31        );
32        let json = serde_json::from_reader::<_, serde_json::Value>(
33            fs::File::open(&path).map_err(|e| format!("{e} at {}", path.to_string_lossy()))?,
34        )?;
35        enum_template("command", json.as_object().expect("invalid template"), 0)
36    };
37    expanded.unwrap().into()
38}
39
40fn enum_template(
41    command: &str,
42    map: &serde_json::Map<String, serde_json::Value>,
43    depth: usize,
44) -> proc_macro2::TokenStream {
45    let enum_name = format_ident!("{}", to_uppercase(command));
46
47    let mut variants = quote::quote! {};
48    let mut writes = quote::quote! {};
49    let mut froms = quote::quote! {};
50    let mut sub_commands = quote::quote! {};
51
52    let padding = map.get("padding").map(|padding| {
53        padding
54            .as_number()
55            .expect("invalid template: padding is not unsigned number")
56            .as_u64()
57            .expect("invalid template: padding is not unsigned integer")
58    });
59
60    for (command, fields) in map {
61        if command == "padding" {
62            continue;
63        }
64
65        let command_uppercase = format_ident!("{}", to_uppercase(command));
66
67        let command_ty = if depth == 0 {
68            let command_mod_name = format_ident!("{}", command);
69            quote::quote! { #command_mod_name::#command_uppercase }
70        } else {
71            quote::quote! { #command_uppercase}
72        };
73
74        variants.extend(quote::quote! { #command_uppercase(#command_ty), });
75        writes.extend(quote::quote! { Self::#command_uppercase(sc) => sc.fmt(f), });
76        froms.extend(quote! {
77            impl From<#command_ty> for #enum_name {
78                fn from(sc: #command_ty) -> Self {
79                    Self::#command_uppercase(sc)
80                }
81            }
82        });
83
84        match fields {
85            serde_json::Value::Array(fields) => {
86                sub_commands.extend(struct_template(command, fields, padding, depth + 1))
87            }
88            serde_json::Value::Object(sub_command) => {
89                sub_commands.extend(enum_template(command, sub_command, depth + 1))
90            }
91            serde_json::Value::Number(_padding) => {}
92            _ => panic!("invalid template"),
93        }
94    }
95
96    let self_write = if depth == 1 {
97        quote::quote! { write!(f, "{} ", #command)?; }
98    } else {
99        quote::quote! {}
100    };
101
102    let define = quote::quote! {
103        #[derive(Debug, Clone)]
104        pub enum #enum_name {
105            #variants
106        }
107
108        impl std::fmt::Display for #enum_name {
109            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110                #self_write
111                match self {
112                    #writes
113                }
114            }
115        }
116
117        #froms
118
119        #sub_commands
120
121    };
122    wrap_in_module(depth, command, define)
123}
124
125fn struct_template(
126    command: &str,
127    fields: &[serde_json::Value],
128    padding: Option<u64>,
129    depth: usize,
130) -> proc_macro2::TokenStream {
131    let struct_name = format_ident!("{}", to_uppercase(command));
132
133    let padding = padding
134        .map(|padding| padding as usize - fields.len())
135        .unwrap_or_default();
136
137    let writes = fields
138        .iter()
139        .map(|field| {
140            let field = format_ident!("{}", field.as_str().expect("invalid template"));
141            quote::quote! { write!(f, " {}", self.#field)?; }
142        })
143        .chain((0..padding).map(|_| quote::quote! {write!(f, " 0")?;}));
144
145    let fields = fields
146        .iter()
147        .map(|field| format_ident!("{}", field.as_str().expect("invalid template")));
148    let struct_define = quote::quote! {
149        #[derive(Debug, Clone)]
150        pub struct #struct_name {
151            #(pub #fields: crate::String,)*
152        }
153
154        impl std::fmt::Display for #struct_name {
155            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156                write!(f, #command)?;
157                #( #writes )*
158                Ok(())
159            }
160        }
161    };
162
163    wrap_in_module(depth, command, struct_define)
164}
165
166fn wrap_in_module(
167    depth: usize,
168    command: &str,
169    define: proc_macro2::TokenStream,
170) -> proc_macro2::TokenStream {
171    if depth == 1 {
172        let mod_name = format_ident!("{}", command);
173        quote::quote! {
174            pub mod #mod_name {
175                #define
176            }
177        }
178    } else {
179        define
180    }
181}
182
183fn to_uppercase(src: &str) -> String {
184    let mut string = src.to_owned();
185    unsafe { string.as_bytes_mut()[0] = string.as_bytes_mut()[0].to_ascii_uppercase() };
186    string
187}