use quote::{ToTokens, quote};
use syn::{
Expr,
ExprAssign,
ExprPath,
Ident,
Token,
parse::{self, Parse, ParseStream},
visit::Visit,
};
fn extract_the_only_id_in( expr: &Expr ) -> Option<Ident> {
struct Extractor {
id : Option<Ident>,
cnt : usize,
}
impl<'a> Visit<'a> for Extractor {
fn visit_ident( &mut self, id: &Ident ) {
if self.cnt == 0 && self.id.is_none() {
self.id = Some( id.clone() );
}
self.cnt += 1;
}
}
let mut extractor = Extractor{ id: None, cnt: 0 };
extractor.visit_expr( &expr );
extractor.id
}
enum ExprOrIdent {
Expr( Expr ),
Ident( Ident ),
}
fn get_expr_or_id( expr: Expr ) -> ExprOrIdent {
if let Expr::Path( ExprPath{ attrs, qself, path }) = &expr {
if attrs.is_empty() && qself.is_none() {
if path.leading_colon.is_none() && path.segments.len() == 1 {
let seg = path.segments.first().unwrap();
if seg.arguments.is_none() {
return ExprOrIdent::Ident( seg.ident.clone() );
}
}
}
}
ExprOrIdent::Expr( expr )
}
pub enum Bind {
Id( Ident ),
MutId( Ident ),
IdId( Ident, Ident ),
MutIdId( Ident, Ident ),
IdExpr( Ident, Expr ),
MutIdExpr( Ident, Expr ),
Expr( Ident, Expr ),
MutExpr( Ident, Expr ),
}
impl Parse for Bind {
fn parse( input: ParseStream ) -> parse::Result<Self> {
let immutable = if input.peek( Token![mut] ) {
input.parse::<Token![mut]>()?;
false
} else {
true
};
let expr = input.parse::<Expr>()?;
if let Expr::Assign( expr_assign ) = &expr {
let ExprAssign{ attrs:_, left, eq_token, right } = expr_assign.clone();
let _ = eq_token;
if let ExprOrIdent::Ident( id ) = get_expr_or_id( *left ) {
match get_expr_or_id( *right ) {
ExprOrIdent::Expr( expr ) =>
return Ok( if immutable {
Bind::IdExpr( id, expr )
} else {
Bind::MutIdExpr( id, expr )
}),
ExprOrIdent::Ident( id0 ) =>
return Ok( if immutable {
Bind::IdId( id, id0 )
} else {
Bind::MutIdId( id, id0 )
}),
}
}
} else {
match get_expr_or_id( expr ) {
ExprOrIdent::Expr( expr ) =>
match extract_the_only_id_in( &expr ) {
Some( id ) =>
return Ok( if immutable {
Bind::Expr( id, expr )
} else {
Bind::MutExpr( id, expr )
}),
None => (),
}
ExprOrIdent::Ident( id ) =>
return Ok( if immutable {
Bind::Id( id )
} else {
Bind::MutId( id )
}),
}
}
panic!( "Invalid input for `bind!()`: {input:?}" );
}
}
impl ToTokens for Bind {
fn to_tokens( &self, tokens: &mut proc_macro2::TokenStream ) {
tokens.extend( match self {
Bind::Id( id ) => quote!{ let #id = #id .clone(); },
Bind::MutId( id ) => quote!{ let mut #id = #id .clone(); },
Bind::IdId( id, id0 ) => quote!{ let #id = #id0 .clone(); },
Bind::MutIdId( id, id0 ) => quote!{ let mut #id = #id0 .clone(); },
Bind::IdExpr( id, expr ) => quote!{ let #id = #expr ; },
Bind::MutIdExpr( id, expr ) => quote!{ let mut #id = #expr ; },
Bind::Expr( id, expr ) => quote!{ let #id = #expr ; },
Bind::MutExpr( id, expr ) => quote!{ let mut #id = #expr ; },
});
}
}