sc2_proc_macro/
lib.rs

1#[macro_use]
2extern crate quote;
3
4use proc_macro::TokenStream;
5use regex::Regex;
6use syn::{
7	parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, ItemEnum, ItemFn, ItemStruct, Meta,
8	NestedMeta, Stmt,
9};
10
11#[proc_macro_attribute]
12pub fn bot(_attr: TokenStream, item: TokenStream) -> TokenStream {
13	// let attr = parse_macro_input!(attr as AttributeArgs);
14	let item = parse_macro_input!(item as ItemStruct);
15
16	let name = item.ident;
17	let vis = item.vis;
18	let attrs = item.attrs;
19	let generics = item.generics;
20	let fields = match item.fields {
21		Fields::Named(named_fields) => {
22			let named = named_fields.named;
23			quote! {#named}
24		}
25		Fields::Unnamed(_) => panic!("#[bot] is not allowed for tuple structs"),
26		unit => quote! {#unit},
27	};
28
29	TokenStream::from(quote! {
30		#(#attrs)*
31		#vis struct #name#generics {
32			_bot: rust_sc2::bot::Bot,
33			#fields
34		}
35		impl std::ops::Deref for #name {
36			type Target = rust_sc2::bot::Bot;
37
38			fn deref(&self) -> &Self::Target {
39				&self._bot
40			}
41		}
42		impl std::ops::DerefMut for #name {
43			fn deref_mut(&mut self) -> &mut Self::Target {
44				&mut self._bot
45			}
46		}
47	})
48}
49
50#[proc_macro_attribute]
51pub fn bot_new(_attr: TokenStream, item: TokenStream) -> TokenStream {
52	// let attr = parse_macro_input!(attr as AttributeArgs);
53	let item = parse_macro_input!(item as ItemFn);
54
55	let vis = item.vis;
56	let signature = item.sig;
57	let blocks = item.block.stmts.iter().map(|s| match s {
58		Stmt::Expr(expr) => match expr {
59			Expr::Struct(struct_expr) => {
60				let path = &struct_expr.path;
61				let rest = match &struct_expr.rest {
62					Some(expr) => quote! {#expr},
63					None => quote! {},
64				};
65				let fields = struct_expr.fields.iter();
66
67				quote! {
68					#path {
69						_bot: Default::default(),
70						#(#fields,)*
71						..#rest
72					}
73				}
74			}
75			n => quote! {#n},
76		},
77		n => quote! {#n},
78	});
79
80	TokenStream::from(quote! {
81		#vis #signature {
82			#(#blocks)*
83		}
84	})
85}
86
87#[proc_macro_derive(FromStr, attributes(enum_from_str))]
88pub fn enum_from_str_derive(input: TokenStream) -> TokenStream {
89	let item = parse_macro_input!(input as DeriveInput);
90	if let Data::Enum(data) = item.data {
91		let name = item.ident;
92		let variants = data.variants.iter().map(|v| &v.ident);
93		// let variants2 = variants.clone().map(|v| format!("{}::{}", name, v));
94
95		let additional_attributes = |a: &Attribute| {
96			if a.path.is_ident("enum_from_str") {
97				if let Meta::List(list) = a.parse_meta().unwrap() {
98					return list.nested.iter().any(|n| {
99						if let NestedMeta::Meta(Meta::Path(path)) = n {
100							path.is_ident("use_primitives")
101						} else {
102							false
103						}
104					});
105				} else {
106					unreachable!("No options found in attribute `enum_from_str`")
107				}
108			}
109			false
110		};
111		let other_cases = if item.attrs.iter().any(additional_attributes) {
112			quote! {
113				n => {
114					if let Ok(num) = n.parse() {
115						if let Some(result) = Self::from_i64(num) {
116							return Ok(result);
117						}
118					}
119					return Err(sc2_macro::ParseEnumError);
120				}
121			}
122		} else {
123			quote! {_ => return Err(sc2_macro::ParseEnumError)}
124		};
125		TokenStream::from(quote! {
126			impl std::str::FromStr for #name {
127				type Err = sc2_macro::ParseEnumError;
128
129				fn from_str(s: &str) -> Result<Self, Self::Err> {
130					Ok(match s {
131						#(
132							stringify!(#variants) => Self::#variants,
133							// #variants2 => Self::#variants,
134
135						)*
136						#other_cases,
137					})
138				}
139			}
140		})
141	} else {
142		panic!("Can only derive FromStr for enums")
143	}
144}
145
146#[proc_macro_attribute]
147pub fn variant_checkers(_attr: TokenStream, item: TokenStream) -> TokenStream {
148	// let attr = parse_macro_input!(attr as AttributeArgs);
149	let item = parse_macro_input!(item as ItemEnum);
150
151	let name = &item.ident;
152	let variants = item.variants.iter().map(|v| &v.ident);
153	let re = Regex::new(r"[A-Z0-9]{1}[a-z0-9]*").unwrap();
154	let snake_variants = variants.clone().map(|v| {
155		format_ident!(
156			"is_{}",
157			re.find_iter(&v.to_string())
158				.map(|m| m.as_str().to_ascii_lowercase())
159				.collect::<Vec<String>>()
160				.join("_")
161		)
162	});
163
164	TokenStream::from(quote! {
165		#item
166		impl #name {
167			#(
168				#[inline]
169				pub fn #snake_variants(self) -> bool {
170					matches!(self, Self::#variants)
171				}
172			)*
173		}
174	})
175}