conjure_cp_rule_macros/
lib.rs1use proc_macro::TokenStream;
2
3use proc_macro2::Span;
4use quote::quote;
5use syn::token::Comma;
6use syn::{
7 ExprClosure, Ident, ItemFn, LitInt, LitStr, Result, bracketed, parenthesized, parse::Parse,
8 parse::ParseStream, parse_macro_input,
9};
10
11struct RegisterRuleArgs {
12 rule_sets: Vec<LitStr>,
13 priority: LitInt,
14 applicable_variants: Vec<Ident>,
17}
18
19impl Parse for RegisterRuleArgs {
20 fn parse(input: ParseStream) -> Result<Self> {
21 if input.is_empty() {
22 return Ok(RegisterRuleArgs {
23 rule_sets: Vec::new(),
24 priority: LitInt::new("0", Span::call_site()),
25 applicable_variants: Vec::new(),
26 });
27 }
28
29 let rule_sets = if input.peek(syn::token::Bracket) {
30 let content;
31 bracketed!(content in input);
32
33 let mut rule_sets = Vec::new();
34 while !content.is_empty() {
35 let rule_set: LitStr = content.parse()?;
36 rule_sets.push(rule_set);
37 if content.is_empty() {
38 break;
39 }
40 let _: Comma = content.parse()?;
41 }
42 rule_sets
43 } else {
44 vec![input.parse()?]
45 };
46
47 let _: Comma = input.parse()?;
48 let priority: LitInt = input.parse()?;
49
50 let mut applicable_variants = Vec::new();
52 if input.peek(Comma) {
53 let _: Comma = input.parse()?;
54 let content;
55 bracketed!(content in input);
56 while !content.is_empty() {
57 let variant: Ident = content.parse()?;
58 applicable_variants.push(variant);
59 if content.is_empty() {
60 break;
61 }
62 let _: Comma = content.parse()?;
63 }
64 }
65
66 Ok(RegisterRuleArgs {
67 rule_sets,
68 priority,
69 applicable_variants,
70 })
71 }
72}
73
74#[proc_macro_attribute]
76pub fn register_rule(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
77 let func = parse_macro_input!(item as ItemFn);
78 let rule_ident = &func.sig.ident;
79 let static_name = format!("CONJURE_GEN_RULE_{rule_ident}").to_uppercase();
80 let static_ident = Ident::new(&static_name, rule_ident.span());
81
82 let args = parse_macro_input!(arg_tokens as RegisterRuleArgs);
83
84 let rule_sets_token = if args.rule_sets.is_empty() {
85 quote! { &[] }
86 } else {
87 let rule_sets = &args.rule_sets;
88 let priority = &args.priority;
89 quote! { &[#((#rule_sets, #priority as u16)),*] }
90 };
91
92 let applicable_to = if args.applicable_variants.is_empty() {
93 quote! { None }
94 } else {
95 let variants = &args.applicable_variants;
96 quote! {
97 Some(&[#(::conjure_cp::discriminant_from_name!(#variants)),*])
98 }
99 };
100
101 let expanded = quote! {
102 #func
103
104 use ::conjure_cp::rule_engine::_dependencies::*; #[::conjure_cp::rule_engine::_dependencies::distributed_slice(::conjure_cp::rule_engine::RULES_DISTRIBUTED_SLICE)]
107 pub static #static_ident: ::conjure_cp::rule_engine::Rule<'static> = ::conjure_cp::rule_engine::Rule {
108 name: stringify!(#rule_ident),
109 application: #rule_ident,
110 rule_sets: #rule_sets_token,
111 applicable_to: #applicable_to,
112 };
113 };
114
115 TokenStream::from(expanded)
116}
117
118fn parse_parenthesized<T: Parse>(input: ParseStream) -> Result<Vec<T>> {
119 let content;
120 parenthesized!(content in input);
121
122 let mut paths = Vec::new();
123 while !content.is_empty() {
124 let path = content.parse()?;
125 paths.push(path);
126 if content.is_empty() {
127 break;
128 }
129 content.parse::<Comma>()?;
130 }
131
132 Ok(paths)
133}
134
135struct RuleSetArgs {
136 name: LitStr,
137 dependencies: Vec<LitStr>,
138 applies_fn: Option<ExprClosure>,
139}
140
141impl Parse for RuleSetArgs {
142 fn parse(input: ParseStream) -> Result<Self> {
143 let name = input.parse()?;
144
145 if input.is_empty() {
146 return Ok(Self {
147 name,
148 dependencies: Vec::new(),
149 applies_fn: None,
150 });
151 }
152
153 input.parse::<Comma>()?;
154 let dependencies = parse_parenthesized::<LitStr>(input)?;
155
156 if input.is_empty() {
157 return Ok(Self {
158 name,
159 dependencies,
160 applies_fn: None,
161 });
162 }
163
164 input.parse::<Comma>()?;
165 let applies_fn = input.parse::<ExprClosure>()?;
166
167 Ok(Self {
168 name,
169 dependencies,
170 applies_fn: Some(applies_fn),
171 })
172 }
173}
174
175#[proc_macro]
185pub fn register_rule_set(args: TokenStream) -> TokenStream {
186 let RuleSetArgs {
187 name,
188 dependencies,
189 applies_fn,
190 } = parse_macro_input!(args as RuleSetArgs);
191
192 let static_name = format!("CONJURE_GEN_RULE_SET_{}", name.value()).to_uppercase();
193 let static_ident = Ident::new(&static_name, Span::call_site());
194
195 let dependencies = quote! {
196 #(#dependencies),*
197 };
198
199 let applies_to_family = match applies_fn {
200 None => quote! { |_: &::conjure_cp::settings::SolverFamily| false },
202 Some(func) => quote! { #func },
203 };
204
205 let expanded = quote! {
206 use ::conjure_cp::rule_engine::_dependencies::*; #[::conjure_cp::rule_engine::_dependencies::distributed_slice(::conjure_cp::rule_engine::RULE_SETS_DISTRIBUTED_SLICE)]
208 pub static #static_ident: ::conjure_cp::rule_engine::RuleSet<'static> =
209 ::conjure_cp::rule_engine::RuleSet::new(#name, &[#dependencies], #applies_to_family);
210 };
211
212 TokenStream::from(expanded)
213}