Skip to main content

besl_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4	parse_macro_input, Attribute, Data, DeriveInput, Expr, ExprLit, Fields, GenericArgument, Lit, LitStr, PathArguments, Type,
5};
6
7#[proc_macro_derive(BeslStruct, attributes(besl, besl_name, besl_type))]
8pub fn derive_besl_struct(input: TokenStream) -> TokenStream {
9	match derive_besl_struct_impl(parse_macro_input!(input as DeriveInput)) {
10		Ok(tokens) => tokens,
11		Err(error) => error.to_compile_error().into(),
12	}
13}
14
15fn derive_besl_struct_impl(input: DeriveInput) -> syn::Result<TokenStream> {
16	let ident = input.ident;
17	let struct_name = parse_name_override(&input.attrs)?.unwrap_or_else(|| ident.to_string());
18
19	let fields = match input.data {
20		Data::Struct(data) => data.fields,
21		_ => {
22			return Err(syn::Error::new_spanned(
23				ident,
24				"Invalid BESL struct derive target. The most likely cause is that `BeslStruct` was used on a non-struct item.",
25			));
26		}
27	};
28
29	let named_fields = match fields {
30		Fields::Named(fields) => fields.named,
31		_ => {
32			return Err(syn::Error::new_spanned(
33				ident,
34				"Invalid BESL struct fields. The most likely cause is that `BeslStruct` requires named struct fields.",
35			));
36		}
37	};
38
39	let field_nodes = named_fields
40		.iter()
41		.map(|field| {
42			let field_ident = field.ident.as_ref().ok_or_else(|| {
43				syn::Error::new_spanned(field, "Missing field name. The most likely cause is an unnamed struct field.")
44			})?;
45			let field_name = parse_name_override(&field.attrs)?.unwrap_or_else(|| field_ident.to_string());
46			let field_type = parse_type_override(&field.attrs)?.unwrap_or(type_to_besl(&field.ty)?);
47			let field_name_literal = LitStr::new(&field_name, field_ident.span());
48			let field_type_literal = LitStr::new(&field_type, field_ident.span());
49
50			Ok(quote! {
51				::besl::ParserNode::member(#field_name_literal, #field_type_literal)
52			})
53		})
54		.collect::<syn::Result<Vec<_>>>()?;
55
56	let struct_name_literal = LitStr::new(&struct_name, ident.span());
57
58	Ok(TokenStream::from(quote! {
59		impl ::besl::BeslStructDefinition for #ident {
60			fn besl_struct_node() -> ::besl::ParserNode<'static> {
61				::besl::ParserNode::r#struct(#struct_name_literal, vec![#(#field_nodes),*])
62			}
63		}
64	}))
65}
66
67fn parse_name_override(attributes: &[Attribute]) -> syn::Result<Option<String>> {
68	let mut result = None;
69
70	for attribute in attributes {
71		if attribute.path().is_ident("besl_name") {
72			if result.is_some() {
73				return Err(syn::Error::new_spanned(
74					attribute,
75					"Duplicate BESL name override. The most likely cause is multiple `#[besl_name = ...]` attributes.",
76				));
77			}
78
79			result = Some(parse_name_value_attribute(attribute, "besl_name")?);
80			continue;
81		}
82
83		if !attribute.path().is_ident("besl") {
84			continue;
85		}
86
87		attribute.parse_nested_meta(|meta| {
88			if meta.path.is_ident("name") {
89				if result.is_some() {
90					return Err(
91						meta.error("Duplicate BESL name override. The most likely cause is multiple `name = ...` attributes.")
92					);
93				}
94
95				let value = if meta.input.peek(syn::token::Paren) {
96					let content;
97					syn::parenthesized!(content in meta.input);
98					content.parse::<LitStr>()?
99				} else {
100					meta.value()?.parse::<LitStr>()?
101				};
102				result = Some(value.value());
103				Ok(())
104			} else if meta.path.is_ident("besl_type") {
105				Ok(())
106			} else {
107				Err(meta.error("Unknown BESL attribute. The most likely cause is an unsupported `#[besl(...)]` key."))
108			}
109		})?;
110	}
111
112	Ok(result)
113}
114
115fn parse_type_override(attributes: &[Attribute]) -> syn::Result<Option<String>> {
116	let mut result = None;
117
118	for attribute in attributes {
119		if attribute.path().is_ident("besl_type") {
120			if result.is_some() {
121				return Err(syn::Error::new_spanned(
122					attribute,
123					"Duplicate BESL type override. The most likely cause is multiple `#[besl_type = ...]` attributes.",
124				));
125			}
126
127			result = Some(parse_name_value_attribute(attribute, "besl_type")?);
128			continue;
129		}
130
131		if !attribute.path().is_ident("besl") {
132			continue;
133		}
134
135		attribute.parse_nested_meta(|meta| {
136			if meta.path.is_ident("besl_type") {
137				if result.is_some() {
138					return Err(meta.error(
139						"Duplicate BESL type override. The most likely cause is multiple `besl_type = ...` attributes.",
140					));
141				}
142
143				let value = if meta.input.peek(syn::token::Paren) {
144					let content;
145					syn::parenthesized!(content in meta.input);
146					content.parse::<LitStr>()?
147				} else {
148					meta.value()?.parse::<LitStr>()?
149				};
150				result = Some(value.value());
151				Ok(())
152			} else if meta.path.is_ident("name") {
153				Ok(())
154			} else {
155				Err(meta.error("Unknown BESL attribute. The most likely cause is an unsupported `#[besl(...)]` key."))
156			}
157		})?;
158	}
159
160	Ok(result)
161}
162
163fn parse_name_value_attribute(attribute: &Attribute, attribute_name: &str) -> syn::Result<String> {
164	match &attribute.meta {
165		syn::Meta::NameValue(name_value) => match &name_value.value {
166			Expr::Lit(ExprLit {
167				lit: Lit::Str(value), ..
168			}) => Ok(value.value()),
169			_ => Err(syn::Error::new_spanned(
170				attribute,
171				format!("Invalid {attribute_name} attribute. The most likely cause is a non-string literal value."),
172			)),
173		},
174		_ => Err(syn::Error::new_spanned(
175			attribute,
176			format!("Invalid {attribute_name} attribute. The most likely cause is missing `= \"...\"` syntax."),
177		)),
178	}
179}
180
181fn type_to_besl(ty: &Type) -> syn::Result<String> {
182	match ty {
183		Type::Path(path) => path_to_besl(path),
184		Type::Array(array) => {
185			let element_type = type_to_besl(&array.elem)?;
186			let count = match &array.len {
187				Expr::Lit(ExprLit {
188					lit: Lit::Int(value), ..
189				}) => value.base10_digits().to_string(),
190				_ => {
191					return Err(syn::Error::new_spanned(
192						&array.len,
193						"Invalid BESL array length. The most likely cause is a non-literal array size.",
194					));
195				}
196			};
197
198			Ok(format!("{element_type}[{count}]"))
199		}
200		_ => Err(syn::Error::new_spanned(
201			ty,
202			"Unsupported BESL field type. The most likely cause is that the field type is not a path or fixed-size array.",
203		)),
204	}
205}
206
207fn path_to_besl(path: &syn::TypePath) -> syn::Result<String> {
208	let mut segments = Vec::new();
209
210	for segment in &path.path.segments {
211		match &segment.arguments {
212			PathArguments::None => segments.push(segment.ident.to_string()),
213			PathArguments::AngleBracketed(arguments) => {
214				let generic_arguments = arguments
215					.args
216					.iter()
217					.map(|argument| match argument {
218						GenericArgument::Type(ty) => type_to_besl(ty),
219						_ => Err(syn::Error::new_spanned(
220							argument,
221							"Unsupported BESL generic argument. The most likely cause is a non-type generic argument.",
222						)),
223					})
224					.collect::<syn::Result<Vec<_>>>()?;
225
226				segments.push(format!("{}<{}>", segment.ident, generic_arguments.join(",")));
227			}
228			_ => {
229				return Err(syn::Error::new_spanned(
230					segment,
231					"Unsupported BESL path arguments. The most likely cause is parenthesized path arguments.",
232				));
233			}
234		}
235	}
236
237	segments.last().cloned().ok_or_else(|| {
238		syn::Error::new_spanned(
239			path,
240			"Invalid BESL type path. The most likely cause is an empty Rust type path.",
241		)
242	})
243}