solverforge_macros/
lib.rs

1//! Macros for SolverForge domain models.
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::parse::Parser;
6use syn::{parse_macro_input, Attribute, DeriveInput, Expr, ItemStruct, Lit, Meta};
7
8mod planning_entity;
9mod planning_solution;
10mod problem_fact;
11
12#[proc_macro_attribute]
13pub fn planning_entity(_attr: TokenStream, item: TokenStream) -> TokenStream {
14    let input = parse_macro_input!(item as ItemStruct);
15    let name = &input.ident;
16    let vis = &input.vis;
17    let generics = &input.generics;
18    let attrs: Vec<_> = input.attrs.iter().collect();
19    let fields = &input.fields;
20
21    let expanded = quote! {
22        #[derive(Clone, Debug, PartialEq, Eq, Hash, ::solverforge::PlanningEntityImpl)]
23        #(#attrs)*
24        #vis struct #name #generics #fields
25    };
26    expanded.into()
27}
28
29#[proc_macro_attribute]
30pub fn planning_solution(_attr: TokenStream, item: TokenStream) -> TokenStream {
31    let input = parse_macro_input!(item as ItemStruct);
32    let name = &input.ident;
33    let vis = &input.vis;
34    let generics = &input.generics;
35    let attrs: Vec<_> = input.attrs.iter().collect();
36    let fields = &input.fields;
37
38    let expanded = quote! {
39        #[derive(Clone, Debug, ::solverforge::PlanningSolutionImpl)]
40        #(#attrs)*
41        #vis struct #name #generics #fields
42    };
43    expanded.into()
44}
45
46#[proc_macro_attribute]
47pub fn problem_fact(_attr: TokenStream, item: TokenStream) -> TokenStream {
48    let input = parse_macro_input!(item as ItemStruct);
49    let name = &input.ident;
50    let vis = &input.vis;
51    let generics = &input.generics;
52    let attrs: Vec<_> = input.attrs.iter().collect();
53    let fields = &input.fields;
54
55    let expanded = quote! {
56        #[derive(Clone, Debug, PartialEq, Eq, ::solverforge::ProblemFactImpl)]
57        #(#attrs)*
58        #vis struct #name #generics #fields
59    };
60    expanded.into()
61}
62
63#[proc_macro_derive(
64    PlanningEntityImpl,
65    attributes(planning_id, planning_variable, planning_list_variable, planning_pin)
66)]
67pub fn derive_planning_entity(input: TokenStream) -> TokenStream {
68    let input = parse_macro_input!(input as DeriveInput);
69    planning_entity::expand_derive(input)
70        .unwrap_or_else(|e| e.to_compile_error())
71        .into()
72}
73
74#[proc_macro_derive(
75    PlanningSolutionImpl,
76    attributes(
77        planning_entity_collection,
78        problem_fact_collection,
79        planning_score,
80        value_range_provider
81    )
82)]
83pub fn derive_planning_solution(input: TokenStream) -> TokenStream {
84    let input = parse_macro_input!(input as DeriveInput);
85    planning_solution::expand_derive(input)
86        .unwrap_or_else(|e| e.to_compile_error())
87        .into()
88}
89
90#[proc_macro_derive(ProblemFactImpl, attributes(planning_id))]
91pub fn derive_problem_fact(input: TokenStream) -> TokenStream {
92    let input = parse_macro_input!(input as DeriveInput);
93    problem_fact::expand_derive(input)
94        .unwrap_or_else(|e| e.to_compile_error())
95        .into()
96}
97
98fn has_attribute(attrs: &[Attribute], name: &str) -> bool {
99    attrs.iter().any(|attr| attr.path().is_ident(name))
100}
101
102fn get_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attribute> {
103    attrs.iter().find(|attr| attr.path().is_ident(name))
104}
105
106fn parse_attribute_bool(attr: &Attribute, key: &str) -> Option<bool> {
107    if let Meta::List(meta_list) = &attr.meta {
108        let parser = syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
109        if let Ok(nested) = parser.parse2(meta_list.tokens.clone()) {
110            for meta in nested {
111                if let Meta::NameValue(nv) = meta {
112                    if nv.path.is_ident(key) {
113                        if let Expr::Lit(expr_lit) = &nv.value {
114                            if let Lit::Bool(lit_bool) = &expr_lit.lit {
115                                return Some(lit_bool.value());
116                            }
117                        }
118                    }
119                }
120            }
121        }
122    }
123    None
124}
125
126fn parse_attribute_string(attr: &Attribute, key: &str) -> Option<String> {
127    if let Meta::List(meta_list) = &attr.meta {
128        let parser = syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated;
129        if let Ok(nested) = parser.parse2(meta_list.tokens.clone()) {
130            for meta in nested {
131                if let Meta::NameValue(nv) = meta {
132                    if nv.path.is_ident(key) {
133                        if let Expr::Lit(expr_lit) = &nv.value {
134                            if let Lit::Str(lit_str) = &expr_lit.lit {
135                                return Some(lit_str.value());
136                            }
137                        }
138                    }
139                }
140            }
141        }
142    }
143    None
144}