for_else/
lib.rs

1//! `for-else` - Enhanced loop control in Rust
2//!
3//! This crate provides a procedural macro, `for_!`, that enhances
4//! the behavior of the standard `for` loop in Rust. It allows for an additional `else` block
5//! that gets executed if the loop completes without encountering a `break` statement.
6//!
7//! # Usage
8//!
9//! Add the crate to your `Cargo.toml` dependencies and import the macros:
10//!
11//! ```bash
12//! cargo add for-else
13//! ```
14//!
15//! In your Rust code:
16//!
17//! ```rust
18//! use for_else::for_;
19//!
20//! // not the best way to test primality, just for demonstration
21//! fn is_prime(n: u32) -> bool {
22//!     if n <= 1 {
23//!         return false;
24//!     }
25//!     for i in 2..n {
26//!         if n % i == 0 {
27//!             return false;
28//!         }
29//!     }
30//!     true
31//! }
32//!
33//! for_! { n in 2100..=2110 {
34//!     if is_prime(n) {
35//!         println!("Found a prime number: {}", n);
36//!         break;
37//!     }
38//! } else {
39//!     println!("No prime numbers found in the range.");
40//! }}
41//! ```
42//!
43//! In this example, the program searches for the first prime number in the range [2100, 2110]. If a prime is found, it prints out the number. If no prime is found in the range, the `else` block within the `for_!` macro is executed, notifying the user.
44//!
45//! See the `for_!` macro documentation for more detailed examples and usage information.
46
47extern crate proc_macro;
48
49use proc_macro::TokenStream;
50use quote::quote;
51use syn::parse::{Parse, ParseStream};
52use syn::token::Brace;
53use syn::{
54    parse2, parse_macro_input, Block, Expr, ExprBlock, ExprBreak, ExprForLoop, ExprIf, ExprLoop,
55    ExprMatch, ExprUnsafe, ExprWhile, Pat, Stmt, Token,
56};
57
58struct ForLoop {
59    var: Pat,
60    expr: Expr,
61    body: Block,
62    else_block: Block,
63    label: Option<syn::Label>,
64}
65
66impl Parse for ForLoop {
67    fn parse(input: ParseStream) -> syn::Result<Self> {
68        // Check for optional label at the beginning
69        let label = if input.peek(syn::Lifetime) && input.peek2(Token![:]) {
70            let lifetime: syn::Lifetime = input.parse()?;
71            input.parse::<Token![:]>()?;
72            Some(syn::Label {
73                name: lifetime,
74                colon_token: Token![:](proc_macro2::Span::call_site()),
75            })
76        } else {
77            None
78        };
79
80        let var = Pat::parse_single(input)?;
81        input.parse::<Token![in]>()?;
82
83        // Use a fork to try parsing different amounts of the input as the expression
84        // We'll keep extending until we can successfully parse what's left as "{ body } else { else_block }"
85        let checkpoint = input.fork();
86        let mut expr_tokens = proc_macro2::TokenStream::new();
87
88        // Collect all tokens until we find a valid parse point
89        while !input.is_empty() {
90            // Check if the remaining input can be parsed as "{ body } else { else_block }"
91            let remaining = input.fork();
92            if remaining.peek(Brace) {
93                // Try to parse: Block else Block
94                let test_remaining = remaining.fork();
95                if test_remaining.parse::<Block>().is_ok()
96                    && test_remaining.peek(Token![else])
97                    && test_remaining.peek2(Brace)
98                {
99                    let _ = test_remaining.parse::<Token![else]>();
100                    if test_remaining.parse::<Block>().is_ok() {
101                        // Successfully parsed the remaining as "{ body } else { else_block }"
102                        break;
103                    }
104                }
105            }
106
107            // Add the next token to our expression
108            let tt: proc_macro2::TokenTree = input.parse()?;
109            expr_tokens.extend(std::iter::once(tt));
110        }
111
112        // Parse the expression from collected tokens
113        let expr: Expr = if expr_tokens.is_empty() {
114            return Err(syn::Error::new(
115                checkpoint.span(),
116                "expected expression after 'in'",
117            ));
118        } else {
119            syn::parse2(expr_tokens)?
120        };
121
122        let body: Block = input.parse()?;
123        input.parse::<Token![else]>()?;
124        let else_block: Block = input.parse()?;
125
126        Ok(ForLoop {
127            var,
128            expr,
129            body,
130            else_block,
131            label,
132        })
133    }
134}
135
136fn modify_breaks_in_block(
137    body: &mut Block,
138    this_is_my_loop: bool,
139    loops_label: Option<&syn::Label>,
140) {
141    for stmt in &mut body.stmts {
142        if let Stmt::Expr(expr, _) = stmt {
143            modify_breaks_in_expression(expr, this_is_my_loop, loops_label);
144        }
145    }
146}
147
148fn modify_breaks_in_expression(
149    expression: &mut Expr,
150    this_is_my_loop: bool,
151    loops_label: Option<&syn::Label>,
152) {
153    match expression {
154        Expr::Break(break_expr) => {
155            let replacement = modify_single_break(break_expr, this_is_my_loop, loops_label);
156            if let Some(replacement) = replacement {
157                *expression = parse2(replacement).unwrap();
158            }
159        }
160        Expr::Block(ExprBlock { block, .. }) => {
161            modify_breaks_in_block(block, this_is_my_loop, loops_label);
162        }
163        Expr::Unsafe(ExprUnsafe { block, .. }) => {
164            modify_breaks_in_block(block, this_is_my_loop, loops_label);
165        }
166        Expr::If(ExprIf {
167            then_branch,
168            else_branch,
169            ..
170        }) => {
171            modify_breaks_in_block(then_branch, this_is_my_loop, loops_label);
172            if let Some((_, else_block)) = else_branch {
173                if let Expr::Block(ExprBlock { block, .. }) = &mut **else_block {
174                    modify_breaks_in_block(block, this_is_my_loop, loops_label);
175                }
176            }
177        }
178        Expr::Match(ExprMatch { arms, .. }) => {
179            for arm in arms {
180                modify_breaks_in_expression(&mut arm.body, this_is_my_loop, loops_label);
181            }
182        }
183        Expr::ForLoop(ExprForLoop { body, .. }) => {
184            modify_breaks_in_block(body, false, loops_label);
185        }
186        Expr::While(ExprWhile { body, .. }) => {
187            modify_breaks_in_block(body, false, loops_label);
188        }
189        Expr::Loop(ExprLoop { body, .. }) => {
190            modify_breaks_in_block(body, false, loops_label);
191        }
192        _ => {}
193    }
194}
195
196// We need to replace a stement with another statement, but we have two statements instead,
197// so we put them into a block to make it a single statement
198fn modify_single_break(
199    break_expr: &ExprBreak,
200    this_is_my_loop: bool,
201    loops_label: Option<&syn::Label>,
202) -> Option<proc_macro2::TokenStream> {
203    let replacement = if let Some(breaks_label) = &break_expr.label {
204        // We don't want to touch breaks with labels if it's not our label
205        if let Some(loops_label) = loops_label {
206            if breaks_label.ident != loops_label.name.ident {
207                return None;
208            }
209        }
210        quote! {
211            {
212                _for_else_break_occurred = true;
213                break #breaks_label;
214            }
215        }
216    } else {
217        // We don't want to touch breaks in inner loops that don't have our label
218        if !this_is_my_loop {
219            return None;
220        }
221        quote! {
222            {
223                _for_else_break_occurred = true;
224                break;
225            }
226        }
227    };
228
229    Some(replacement)
230}
231
232/// The `for_!` procedural macro with enhanced loop control.
233///
234/// This macro is an extension of the standard `for` loop in Rust. It allows users to
235/// have an additional `else` block that executes if the loop completed without encountering a `break` statement.
236///
237/// # Syntax
238///
239/// ```ignore
240/// for_! { variable in iterable {
241///     // loop body
242/// } else {
243///     // else block
244/// }}
245///
246/// // With optional label:
247/// for_! { 'label: variable in iterable {
248///     // loop body
249/// } else {
250///     // else block
251/// }}
252/// ```
253///
254/// # Behavior
255///
256/// - The loop iterates over all elements in the iterable
257/// - If the loop completes by exhausting all elements, the `else` block is executed
258/// - If the loop exits via a `break` statement, the `else` block is **not** executed
259/// - `continue` statements work normally and do not affect the `else` block execution
260///
261/// # Examples
262///
263/// ## Prime number search
264///
265/// ```rust
266/// use for_else::for_;
267///
268/// fn is_prime(n: u32) -> bool {
269///     if n <= 1 { return false; }
270///     for i in 2..n {
271///         if n % i == 0 { return false; }
272///     }
273///     true
274/// }
275///
276/// for_! { n in 2100..=2110 {
277///     if is_prime(n) {
278///         println!("Found prime: {}", n);
279///         break;
280///     }
281/// } else {
282///     println!("No prime numbers found in range");
283/// }}
284/// ```
285///
286/// ## Finding an element in a collection
287///
288/// ```rust
289/// use for_else::for_;
290///
291/// fn find_user(users: &[&str], target: &str) -> bool {
292///     for_! { user in users {
293///         if *user == target {
294///             println!("Found user: {}", user);
295///             return true;
296///         }
297///     } else {
298///         println!("User '{}' not found", target);
299///     }}
300///     false
301/// }
302///
303/// let users = ["alice", "bob", "charlie"];
304/// find_user(&users, "dave");  // Prints: User 'dave' not found
305/// ```
306///
307/// ## Validation with early exit
308///
309/// ```rust
310/// use for_else::for_;
311///
312/// fn validate_data(numbers: &[i32]) -> bool {
313///     for_! { &num in numbers {
314///         if num < 0 {
315///             println!("Invalid negative number found: {}", num);
316///             return false;
317///         }
318///         if num > 100 {
319///             println!("Number too large: {}", num);
320///             return false;
321///         }
322///     } else {
323///         println!("All numbers are valid");
324///         return true;
325///     }}
326///     false
327/// }
328/// ```
329///
330/// ## Working with complex expressions
331///
332/// ```rust
333/// use for_else::for_;
334///
335/// struct DataSource;
336/// impl DataSource {
337///     fn items(&self) -> impl Iterator<Item = i32> {
338///         vec![1, 2, 3, 4, 5].into_iter()
339///     }
340/// }
341///
342/// // Note: Complex expressions may need parentheses
343/// for_! { item in (DataSource {}).items() {
344///     if item > 10 {
345///         println!("Found large item: {}", item);
346///         break;
347///     }
348/// } else {
349///     println!("No large items found");
350/// }}
351/// ```
352///
353/// ## Nested loops with labels
354/// Labels should be put inside the macro
355///
356/// ```rust
357/// use for_else::for_;
358///
359/// for_! { 'outer: i in 0..3 {
360///     for_! { j in 0..3 {
361///         if i == 1 && j == 1 {
362///             break 'outer;  // Break outer loop
363///         }
364///         println!("({}, {})", i, j);
365///     } else {
366///         println!("Inner loop {} completed", i);
367///     }}
368/// } else {
369///     println!("Both loops completed naturally");
370/// }}
371/// ```
372///
373/// # Comparison with Python
374///
375/// This macro brings Python-like `for-else` behavior to Rust:
376///
377/// **Python:**
378/// ```python
379/// for item in iterable:
380///     # loop body
381///     if some_condition:
382///         break
383/// else:
384///     # executed if loop completed without break
385///     pass
386/// ```
387///
388/// **Rust with `for_!`:**
389/// ```ignore
390/// for_! { item in iterable {
391///     // loop body
392///     if some_condition {
393///         break;
394///     }
395/// } else {
396///     // executed if loop completed without break
397/// }}
398/// ```
399///
400/// # Notes
401///
402/// - The macro supports all the same iterables as standard `for` loops
403/// - Loop labels work normally for controlling nested loops
404/// - Complex expressions in the iterable position may require parentheses due to Rust's parsing rules
405#[proc_macro]
406pub fn for_(input: TokenStream) -> TokenStream {
407    let mut input = parse_macro_input!(input as ForLoop);
408
409    let label = input.label;
410
411    modify_breaks_in_block(&mut input.body, true, label.as_ref());
412
413    let var = input.var;
414    let expr = input.expr;
415    let body = input.body;
416    let else_block = input.else_block;
417
418    let expanded = if let Some(label) = label {
419        quote! {
420            {
421                let mut _for_else_break_occurred = false;
422                #label for #var in #expr
423                    #body
424                if !_for_else_break_occurred
425                    #else_block
426            }
427        }
428    } else {
429        quote! {
430            {
431                let mut _for_else_break_occurred = false;
432                for #var in #expr
433                    #body
434                if !_for_else_break_occurred
435                    #else_block
436            }
437        }
438    };
439
440    expanded.into()
441}
442
443struct WhileLoop {
444    cond: Expr,
445    body: Block,
446    else_block: Block,
447    label: Option<syn::Label>,
448}
449
450impl Parse for WhileLoop {
451    fn parse(input: ParseStream) -> syn::Result<Self> {
452        // Check for optional label at the beginning
453        let label = if input.peek(syn::Lifetime) && input.peek2(Token![:]) {
454            let lifetime: syn::Lifetime = input.parse()?;
455            input.parse::<Token![:]>()?;
456            Some(syn::Label {
457                name: lifetime,
458                colon_token: Token![:](proc_macro2::Span::call_site()),
459            })
460        } else {
461            None
462        };
463
464        // Use the same lookahead approach as for_! macro
465        let checkpoint = input.fork();
466        let mut cond_tokens = proc_macro2::TokenStream::new();
467
468        // Collect all tokens until we find a valid parse point
469        while !input.is_empty() {
470            // Check if the remaining input can be parsed as "{ body } else { else_block }"
471            let remaining = input.fork();
472            if remaining.peek(Brace) {
473                // Try to parse: Block else Block
474                let test_remaining = remaining.fork();
475                if test_remaining.parse::<Block>().is_ok()
476                    && test_remaining.peek(Token![else])
477                    && test_remaining.peek2(Brace)
478                {
479                    let _ = test_remaining.parse::<Token![else]>();
480                    if test_remaining.parse::<Block>().is_ok() {
481                        // Successfully parsed the remaining as "{ body } else { else_block }"
482                        break;
483                    }
484                }
485            }
486
487            // Add the next token to our condition expression
488            let tt: proc_macro2::TokenTree = input.parse()?;
489            cond_tokens.extend(std::iter::once(tt));
490        }
491
492        // Parse the condition from collected tokens
493        let cond: Expr = if cond_tokens.is_empty() {
494            return Err(syn::Error::new(
495                checkpoint.span(),
496                "expected condition expression",
497            ));
498        } else {
499            syn::parse2(cond_tokens)?
500        };
501
502        let body: Block = input.parse()?;
503        input.parse::<Token![else]>()?;
504        let else_block: Block = input.parse()?;
505
506        Ok(WhileLoop {
507            cond,
508            body,
509            else_block,
510            label,
511        })
512    }
513}
514
515/// The `while_!` procedural macro with enhanced loop control.
516///
517/// This macro is an extension of the standard `while` loop in Rust. It allows users to
518/// have an additional `else` block that executes if the loop completed without encountering a `break` statement.
519///
520/// # Syntax
521///
522/// ```ignore
523/// while_! { condition {
524///     // loop body
525/// } else {
526///     // else block
527/// }}
528///
529/// // With optional label:
530/// while_! { 'label: condition {
531///     // loop body
532/// } else {
533///     // else block
534/// }}
535/// ```
536///
537/// # Notes
538///
539/// - The macro supports all the same conditions as standard `while` loops
540/// - Loop labels work normally for controlling nested loops
541/// - Complex expressions in the condition position are fully supported
542///
543/// # Behavior
544///
545/// - The loop continues to execute as long as the `condition` evaluates to `true`
546/// - If the loop exits naturally (condition becomes `false`), the `else` block is executed
547/// - If the loop exits via a `break` statement, the `else` block is **not** executed
548/// - `continue` statements work normally and do not affect the `else` block execution
549///
550/// # Examples
551///
552/// ## Basic usage
553///
554/// ```rust
555/// use for_else::while_;
556///
557/// let mut count = 0;
558/// let mut found = false;
559///
560/// while_! { count < 5 {
561///     if count == 10 {  // This condition is never true
562///         found = true;
563///         break;
564///     }
565///     count += 1;
566/// } else {
567///     println!("Loop completed without finding target value");
568/// }}
569///
570/// assert!(!found);  // Loop completed naturally, else block executed
571/// ```
572///
573/// ## Search with early termination
574///
575/// ```rust
576/// use for_else::while_;
577///
578/// fn find_target(data: &[i32], target: i32) -> Option<usize> {
579///     let mut index = 0;
580///     let mut result = None;
581///     
582///     while_! { index < data.len() {
583///         if data[index] == target {
584///             result = Some(index);
585///             break;  // Found it, exit early
586///         }
587///         index += 1;
588///     } else {
589///         println!("Target {} not found in data", target);
590///     }}
591///     
592///     result
593/// }
594/// ```
595///
596/// ## Retry mechanism with timeout
597///
598/// ```rust
599/// use for_else::while_;
600/// use std::time::{Duration, Instant};
601///
602/// fn retry_operation() -> bool {
603///     let start = Instant::now();
604///     let timeout = Duration::from_secs(5);
605///     
606///     while_! { start.elapsed() < timeout {
607///         if attempt_operation() {
608///             println!("Operation succeeded!");
609///             return true;
610///         }
611///         std::thread::sleep(Duration::from_millis(100));
612///     } else {
613///         println!("Operation timed out after 5 seconds");
614///     }}
615///     
616///     false
617/// }
618///
619/// fn attempt_operation() -> bool {
620///     // Some operation that might succeed or fail
621///     false
622/// }
623/// ```
624///
625/// ## Nested loops with labels
626/// Labels should be put inside the macro
627///
628/// ```rust
629/// use for_else::while_;
630///
631/// let mut found_target = false;
632/// let mut outer_count = 0;
633///
634/// while_! { 'outer: outer_count < 5 {
635///     let mut inner_count = 0;
636///     while_! { inner_count < 5 {
637///         if outer_count == 2 && inner_count == 3 {
638///             println!("Found target at ({}, {})", outer_count, inner_count);
639///             found_target = true;
640///             break 'outer;  // Break outer loop
641///         }
642///         inner_count += 1;
643///     } else {
644///         println!("Inner loop completed for outer_count = {}", outer_count);
645///     }}
646///     outer_count += 1;
647/// } else {
648///     println!("Search completed without early termination");
649/// }}
650///
651/// assert!(found_target);
652/// ```
653///
654/// # Comparison with Python
655///
656/// This macro brings Python-like `while-else` behavior to Rust:
657///
658/// **Python:**
659/// ```python
660/// while condition:
661///     # loop body
662///     if some_condition:
663///         break
664/// else:
665///     # executed if loop completed without break
666///     pass
667/// ```
668///
669/// **Rust with `while_!`:**
670/// ```ignore
671/// while_! { condition {
672///     // loop body
673///     if some_condition {
674///         break;
675///     }
676/// } else {
677///     // executed if loop completed without break
678/// }}
679/// ```
680#[proc_macro]
681pub fn while_(input: TokenStream) -> TokenStream {
682    let mut input = parse_macro_input!(input as WhileLoop);
683
684    let label = input.label;
685
686    modify_breaks_in_block(&mut input.body, true, label.as_ref());
687
688    let cond = input.cond;
689    let body = input.body;
690    let else_block = input.else_block;
691
692    let expanded = if let Some(label) = label {
693        quote! {
694            {
695                let mut _for_else_break_occurred = false;
696                #label while #cond
697                    #body
698                if !_for_else_break_occurred
699                    #else_block
700            }
701        }
702    } else {
703        quote! {
704            {
705                let mut _for_else_break_occurred = false;
706                while #cond
707                    #body
708                if !_for_else_break_occurred
709                    #else_block
710            }
711        }
712    };
713
714    expanded.into()
715}