1
use proc_macro::TokenStream;
2

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

            
12
#[derive(Debug)]
13
struct RuleSetAndPriority {
14
    rule_set: LitStr,
15
    priority: LitInt,
16
}
17

            
18
impl Parse for RuleSetAndPriority {
19
165
    fn parse(input: ParseStream) -> Result<Self> {
20
165
        let content;
21
165
        parenthesized!(content in input);
22
165
        let rule_set: LitStr = content.parse()?;
23
165
        let _: Comma = content.parse()?;
24
165
        let priority: LitInt = content.parse()?;
25
165
        Ok(RuleSetAndPriority { rule_set, priority })
26
165
    }
27
}
28

            
29
#[derive(Debug)]
30
struct RegisterRuleArgs {
31
    pub rule_sets: Vec<RuleSetAndPriority>,
32
}
33

            
34
impl Parse for RegisterRuleArgs {
35
165
    fn parse(input: ParseStream) -> Result<Self> {
36
165
        let rule_sets = Punctuated::<RuleSetAndPriority, Comma>::parse_terminated(input)?;
37
165
        Ok(RegisterRuleArgs {
38
165
            rule_sets: rule_sets.into_iter().collect(),
39
165
        })
40
165
    }
41
}
42

            
43
/**
44
 * Register a rule with the given rule sets and priorities.
45
 */
46
#[proc_macro_attribute]
47
165
pub fn register_rule(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
48
165
    let func = parse_macro_input!(item as ItemFn);
49
165
    let rule_ident = &func.sig.ident;
50
165
    let static_name = format!("CONJURE_GEN_RULE_{}", rule_ident).to_uppercase();
51
165
    let static_ident = Ident::new(&static_name, rule_ident.span());
52

            
53
165
    let args = parse_macro_input!(arg_tokens as RegisterRuleArgs);
54
165
    let rule_sets = args
55
165
        .rule_sets
56
165
        .iter()
57
165
        .map(|rule_set| {
58
165
            let rule_set_name = &rule_set.rule_set;
59
165
            let priority = &rule_set.priority;
60
165
            quote! {
61
165
                (#rule_set_name, #priority as u8)
62
165
            }
63
165
        })
64
165
        .collect::<Vec<_>>();
65

            
66
165
    let expanded = quote! {
67
165
        #func
68
165

            
69
165
        use ::conjure_core::rule_engine::_dependencies::*; // ToDo idk if we need to explicitly do that?
70
165

            
71
165
        #[::conjure_core::rule_engine::_dependencies::distributed_slice(::conjure_core::rule_engine::RULES_DISTRIBUTED_SLICE)]
72
165
        pub static #static_ident: ::conjure_core::rule_engine::Rule<'static> = ::conjure_core::rule_engine::Rule {
73
165
            name: stringify!(#rule_ident),
74
165
            application: #rule_ident,
75
165
            rule_sets: &[#(#rule_sets),*],
76
165
        };
77
165
    };
78

            
79
165
    TokenStream::from(expanded)
80
165
}
81

            
82
35
fn parse_parenthesized<T: Parse>(input: ParseStream) -> Result<Vec<T>> {
83
35
    let content;
84
35
    parenthesized!(content in input);
85

            
86
35
    let mut paths = Vec::new();
87
35
    while !content.is_empty() {
88
25
        let path = content.parse()?;
89
25
        paths.push(path);
90
25
        if content.is_empty() {
91
25
            break;
92
        }
93
        content.parse::<Comma>()?;
94
    }
95

            
96
35
    Ok(paths)
97
35
}
98

            
99
struct RuleSetArgs {
100
    name: LitStr,
101
    priority: LitInt,
102
    dependencies: Vec<LitStr>,
103
    solver_families: Vec<Path>,
104
}
105

            
106
impl Parse for RuleSetArgs {
107
25
    fn parse(input: ParseStream) -> Result<Self> {
108
25
        let name = input.parse()?;
109
25
        input.parse::<Comma>()?;
110
25
        let priority = input.parse()?;
111

            
112
25
        if input.is_empty() {
113
            return Ok(Self {
114
                name,
115
                priority,
116
                dependencies: Vec::new(),
117
                solver_families: Vec::new(),
118
            });
119
25
        }
120
25

            
121
25
        input.parse::<Comma>()?;
122
25
        let dependencies = parse_parenthesized::<LitStr>(input)?;
123

            
124
25
        if input.is_empty() {
125
15
            return Ok(Self {
126
15
                name,
127
15
                priority,
128
15
                dependencies,
129
15
                solver_families: Vec::new(),
130
15
            });
131
10
        }
132
10

            
133
10
        input.parse::<Comma>()?;
134
10
        let solver_families = parse_parenthesized::<Path>(input)?;
135

            
136
10
        Ok(Self {
137
10
            name,
138
10
            priority,
139
10
            dependencies,
140
10
            solver_families,
141
10
        })
142
25
    }
143
}
144

            
145
/**
146
* Register a rule set with the given name, priority, and dependencies.
147
*
148
* # Example
149
* ```rust
150
 * use conjure_macros::register_rule_set;
151
 * register_rule_set!("MyRuleSet", 10, ("DependencyRuleSet", "AnotherRuleSet"));
152
* ```
153
 */
154
#[proc_macro]
155
25
pub fn register_rule_set(args: TokenStream) -> TokenStream {
156
    let RuleSetArgs {
157
25
        name,
158
25
        priority,
159
25
        dependencies,
160
25
        solver_families,
161
25
    } = parse_macro_input!(args as RuleSetArgs);
162

            
163
25
    let static_name = format!("CONJURE_GEN_RULE_SET_{}", name.value()).to_uppercase();
164
25
    let static_ident = Ident::new(&static_name, Span::call_site());
165

            
166
25
    let dependencies = quote! {
167
25
        #(#dependencies),*
168
25
    };
169

            
170
25
    let solver_families = quote! {
171
25
        #(#solver_families),*
172
25
    };
173

            
174
25
    let expanded = quote! {
175
25
        use ::conjure_core::rule_engine::_dependencies::*; // ToDo idk if we need to explicitly do that?
176
25
        #[::conjure_core::rule_engine::_dependencies::distributed_slice(::conjure_core::rule_engine::RULE_SETS_DISTRIBUTED_SLICE)]
177
25
        pub static #static_ident: ::conjure_core::rule_engine::RuleSet<'static> = ::conjure_core::rule_engine::RuleSet::new(#name, #priority, &[#dependencies], &[#solver_families]);
178
25
    };
179
25

            
180
25
    TokenStream::from(expanded)
181
25
}