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}