1mod util;
2
3use itertools::Itertools;
4use proc_macro::TokenStream;
5
6use proc_macro2::Span;
7use quote::{ToTokens, quote};
8use std::collections::HashMap;
9use syn::token::Comma;
10use syn::{
11 ExprClosure, GenericParam, Ident, ItemFn, ItemImpl, ItemStruct, LitInt, LitStr, Result, Token,
12 TypePath, bracketed, parenthesized, parse::Parse, parse::ParseStream, parse_macro_input,
13};
14
15use crate::util::{rename_ident_in_impl, type_is_ident, type_is_option_of_ident};
16use util::{build_serde_as_type, rename_fn, rename_ident_in_fn, type_contains_ident};
17
18enum ParsedPrefilter {
49 Variant(Ident),
51 Child { child: Ident },
53 VariantChild { variant: Ident, child: Ident },
55 Atom(Ident),
57 ChildAtom(Ident),
59}
60
61struct RegisterRuleArgs {
62 rule_sets: Vec<LitStr>,
64 priority: LitInt,
66 prefilters: Vec<ParsedPrefilter>,
68 symbols_only: bool,
70}
71
72impl Parse for RegisterRuleArgs {
73 fn parse(input: ParseStream) -> Result<Self> {
74 if input.is_empty() {
75 return Ok(RegisterRuleArgs {
76 rule_sets: Vec::new(),
77 priority: LitInt::new("0", Span::call_site()),
78 prefilters: Vec::new(),
79 symbols_only: false,
80 });
81 }
82
83 let rule_sets = if input.peek(syn::token::Bracket) {
84 let content;
85 bracketed!(content in input);
86
87 let mut rule_sets = Vec::new();
88 while !content.is_empty() {
89 let rule_set: LitStr = content.parse()?;
90 rule_sets.push(rule_set);
91 if content.is_empty() {
92 break;
93 }
94 let _: Comma = content.parse()?;
95 }
96 rule_sets
97 } else {
98 vec![input.parse()?]
99 };
100
101 let _: Comma = input.parse()?;
102 let priority: LitInt = input.parse()?;
103
104 let mut prefilters = Vec::new();
106 if input.peek(Comma) {
107 let _: Comma = input.parse()?;
108 let content;
109 bracketed!(content in input);
110 while !content.is_empty() {
111 if content.peek(Token![*]) {
112 let _: Token![*] = content.parse()?;
113 let _: Token![/] = content.parse()?;
114 let variant: Ident = content.parse()?;
115 if variant == "Atomic" && content.peek(Token![/]) {
116 let _: Token![/] = content.parse()?;
117 let subvariant: Ident = content.parse()?;
118 match subvariant.to_string().as_str() {
119 "Literal" | "Reference" => {
120 prefilters.push(ParsedPrefilter::ChildAtom(subvariant));
121 }
122 _ => {
123 return Err(syn::Error::new(
124 subvariant.span(),
125 "expected Literal or Reference after * / Atomic /",
126 ));
127 }
128 }
129 } else {
130 prefilters.push(ParsedPrefilter::Child { child: variant });
131 }
132 } else {
133 let variant: Ident = content.parse()?;
134 if content.peek(Token![/]) {
135 let _: Token![/] = content.parse()?;
136 let subvariant: Ident = content.parse()?;
137 if variant != "Atomic" {
138 prefilters.push(ParsedPrefilter::VariantChild {
139 variant,
140 child: subvariant,
141 });
142 } else {
143 match subvariant.to_string().as_str() {
144 "Literal" | "Reference" => {
145 prefilters.push(ParsedPrefilter::Atom(subvariant));
146 }
147 _ => {
148 return Err(syn::Error::new(
149 subvariant.span(),
150 "expected Literal or Reference after Atomic /",
151 ));
152 }
153 }
154 }
155 } else {
156 prefilters.push(ParsedPrefilter::Variant(variant));
157 }
158 }
159 if content.is_empty() {
160 break;
161 }
162 let _: Comma = content.parse()?;
163 }
164 }
165
166 let symbols_only = if input.peek(Comma) {
167 let _: Comma = input.parse()?;
168 let dependency: Ident = input.parse()?;
169 if dependency != "symbols_only" {
170 return Err(syn::Error::new(
171 dependency.span(),
172 "expected `symbols_only`",
173 ));
174 }
175 true
176 } else {
177 false
178 };
179
180 Ok(RegisterRuleArgs {
181 rule_sets,
182 priority,
183 prefilters,
184 symbols_only,
185 })
186 }
187}
188
189#[proc_macro_attribute]
191pub fn register_rule(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
192 let func = parse_macro_input!(item as ItemFn);
193 let rule_ident = &func.sig.ident;
194 let static_name = format!("CONJURE_GEN_RULE_{rule_ident}").to_uppercase();
195 let static_ident = Ident::new(&static_name, rule_ident.span());
196
197 let args = parse_macro_input!(arg_tokens as RegisterRuleArgs);
198
199 let rule_sets_token = if args.rule_sets.is_empty() {
200 quote! { &[] }
201 } else {
202 let rule_sets = &args.rule_sets;
203 let priority = &args.priority;
204 quote! { &[#((#rule_sets, #priority as u16)),*] }
205 };
206
207 let prefilters = if args.prefilters.is_empty() {
208 quote! { None }
209 } else {
210 let prefilter_tokens = args.prefilters.iter().map(|prefilter| match prefilter {
211 ParsedPrefilter::Variant(variant) => {
212 quote! {
213 ::conjure_cp::rule_engine::RulePrefilter::Variant(
214 ::conjure_cp::discriminant_from_name!(#variant)
215 )
216 }
217 }
218 ParsedPrefilter::Child { child } => {
219 quote! {
220 ::conjure_cp::rule_engine::RulePrefilter::Child {
221 child: ::conjure_cp::discriminant_from_name!(#child),
222 }
223 }
224 }
225 ParsedPrefilter::VariantChild { variant, child } => {
226 quote! {
227 ::conjure_cp::rule_engine::RulePrefilter::VariantChild {
228 variant: ::conjure_cp::discriminant_from_name!(#variant),
229 child: ::conjure_cp::discriminant_from_name!(#child),
230 }
231 }
232 }
233 ParsedPrefilter::Atom(atom_variant) => {
234 quote! {
235 ::conjure_cp::rule_engine::RulePrefilter::Atom(
236 ::conjure_cp::rule_engine::AtomKind::#atom_variant
237 )
238 }
239 }
240 ParsedPrefilter::ChildAtom(atom_variant) => {
241 quote! {
242 ::conjure_cp::rule_engine::RulePrefilter::ChildAtom(
243 ::conjure_cp::rule_engine::AtomKind::#atom_variant
244 )
245 }
246 }
247 });
248 quote! {
249 Some(&[#(#prefilter_tokens),*])
250 }
251 };
252 let failure_invalidation = if args.symbols_only {
253 quote! { ::conjure_cp::rule_engine::RuleFailureInvalidation::SymbolsOnly }
254 } else {
255 quote! { ::conjure_cp::rule_engine::RuleFailureInvalidation::ExpressionOrSymbols }
256 };
257
258 let expanded = quote! {
259 #func
260
261 pub static #static_ident: ::conjure_cp::rule_engine::Rule<'static> = ::conjure_cp::rule_engine::Rule {
262 name: stringify!(#rule_ident),
263 application: #rule_ident,
264 rule_sets: #rule_sets_token,
265 prefilters: #prefilters,
266 failure_invalidation: #failure_invalidation,
267 };
268
269 ::conjure_cp::rule_engine::_dependencies::inventory::submit! {
270 &#static_ident
271 }
272 };
273
274 TokenStream::from(expanded)
275}
276#[allow(clippy::large_enum_variant)]
278enum ReprStateType {
279 Struct(ItemStruct, Vec<ItemImpl>),
280 Path(TypePath),
281}
282
283impl Parse for ReprStateType {
284 fn parse(input: ParseStream) -> Result<Self> {
285 if let Ok(strct) = input.parse::<ItemStruct>() {
286 let mut imps = Vec::new();
287 while let Ok(imp) = input.parse::<ItemImpl>() {
288 imps.push(imp);
289 }
290 return Ok(ReprStateType::Struct(strct, imps));
291 }
292 if let Ok(path) = input.parse::<TypePath>() {
293 return Ok(ReprStateType::Path(path));
294 }
295 Err(input.error(
296 "Expected one of:\
297 - A struct definition, e.g `struct State<T> {...}`\
298 - A path to an existing type, e.g `Vec<T>`\
299 ",
300 ))
301 }
302}
303
304struct ReprDefArgs {
305 ident: Ident,
307 short_name: LitStr,
309 state_ty: ReprStateType,
311 init_fn: ItemFn,
313 structural_fn: ItemFn,
315 down_fn: ItemFn,
317 up_fn: ItemFn,
319 repr_vars_fn: Option<ItemFn>,
322 compactness_fn: Option<ItemFn>,
325 applies_fn: Option<ItemFn>,
328 integer_encoding_fn: Option<ItemFn>,
329 probe_score_fn: Option<ItemFn>,
330}
331
332impl Parse for ReprDefArgs {
333 fn parse(input: ParseStream) -> Result<Self> {
334 let ident = input.parse::<Ident>()?;
335 let short_name_content;
336 parenthesized!(short_name_content in input);
337 let short_name = short_name_content.parse::<LitStr>()?;
338 let state_ty = input.parse::<ReprStateType>()?;
339
340 let mut funcs = HashMap::<String, ItemFn>::new();
343 let mut errors: Vec<syn::Error> = Vec::new();
344 for _ in 0..9 {
345 match input.parse::<ItemFn>() {
346 Ok(func) => {
347 let ident = func.sig.ident.to_string();
348 funcs.insert(ident, func);
349 }
350 Err(e) => errors.push(e),
351 }
352 }
353
354 let fmt_errors = format!(
355 "\nErrors:\n{}",
356 errors.iter().map(syn::Error::to_string).join("\n")
357 );
358
359 let init_fn = funcs.remove("init").ok_or_else(|| {
360 input.error(format!("Expected `fn init(DomainPtr) -> Result<State<DomainPtr>, ReprInitError>`{fmt_errors}"))
361 })?;
362 let structural_fn = funcs.remove("structural").ok_or_else(|| {
363 input.error(format!(
364 "Expected `fn structural(&State<DeclarationPtr>) -> Vec<Expression>`{fmt_errors}"
365 ))
366 })?;
367 let down_fn = funcs.remove("down").ok_or_else(|| input.error(format!("Expected `fn down(&State<DomainPtr>, Literal) -> Result<State<Literal>, ReprDownError>`{fmt_errors}")))?;
368 let up_fn = funcs.remove("up").ok_or_else(|| {
369 input.error(format!(
370 "Expected `fn up(State<Literal>) -> Literal`{fmt_errors}"
371 ))
372 })?;
373 let repr_vars_fn = funcs.remove("repr_vars");
374 let compactness_fn = funcs.remove("compactness");
375 let applies_fn = funcs.remove("applies");
376 let integer_encoding_fn = funcs.remove("integer_encoding");
377 let probe_score_fn = funcs.remove("probe_score");
378
379 if repr_vars_fn.is_none() && matches!(state_ty, ReprStateType::Path(..)) {
380 return Err(input.error("A repr_vars implementation is required for external types"));
381 }
382
383 Ok(Self {
384 ident,
385 short_name,
386 state_ty,
387 init_fn,
388 structural_fn,
389 down_fn,
390 up_fn,
391 repr_vars_fn,
392 compactness_fn,
393 applies_fn,
394 integer_encoding_fn,
395 probe_score_fn,
396 })
397 }
398}
399
400#[proc_macro]
401pub fn register_representation(input: TokenStream) -> TokenStream {
402 let args = parse_macro_input!(input as ReprDefArgs);
403 let repr_ident = &args.ident;
404 let repr_short_name = &args.short_name;
405 let repr_name_str = repr_ident.to_string();
406
407 let prefix = format!("CONJURE_GEN_REPR_{}_", repr_name_str);
409
410 let (user_state_ident, struct_def_tokens) = match &args.state_ty {
411 ReprStateType::Struct(item_struct, _) => {
412 let ident = item_struct.ident.clone();
414 let prefixed_ident = Ident::new(
415 &format!("{}{}", repr_name_str, ident),
416 item_struct.ident.span(),
417 );
418 let tokens = generate_struct_def(item_struct, &prefixed_ident);
419 (ident, tokens)
420 }
421 ReprStateType::Path(type_path) => {
422 let ident = type_path
424 .path
425 .segments
426 .last()
427 .expect("state type path must have at least one segment")
428 .ident
429 .clone();
430 (ident, quote! {})
431 }
432 };
433
434 let state_ident = match &args.state_ty {
436 ReprStateType::Struct(..) => {
437 Ident::new(
439 &format!("{}{}", repr_name_str, user_state_ident),
440 user_state_ident.span(),
441 )
442 }
443 ReprStateType::Path(_) => user_state_ident.clone(),
445 };
446
447 let prefixed_init = Ident::new(&format!("{}init", prefix), args.init_fn.sig.ident.span());
452 let prefixed_structural = Ident::new(
453 &format!("{}structural", prefix),
454 args.structural_fn.sig.ident.span(),
455 );
456 let prefixed_down = Ident::new(&format!("{}down", prefix), args.down_fn.sig.ident.span());
457 let prefixed_up = Ident::new(&format!("{}up", prefix), args.up_fn.sig.ident.span());
458 let prefixed_repr_vars = args
459 .repr_vars_fn
460 .as_ref()
461 .map(|f| Ident::new(&format!("{}repr_vars", prefix), f.sig.ident.span()));
462 let prefixed_compactness = args
463 .compactness_fn
464 .as_ref()
465 .map(|f| Ident::new(&format!("{}compactness", prefix), f.sig.ident.span()));
466 let prefixed_applies = args
467 .applies_fn
468 .as_ref()
469 .map(|f| Ident::new(&format!("{}applies", prefix), f.sig.ident.span()));
470 let applies_fn = args.applies_fn.map(|f| {
471 let f = rename_fn(f, prefixed_applies.as_ref().unwrap());
472 quote! {
473 #[allow(non_snake_case)]
474 #f
475 }
476 });
477 let applies_impl = match &prefixed_applies {
478 Some(ident) => quote! {
479 fn applies_to(family: ::conjure_cp::settings::SolverFamily) -> bool {
480 #ident(family)
481 }
482 },
483 None => quote! {},
484 };
485
486 let probe_score_impl = args.probe_score_fn.map(|f| quote! { #f });
487
488 let integer_encoding_impl = args.integer_encoding_fn.map(|mut f| {
489 f.sig.ident = Ident::new("is_integer_encoding", f.sig.ident.span());
490 quote! { #f }
491 });
492
493 let mut init_fn = rename_fn(args.init_fn, &prefixed_init);
494 let mut structural_fn = rename_fn(args.structural_fn, &prefixed_structural);
495 let mut down_fn = rename_fn(args.down_fn, &prefixed_down);
496 let mut up_fn = rename_fn(args.up_fn, &prefixed_up);
497 let mut repr_vars_fn = args
498 .repr_vars_fn
499 .map(|f| rename_fn(f, prefixed_repr_vars.as_ref().unwrap()));
500 let mut compactness_fn = args
501 .compactness_fn
502 .map(|f| rename_fn(f, prefixed_compactness.as_ref().unwrap()));
503
504 if matches!(&args.state_ty, ReprStateType::Struct(..)) {
505 init_fn = rename_ident_in_fn(init_fn, &user_state_ident, &state_ident);
506 structural_fn = rename_ident_in_fn(structural_fn, &user_state_ident, &state_ident);
507 down_fn = rename_ident_in_fn(down_fn, &user_state_ident, &state_ident);
508 up_fn = rename_ident_in_fn(up_fn, &user_state_ident, &state_ident);
509 repr_vars_fn = repr_vars_fn.map(|f| rename_ident_in_fn(f, &user_state_ident, &state_ident));
510 compactness_fn =
511 compactness_fn.map(|f| rename_ident_in_fn(f, &user_state_ident, &state_ident));
512 }
513
514 let renamed_impls = if let ReprStateType::Struct(_, impls) = args.state_ty {
516 impls
517 .into_iter()
518 .map(|imp| rename_ident_in_impl(imp, &user_state_ident, &state_ident))
519 .collect()
520 } else {
521 Vec::new()
522 };
523
524 let repr_vars_impl = if repr_vars_fn.is_some() {
525 quote! {#prefixed_repr_vars(self)}
526 } else {
527 quote! {self.__collect_t_children()}
528 };
529 let repr_vars_fn_toks = repr_vars_fn.map(|f| {
530 quote! {
531 #[allow(non_snake_case)]
532 #f
533 }
534 });
535 let compactness_impl = if compactness_fn.is_some() {
536 quote! {#prefixed_compactness(self)}
537 } else {
538 quote! {
539 self.__collect_t_children()
540 .iter()
541 .map(default_impls::domain_size)
542 .fold(1usize, usize::saturating_mul)
543 }
544 };
545 let compactness_fn_toks = compactness_fn.map(|f| {
546 quote! {
547 #[allow(non_snake_case)]
548 #f
549 }
550 });
551
552 let dist_slice_name = format!("CONJURE_GEN_REPR_{}", repr_name_str).to_uppercase();
554 let dist_slice_ident = Ident::new(&dist_slice_name, repr_ident.span());
555
556 let init_cache_name = format!("CONJURE_GEN_REPR_{}_INIT_CACHE", repr_name_str).to_uppercase();
557 let init_cache_ident = Ident::new(&init_cache_name, repr_ident.span());
558
559 let expanded = quote! {
560 use ::conjure_cp::representation::_dependencies::*;
562 use ::conjure_cp::ast::{
563 DeclarationPtr, DomainPtr, Expression, Literal, SymbolTable, Name
564 };
565
566 #struct_def_tokens
568
569 #(#renamed_impls)*
571
572 #[allow(non_snake_case)]
574 #init_fn
575 #[allow(non_snake_case)]
576 #structural_fn
577 #[allow(non_snake_case)]
578 #down_fn
579 #[allow(non_snake_case)]
580 #up_fn
581 #repr_vars_fn_toks
582 #compactness_fn_toks
583
584 static #init_cache_ident: std::sync::LazyLock<FrozenMap<DomainPtr, Box<#state_ident<DomainPtr>>>> = std::sync::LazyLock::new(|| FrozenMap::new());
585
586 impl ReprDomainLevel for #state_ident<DomainPtr> {
588 const RULE: &'static dyn ReprRuleStored = &#repr_ident;
589 type Assignment = #state_ident<Literal>;
590 type DeclLevel = #state_ident<DeclarationPtr>;
591
592 fn init(dom: DomainPtr) -> ::core::result::Result<Self, ReprInitError>
593 where
594 Self: Sized,
595 {
596 if let Some(res) = #init_cache_ident.get(&dom) {
597 return Ok(res.clone());
598 }
599
600 let res = #prefixed_init(dom.clone())?;
601 let _ = #init_cache_ident.insert(dom, Box::new(res.clone()));
602 Ok(res)
603 }
604
605 fn compactness_score(&self) -> usize {
606 #compactness_impl
607 }
608
609 fn down(
610 &self,
611 value: Literal,
612 ) -> ::core::result::Result<Self::Assignment, ReprDownError> {
613 #prefixed_down(self, value)
614 }
615
616 fn instantiate(self, decl: DeclarationPtr) -> ReprInstantiateResult<Self::DeclLevel> {
617 default_impls::instantiate_default_impl(self, decl, #prefixed_structural)
618 }
619 }
620
621 impl ReprDeclLevel for #state_ident<DeclarationPtr> {
622 const RULE: &'static dyn ReprRuleStored = &#repr_ident;
623 type Assignment = #state_ident<Literal>;
624 type DomainLevel = #state_ident<DomainPtr>;
625
626 fn to_domain_level(self) -> Self::DomainLevel {
627 default_impls::to_domain_level_default_impl(self)
628 }
629
630 fn lookup_via(
631 &self,
632 lookup: &LookupFn<'_>,
633 ) -> ::core::result::Result<Self::Assignment, ReprUpError> {
634 default_impls::lookup_via_default_impl(self, lookup)
635 }
636
637 fn repr_vars(&self) -> ::std::collections::VecDeque<DeclarationPtr> {
638 #repr_vars_impl
639 }
640 }
641
642 impl ReprAssignment for #state_ident<Literal> {
643 fn up(self) -> Literal {
644 #prefixed_up(self)
645 }
646 }
647
648 pub struct #repr_ident;
650
651 #applies_fn
652
653 impl ReprRule for #repr_ident {
654 const STORED: &'static dyn ReprRuleStored = &#repr_ident;
655 const NAME: &'static str = #repr_name_str;
656 const SHORT_NAME: &'static str = #repr_short_name;
657 type Assignment = #state_ident<Literal>;
658 type DeclLevel = #state_ident<DeclarationPtr>;
659 type DomainLevel = #state_ident<DomainPtr>;
660
661 #applies_impl
662 #integer_encoding_impl
663 #probe_score_impl
664 }
665
666 pub static #dist_slice_ident: &'static dyn ReprRuleStored = &#repr_ident;
668
669 ::conjure_cp::representation::_dependencies::inventory::submit! {
670 #dist_slice_ident
671 }
672 };
673
674 TokenStream::from(expanded)
675}
676
677fn generate_struct_def(
680 item_struct: &ItemStruct,
681 prefixed_ident: &Ident,
682) -> proc_macro2::TokenStream {
683 let generic_param_ident = item_struct
685 .generics
686 .params
687 .iter()
688 .find_map(|p| {
689 if let GenericParam::Type(tp) = p {
690 Some(tp.ident.clone())
691 } else {
692 None
693 }
694 })
695 .expect("state struct must have exactly one type parameter");
696
697 let generics = &item_struct.generics;
698
699 let serde_bound = format!(
700 "ReprStateSerde: SerializeAs<{0}> + for<'d> DeserializeAs<'d, {0}>",
701 generic_param_ident
702 );
703
704 let mut collect_children_exprs: Vec<proc_macro2::TokenStream> = Vec::new();
705 let mut biplate_bounds: Vec<proc_macro2::TokenStream> = vec![quote! {
706 #generic_param_ident: ::conjure_cp::representation::_dependencies::uniplate::Uniplate
707 }];
708
709 let fields = match &item_struct.fields {
710 syn::Fields::Named(named) => {
711 let field_tokens: Vec<_> = named
712 .named
713 .iter()
714 .map(|f| {
715 let field_attrs = &f.attrs;
716 let field_vis = &f.vis;
717 let field_ident = f
718 .ident
719 .as_ref()
720 .expect("named field must have an identifier");
721 let field_ty = &f.ty;
722
723 if type_contains_ident(&f.ty, &generic_param_ident) {
724 if type_is_ident(&f.ty, &generic_param_ident) {
725 collect_children_exprs.push(quote! {
726 children.push_back(self.#field_ident.clone());
727 });
728 } else if type_is_option_of_ident(&f.ty, &generic_param_ident) {
729 collect_children_exprs.push(quote! {
730 children.extend(self.#field_ident.iter().cloned());
731 });
732 } else {
733 collect_children_exprs.push(quote! {
734 children.extend(
735 ::conjure_cp::representation::_dependencies::uniplate::Biplate::<#generic_param_ident>::children_bi(&self.#field_ident)
736 );
737 });
738 biplate_bounds.push(quote! {
739 #field_ty: ::conjure_cp::representation::_dependencies::uniplate::Biplate<#generic_param_ident>
740 });
741 }
742
743 let serde_as_ty =
744 build_serde_as_type(field_ty, &generic_param_ident, "ReprStateSerde");
745 let serde_as_str = serde_as_ty.to_token_stream().to_string();
746 quote! {
747 #(#field_attrs)*
748 #[serde_as(as = #serde_as_str)]
749 #field_vis #field_ident: #field_ty
750 }
751 } else {
752 quote! {
753 #(#field_attrs)*
754 #field_vis #field_ident: #field_ty
755 }
756 }
757 })
758 .collect();
759
760 quote! { { #(#field_tokens),* } }
761 }
762 syn::Fields::Unnamed(unnamed) => {
763 let field_tokens: Vec<_> = unnamed
764 .unnamed
765 .iter()
766 .enumerate()
767 .map(|(idx, f)| {
768 let field_attrs = &f.attrs;
769 let field_vis = &f.vis;
770 let field_ty = &f.ty;
771
772 if type_contains_ident(&f.ty, &generic_param_ident) {
773 let index = syn::Index::from(idx);
774 if type_is_ident(&f.ty, &generic_param_ident) {
775 collect_children_exprs.push(quote! {
776 children.push_back(self.#index.clone());
777 });
778 } else if type_is_option_of_ident(&f.ty, &generic_param_ident) {
779 collect_children_exprs.push(quote! {
780 children.extend(self.#index.iter().cloned());
781 });
782 } else {
783 collect_children_exprs.push(quote! {
784 children.extend(
785 ::conjure_cp::representation::_dependencies::uniplate::Biplate::<#generic_param_ident>::children_bi(&self.#index)
786 );
787 });
788 biplate_bounds.push(quote! {
789 #field_ty: ::conjure_cp::representation::_dependencies::uniplate::Biplate<#generic_param_ident>
790 });
791 }
792
793 let serde_as_ty =
794 build_serde_as_type(field_ty, &generic_param_ident, "ReprStateSerde");
795 let serde_as_str = serde_as_ty.to_token_stream().to_string();
796 quote! {
797 #(#field_attrs)*
798 #[serde_as(as = #serde_as_str)]
799 #field_vis #field_ty
800 }
801 } else {
802 quote! {
803 #(#field_attrs)*
804 #field_vis #field_ty
805 }
806 }
807 })
808 .collect();
809
810 quote! { ( #(#field_tokens),* ); }
811 }
812 syn::Fields::Unit => quote! { ; },
813 };
814
815 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
816 let method_where_clause = if biplate_bounds.is_empty() {
817 quote! {}
818 } else {
819 quote! { where #(#biplate_bounds),* }
820 };
821
822 quote! {
823 #[allow(non_camel_case_types)]
824 #[::conjure_cp::representation::_dependencies::serde_with::serde_as(
825 crate = "::conjure_cp::representation::_dependencies::serde_with"
826 )]
827 #[derive(
828 Debug,
829 Clone,
830 PartialEq,
831 Eq,
832 ::conjure_cp::representation::_dependencies::funcmap::FuncMap,
833 ::conjure_cp::representation::_dependencies::funcmap::TryFuncMap,
834 ::conjure_cp::representation::_dependencies::serde::Serialize,
835 ::conjure_cp::representation::_dependencies::serde::Deserialize
836 )]
837 #[serde(
838 crate = "::conjure_cp::representation::_dependencies::serde",
839 bound = #serde_bound
840 )]
841 #[funcmap(crate = "::conjure_cp::representation::_dependencies::funcmap")]
842 pub struct #prefixed_ident #generics #fields
843
844 impl #impl_generics #prefixed_ident #ty_generics #where_clause {
845 fn __collect_t_children(&self) -> ::std::collections::VecDeque<#generic_param_ident>
846 #method_where_clause
847 {
848 let mut children = ::std::collections::VecDeque::new();
849 #(#collect_children_exprs)*
850 children
851 }
852 }
853 }
854}
855
856fn parse_parenthesized<T: Parse>(input: ParseStream) -> Result<Vec<T>> {
857 let content;
858 parenthesized!(content in input);
859
860 let mut paths = Vec::new();
861 while !content.is_empty() {
862 let path = content.parse()?;
863 paths.push(path);
864 if content.is_empty() {
865 break;
866 }
867 content.parse::<Comma>()?;
868 }
869
870 Ok(paths)
871}
872
873struct RuleSetArgs {
874 name: LitStr,
875 dependencies: Vec<LitStr>,
876 applies_fn: Option<ExprClosure>,
877}
878
879impl Parse for RuleSetArgs {
880 fn parse(input: ParseStream) -> Result<Self> {
881 let name = input.parse()?;
882
883 if input.is_empty() {
884 return Ok(Self {
885 name,
886 dependencies: Vec::new(),
887 applies_fn: None,
888 });
889 }
890
891 input.parse::<Comma>()?;
892 let dependencies = parse_parenthesized::<LitStr>(input)?;
893
894 if input.is_empty() {
895 return Ok(Self {
896 name,
897 dependencies,
898 applies_fn: None,
899 });
900 }
901
902 input.parse::<Comma>()?;
903 let applies_fn = input.parse::<ExprClosure>()?;
904
905 Ok(Self {
906 name,
907 dependencies,
908 applies_fn: Some(applies_fn),
909 })
910 }
911}
912
913#[proc_macro]
923pub fn register_rule_set(args: TokenStream) -> TokenStream {
924 let RuleSetArgs {
925 name,
926 dependencies,
927 applies_fn,
928 } = parse_macro_input!(args as RuleSetArgs);
929
930 let static_name = format!("CONJURE_GEN_RULE_SET_{}", name.value()).to_uppercase();
931 let static_ident = Ident::new(&static_name, Span::call_site());
932
933 let dependencies = quote! {
934 #(#dependencies),*
935 };
936
937 let applies_to_family = match applies_fn {
938 None => quote! { |_: &::conjure_cp::settings::SolverFamily| false },
940 Some(func) => quote! { #func },
941 };
942
943 let expanded = quote! {
944 pub static #static_ident: ::conjure_cp::rule_engine::RuleSet<'static> =
945 ::conjure_cp::rule_engine::RuleSet::new(#name, &[#dependencies], #applies_to_family);
946
947 ::conjure_cp::rule_engine::_dependencies::inventory::submit! {
948 &#static_ident
949 }
950 };
951
952 TokenStream::from(expanded)
953}