loop-let 0.1.0

An immutable loop structure that can be used as an expression.
Documentation
//! # loop_let
//! 
//! This crate provides a macro for a new control flow mechanism in Rust proposal `loop let`. 
//! Which is a immutable loop pattern that is meant for tail call recursive algorithms earning
//! tail call optimization for free.
//! 
//! This crate provides the `loop_let!` macro that implements the `loop let` pattern. The syntax
//! is as follows:
//! 
//! ```rs no_run
//! loop_let!((pattern) = (initial); {
//!    // code ...
//! });
//! 
//! loop_let!(fails (pattern) = (initial); {
//!   // code ...
//! });
//! ```
//! 
//! The `pattern` is the pattern that will be matched against the input. The `initial` is the
//! initial value of the input. The code block is the code that will be executed in every iteration
//! of loop. It's important to notice that the code block must return either a `Continue` or
//! a `Break`.
//! 
//! `Continue` is used to set the input value for the next iteration of the loop. `Break` is
//! used to break the loop and return the value.
//! 
//! The `fails` keyword is used before the pattern when using fallible patterns. This way, the loop
//! breaks once the pattern fails. By using the `fails` keyword, the `loop_let` macro will return
//! `()` (this means you can only use `Break(())`). So it can't be used as an expression.
//! 
//! # Why?
//! 
//! The main purpose of this crate is to encourage the utilization of tail call recursive algorithms
//! in Rust rather than the traditional recursion which relies on the compiler to optimize the code.
//! 
//! Some other earnings of using the `loop let` pattern in comparison to other tail call recursive
//! approaches are:
//! 
//! - Support for pattern matching (even fallible patterns)
//! - Loops as an expression
//! - Avoids the need for `mut`
//! - Avoids the need of tail call optimization at the compiler level
//! - Increased readability
//! 
//! This pattern is used in Clojure with the loop/recur pattern where a loop might return a value
//! or call itself with new arguments.
//!  
//! This is a proposal for a new control flow mechanism in Rust as a language construct `loop let`.
//! 
//! ```rs no_run
//! // Proposed syntax (no need for `fails` keyword)
//! loop let (pattern) = (initial) {
//!     // ...
//!     continue (new_input); // New syntax for continue
//!     // ...
//!     break (result);
//! }
//! ```

/// Implementation of the `loop let` immutable loop pattern. This implements a loop that 
/// is meant for tail call recursive algorithms earning tail call optimization for free.
/// 
/// The syntax is as follows:
/// 
/// ```rs no_run
/// loop_let!((pattern) = (initial); { 
///     // code ... 
/// });
/// ```
/// The `pattern` is the pattern that will be matched against the input. The `initial` is the 
/// initial value of the input. The code block is the code that will be executed in every iteration 
/// of loop. It's important to notice that the code block must return either a `Continue` or 
/// a `Break`.
/// 
/// `Continue` is used to set the input value for the next iteration of the loop. `Break` is 
/// used to break the loop and return the value.
/// 
/// When using fallible patterns, the `fails` keyword must be used before the pattern. 
/// This way, the loop breaks once the pattern fails. By using the `fails` keyword, the
/// `loop_let` macro will return `()` (this means you can only use `Break(())`). So it can't 
/// be used as an expression.
/// 
/// # Panics
/// 
/// If a fallible pattern is used without the `fails` keyword, the loop will panic once the
/// pattern matching fails.
/// 
/// # Example
/// ```rs
/// use loop_let::loop_let;
/// 
/// let fib = loop_let!((n, curr, next) = (6, 0, 1); {
///     if n == 0 {
///        Break(curr)
///     } else {
///        Continue((n - 1, next, curr + next))
///     }
/// });
/// 
/// assert_eq!(fib, 8);
/// ```
#[macro_export]
macro_rules! loop_let {
    ($pattern:pat = $initial:expr; $body:block) => {
        {
            let mut input = $initial;

            loop {
                match input {
                    $pattern => {
                        use std::ops::ControlFlow::*;

                        match $body {
                            Continue(new_input) => {
                                input = new_input;
                            },
                            Break(result) => break result,
                        }
                    },
                    #[allow(unreachable_patterns)]
                    _ => panic!("Pattern failed in `loop_let` macro, use `fails` keyword for fallible patterns as in `loop_let!(fails Ok(o) = result_fn(); {{ ... }})`"),
                }
            }
        }
    };

    (fails $pattern:pat = $initial:expr; $body:block) => {
        {
            let mut input = $initial;

            loop {
                match input {
                    $pattern => {
                        use std::ops::ControlFlow::*;

                        match $body {
                            Continue(new_input) => {
                                input = new_input;
                            },
                            Break(()) => break,
                        }
                    },
                    _ => break,
                }
            }
        }
    };
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_loop_let() {
        let result = loop_let!(x = 0; {
            if x < 10 {
                Continue(x + 1)
            } else {
                Break(x)
            }
        });

        assert_eq!(result, 10);
    }

    #[test]
    fn test_loop_let_fails() {
        let result = loop_let!(fails Ok(_) = Err::<u8, &str>("error"); {
            Continue(Err("error"))
        });

        assert_eq!(result, ());
    }

    #[test]
    fn test_fibo() {
        let fib = loop_let!((n, curr, next) = (6, 0, 1); {
            if n == 0 {
                Break(curr)
            } else {
                Continue((n - 1, next, curr + next))
            }
        });

        assert_eq!(fib, 8);
    }
}