1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3
4use proc_macro::TokenStream;
5use proc_macro_crate::{FoundCrate, crate_name};
6use proc_macro2::{Delimiter, Spacing, TokenStream as TokenStream2, TokenTree};
7use quote::quote;
8use syn::Ident;
9
10mod bind;
11mod constraint;
12mod extrema;
13mod index;
14mod indicator;
15mod objective;
16mod param;
17mod set;
18mod soc;
19mod sos;
20mod sum;
21mod variable;
22
23use bind::{Binds, IndexBind};
24
25fn oximo_root() -> TokenStream2 {
29 fn to_path(found: &FoundCrate, fallback: &str) -> TokenStream2 {
30 let name = match found {
31 FoundCrate::Itself => fallback,
32 FoundCrate::Name(n) => n.as_str(),
33 };
34 let id = Ident::new(name, proc_macro2::Span::call_site());
35 quote!(::#id)
36 }
37
38 if let Ok(found) = crate_name("oximo") {
39 return to_path(&found, "oximo");
40 }
41 if let Ok(found) = crate_name("oximo-core") {
42 return to_path(&found, "oximo_core");
43 }
44 quote!(::oximo_core)
45}
46
47#[proc_macro]
50pub fn variable(input: TokenStream) -> TokenStream {
51 variable::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
52}
53
54#[proc_macro]
62pub fn constraint(input: TokenStream) -> TokenStream {
63 constraint::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
64}
65
66#[proc_macro]
69pub fn indicator_constraint(input: TokenStream) -> TokenStream {
70 indicator::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
71}
72
73#[proc_macro]
78pub fn soc_constraint(input: TokenStream) -> TokenStream {
79 soc::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
80}
81
82#[proc_macro]
86pub fn sos_constraint(input: TokenStream) -> TokenStream {
87 sos::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
88}
89
90#[proc_macro]
94pub fn objective(input: TokenStream) -> TokenStream {
95 objective::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
96}
97
98#[proc_macro]
105pub fn sum(input: TokenStream) -> TokenStream {
106 sum::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
107}
108
109#[proc_macro]
111pub fn min(input: TokenStream) -> TokenStream {
112 extrema::expand(input.into(), extrema::Extremum::Min)
113 .unwrap_or_else(syn::Error::into_compile_error)
114 .into()
115}
116
117#[proc_macro]
119pub fn max(input: TokenStream) -> TokenStream {
120 extrema::expand(input.into(), extrema::Extremum::Max)
121 .unwrap_or_else(syn::Error::into_compile_error)
122 .into()
123}
124
125#[proc_macro]
128pub fn param(input: TokenStream) -> TokenStream {
129 param::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
130}
131
132#[proc_macro]
137pub fn set(input: TokenStream) -> TokenStream {
138 set::expand(input.into()).unwrap_or_else(syn::Error::into_compile_error).into()
139}
140
141#[derive(Copy, Clone, PartialEq, Eq)]
149enum RelOp {
150 Le,
151 Ge,
152 Eq,
153}
154
155impl RelOp {
156 fn method(self) -> Ident {
158 let name = match self {
159 RelOp::Le => "le",
160 RelOp::Ge => "ge",
161 RelOp::Eq => "eq",
162 };
163 Ident::new(name, proc_macro2::Span::call_site())
164 }
165}
166
167fn split_top_commas(ts: TokenStream2) -> Vec<TokenStream2> {
169 let mut out = Vec::new();
170 let mut cur = Vec::new();
171 for tt in ts {
172 if let TokenTree::Punct(p) = &tt
173 && p.as_char() == ','
174 {
175 out.push(cur.drain(..).collect());
176 continue;
177 }
178 cur.push(tt);
179 }
180 out.push(cur.into_iter().collect());
181 out
182}
183
184fn split_relops(ts: &TokenStream2) -> (Vec<TokenStream2>, Vec<RelOp>) {
187 let tts: Vec<TokenTree> = ts.clone().into_iter().collect();
188 let mut segs: Vec<TokenStream2> = Vec::new();
189 let mut ops: Vec<RelOp> = Vec::new();
190 let mut cur: Vec<TokenTree> = Vec::new();
191
192 let mut i = 0;
193 while i < tts.len() {
194 if let TokenTree::Punct(p1) = &tts[i]
195 && p1.spacing() == Spacing::Joint
196 && i + 1 < tts.len()
197 && let TokenTree::Punct(p2) = &tts[i + 1]
198 {
199 let op = match (p1.as_char(), p2.as_char()) {
200 ('<', '=') => Some(RelOp::Le),
201 ('>', '=') => Some(RelOp::Ge),
202 ('=', '=') => Some(RelOp::Eq),
203 _ => None,
204 };
205 if let Some(op) = op {
206 segs.push(cur.drain(..).collect());
207 ops.push(op);
208 i += 2;
209 continue;
210 }
211 }
212 cur.push(tts[i].clone());
213 i += 1;
214 }
215 segs.push(cur.into_iter().collect());
216 (segs, ops)
217}
218
219struct Named {
222 name: Ident,
223 binds: Option<Vec<IndexBind>>,
224 cond: Option<syn::Expr>,
225}
226
227fn parse_named(seg: TokenStream2) -> syn::Result<Named> {
229 let tts: Vec<TokenTree> = seg.into_iter().collect();
230 let span = tts.first().map_or_else(proc_macro2::Span::call_site, TokenTree::span);
231 let TokenTree::Ident(name) = tts
232 .first()
233 .cloned()
234 .ok_or_else(|| syn::Error::new(span, "expected a variable/constraint name identifier"))?
235 else {
236 return Err(syn::Error::new(span, "expected a name identifier"));
237 };
238
239 let (binds, cond) = match tts.get(1) {
240 None => (None, None),
241 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
242 let parsed: Binds = syn::parse2(g.stream())?;
243 if parsed.binds.is_empty() {
244 return Err(syn::Error::new(
245 g.span(),
246 "index family needs at least one binding, e.g. `name[i in domain]`",
247 ));
248 }
249 (Some(parsed.binds), parsed.cond)
250 }
251 Some(other) => {
252 return Err(syn::Error::new(other.span(), "expected `[index in domain, ...]`"));
253 }
254 };
255 if let Some(extra) = tts.get(2) {
256 return Err(syn::Error::new(extra.span(), "unexpected tokens after the index bindings"));
257 }
258 Ok(Named { name, binds, cond })
259}
260
261fn build_set(binds: &[IndexBind], root: &TokenStream2) -> syn::Result<TokenStream2> {
263 let Some((first, rest)) = binds.split_first() else {
264 return Err(syn::Error::new(
265 proc_macro2::Span::call_site(),
266 "an index family needs at least one binding",
267 ));
268 };
269 let dom = &first.domain;
270 let acc = quote!(#root::__macro_support::as_set(&(#dom)));
271 Ok(rest.iter().fold(acc, |set, b| {
272 let dom = &b.domain;
273 quote!(#root::__macro_support::product(
274 &(#set),
275 &(#root::__macro_support::as_set(&(#dom))),
276 ))
277 }))
278}
279
280fn next_seg(segs: &mut std::vec::IntoIter<TokenStream2>) -> syn::Result<TokenStream2> {
282 segs.next().ok_or_else(|| {
283 syn::Error::new(proc_macro2::Span::call_site(), "malformed relation: missing an operand")
284 })
285}