use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{parse_macro_input, spanned::Spanned, Expr, ExprBinary, Token};
#[proc_macro]
pub fn chmp(tokens: TokenStream) -> TokenStream {
let ast = parse_macro_input!(tokens as ExprBinary);
match cmp_tree_to_conjunction_tree(ast) {
Ok(expr) => expr.into_token_stream(),
Err(err) => err.to_compile_error(),
}
.into()
}
fn cmp_tree_to_conjunction_tree(cmp_tree: ExprBinary) -> Result<Expr, syn::Error> {
let mut exprs = Vec::new();
flatten_tree(cmp_tree, &mut exprs).map(|_| build_conjunction_tree(exprs))
}
fn is_comparison_op(op: &syn::BinOp) -> bool {
use syn::BinOp::*;
matches!(op, Ne(_) | Eq(_) | Le(_) | Ge(_) | Lt(_) | Gt(_))
}
fn is_comparison(expr: &ExprBinary) -> bool {
is_comparison_op(&expr.op)
}
fn flatten_tree(mut tree: ExprBinary, container: &mut Vec<Expr>) -> Result<(), syn::Error> {
let op = tree.op;
if !is_comparison_op(&op) {
let err = syn::Error::new_spanned(
op,
format!(
"Expected one of `<`, `<=`, `>`, `>=`, `==`, `!=`, found: `{}`",
op.to_token_stream()
),
);
return Err(err);
}
match &*tree.left {
Expr::Binary(rest) if is_comparison(rest) => {
let lhs = rest.right.clone();
let rest = match *std::mem::replace(&mut tree.left, lhs) {
Expr::Binary(expr) => expr,
_ => unreachable!(),
};
container.push(into_expr(tree));
flatten_tree(rest, container)
}
_ => {
container.push(into_expr(tree));
Ok(())
}
}
}
fn build_conjunction_tree(mut exprs: Vec<Expr>) -> Expr {
let expr = exprs
.pop()
.expect("need at least one expression to build tree");
if exprs.is_empty() {
expr
} else {
into_expr(new_conjuction(expr, build_conjunction_tree(exprs)))
}
}
fn into_expr(bin_expr: ExprBinary) -> Expr {
Expr::Binary(bin_expr)
}
fn new_conjuction(left: Expr, right: Expr) -> ExprBinary {
let (left_span, right_span) = (left.span(), right.span());
ExprBinary {
attrs: vec![],
left: Box::new(left),
op: syn::BinOp::And(Token),
right: Box::new(right),
}
}