1use proc_macro::TokenStream;
2
3use proc_macro2::Span;
4use quote::quote;
5use syn::punctuated::Punctuated;
6use syn::token::Comma;
7use syn::{
8 parenthesized, parse::Parse, parse::ParseStream, parse_macro_input, Ident, ItemFn, LitInt,
9 LitStr, Path, Result,
10};
11
12#[derive(Debug)]
13struct RuleSetAndPriority {
14 rule_set: LitStr,
15 priority: LitInt,
16}
17
18impl Parse for RuleSetAndPriority {
19 fn parse(input: ParseStream) -> Result<Self> {
20 let content;
21 parenthesized!(content in input);
22 let rule_set: LitStr = content.parse()?;
23 let _: Comma = content.parse()?;
24 let priority: LitInt = content.parse()?;
25 Ok(RuleSetAndPriority { rule_set, priority })
26 }
27}
28
29#[derive(Debug)]
30struct RegisterRuleArgs {
31 pub rule_sets: Vec<RuleSetAndPriority>,
32}
33
34impl Parse for RegisterRuleArgs {
35 fn parse(input: ParseStream) -> Result<Self> {
36 let rule_sets = Punctuated::<RuleSetAndPriority, Comma>::parse_terminated(input)?;
37 Ok(RegisterRuleArgs {
38 rule_sets: rule_sets.into_iter().collect(),
39 })
40 }
41}
42
43#[proc_macro_attribute]
47pub fn register_rule(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
48 let func = parse_macro_input!(item as ItemFn);
49 let rule_ident = &func.sig.ident;
50 let static_name = format!("CONJURE_GEN_RULE_{}", rule_ident).to_uppercase();
51 let static_ident = Ident::new(&static_name, rule_ident.span());
52
53 let args = parse_macro_input!(arg_tokens as RegisterRuleArgs);
54 let rule_sets = args
55 .rule_sets
56 .iter()
57 .map(|rule_set| {
58 let rule_set_name = &rule_set.rule_set;
59 let priority = &rule_set.priority;
60 quote! {
61 (#rule_set_name, #priority as u16)
62 }
63 })
64 .collect::<Vec<_>>();
65
66 let expanded = quote! {
67 #func
68
69 use ::conjure_core::rule_engine::_dependencies::*; #[::conjure_core::rule_engine::_dependencies::distributed_slice(::conjure_core::rule_engine::RULES_DISTRIBUTED_SLICE)]
72 pub static #static_ident: ::conjure_core::rule_engine::Rule<'static> = ::conjure_core::rule_engine::Rule {
73 name: stringify!(#rule_ident),
74 application: #rule_ident,
75 rule_sets: &[#(#rule_sets),*],
76 };
77 };
78
79 TokenStream::from(expanded)
80}
81
82fn parse_parenthesized<T: Parse>(input: ParseStream) -> Result<Vec<T>> {
83 let content;
84 parenthesized!(content in input);
85
86 let mut paths = Vec::new();
87 while !content.is_empty() {
88 let path = content.parse()?;
89 paths.push(path);
90 if content.is_empty() {
91 break;
92 }
93 content.parse::<Comma>()?;
94 }
95
96 Ok(paths)
97}
98
99struct RuleSetArgs {
100 name: LitStr,
101 dependencies: Vec<LitStr>,
102 solver_families: Vec<Path>,
103}
104
105impl Parse for RuleSetArgs {
106 fn parse(input: ParseStream) -> Result<Self> {
107 let name = input.parse()?;
108
109 if input.is_empty() {
110 return Ok(Self {
111 name,
112 dependencies: Vec::new(),
113 solver_families: Vec::new(),
114 });
115 }
116
117 input.parse::<Comma>()?;
118 let dependencies = parse_parenthesized::<LitStr>(input)?;
119
120 if input.is_empty() {
121 return Ok(Self {
122 name,
123 dependencies,
124 solver_families: Vec::new(),
125 });
126 }
127
128 input.parse::<Comma>()?;
129 let solver_families = parse_parenthesized::<Path>(input)?;
130
131 Ok(Self {
132 name,
133 dependencies,
134 solver_families,
135 })
136 }
137}
138
139#[proc_macro]
149pub fn register_rule_set(args: TokenStream) -> TokenStream {
150 let RuleSetArgs {
151 name,
152 dependencies,
153 solver_families,
154 } = parse_macro_input!(args as RuleSetArgs);
155
156 let static_name = format!("CONJURE_GEN_RULE_SET_{}", name.value()).to_uppercase();
157 let static_ident = Ident::new(&static_name, Span::call_site());
158
159 let dependencies = quote! {
160 #(#dependencies),*
161 };
162
163 let solver_families = quote! {
164 #(#solver_families),*
165 };
166
167 let expanded = quote! {
168 use ::conjure_core::rule_engine::_dependencies::*; #[::conjure_core::rule_engine::_dependencies::distributed_slice(::conjure_core::rule_engine::RULE_SETS_DISTRIBUTED_SLICE)]
170 pub static #static_ident: ::conjure_core::rule_engine::RuleSet<'static> = ::conjure_core::rule_engine::RuleSet::new(#name, &[#dependencies], &[#solver_families]);
171 };
172
173 TokenStream::from(expanded)
174}