1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3#![allow(clippy::let_and_return)]
4
5use std::{collections::HashSet, env};
6
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9use quote::quote;
10use syn::{
11 parse::{Parse, ParseStream},
12 parse_macro_input,
13 punctuated::Punctuated,
14 AttrStyle, Attribute, Data, DeriveInput, Expr, Fields, Ident, Meta, MetaList, Token, Type,
15};
16
17struct CvarsDef {
19 attrs: Vec<Attribute>,
20 sorted: bool,
24 cvars: Vec<CvarDef>,
25}
26
27impl Parse for CvarsDef {
28 fn parse(input: ParseStream) -> syn::Result<Self> {
29 let attrs_raw = input.call(Attribute::parse_inner)?;
30 let mut attrs = Vec::new();
31 let mut sorted = false;
32 for attr in attrs_raw {
33 if is_sorted(&attr) {
34 sorted = true;
35 } else {
36 attrs.push(attr);
37 }
38 }
39
40 let punctuated = Punctuated::<CvarDef, Token![,]>::parse_terminated(input)?;
41 let cvars = punctuated.into_iter().collect();
42
43 Ok(CvarsDef {
44 attrs,
45 sorted,
46 cvars,
47 })
48 }
49}
50
51struct CvarDef {
53 attrs: Vec<Attribute>,
54 skip: bool,
56 name: Ident,
57 ty: Type,
58 value: Expr,
59}
60
61impl Parse for CvarDef {
62 fn parse(input: ParseStream) -> syn::Result<Self> {
63 let attrs_raw = input.call(Attribute::parse_outer)?;
64 let mut attrs = Vec::new();
65 let mut skip = false;
66 for attr in attrs_raw {
67 if is_skip(&attr) {
68 skip = true;
69 } else {
70 attrs.push(attr);
71 }
72 }
73 let name = input.parse()?;
74 let _: Token![:] = input.parse()?;
75 let ty = input.parse()?;
76 let _: Token![=] = input.parse()?;
77 let value = input.parse()?;
78 Ok(CvarDef {
79 attrs,
80 skip,
81 name,
82 ty,
83 value,
84 })
85 }
86}
87
88fn is_sorted(attr: &Attribute) -> bool {
90 if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta {
91 if !path.is_ident("cvars") {
92 return false;
93 }
94
95 if tokens.to_string() == "sorted" {
96 return true;
97 } else {
98 panic!("Unknown cvars attribute: {}", tokens);
99 }
100 }
101
102 false
103}
104
105fn is_skip(attr: &Attribute) -> bool {
107 if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta {
108 if !path.is_ident("cvars") {
109 return false;
110 }
111
112 if tokens.to_string() == "skip" {
113 return true;
114 } else {
115 panic!("Unknown cvars attribute: {}", tokens);
116 }
117 }
118
119 false
120}
121
122#[proc_macro]
176pub fn cvars(input: TokenStream) -> TokenStream {
177 let begin = std::time::Instant::now();
178
179 let cvars_def: CvarsDef = parse_macro_input!(input);
180
181 let mut cvars_attrs = cvars_def.attrs;
182 for attr in &mut cvars_attrs {
183 attr.style = AttrStyle::Outer;
184 }
185
186 let mut attrss = Vec::new();
187 let mut skips = Vec::new();
188 let mut names = Vec::new();
189 let mut tys = Vec::new();
190 let mut values = Vec::new();
191 for cvar_def in cvars_def.cvars {
192 attrss.push(cvar_def.attrs);
193 skips.push(cvar_def.skip);
194 names.push(cvar_def.name);
195 tys.push(cvar_def.ty);
196 values.push(cvar_def.value);
197 }
198
199 let struct_name = Ident::new("Cvars", Span::call_site());
200 let generated = generate(struct_name, cvars_def.sorted, &skips, &names, &tys);
201
202 let expanded = quote! {
203 #(
204 #cvars_attrs
205 )*
206 pub struct Cvars {
207 #(
208 #( #attrss )*
209 pub #names: #tys,
210 )*
211 }
212
213 #[automatically_derived]
214 impl ::core::default::Default for Cvars {
215 fn default() -> Self {
216 Self {
217 #( #names: #values, )*
218 }
219 }
220 }
221
222 #generated
223 };
224 let expanded = expanded.into();
225
226 let end = std::time::Instant::now();
227 if env::var("CVARS_STATS").is_ok() {
228 eprintln!("cvars! took {:?}", end - begin);
229 }
230
231 expanded
232}
233
234#[proc_macro_derive(SetGet, attributes(cvars))]
264pub fn derive(input: TokenStream) -> TokenStream {
265 let begin = std::time::Instant::now();
266
267 let input: DeriveInput = parse_macro_input!(input);
268
269 let struct_name = input.ident;
270 let named_fields = match input.data {
271 Data::Struct(struct_data) => match struct_data.fields {
272 Fields::Named(named_fields) => named_fields,
273 Fields::Unnamed(_) => panic!("tuple structs are not supported, use named fields"),
274 Fields::Unit => panic!("unit structs are not supported, use curly braces"),
275 },
276 Data::Enum(_) => panic!("enums are not supported, use a struct"),
277 Data::Union(_) => panic!("unions are not supported, use a struct"),
278 };
279 let sorted = input.attrs.iter().any(is_sorted);
280
281 let mut skips = Vec::new();
283 let mut names = Vec::new();
284 let mut tys = Vec::new();
285 for field in named_fields.named {
286 let contains_skip = field.attrs.iter().any(is_skip);
287 skips.push(contains_skip);
288 let name = field.ident.expect("unreachable: ident was None");
289 names.push(name);
290 tys.push(field.ty);
291 }
292
293 let expanded = generate(struct_name, sorted, &skips, &names, &tys);
294 let expanded = expanded.into();
295
296 let end = std::time::Instant::now();
297 if env::var("CVARS_STATS").is_ok() {
298 eprintln!("derive(SetGet) took {:?}", end - begin);
299 }
300
301 expanded
302}
303
304fn generate(
305 struct_name: Ident,
306 sorted: bool,
307 skips: &[bool],
308 names_all: &[Ident],
309 tys_all: &[Type],
310) -> proc_macro2::TokenStream {
311 let mut names = Vec::new();
312 let mut tys = Vec::new();
313 for i in 0..skips.len() {
314 if skips[i] {
315 continue;
316 }
317
318 names.push(&names_all[i]);
319 tys.push(&tys_all[i]);
320 }
321
322 if sorted {
323 for pair in names.windows(2) {
324 if pair[0] >= pair[1] {
325 panic!("cvars not sorted: `{}` >= `{}`", pair[0], pair[1]);
328 }
329 }
330 }
331
332 let cvar_count = names.len();
333
334 let set_get_impl = impl_set_get(&struct_name);
335
336 let unique_tys: HashSet<_> = tys.iter().collect();
341 let mut trait_impls = Vec::new();
342 for unique_ty in unique_tys {
343 let mut getter_arms = Vec::new();
344 let mut setter_arms = Vec::new();
345
346 for i in 0..names.len() {
347 let field = names[i];
348 let ty = tys[i];
349 if ty == *unique_ty {
352 let getter_arm = quote! {
353 stringify!(#field) => ::core::result::Result::Ok(cvars.#field.clone()),
354 };
355 getter_arms.push(getter_arm);
356
357 let setter_arm = quote! {
358 stringify!(#field) => ::core::result::Result::Ok(cvars.#field = value),
359 };
360 setter_arms.push(setter_arm);
361 }
362 }
363
364 let trait_impl = quote! {
368 #[automatically_derived]
369 impl SetGetType for #unique_ty {
370 fn get(cvars: &Cvars, cvar_name: &str) -> ::core::result::Result<Self, String> {
371 match cvar_name {
372 #( #getter_arms )*
373 _ => ::core::result::Result::Err(format!(
374 "Cvar named {} with type {} not found",
375 cvar_name,
376 stringify!(#unique_ty)
377 )),
378 }
379 }
380
381 fn set(cvars: &mut Cvars, cvar_name: &str, value: Self) -> ::core::result::Result<(), String> {
382 match cvar_name {
383 #( #setter_arms )*
384 _ => ::core::result::Result::Err(format!(
385 "Cvar named {} with type {} not found",
386 cvar_name,
387 stringify!(#unique_ty),
388 )),
389 }
390 }
391 }
392 };
393 trait_impls.push(trait_impl);
394 }
395
396 quote! {
397 #[automatically_derived]
398 impl #struct_name {
399 pub fn get<T: SetGetType>(&self, cvar_name: &str) -> ::core::result::Result<T, String> {
403 SetGetType::get(self, cvar_name)
407 }
408
409 pub fn get_string(&self, cvar_name: &str) -> ::core::result::Result<String, String> {
413 #[inline(never)]
417 fn get_string<T: ::core::fmt::Display>(cvar: &T) -> ::core::result::Result<String, String> {
418 ::core::result::Result::Ok(cvar.to_string())
419 }
420 match cvar_name {
421 #( stringify!(#names) => get_string(&self.#names), )*
422 _ => ::core::result::Result::Err(format!(
423 "Cvar named {} not found",
424 cvar_name,
425 )),
426 }
427 }
428
429 pub fn set<T: SetGetType>(&mut self, cvar_name: &str, value: T) -> ::core::result::Result<(), String> {
444 SetGetType::set(self, cvar_name, value)
445 }
446
447 pub fn set_str(&mut self, cvar_name: &str, str_value: &str) -> ::core::result::Result<(), String> {
451 #[inline(never)]
458 fn set_str<T>(cvar: &mut T, mut str_value: &str) -> ::core::result::Result<(), String>
459 where
460 T: ::core::str::FromStr,
461 T::Err: ::core::fmt::Display,
462 {
463 if ::std::any::type_name::<T>() == "bool" {
464 if str_value == "t" || str_value == "1" {
465 str_value = "true";
466 } else if str_value == "f" || str_value == "0" {
467 str_value = "false";
468 }
469 }
470 match str_value.parse() {
471 ::core::result::Result::Ok(val) => ::core::result::Result::Ok(*cvar = val),
472 ::core::result::Result::Err(err) => ::core::result::Result::Err(format!("failed to parse {} as type {}: {}",
473 str_value,
474 ::std::any::type_name::<T>(),
475 err,
476 ))
477 }
478 }
479 match cvar_name {
480 #( stringify!(#names) => set_str(&mut self.#names, str_value), )*
481 _ => ::core::result::Result::Err(format!(
482 "Cvar named {} not found",
483 cvar_name
484 )),
485 }
486 }
487
488 pub fn cvar_count(&self) -> usize {
490 #cvar_count
491 }
492
493 pub const CVAR_COUNT: usize = #cvar_count;
495 }
496
497 #set_get_impl
498
499 pub trait SetGetType {
503 fn get(cvars: &Cvars, cvar_name: &str) -> ::core::result::Result<Self, String>
504 where Self: Sized;
505 fn set(cvars: &mut Cvars, cvar_name: &str, value: Self) -> ::core::result::Result<(), String>;
506 }
507
508 #( #trait_impls )*
509 }
510}
511
512#[doc(hidden)]
520#[proc_macro_derive(SetGetDummy, attributes(cvars))]
521pub fn derive_dummy(input: TokenStream) -> TokenStream {
522 let begin = std::time::Instant::now();
523
524 let input: DeriveInput = parse_macro_input!(input);
525 let struct_name = input.ident;
526 let set_get_impl = impl_set_get(&struct_name);
527
528 let expanded = quote! {
529 #[automatically_derived]
530 impl #struct_name {
531 pub fn get<T>(&self, cvar_name: &str) -> ::core::result::Result<T, String> {
532 unimplemented!("SetGetDummy is only for compile time testing.");
533 }
534 pub fn get_string(&self, cvar_name: &str) -> ::core::result::Result<String, String> {
535 unimplemented!("SetGetDummy is only for compile time testing.");
536 }
537 pub fn set<T>(&mut self, cvar_name: &str, value: T) -> ::core::result::Result<(), String> {
538 unimplemented!("SetGetDummy is only for compile time testing.");
539 }
540 pub fn set_str(&mut self, cvar_name: &str, str_value: &str) -> ::core::result::Result<(), String> {
541 unimplemented!("SetGetDummy is only for compile time testing.");
542 }
543 pub fn cvar_count(&self) -> usize {
544 unimplemented!("SetGetDummy is only for compile time testing.");
545 }
546 }
547
548 #set_get_impl
549 };
550 let expanded = TokenStream::from(expanded);
551
552 let end = std::time::Instant::now();
553 if env::var("CVARS_STATS").is_ok() {
554 eprintln!("derive(SetGetDummy) took {:?}", end - begin);
555 }
556
557 expanded
558}
559
560fn impl_set_get(struct_name: &Ident) -> proc_macro2::TokenStream {
561 quote! {
562 #[automatically_derived]
563 impl ::cvars::SetGet for #struct_name {
564 fn get_string(&self, cvar_name: &str) -> ::core::result::Result<String, String> {
565 self.get_string(cvar_name)
566 }
567
568 fn set_str(&mut self, cvar_name: &str, cvar_value: &str) -> ::core::result::Result<(), String> {
569 self.set_str(cvar_name, cvar_value)
570 }
571
572 fn cvar_count(&self) -> usize {
573 self.cvar_count()
574 }
575 }
576 }
577}