1
use proc_macro::TokenStream;
2

            
3
use proc_macro2::Span;
4
use quote::quote;
5
use syn::token::Comma;
6
use syn::{
7
    ExprClosure, Ident, ItemFn, LitInt, LitStr, Result, bracketed, parenthesized, parse::Parse,
8
    parse::ParseStream, parse_macro_input,
9
};
10

            
11
struct RegisterRuleArgs {
12
    rule_sets: Vec<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

            
19
impl Parse for RegisterRuleArgs {
20
713
    fn parse(input: ParseStream) -> Result<Self> {
21
713
        if input.is_empty() {
22
2
            return Ok(RegisterRuleArgs {
23
2
                rule_sets: Vec::new(),
24
2
                priority: LitInt::new("0", Span::call_site()),
25
2
                applicable_variants: Vec::new(),
26
2
            });
27
711
        }
28

            
29
711
        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
711
            vec![input.parse()?]
45
        };
46

            
47
711
        let _: Comma = input.parse()?;
48
711
        let priority: LitInt = input.parse()?;
49

            
50
        // Parse optional variant names in brackets: "Minion", 4200, [Add, Sub]
51
711
        let mut applicable_variants = Vec::new();
52
711
        if input.peek(Comma) {
53
655
            let _: Comma = input.parse()?;
54
            let content;
55
655
            bracketed!(content in input);
56
865
            while !content.is_empty() {
57
865
                let variant: Ident = content.parse()?;
58
865
                applicable_variants.push(variant);
59
865
                if content.is_empty() {
60
655
                    break;
61
210
                }
62
210
                let _: Comma = content.parse()?;
63
            }
64
56
        }
65

            
66
711
        Ok(RegisterRuleArgs {
67
711
            rule_sets,
68
711
            priority,
69
711
            applicable_variants,
70
711
        })
71
713
    }
72
}
73

            
74
/// Register a rule with the given rule sets and priorities.
75
#[proc_macro_attribute]
76
713
pub fn register_rule(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
77
713
    let func = parse_macro_input!(item as ItemFn);
78
713
    let rule_ident = &func.sig.ident;
79
713
    let static_name = format!("CONJURE_GEN_RULE_{rule_ident}").to_uppercase();
80
713
    let static_ident = Ident::new(&static_name, rule_ident.span());
81

            
82
713
    let args = parse_macro_input!(arg_tokens as RegisterRuleArgs);
83

            
84
713
    let rule_sets_token = if args.rule_sets.is_empty() {
85
2
        quote! { &[] }
86
    } else {
87
711
        let rule_sets = &args.rule_sets;
88
711
        let priority = &args.priority;
89
711
        quote! { &[#((#rule_sets, #priority as u16)),*] }
90
    };
91

            
92
713
    let applicable_to = if args.applicable_variants.is_empty() {
93
58
        quote! { None }
94
    } else {
95
655
        let variants = &args.applicable_variants;
96
655
        quote! {
97
            Some(&[#(::conjure_cp::discriminant_from_name!(#variants)),*])
98
        }
99
    };
100

            
101
713
    let expanded = quote! {
102
        #func
103

            
104
        use ::conjure_cp::rule_engine::_dependencies::*; // ToDo idk if we need to explicitly do that?
105

            
106
        #[::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
713
    TokenStream::from(expanded)
116
713
}
117

            
118
67
fn parse_parenthesized<T: Parse>(input: ParseStream) -> Result<Vec<T>> {
119
    let content;
120
67
    parenthesized!(content in input);
121

            
122
67
    let mut paths = Vec::new();
123
69
    while !content.is_empty() {
124
55
        let path = content.parse()?;
125
55
        paths.push(path);
126
55
        if content.is_empty() {
127
53
            break;
128
2
        }
129
2
        content.parse::<Comma>()?;
130
    }
131

            
132
67
    Ok(paths)
133
67
}
134

            
135
struct RuleSetArgs {
136
    name: LitStr,
137
    dependencies: Vec<LitStr>,
138
    applies_fn: Option<ExprClosure>,
139
}
140

            
141
impl Parse for RuleSetArgs {
142
68
    fn parse(input: ParseStream) -> Result<Self> {
143
68
        let name = input.parse()?;
144

            
145
68
        if input.is_empty() {
146
1
            return Ok(Self {
147
1
                name,
148
1
                dependencies: Vec::new(),
149
1
                applies_fn: None,
150
1
            });
151
67
        }
152

            
153
67
        input.parse::<Comma>()?;
154
67
        let dependencies = parse_parenthesized::<LitStr>(input)?;
155

            
156
67
        if input.is_empty() {
157
34
            return Ok(Self {
158
34
                name,
159
34
                dependencies,
160
34
                applies_fn: None,
161
34
            });
162
33
        }
163

            
164
33
        input.parse::<Comma>()?;
165
33
        let applies_fn = input.parse::<ExprClosure>()?;
166

            
167
33
        Ok(Self {
168
33
            name,
169
33
            dependencies,
170
33
            applies_fn: Some(applies_fn),
171
33
        })
172
68
    }
173
}
174

            
175
/**
176
* Register a rule set with the given name, dependencies, and metadata.
177
*
178
* # Example
179
* ```rust
180
 * use conjure_cp_rule_macros::register_rule_set;
181
 * register_rule_set!("MyRuleSet", ("DependencyRuleSet", "AnotherRuleSet"));
182
* ```
183
 */
184
#[proc_macro]
185
68
pub fn register_rule_set(args: TokenStream) -> TokenStream {
186
    let RuleSetArgs {
187
68
        name,
188
68
        dependencies,
189
68
        applies_fn,
190
68
    } = parse_macro_input!(args as RuleSetArgs);
191

            
192
68
    let static_name = format!("CONJURE_GEN_RULE_SET_{}", name.value()).to_uppercase();
193
68
    let static_ident = Ident::new(&static_name, Span::call_site());
194

            
195
68
    let dependencies = quote! {
196
        #(#dependencies),*
197
    };
198

            
199
68
    let applies_to_family = match applies_fn {
200
        // Does not apply by default, e.g. only used as a dependency
201
35
        None => quote! { |_: &::conjure_cp::settings::SolverFamily| false },
202
33
        Some(func) => quote! { #func },
203
    };
204

            
205
68
    let expanded = quote! {
206
        use ::conjure_cp::rule_engine::_dependencies::*; // ToDo idk if we need to explicitly do that?
207
        #[::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
68
    TokenStream::from(expanded)
213
68
}