1use quote::{ToTokens, quote};
11
12use syn::{
13 Expr,
14 ExprAssign,
15 ExprPath,
16 Ident,
17 Token,
18 parse::{self, Parse, ParseStream},
19 visit::Visit,
20};
21
22fn extract_the_only_id_in( expr: &Expr ) -> Option<Ident> {
23 struct Extractor {
24 id : Option<Ident>,
25 cnt : usize,
26 }
27
28 impl<'a> Visit<'a> for Extractor {
29 fn visit_ident( &mut self, id: &Ident ) {
30 if self.cnt == 0 && self.id.is_none() {
31 self.id = Some( id.clone() );
32 }
33 self.cnt += 1;
34 }
35 }
36
37 let mut extractor = Extractor{ id: None, cnt: 0 };
38 extractor.visit_expr( &expr );
39 extractor.id
40}
41
42enum ExprOrIdent {
43 Expr( Expr ),
44 Ident( Ident ),
45}
46
47fn get_expr_or_id( expr: Expr ) -> ExprOrIdent {
48 if let Expr::Path( ExprPath{ attrs, qself, path }) = &expr {
49 if attrs.is_empty() && qself.is_none() {
50 if path.leading_colon.is_none() && path.segments.len() == 1 {
51 let seg = path.segments.first().unwrap();
52 if seg.arguments.is_none() {
53 return ExprOrIdent::Ident( seg.ident.clone() );
54 }
55 }
56 }
57 }
58 ExprOrIdent::Expr( expr )
59}
60
61pub enum Bind {
63 Id( Ident ),
65 MutId( Ident ),
67 IdId( Ident, Ident ),
69 MutIdId( Ident, Ident ),
71 IdExpr( Ident, Expr ),
73 MutIdExpr( Ident, Expr ),
75 Expr( Ident, Expr ),
77 MutExpr( Ident, Expr ),
79}
80
81impl Parse for Bind {
82 fn parse( input: ParseStream ) -> parse::Result<Self> {
83 let immutable = if input.peek( Token![mut] ) {
84 input.parse::<Token![mut]>()?;
85 false
86 } else {
87 true
88 };
89
90 let expr = input.parse::<Expr>()?;
91
92 if let Expr::Assign( expr_assign ) = &expr {
93 let ExprAssign{ attrs:_, left, eq_token, right } = expr_assign.clone();
94 let _ = eq_token;
95 if let ExprOrIdent::Ident( id ) = get_expr_or_id( *left ) {
96 match get_expr_or_id( *right ) {
97 ExprOrIdent::Expr( expr ) =>
98 return Ok( if immutable {
99 Bind::IdExpr( id, expr )
100 } else {
101 Bind::MutIdExpr( id, expr )
102 }),
103 ExprOrIdent::Ident( id0 ) =>
104 return Ok( if immutable {
105 Bind::IdId( id, id0 )
106 } else {
107 Bind::MutIdId( id, id0 )
108 }),
109 }
110 }
111 } else {
112 match get_expr_or_id( expr ) {
113 ExprOrIdent::Expr( expr ) =>
114 match extract_the_only_id_in( &expr ) {
115 Some( id ) =>
116 return Ok( if immutable {
117 Bind::Expr( id, expr )
118 } else {
119 Bind::MutExpr( id, expr )
120 }),
121 None => (),
122 }
123 ExprOrIdent::Ident( id ) =>
124 return Ok( if immutable {
125 Bind::Id( id )
126 } else {
127 Bind::MutId( id )
128 }),
129 }
130 }
131
132 panic!( "Invalid input for `bind!()`: {input:?}" );
133 }
134}
135
136impl ToTokens for Bind {
137 fn to_tokens( &self, tokens: &mut proc_macro2::TokenStream ) {
138 tokens.extend( match self {
139 Bind::Id( id ) => quote!{ let #id = #id .clone(); },
140 Bind::MutId( id ) => quote!{ let mut #id = #id .clone(); },
141 Bind::IdId( id, id0 ) => quote!{ let #id = #id0 .clone(); },
142 Bind::MutIdId( id, id0 ) => quote!{ let mut #id = #id0 .clone(); },
143 Bind::IdExpr( id, expr ) => quote!{ let #id = #expr ; },
144 Bind::MutIdExpr( id, expr ) => quote!{ let mut #id = #expr ; },
145 Bind::Expr( id, expr ) => quote!{ let #id = #expr ; },
146 Bind::MutExpr( id, expr ) => quote!{ let mut #id = #expr ; },
147 });
148 }
149}