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}