conjure_macros/
lib.rs

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/**
44 * Register a rule with the given rule sets and priorities.
45 */
46#[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::*; // ToDo idk if we need to explicitly do that?
70
71        #[::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/**
140* Register a rule set with the given name, dependencies, and metadata.
141*
142* # Example
143* ```rust
144 * use conjure_macros::register_rule_set;
145 * register_rule_set!("MyRuleSet", ("DependencyRuleSet", "AnotherRuleSet"));
146* ```
147 */
148#[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::*; // ToDo idk if we need to explicitly do that?
169        #[::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}