use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Expr, Ident, Token};
enum ObjectiveInput {
Feasibility { model: Expr },
Optimize { model: Expr, sense: Ident, expr: Expr },
}
fn is_feasibility_kw(ident: &Ident) -> bool {
matches!(ident.to_string().as_str(), "Feasibility" | "feasibility" | "Feas" | "feas")
}
impl Parse for ObjectiveInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let model = input.parse::<Expr>()?;
input.parse::<Token![,]>()?;
let sense = input.parse::<Ident>()?;
if input.is_empty() {
if is_feasibility_kw(&sense) {
return Ok(Self::Feasibility { model });
}
return Err(syn::Error::new(
sense.span(),
"expected an objective expression after the sense, or \
`Feasibility` for a feasibility problem",
));
}
input.parse::<Token![,]>()?;
let expr = input.parse::<Expr>()?;
if is_feasibility_kw(&sense) {
return Err(syn::Error::new(
sense.span(),
"`Feasibility` takes no objective expression; write `objective!(m, Feasibility)`",
));
}
Ok(Self::Optimize { model, sense, expr })
}
}
pub(crate) fn expand(input: TokenStream2) -> syn::Result<TokenStream2> {
let input = crate::index::rewrite_index_subscripts(input);
match syn::parse2(input)? {
ObjectiveInput::Feasibility { model } => Ok(quote!( (#model).__feasibility() )),
ObjectiveInput::Optimize { model, sense, expr } => {
let method = match sense.to_string().as_str() {
"Min" | "Minimize" | "min" => quote!(__minimize),
"Max" | "Maximize" | "max" => quote!(__maximize),
_ => {
return Err(syn::Error::new(
sense.span(),
"objective sense must be `Min`/`min`/`Minimize` or \
`Max`/`max`/`Maximize`",
));
}
};
Ok(quote!( (#model).#method(#expr) ))
}
}
}