grb_macro/
lib.rs

1//! See the [`grb`](https://docs.rs/grb) crate for documentation.
2use 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    // Forward-scan for the `in` keyword -- top level tokens only, don't walk the whole tree
127    // Heuristic that is more efficient than speculative parsing, and gives better error messages
128    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}