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),
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),
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}