floop 0.1.2

A more convenient and less error prone replacement for loop `{ select! { .. }}`
Documentation
//! [`floop`] is a more convenient and less error-prone replacement for `loop { select! { ... }}` with support for any runtime, no-std, and no-alloc.  
//! [`floop`] is not a replacement for `select` without a loop and does not need to be put inside a loop (it generates its own loop).
//! 
//! unlike loop select, **cancel-safety doesn't matter**, as futures are not cancelled when another arm finishes  
//! (loop select requires cancel-safe futures or work arounds to make futures cancel safe, [`floop`] doesn't,  
//! the worst part is that loop select doesn't warn you when a future is not cancel safe, it just silently causes unspecified behaviour),  
//! futures also don't need to be fused, [`unpin`], or have some other obscure property.
//! 
//! attempts are made to generate rust-analyzer friendly code when an error occures, instead of just panicking.
//! 
//! [`floop`] is a proc-macro, but it does not depend on syn, it uses unsynn, so compile times shouldn't be too bad.  
//! 
//! like loop select, each arm can **mutate shared state without synchronization**, but the futures can't,
//! the futures run concurrently, the arms serially, both are async.
//! 
//! arms can also have conditions (including `if let` conditions),
//! each arm's condition is only evaluated when the future would be created/recreated, not while its's running.
//! 
//! a `before` and `after` arm can also be added (both are optional), `before` runs at the start of the loop, `after` runs at the end.
//! 
//! # Syntax
//! syntax is difficult to explain so here's an example (the functions are not part of the macro):
//! ```no_run
//! floop! {
//!     // you need to specify whether `floop` should be `biased` or `unbiased`.
//!     unbiased
//!     // the main difference to normal select is that `=` has been replaced with `in`.
//!     //  ↓↓
//!     foo in timer::every(Duration::from_millis(40)) => println!("tick"),
//!     // `after` doesn't need to be last, and `before` doesn't need to be first,
//!     // but putting them at the start and end is more readable.
//!     after => at_the_end_of_loop(),
//!     // and the condition has been moved before the future,
//!     // this makes `if let` conditions more rust-analyzer-friendly.
//!     // note the comma between the condition and future.
//!     //                                        ↓
//!     (bar, baz) in if should_receive_messages(), receive() => {}
//! }
//! // note: `floop` evaluates to a future, so you'll need to await it (it's a expression) or otherwise poll it to completion.
//! ```
//! 
//! # Break
//! each arm can `break`, but `break` only stops the specific arm, the loop stops once all arms broke (meaning `break` doesn't cancel any futures).
//! the value of the future generated by [`floop`] is a tuple of all break values.  
//! (`before` and `after` can't break, they are either not inside a loop, or the break is turned into a compilation error if they are inside a loop)
//!
//! # Biasedness
//! `biased` just polls the futures in order,
//! `unbiased` doesn't use randomness, instead the futures are polled in order,
//! but polling doesn't start at the 0th future, it starts 1 after the last-polled future
//! (and wraps around the end when trying to poll a future past the end),  
//! this ensures that each future has an equal oppertunity to finish,
//! but without the added non-determism and complexity of random polling.
//! 
//! # Footguns
//! using `return` should work as expected, it returns from your function,  
//! however returning results in all local variables being dropped, which results in all unfinished futures being canceled,
//! which brings back the entire cancel safety issue.
//!
//! you can use await inside the arms, but they run serially, not concurrently,  
//! so waiting in a arm blocks the entire loop, which can lead to deadlocks or low throughput/high latency.
//! (this is the same behaviour as loop select)
//!
//! conditions are only checked after a future finishes, not while waiting,
//! for example the following would randomly deadlock:
//! ```
//! floop! {
//!     // biasedness has no effect on the result of this example.
//!     unbiased;
//!     // if the coinflip fails pending will run, which never finishes, so the coin won't have a chance to be flipped again,
//!     // and it will eventually fail.
//!     message in if coinflip(), receive_message => process_message(message),
//!     _ in pending() => {}
//!     // one fix would be to use a timer to make `floop` constantly reevaluate the condition, but that's a hack.
//!     // fix in timer::ever(Duration::from_millis(20)) => {}
//! }
//! ```
//! the best solution would be to ensure conditions are only changed inside the arms,
//! that way they will be revealuted every time they may change.
//! 
//!
//! # Non-footguns
//! using references to futures would result in a finished future being polled (which is very bad), and would be a giant footgun,  
//! but attempts to do so are **detected at compile time** and cause a readable, detailed, and friendly compilation error.
//! (owned futures containing references, such as a socket's receive future with a reference to the buffer to store a UDP datagram in, in are fine)  
//! for example the following **won't compile**:
//! ```no_run
//! let mut future = async {
//!     ...
//! }
//!
//! floop! {
//!     // you can also use the `footgun` keyword to disable the check and make this compile,
//!     // you shouldn't in most cases, but there may be some future that should be polled after it is finished.
//!     // (e.g. `foo in footgun &mut future`).
//!     bad in &mut future => println!("YAY, poll after finish!"),
//! }
//! ```
//! however the footgun detector may get disabled by compiler bugs (it currently uses work arounds for [140655](https://github.com/rust-lang/rust/issues/140655)),  
//! please open an issue if you can find any edge case where a future of type `&mut T` or `&T` compiles.
//!
//! # Cancel safety
//! the future returned by [`floop`] is not cancel safe, even if all futures used inside it are.
//! 
//! none of the futures used inside [`floop`] need to be cancel safe, [`floop`] does not cancel futures.
//!
//! # Implementation detatils
//! floop expands to roughly the following code, ignoring all the work arounds, edge cases, footgun prevention, break, and unbiasedness.  
//! (arms are destructured into (`$pattern_n in if $condition_n, $future_expr_n => $arm_n`)
//!
//! [`Unpin`]: ::core::marker::Unpin
//! ```
//! async {
//!     enum Output<T0..TN> {
//!         VariantN(TN)
//!     }
//!     let future_n = None;
//!     let pin_n = None;
//!
//!     loop {
//!         #before;
//!         if future_n.is_none() && condition_n {
//!             future_n = future_expr_n;
//!             future_ref = ...; // setting a option to Some and getting a mutable reference to its value is surprisngly annoying.
//!             unsafe {
//!                 pin_n = Pin::new_unchecked(future_ref);
//!             }
//!         }
//!
//!         let function = |ctx| {
//!             if let Poll::Ready(ready) = pin_n.poll(ctx) {
//!                 return Poll::Ready(Output::VariantN(ready));
//!             }
//!         }
//!         let function = poll_fn(function);
//!
//!         match function.await {
//!             Output::VariantN => {
//!                 #arm_n;
//!
//!                 pin_n = None;
//!                 future_n = None;
//!             }
//!         }
//!
//!         #after;
//!     }
//! }
//! ```
// TODO: add examples.

// for some reason `footgun_detector()` uses very deep recursion.
#![recursion_limit = "512"]

mod implemenation;
mod parser;
mod code_generation;

/// see the [crate level docs](crate)
#[proc_macro]
pub fn floop(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
    implemenation::floop(tokens.into()).into()
}