1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{format_ident, quote, ToTokens}; use syn::{
5 parse::Parser,
6 parse_macro_input,
7 punctuated::Punctuated,
8 spanned::Spanned,
9 Attribute,
10 Data,
11 DeriveInput,
12 Expr,
13 ExprLit,
14 ExprPath,
15 Ident,
16 Lit,
17 LitStr,
18 Meta,
19 Path, Token,
21};
22
23#[derive(Debug)]
25struct RepresentationAttrs {
26 name: LitStr,
27 is_self_dual: bool,
28 custom_dual_name: Option<Ident>,
29}
30
31fn parse_representation_attributes(attrs: &[Attribute]) -> Result<RepresentationAttrs, syn::Error> {
32 let mut rep_name: Option<LitStr> = None;
34 let mut is_self_dual = false;
35 let mut custom_dual_name: Option<Ident> = None;
36
37 let rep_attr = attrs
38 .iter()
39 .find(|attr| attr.path().is_ident("representation"))
40 .ok_or_else(|| {
41 syn::Error::new(
42 Span::call_site(), "Missing #[representation(...)] attribute",
44 )
45 })?;
46
47 let meta = &rep_attr.meta;
48 let list = match meta {
49 Meta::List(list) => list,
50 _ => {
51 return Err(syn::Error::new_spanned(
52 meta,
53 "Expected #[representation(...)] format",
54 ))
55 }
56 };
57
58 let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
59 let nested_metas = parser.parse2(list.tokens.clone()).map_err(|e| {
60 syn::Error::new(
61 e.span(),
62 format!("Failed to parse attribute arguments: {}", e),
63 )
64 })?;
65
66 for meta_item in nested_metas.iter() {
67 match meta_item {
68 Meta::NameValue(nv) if nv.path.is_ident("name") => {
69 if rep_name.is_some() {
70 return Err(syn::Error::new_spanned(nv, "Duplicate `name` specified"));
71 }
72 if let Expr::Lit(ExprLit {
73 lit: Lit::Str(lit_str),
74 ..
75 }) = &nv.value
76 {
77 rep_name = Some(lit_str.clone());
78 } else {
79 return Err(syn::Error::new_spanned(
80 &nv.value,
81 "Expected string literal for `name`",
82 ));
83 }
84 }
85 Meta::Path(path) if path.is_ident("self_dual") => {
86 if is_self_dual {
87 return Err(syn::Error::new_spanned(
88 path,
89 "Duplicate `self_dual` specified",
90 ));
91 }
92 is_self_dual = true;
93 }
94 Meta::NameValue(nv) if nv.path.is_ident("dual_name") => {
95 if custom_dual_name.is_some() {
96 return Err(syn::Error::new_spanned(
97 nv,
98 "Duplicate `dual_name` specified",
99 ));
100 }
101 match &nv.value {
102 Expr::Lit(ExprLit {
103 lit: Lit::Str(lit_str),
104 ..
105 }) => {
106 custom_dual_name = Some(Ident::new(&lit_str.value(), lit_str.span()));
107 }
108 Expr::Path(ExprPath { path, .. }) => {
109 if let Some(ident) = path.get_ident() {
110 custom_dual_name = Some(ident.clone());
111 } else {
112 return Err(syn::Error::new_spanned(
113 &nv.value,
114 "Expected simple identifier for `dual_name` (e.g., MyDualName)",
115 ));
116 }
117 }
118 _ => {
119 return Err(syn::Error::new_spanned(
120 &nv.value,
121 "Expected string literal or identifier for `dual_name`",
122 ));
123 }
124 }
125 }
126 _ => {
127 return Err(syn::Error::new_spanned(
128 meta_item,
129 "Unsupported item in #[representation(...)] attribute",
130 ));
131 }
132 }
133 }
134
135 let name = rep_name.ok_or_else(|| {
136 syn::Error::new_spanned(
137 list.tokens.clone(),
138 "Missing required `name = \"...\"` in #[representation(...)]",
139 )
140 })?;
141
142 if is_self_dual && custom_dual_name.is_some() {
143 let error_span = nested_metas
144 .iter()
145 .find(|m| matches!(m, Meta::NameValue(nv) if nv.path.is_ident("dual_name")))
146 .map_or_else(|| list.tokens.span(), |m| m.span());
147
148 return Err(syn::Error::new(
149 error_span,
150 "`dual_name` cannot be specified for a `self_dual` representation",
151 ));
152 }
153
154 Ok(RepresentationAttrs {
155 name,
156 is_self_dual,
157 custom_dual_name,
158 })
159}
160
161fn get_filtered_derive_paths(attrs: &[Attribute]) -> Result<Vec<Path>, syn::Error> {
163 let mut derived_traits = Vec::new();
164
165 for attr in attrs {
166 if attr.path().is_ident("derive") {
168 match attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated) {
170 Ok(nested_metas) => {
171 for meta in nested_metas {
173 if let Meta::Path(path) = meta {
175 let is_target_derive = path
177 .segments
178 .last()
179 .is_some_and(|segment| segment.ident == "SimpleRepresentation");
180
181 if !is_target_derive {
182 derived_traits.push(path); }
184 } else {
185 return Err(syn::Error::new_spanned(
189 meta, "Expected simple trait paths (e.g., Debug, Clone) in derive attribute, found other meta item.",
191 ));
192 }
193 }
194 }
195 Err(e) => {
196 return Err(syn::Error::new_spanned(
199 attr.to_token_stream(), format!("Failed to parse derive arguments: {}. Check syntax inside #[derive(...)].", e),
201 ));
202 }
203 }
204 }
205 }
206
207 Ok(derived_traits)
208}
209
210#[proc_macro_derive(SimpleRepresentation, attributes(representation))]
211pub fn derive_simple_representation(input: TokenStream) -> TokenStream {
212 let input = parse_macro_input!(input as DeriveInput);
213
214 let fields = match &input.data {
216 Data::Struct(s) => s.fields.clone(),
217 _ => {
218 return syn::Error::new_spanned(
219 &input.ident,
220 "SimpleRepresentation can only be derived for structs",
221 )
222 .to_compile_error()
223 .into();
224 }
225 };
226
227 let vis = &input.vis;
229 let repr_attrs = match parse_representation_attributes(&input.attrs) {
230 Ok(attrs) => attrs,
231 Err(e) => return e.to_compile_error().into(),
232 };
233 let derived_traits = match get_filtered_derive_paths(&input.attrs) {
235 Ok(traits) => traits,
236 Err(e) => return e.to_compile_error().into(),
237 };
238
239 let base_type_ident = &input.ident;
240 let name_lit = &repr_attrs.name;
241 let is_self_dual = repr_attrs.is_self_dual;
242
243 let base_bounds = quote! { Default + Copy };
245 let dual_bounds = quote! { Default + Copy };
246
247 let base_repname_common_impl = quote! {
249 #[inline]
250 fn from_library_rep(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, ::spenso::structure::representation::RepresentationError>{
251 rep.try_into()
252 }
253 #[inline] fn base(&self) -> Self::Base where Self::Base: Default { Self::Base::default() }
254 #[inline] fn is_base(&self) -> bool { ::std::any::TypeId::of::<Self>() == ::std::any::TypeId::of::<Self::Base>() }
255 };
256
257 let base_display_impl = quote! {
259 impl ::std::fmt::Display for #base_type_ident where #base_type_ident: Copy + Into<::spenso::structure::representation::LibraryRep> {
260 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { write!(f, "{}", ::spenso::structure::representation::LibraryRep::from(*self)) }
261 }
262 };
263
264 let expanded = if is_self_dual {
266 let rep_new_call =
268 quote! { ::spenso::structure::representation::LibraryRep::new_self_dual(#name_lit) };
269 let base_from_impl = quote! {
270 impl From<#base_type_ident> for ::spenso::structure::representation::LibraryRep
271 where #base_type_ident: Copy
272 {
273 fn from(_value: #base_type_ident) -> Self {
274 #rep_new_call.expect(concat!("Failed to create self-dual Rep for ", #name_lit))
275 }
276 }
277 };
278
279 let base_try_from_impl = quote! {
280 impl TryFrom<::spenso::structure::representation::LibraryRep> for #base_type_ident where #base_type_ident: Default {
281 type Error = ::spenso::structure::representation::RepresentationError;
282
283 fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error> {
284 let expected_rep = #rep_new_call.expect(concat!("Failed to create self-dual Rep for ", #name_lit));
285 if rep == expected_rep {
286 ::std::result::Result::Ok(#base_type_ident::default())
287 } else {
288 ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(#name_lit.to_owned(), rep.to_string()))
289 }
290 }
291 }
292 };
293
294 let base_repname_impl = quote! {
295 impl ::spenso::structure::representation::RepName for #base_type_ident where #base_type_ident: #base_bounds {
296 type Base = #base_type_ident;
297 type Dual = #base_type_ident;
298
299 #[inline]
300 fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
301 ::linnet::half_edge::involution::Orientation::Undirected
302 }
303
304 #base_repname_common_impl
305 #[inline]
306 fn is_dual(self) -> bool { true }
307 #[inline] fn matches(&self, _other: &Self::Dual) -> bool { true }
308 #[inline] fn dual(self) -> Self::Dual { self }
309 }
310 };
311 quote! {
312 impl #base_type_ident {
313 pub const NAME: &'static str = #name_lit;
314 }
315 #base_from_impl
316 #base_try_from_impl
317 #base_repname_impl
318 #base_display_impl
319 }
320 } else {
321 let dual_type_ident = match &repr_attrs.custom_dual_name {
325 Some(custom_name) => custom_name.clone(),
326 None => format_ident!("Dual{}", base_type_ident, span = base_type_ident.span()),
327 };
328
329 let derive_attr = if !derived_traits.is_empty() {
331 quote! { #[derive( #(#derived_traits),* )] }
333 } else {
334 quote! {}
335 };
336 let dual_struct_def = quote! {
337 #derive_attr
338 #vis struct #dual_type_ident #fields
339 };
340
341 let rep_new_base_call =
343 quote! { ::spenso::structure::representation::LibraryRep::new_dual(#name_lit) };
344 let rep_new_dual_call = quote! { #rep_new_base_call.expect(concat!("Failed to create dual Rep for ", #name_lit)).dual() };
345
346 let base_from_impl = quote! {
348 impl From<#base_type_ident> for ::spenso::structure::representation::LibraryRep where #base_type_ident: Copy {
349 fn from(_value: #base_type_ident) -> Self {
350 #rep_new_base_call.expect(concat!("Failed to create Rep for ", #name_lit))
351 }
352 }
353 };
354 let base_try_from_impl = quote! {
355 impl TryFrom<::spenso::structure::representation::LibraryRep> for #base_type_ident where #base_type_ident: Default {
356 type Error = ::spenso::structure::representation::RepresentationError;
357
358 fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error> {
359 let expected_rep = #rep_new_base_call.expect(concat!("Failed to create Rep for ", #name_lit));
360 if rep == expected_rep {
361 ::std::result::Result::Ok(#base_type_ident::default())
362 } else {
363 ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(#name_lit.to_owned(), rep.to_string()))
364 }
365 }
366 }
367 };
368
369 let base_repname_impl = quote! {
370 impl ::spenso::structure::representation::RepName for #base_type_ident where #base_type_ident: #base_bounds, #dual_type_ident: #dual_bounds {
371 type Base = #base_type_ident;
372 type Dual = #dual_type_ident;
373
374
375 #[inline]
376 fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
377 ::linnet::half_edge::involution::Orientation::Default
378 }
379
380 #base_repname_common_impl
381 #[inline]
382 fn is_dual(self) -> bool { false }
383 #[inline]
384 fn matches(&self, _other: &Self::Dual) -> bool { true }
385 #[inline]
386 fn dual(self) -> Self::Dual where Self::Dual: Default {
387 #dual_type_ident::default()
388 }
389 }
390 };
391 let base_impls = quote! {
392 impl #base_type_ident {
393 pub const NAME: &'static str = #name_lit;
394 }
395 #base_from_impl
396 #base_try_from_impl
397 #base_repname_impl
398 #base_display_impl
399 };
400
401 let dual_display_impl = quote! {
403 impl ::std::fmt::Display for #dual_type_ident where #dual_type_ident: Copy + Into<::spenso::structure::representation::LibraryRep> {
404 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
405 write!(f, "{}", ::spenso::structure::representation::LibraryRep::from(*self))
406 }
407 }
408 };
409 let dual_from_impl = quote! {
410 impl From<#dual_type_ident> for ::spenso::structure::representation::LibraryRep where #dual_type_ident: Copy {
411 fn from(_value: #dual_type_ident) -> Self { #rep_new_dual_call }
412 }
413 };
414 let dual_try_from_impl = quote! {
415 impl TryFrom<::spenso::structure::representation::LibraryRep> for #dual_type_ident where #dual_type_ident: Default {
416 type Error = ::spenso::structure::representation::RepresentationError; fn try_from(rep: ::spenso::structure::representation::LibraryRep) -> ::std::result::Result<Self, Self::Error> {
417 let base_rep = #rep_new_base_call.expect(concat!("Failed to create dual Rep for ", #name_lit));
418 let expected_rep = base_rep.dual();
419 if rep == expected_rep {
420 ::std::result::Result::Ok(#dual_type_ident::default())
421 } else {
422 ::std::result::Result::Err(::spenso::structure::representation::RepresentationError::WrongRepresentationError(expected_rep.to_string(), rep.to_string()))
423 }
424 }
425 }
426 };
427 let dual_repname_impl = quote! {
428 impl ::spenso::structure::representation::RepName for #dual_type_ident where #dual_type_ident: #dual_bounds, #base_type_ident: #base_bounds {
429 type Base = #base_type_ident;
430 type Dual = #base_type_ident;
431
432 #[inline]
433 fn orientation(self) -> ::linnet::half_edge::involution::Orientation {
434 ::linnet::half_edge::involution::Orientation::Reversed
435 }
436 #base_repname_common_impl
437 #[inline]
438 fn dual(self) -> Self::Dual where Self::Dual: Default { #base_type_ident::default() }
439 #[inline]
440 fn is_dual(self) -> bool { true }
441 #[inline]
442 fn matches(&self, _other: &Self::Dual) -> bool { true }
443 #[inline]
444 fn is_neg(self, i: usize) -> bool where Self: Copy, Self::Dual: Copy + ::spenso::structure::representation::RepName {
445 self.dual().is_neg(i)
446 }
447 }
448 };
449 let dual_impls = quote! {
450 #dual_from_impl
451 #dual_try_from_impl
452 #dual_repname_impl
453 #dual_display_impl
454 };
455
456 quote! {
458 #dual_struct_def
459 #base_impls
460 #dual_impls
461 }
462 };
463
464 TokenStream::from(expanded)
465}