Skip to main content

bind_syn/
lib.rs

1//! # Why This Project
2//!
3//! Provides `enum Bind`, the syntax definition of let-binding shorthand utilized
4//! in `crate bind`.
5//!
6//! This is not a proc-macro library, but a library providing syntax parsing for
7//! those proc macro libraries which provide similar functionality with
8//! `crate bind`.
9
10use 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
61/// The definition of let-binding shorthands.
62pub enum Bind {
63    /// generates `let id = id.clone();`
64       Id(     Ident              ),
65    /// generates `let mut id = id.clone();`
66    MutId(     Ident              ),
67    /// generates `let id = id0.clone();`
68       IdId(   Ident, Ident       ),
69    /// generates `let mut id = id0.clone();`
70    MutIdId(   Ident, Ident       ),
71    /// generates `let id = expr;`
72       IdExpr( Ident,        Expr ),
73    /// generates `let mut id = expr;`
74    MutIdExpr( Ident,        Expr ),
75    /// generates `let id_extracted_from_expr = expr;`
76         Expr( Ident,        Expr ),
77    /// generates `let mut id_extracted_from_expr = expr;`
78      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}