easify_macros/
lib.rs

1use proc_macro::TokenStream;
2// someting goes here
3#[doc(hidden)]
4use quote::quote;
5#[doc(hidden)]
6use syn::{
7    parse::Parse, parse::ParseStream, parse_macro_input, Expr, Ident, LitInt, LitStr,
8    Result, Token,
9};
10
11struct DynamicTupleParams(Expr, LitInt);
12struct SmartSplitParams(Ident, LitStr, LitInt);
13
14impl Parse for DynamicTupleParams {
15    fn parse(input: ParseStream) -> Result<Self> {
16        let value = input.parse()?;
17        input.parse::<Token![,]>()?;
18        let count = input.parse()?;
19        Ok(DynamicTupleParams(value, count))
20    }
21}
22
23impl Parse for SmartSplitParams {
24    fn parse(input: ParseStream) -> Result<Self> {
25        let text = input.parse()?;
26        input.parse::<Token![,]>()?;
27        let sep = input.parse()?;
28        input.parse::<Token![,]>()?;
29        let count = input.parse()?;
30        Ok(SmartSplitParams(text, sep, count))
31    }
32}
33/// A procedural macro to create a tuple with dynamic number of arguments.
34///
35/// This macro takes exactly two arguments, both of which should be valid Rust expressions
36/// that evaluate to integer types. The macro will expand to the tuple, including value of first argument
37/// repeated N times, where N is a value of second argument.
38///
39/// # Examples
40///
41/// Use the macro to sum two integers:
42///
43/// ```
44/// use easify_macros::dynamic_tuple;
45/// let result = dynamic_tuple!(2, 1);
46/// assert_eq!(result, (2, ));
47/// ```
48///
49/// ```
50/// use easify_macros::dynamic_tuple;
51/// let result = dynamic_tuple!(5, 3);
52/// assert_eq!(result, (5, 5, 5));
53/// ```
54///
55/// # Panics
56///
57/// The macro will cause arguments_number compile-time error if not exactly two arguments are provided.
58///
59/// ```
60/// # use easify_macros::dynamic_tuple;
61/// // This will fail to compile
62/// // let result = dynamic_tuple!(5);
63/// ```
64
65#[proc_macro]
66pub fn dynamic_tuple(input: TokenStream) -> TokenStream {
67    let DynamicTupleParams(value, count) = parse_macro_input!(input as DynamicTupleParams);
68
69    // Convert value to TokenStream and then to string
70    let value_ts = quote! { #value }.to_string();
71    let count = count
72        .base10_parse::<usize>()
73        .expect("Invalid count of arguments");
74
75    // Generate the string representation of the tuple
76    let tuple_elements = std::iter::repeat(value_ts)
77        .take(count)
78        .collect::<Vec<_>>()
79        .join(", ");
80    let tuple_string = format!("({})", tuple_elements);
81
82    // Convert the string to arguments_number TokenStream and return
83    tuple_string.parse().unwrap()
84}
85
86/// A procedural macro to split the string and return tuple, where you know the number of
87/// arguments, thus making it easy to unpack.
88///
89/// This macro takes exactly two arguments, both of which should be valid Rust expressions
90/// that evaluate to str types. The macro will expand to the tuple, including value of first argument
91/// repeated N times, where N is arguments_number value of second argument.
92///
93/// # Examples
94///
95/// Use the macro to split the string:
96///
97/// ```
98/// use easify_macros::unpack_split;
99/// let result = unpack_split!("hello,my,name", ",");
100/// assert_eq!(result, ("hello", "my", "name"));
101/// ```
102///
103/// ```
104/// use easify_macros::unpack_split;
105/// let some_text = "hello,world";
106/// let result = unpack_split!(some_text, 2);
107/// assert_eq!(result, ("hello", "world"));
108/// ```
109///
110/// # Panics
111///
112/// The macro will cause arguments_number compile-time error if not exactly two arguments are provided.
113///
114/// ```
115/// # use easify_macros::unpack_split;
116/// // This will fail to compile
117/// // let result = unpack_split!("hello,world");
118/// ```
119
120#[proc_macro]
121pub fn unpack_split(input: TokenStream) -> TokenStream {
122    let SmartSplitParams(text, sep, count) = parse_macro_input!(input as SmartSplitParams);
123    let len = count.base10_parse::<usize>().unwrap();
124
125    let indices = 0..len;
126    let tuple_elems = indices.map(|_| quote! {tuple_elements.next().unwrap()});
127    let output = quote! {
128        {
129
130            let mut tuple_elements = #text.split(#sep);
131            (#( #tuple_elems, )*)
132        }
133    };
134    output.into()
135}
136
137// Define arguments_number struct to hold the parsed elements
138struct LetStatement {
139    vars: Vec<proc_macro2::TokenStream>,
140    astrix: usize,
141    expr: Ident,
142}
143
144// Implement custom parsing for the LetStatement
145impl Parse for LetStatement {
146    fn parse(input: ParseStream) -> Result<Self> {
147        let mut vars = Vec::new();
148
149        let mut count: usize = 0;
150        let mut astrix: usize = 0;
151        // Parse variables
152        loop {
153            if input.peek(Token![*]) {
154                input.parse::<Token![*]>()?; // Parse the '*' if present
155                astrix = count;
156
157            }
158
159            let is_mut = input.peek(Token![mut]);
160            if is_mut {
161                input.parse::<Token![mut]>()?; // Parse the '*' if present
162            }
163            let var: Ident = input.parse()?;
164
165            if is_mut {
166                vars.push(quote!(mut #var));
167            } else {
168                vars.push(quote!(#var));
169
170            }
171
172            if input.peek(Token![,]) {
173                input.parse::<Token![,]>()?;
174            } else {
175                break;
176            }
177            count += 1;
178        }
179
180        input.parse::<Token![=]>()?; // Parse the '='
181
182        // Parse the right-hand side expression
183        let expr: Ident = input.parse()?;
184
185        Ok(LetStatement { vars, astrix, expr })
186    }
187}
188
189/// A procedural macro to unpack like in Python programming language, where you expect array of
190/// known size, thus making it easy to unpack.
191///
192/// This macro consist of specifying initial variables that your given array will be unpacked to.
193/// You can also make your variable mutable, by simply adding `mut` before the variable name.
194/// The astrix `*` is used to unpack the rest of the array, resulting new array.
195/// Note, that you can use * only once in any position. The macro will expand to the
196/// let statements and named to variables you have specified.
197/// This macro can result to unexpected behavior and panicks if you don't know the Python unpacking
198/// Use it with caution.
199/// And remember, with great power comes great responsibility.
200/// # Examples
201///
202///
203///
204/// Use the macro to split the string:
205///
206/// ```
207/// use easify_macros::let_unpack;
208/// let unpacking = vec![1, 2, 3];
209/// let result = let_unpack!(*mut a, b, c = unpacking);
210/// a.push(10);
211/// assert_eq!(a, vec![1, 10]);
212/// assert_eq!(b, 2);
213/// assert_eq!(c, 3);
214/// ```
215///
216/// ```
217/// use easify_macros::let_unpack;
218/// let unpacking = vec![2, 3]
219/// let result = let_unpack!(a, *b, c = unpacking);
220/// a.push(10);
221/// assert_eq!(a, 2);
222/// assert_eq!(b, vec![]);
223/// assert_eq!(c, 3);
224/// ```
225///
226/// # Panics
227///
228/// The macro will cause arguments_number compile-time error if not exactly two arguments are provided.
229///
230/// ```
231/// # use easify_macros::let_unpack;
232/// // This will fail to compile
233/// // let result = let_unpack!("hello,world");
234/// ```
235// Procedural macro
236
237#[proc_macro]
238pub fn let_unpack(input: TokenStream) -> TokenStream {
239    let LetStatement { vars, astrix, expr} = parse_macro_input!(input as LetStatement);
240    let mut values = Vec::new();
241    let arguments_number = vars.len();
242    for index in 0..arguments_number {
243        if index < astrix {
244            values.push(quote! {#expr[#index]});
245        } else if index == astrix {
246            values.push(
247                quote! {&#expr[#index..#expr.len() - #arguments_number + #index + 1].to_vec()}
248            );
249        } else {
250            values.push(quote! {#expr[#expr.len() - #arguments_number + #index]});
251        }
252    }
253    TokenStream::from(quote! {
254        #( let #vars = #values; )*;
255        drop(#expr);
256    })
257
258}