Skip to main content

conjure_cp_rule_macros/
lib.rs

1use 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    /// Expression variant names this rule applies to (e.g. `Add`, `Sub`).
15    /// Empty means applicable to all variants (universal rule).
16    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        // Parse optional variant names in brackets: "Minion", 4200, [Add, Sub]
34        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/// Register a rule with the given rule sets and priorities.
58#[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::*; // ToDo idk if we need to explicitly do that?
88
89        #[::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/**
159* Register a rule set with the given name, dependencies, and metadata.
160*
161* # Example
162* ```rust
163 * use conjure_cp_rule_macros::register_rule_set;
164 * register_rule_set!("MyRuleSet", ("DependencyRuleSet", "AnotherRuleSet"));
165* ```
166 */
167#[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        // Does not apply by default, e.g. only used as a dependency
184        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::*; // ToDo idk if we need to explicitly do that?
190        #[::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}