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_set: 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_set: LitStr::new("", Span::call_site()),
24 priority: LitInt::new("0", Span::call_site()),
25 applicable_variants: Vec::new(),
26 });
27 }
28
29 let rule_set: LitStr = input.parse()?;
30 let _: Comma = input.parse()?;
31 let priority: LitInt = input.parse()?;
32
33 let mut applicable_variants = Vec::new();
35 if input.peek(Comma) {
36 let _: Comma = input.parse()?;
37 let content;
38 bracketed!(content in input);
39 while !content.is_empty() {
40 let variant: Ident = content.parse()?;
41 applicable_variants.push(variant);
42 if content.is_empty() {
43 break;
44 }
45 let _: Comma = content.parse()?;
46 }
47 }
48
49 Ok(RegisterRuleArgs {
50 rule_set,
51 priority,
52 applicable_variants,
53 })
54 }
55}
56
57#[proc_macro_attribute]
59pub fn register_rule(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
60 let func = parse_macro_input!(item as ItemFn);
61 let rule_ident = &func.sig.ident;
62 let static_name = format!("CONJURE_GEN_RULE_{rule_ident}").to_uppercase();
63 let static_ident = Ident::new(&static_name, rule_ident.span());
64
65 let args = parse_macro_input!(arg_tokens as RegisterRuleArgs);
66
67 let rule_sets_token = if args.rule_set.value().is_empty() {
68 quote! { &[] }
69 } else {
70 let rule_set_name = &args.rule_set;
71 let priority = &args.priority;
72 quote! { &[(#rule_set_name, #priority as u16)] }
73 };
74
75 let applicable_to = if args.applicable_variants.is_empty() {
76 quote! { None }
77 } else {
78 let variants = &args.applicable_variants;
79 quote! {
80 Some(&[#(::conjure_cp::discriminant_from_name!(#variants)),*])
81 }
82 };
83
84 let expanded = quote! {
85 #func
86
87 use ::conjure_cp::rule_engine::_dependencies::*; #[::conjure_cp::rule_engine::_dependencies::distributed_slice(::conjure_cp::rule_engine::RULES_DISTRIBUTED_SLICE)]
90 pub static #static_ident: ::conjure_cp::rule_engine::Rule<'static> = ::conjure_cp::rule_engine::Rule {
91 name: stringify!(#rule_ident),
92 application: #rule_ident,
93 rule_sets: #rule_sets_token,
94 applicable_to: #applicable_to,
95 };
96 };
97
98 TokenStream::from(expanded)
99}
100
101fn parse_parenthesized<T: Parse>(input: ParseStream) -> Result<Vec<T>> {
102 let content;
103 parenthesized!(content in input);
104
105 let mut paths = Vec::new();
106 while !content.is_empty() {
107 let path = content.parse()?;
108 paths.push(path);
109 if content.is_empty() {
110 break;
111 }
112 content.parse::<Comma>()?;
113 }
114
115 Ok(paths)
116}
117
118struct RuleSetArgs {
119 name: LitStr,
120 dependencies: Vec<LitStr>,
121 applies_fn: Option<ExprClosure>,
122}
123
124impl Parse for RuleSetArgs {
125 fn parse(input: ParseStream) -> Result<Self> {
126 let name = input.parse()?;
127
128 if input.is_empty() {
129 return Ok(Self {
130 name,
131 dependencies: Vec::new(),
132 applies_fn: None,
133 });
134 }
135
136 input.parse::<Comma>()?;
137 let dependencies = parse_parenthesized::<LitStr>(input)?;
138
139 if input.is_empty() {
140 return Ok(Self {
141 name,
142 dependencies,
143 applies_fn: None,
144 });
145 }
146
147 input.parse::<Comma>()?;
148 let applies_fn = input.parse::<ExprClosure>()?;
149
150 Ok(Self {
151 name,
152 dependencies,
153 applies_fn: Some(applies_fn),
154 })
155 }
156}
157
158#[proc_macro]
168pub fn register_rule_set(args: TokenStream) -> TokenStream {
169 let RuleSetArgs {
170 name,
171 dependencies,
172 applies_fn,
173 } = parse_macro_input!(args as RuleSetArgs);
174
175 let static_name = format!("CONJURE_GEN_RULE_SET_{}", name.value()).to_uppercase();
176 let static_ident = Ident::new(&static_name, Span::call_site());
177
178 let dependencies = quote! {
179 #(#dependencies),*
180 };
181
182 let applies_to_family = match applies_fn {
183 None => quote! { |_: &::conjure_cp::settings::SolverFamily| false },
185 Some(func) => quote! { #func },
186 };
187
188 let expanded = quote! {
189 use ::conjure_cp::rule_engine::_dependencies::*; #[::conjure_cp::rule_engine::_dependencies::distributed_slice(::conjure_cp::rule_engine::RULE_SETS_DISTRIBUTED_SLICE)]
191 pub static #static_ident: ::conjure_cp::rule_engine::RuleSet<'static> =
192 ::conjure_cp::rule_engine::RuleSet::new(#name, &[#dependencies], #applies_to_family);
193 };
194
195 TokenStream::from(expanded)
196}