use std::convert::TryFrom;
use std::iter::FromIterator;
use crate::detail::idents_from_assign_expression;
use crate::macro_parsing::macro_expression::MacroExpression;
use proc_macro2::{Ident, Span};
use quote::ToTokens;
use syn::fold::Fold;
use syn::parse::{Parse, ParseBuffer};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{fold, Expr, Macro, Path, PathArguments, PathSegment, Token};
const DEFAULT_ASSERT2IFY_CRATE_NAME: &str = "assert2ify";
#[derive(Debug, Clone, PartialEq)]
pub enum Style {
Assertify,
Checkify,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Assert2Ification {
configuration: Style,
crate_name: String,
}
impl Assert2Ification {
fn new<S: Into<String>>(configuration: Style, crate_name: Option<S>) -> Assert2Ification {
Assert2Ification {
configuration,
crate_name: crate_name
.map(|n| n.into())
.unwrap_or_else(|| DEFAULT_ASSERT2IFY_CRATE_NAME.to_string()),
}
}
fn assert2_macro_path_with_span(&self, span: Span) -> syn::Path {
let assert2ify = PathSegment {
ident: Ident::new(self.crate_name.as_str(), span),
arguments: PathArguments::None,
};
let replacement_assertion = match self.configuration {
Style::Assertify => PathSegment {
ident: Ident::new("__assertify", span),
arguments: PathArguments::None,
},
Style::Checkify => PathSegment {
ident: Ident::new("__checkify", span),
arguments: PathArguments::None,
},
};
let assert2_segments = Punctuated::<PathSegment, syn::token::Colon2>::from_iter(vec![
assert2ify,
replacement_assertion,
]);
Path {
leading_colon: Some(syn::token::Colon2 { spans: [span; 2] }),
segments: assert2_segments,
}
}
}
impl Parse for Assert2Ification {
fn parse(input: &ParseBuffer) -> Result<Self, syn::parse::Error> {
let arguments: Vec<Expr> = Punctuated::<Expr, Token![,]>::parse_terminated(input)?
.into_iter()
.collect();
let mut crate_name: Option<String> = None;
let mut style: Option<Style> = None;
for args in arguments.iter() {
match args {
Expr::Assign(expr_assign) => {
if let Some((lhs, rhs)) = idents_from_assign_expression(&expr_assign) {
if lhs == "crate" {
if crate_name.is_none() {
crate_name = Some(rhs.to_string());
} else {
return Err(syn::Error::new(
expr_assign.span(),
"Crate name was already specified",
));
}
} else {
return Err(syn::Error::new(
expr_assign.span(),
"Illegal argument. The only legal assignment is crate=...",
));
}
} else {
return Err(syn::Error::new(
expr_assign.span(),
"Illegal assignment. The only legal assignment is crate=...",
));
}
}
Expr::Path(expr_path) => {
if expr_path.path.is_ident("check") {
if style.is_none() {
style = Some(Style::Checkify);
} else {
return Err(syn::Error::new(
expr_path.span(),
"Illegal argument. Assertification style was already specified",
));
}
} else {
return Err(syn::Error::new(
expr_path.span(),
"Illegal argument. Did you mean `check`?",
));
}
}
_ => {
return Err(syn::Error::new(args.span(), "Invalid argument"));
}
}
}
Ok(Assert2Ification::new(
style.unwrap_or(Style::Assertify),
crate_name,
))
}
}
impl Fold for Assert2Ification {
fn fold_macro(&mut self, mac: Macro) -> Macro {
let macro_parse_result = MacroExpression::try_from(mac.clone());
if let Ok(macro_expression) = macro_parse_result {
let span = macro_expression.span();
match macro_expression {
MacroExpression::Assertion(assertion) => {
assertion.assert2ify_with(self.assert2_macro_path_with_span(span))
}
MacroExpression::Other(other_macro) => {
if let Ok(nested_expr) = syn::parse2::<Expr>(other_macro.tokens.clone()) {
let folded = self.fold_expr(nested_expr);
Macro {
tokens: folded.to_token_stream(),
..other_macro
}
} else {
fold::fold_macro(self, other_macro)
}
}
}
} else {
mac
}
}
}