1use proc_macro2::{TokenStream as TokenStream2, TokenTree, Ident, Span};
3use quote::{ToTokens, quote, quote_spanned, TokenStreamExt};
4use syn::{Token, Result, Error, Expr};
5use syn::parse::{ParseStream, Parse};
6use syn::spanned::Spanned;
7
8struct InequalityConstr {
9 lhs : Box<Expr>,
10 sense: TokenStream2,
11 rhs : Box<Expr>,
12}
13
14impl Parse for InequalityConstr {
15 fn parse(input: ParseStream) -> Result<Self> {
16 use syn::BinOp::*;
17
18 let cmpexpr: syn::ExprBinary = input.parse()?;
19 let sense = match cmpexpr.op {
20 Eq(..) => quote! { grb::ConstrSense::Equal },
21 Le(..) => quote! { grb::ConstrSense::Less },
22 Ge(..) => quote! { grb::ConstrSense::Greater },
23 Lt(..) | Gt(..) | Ne(..) => { return Err(Error::new_spanned(cmpexpr.op, "expected >=, <= or ==")); }
24 _ => { return Err(Error::new_spanned(cmpexpr, "expression should be a ==, >= or <= comparison")); }
25 };
26
27 Ok(InequalityConstr {lhs: cmpexpr.left, sense, rhs:cmpexpr.right})
28 }
29}
30
31impl ToTokens for InequalityConstr {
32 fn to_tokens(&self, tokens: &mut TokenStream2) {
33 let lhs = self.lhs.as_ref();
34 let lhs = quote_spanned!{ lhs.span()=> grb::Expr::from(#lhs) };
35 let rhs = self.rhs.as_ref();
36 let rhs = quote_spanned!{ rhs.span()=> grb::Expr::from(#rhs) };
37 let sense = &self.sense;
38 let ts = quote! {
39 grb::constr::IneqExpr{
40 lhs: #lhs,
41 sense: #sense,
42 rhs: #rhs,
43 }
44 };
45 ts.to_tokens(tokens);
46 }
47}
48
49#[derive(Default, Clone)]
50struct GrbRangeExpr {
51 lb: Option<Box<syn::Expr>>,
52 ub: Option<Box<syn::Expr>>,
53}
54
55impl GrbRangeExpr {
56 pub fn ub_to_tokens(&self) -> TokenStream2 {
57 match self.ub {
58 Some(ref x) => quote_spanned!{ x.span()=> #x as f64},
59 None => quote!{ grb::INFINITY },
60 }
61 }
62
63 pub fn lb_to_tokens(&self) -> TokenStream2 {
64 match self.lb {
65 Some(ref x) => quote_spanned!{ x.span()=> #x as f64},
66 None => quote!{ -grb::INFINITY },
67 }
68 }
69}
70
71impl Parse for GrbRangeExpr {
72 fn parse(input: ParseStream) -> Result<Self> {
73 let expr : syn::ExprRange = input.parse()?;
74 match expr.limits {
75 syn::RangeLimits::HalfOpen(..) => {},
76 syn::RangeLimits::Closed(dde) => {
77 return Err(Error::new_spanned(dde, "Use '..' for bounds and range constraints"))
78 },
79 }
80 Ok(GrbRangeExpr {lb: expr.from, ub: expr.to})
81 }
82}
83
84
85struct RangeConstr {
86 expr: syn::Expr,
87 range: GrbRangeExpr,
88}
89
90impl Parse for RangeConstr {
91 fn parse(input: ParseStream) -> Result<Self> {
92 let expr = input.parse()?;
93 input.parse::<Token![in]>()?;
94 let range = input.parse()?;
95 Ok(RangeConstr { expr, range })
96 }
97}
98
99impl ToTokens for RangeConstr {
100 fn to_tokens(&self, tokens: &mut TokenStream2) {
101 let expr = &self.expr;
102 let expr = quote_spanned! { expr.span() => grb::Expr::from(#expr) };
103
104 let lb = self.range.lb_to_tokens();
105 let ub = self.range.ub_to_tokens();
106
107 let ts : TokenStream2 = quote!{
108 grb::constr::RangeExpr{
109 expr: #expr,
110 ub: #ub,
111 lb: #lb,
112 }
113 };
114 ts.to_tokens(tokens)
115 }
116}
117
118#[allow(clippy::large_enum_variant)]
119enum ConstrExpr {
120 Inequality(InequalityConstr),
121 Range(RangeConstr)
122}
123
124impl Parse for ConstrExpr {
125 fn parse(input: ParseStream) -> Result<Self> {
126 let in_found = {
129 let mut curs = input.cursor();
130 let in_ = Ident::new("in", Span::call_site());
131 let mut in_found = false;
132 while let Some((tt, next)) = curs.token_tree() {
133 match tt {
134 TokenTree::Ident(i) if i == in_ => {
135 in_found = true;
136 break;
137 },
138 _ => curs = next,
139 }
140 }
141 in_found
142 };
143
144 if in_found {
145 input.parse::<RangeConstr>().map(ConstrExpr::Range)
146 } else {
147 input.parse::<InequalityConstr>().map(ConstrExpr::Inequality)
148 }
149 }
150}
151
152impl ToTokens for ConstrExpr {
153 fn to_tokens(&self, tokens: &mut TokenStream2) {
154 match self {
155 ConstrExpr::Inequality(e) => e.to_tokens(tokens),
156 ConstrExpr::Range(e) => e.to_tokens(tokens),
157 }
158 }
159}
160
161
162#[proc_macro]
163pub fn c(expr: proc_macro::TokenStream) -> proc_macro::TokenStream {
164 let expr = syn::parse_macro_input!(expr as ConstrExpr);
165 expr.into_token_stream().into()
166}
167
168trait OptionalArg {
169 type Value: Parse;
170 fn name() -> &'static str;
171 fn value(&self) -> &Option<Self::Value>;
172 fn value_mut(&mut self) -> &mut Option<Self::Value>;
173
174 fn match_parse(&mut self, name: &syn::Ident, input: &ParseStream) -> Result<bool> {
175 if name == Self::name() {
176 input.parse::<Token![:]>()?;
177 let v = self.value_mut();
178 if v.is_some() { return Err(Error::new_spanned(name, "duplicate argument"))}
179 *v = Some(input.parse()?);
180 Ok(true)
181 } else {
182 Ok(false)
183 }
184 }
185}
186
187trait OptionalArgDefault: OptionalArg {
188 fn default_value() -> TokenStream2;
189}
190
191macro_rules! impl_optional_arg {
192 ($t:ident, $vt:path, $name:expr, $default:expr) => {
193 impl_optional_arg!{$t, $vt, $name}
194
195 impl OptionalArgDefault for $t {
196 fn default_value() -> TokenStream2 { $default }
197 }
198
199 impl ToTokens for $t {
200 fn to_tokens(&self, tokens: &mut TokenStream2) {
201 match self.value() {
202 None => tokens.append_all(Self::default_value()),
203 Some(v) => v.to_tokens(tokens),
204 }
205 }
206 }
207
208 };
209
210 ($t:ident, $vt:path, $name:expr) => {
211
212 struct $t(Option<$vt>);
213
214 impl OptionalArg for $t {
215 type Value = $vt;
216 fn name() -> &'static str { $name }
217
218 fn value(&self) -> &Option<$vt> { &self.0 }
219 fn value_mut(&mut self) -> &mut Option<$vt> { &mut self.0 }
220 }
221 };
222}
223
224impl_optional_arg!(VarName, syn::Expr, "name", quote!{ "" });
225impl_optional_arg!(VarObj, syn::Expr, "obj", quote!{ 0.0 });
226impl_optional_arg!(VarBounds, GrbRangeExpr, "bounds");
227
228struct OptArgs {
229 name: VarName,
230 obj: VarObj,
231 bounds: VarBounds,
232}
233
234impl OptArgs {
235 pub fn to_token_stream(&self, model: &syn::Ident, vtype: &impl ToTokens) -> TokenStream2 {
236 let name = &self.name;
237 let obj = &self.obj;
238 let (lb, ub) = match self.bounds.0 {
239 Some(ref bounds) => (bounds.lb_to_tokens(), bounds.ub_to_tokens()),
240 None => (quote!{ 0.0f64 }, quote!{ grb::INFINITY })
241 };
242
243 quote!{ #model.add_var(#name, #vtype, #obj as f64, #lb, #ub, std::iter::empty() ) }
244 }
245}
246
247impl Parse for OptArgs {
248 fn parse(input: ParseStream) -> Result<Self> {
249 let mut name = VarName(None);
250 let mut bounds = VarBounds(None);
251 let mut obj = VarObj(None);
252
253 while !input.is_empty() {
254 let comma = input.parse::<Token![,]>()?;
255 let optname: syn::Ident = input.parse().map_err(|e| {
256 if input.is_empty() {
257 Error::new_spanned(comma, "unexpected end of input: remove trailing comma")
258 } else {
259 e
260 }
261 })?;
262
263 if !(name.match_parse(&optname, &input)?
264 || obj.match_parse(&optname, &input)?
265 || bounds.match_parse(&optname, &input)?) {
266 return Err(Error::new_spanned(&optname, format_args!("unknown argument '{}'", &optname)))
267 };
268
269 }
270 Ok(OptArgs{ name, obj, bounds})
271 }
272}
273
274
275struct AddVarInput {
276 model: syn::Ident,
277 vtype: syn::ExprPath,
278 optargs : OptArgs,
279}
280
281impl Parse for AddVarInput {
282 fn parse(input: ParseStream) -> Result<Self> {
283 let model: syn::Ident = input.parse()?;
284 input.parse::<Token![,]>()
285 .map_err(|e| Error::new(e.span(), "expected `,` (macro expects 2 positional args)"))?;
286 let vtype: syn::ExprPath = input.parse()?;
287 let optargs = input.parse()?;
288 Ok(AddVarInput { model, vtype, optargs })
289 }
290}
291
292impl ToTokens for AddVarInput {
293 fn to_tokens(&self, tokens: &mut TokenStream2) {
294 let out = self.optargs.to_token_stream(&self.model, &self.vtype);
295 out.to_tokens(tokens);
296 }
297}
298
299
300macro_rules! specialised_addvar {
301 ($t:ident, $vtype:expr, $procmacroname:ident) => {
302 struct $t {
303 model: syn::Ident,
304 optargs : OptArgs,
305 }
306
307 impl Parse for $t {
308 fn parse(input: ParseStream) -> Result<Self> {
309 let model= input.parse()?;
310 let optargs = input.parse()?;
311 Ok(Self { model, optargs })
312 }
313 }
314
315 impl ToTokens for $t {
316 fn to_tokens(&self, tokens: &mut TokenStream2) {
317 let vtype = $vtype;
318 let out = self.optargs.to_token_stream(&self.model, &vtype);
319 out.to_tokens(tokens);
320 }
321 }
322
323 #[proc_macro]
324 pub fn $procmacroname(expr: proc_macro::TokenStream) -> proc_macro::TokenStream {
325 syn::parse_macro_input!(expr as $t).into_token_stream().into()
326 }
327 };
328}
329
330specialised_addvar!(AddBinVarInput, quote!{ grb::VarType::Binary }, add_binvar);
331specialised_addvar!(AddCtsVarInput, quote!{ grb::VarType::Continuous }, add_ctsvar);
332specialised_addvar!(AddIntVarInput, quote!{ grb::VarType::Integer }, add_intvar);
333
334
335#[proc_macro]
336pub fn add_var(expr: proc_macro::TokenStream) -> proc_macro::TokenStream {
337 syn::parse_macro_input!(expr as AddVarInput).into_token_stream().into()
338}