#![cfg_attr(not(test), no_std)]
#![cfg_attr(test, recursion_limit = "1000")]
#![cfg_attr(feature = "nightly", feature(macro_reexport))]
#[cfg(all(test, // for testing ...
not(feature = "nightly")))] #[macro_use] #[no_link] extern crate static_cond;
#[cfg(feature = "nightly")] #[macro_use] #[no_link] #[macro_reexport(static_cond)] extern crate static_cond;
#[macro_export]
macro_rules! block {
(@as_expr $e:expr) => { $e };
(@error $err:ident) => {{
struct $err;
let _: () = $err;
}};
(@wrap $life:tt () $ret:ident ($($init:tt)*) $out:expr) => {
block!(@as_expr
{
let $ret $($init)*;
$life: loop {
$ret = $out;
break $life;
}
$ret
})
};
(@wrap $life:tt (loop) $ret:ident ($($init:tt)*) $out:expr) => {
block!(@as_expr
{
let $ret $($init)*;
$life: loop {
$out;
}
$ret
})
};
(@scan {} $life:tt $ret:ident () -> ($($out:tt)*) (() $lp:tt $init:tt)) => {
block!(@wrap $life $lp $ret $init { $($out)* })
};
(@scan {} $life:tt $ret:ident () -> ($($out:tt)*) $stack:tt) => {
block!(@up $life $ret { $($out)* } $stack)
};
(@scan () $life:tt $ret:ident () -> ($($out:tt)*) $stack:tt) => {
block!(@up $life $ret ( $($out)* ) $stack)
};
(@scan [] $life:tt $ret:ident () -> ($($out:tt)*) $stack:tt) => {
block!(@up $life $ret [ $($out)* ] $stack)
};
(@scan $paren:tt $life:tt $ret:ident (break) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret () -> ($($out)* block!(@error NoBareBreakInNamedBlock);) $stack)
};
(@scan $paren:tt $life:tt $ret:ident (break; $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* block!(@error NoBareBreakInNamedBlock);) $stack)
};
(@scan $paren:tt $life:tt $ret:ident (continue) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret () -> ($($out)* block!(@error NoBareContinueInNamedBlock);) $stack)
};
(@scan $paren:tt $life:tt $ret:ident (continue; $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* block!(@error NoBareContinueInNamedBlock);) $stack)
};
(@scan $paren:tt $life1:tt $ret:ident (break $life2:tt; $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* break $life2;) $stack)
};
(@scan $paren:tt $life1:tt $ret:ident (break $life2:tt $e:expr; $($tail:tt)*) -> ($($out:tt)*) ($stack:tt $lp:tt $init:tt)) => {
static_cond! {
if $life1 == $life2 {
block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* { $ret = $e; break $life2; }) ($stack $lp ()))
} else {
block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* break $life2 $e;) ($stack $lp ()))
}
}
};
(@scan $paren:tt $life1:tt $ret:ident (break $life2:tt $e:expr) -> ($($out:tt)*) ($stack:tt $lp:tt $init:tt)) => {
static_cond! {
if $life1 == $life2 {
block!(@scan $paren $life1 $ret () -> ($($out)* { $ret = $e; break $life2 }) ($stack $lp ()))
} else {
block!(@scan $paren $life1 $ret () -> ($($out)* break $life2 $e;) ($stack $lp ()))
}
}
};
(@scan $paren:tt $life1:tt $ret:ident (continue $life2:tt; $($tail:tt)*) -> ($($out:tt)*) ($stack:tt () $init:tt)) => {
static_cond! {
if $life1 == $life2 {
block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* block!(@error NoMatchedContinueInNamedBlock);) ($stack () $init))
} else {
block!(@scan $paren $life1 $ret ($($tail)*) -> ($($out)* continue $life2;) ($stack () $init))
}
}
};
(@scan $paren:tt $life1:tt $ret:ident (continue $life2:tt) -> ($($out:tt)*) $stack:tt) => {
static_cond! {
if $life1 == $life2 {
block!(@scan $paren $life1 $ret () -> ($($out)* block!(@error NoMatchedContinueInNamedBlock);) $stack)
} else {
block!(@scan $paren $life1 $ret () -> ($($out)* continue $life2;) $stack)
}
}
};
(@scan_item $paren:tt $life:tt $ret:ident ($ignore:item $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* $ignore) $stack)
};
(@scan $paren:tt $life:tt $ret:ident (#[block(ignore)] $ignore:tt $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* $ignore) $stack)
};
(@scan $paren:tt $life:tt $ret:ident (#[$attr:meta] $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* #[$attr]) $stack)
};
(@scan $paren:tt $life:tt $ret:ident (pub $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (pub $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (use $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (use $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (extern $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (extern $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (mod $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (mod $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (static $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (static $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (const $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (const $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (trait $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (trait $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (unsafe trait $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (unsafe trait $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (impl $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (impl $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (unsafe impl $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (unsafe impl $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (fn $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (fn $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (unsafe fn $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (unsafe fn $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (type $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (type $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (enum $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (enum $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident (struct $($tail:tt)*) -> $out:tt $stack:tt) => {
block!(@scan_item $paren $life $ret (struct $($tail)*) -> $out $stack)
};
(@scan $paren:tt $life:tt $ret:ident ({ $($inner:tt)* } $($tail:tt)*) -> $out:tt ($stack:tt $lp:tt $init:tt)) => {
block!(@scan {} $life $ret ($($inner)*) -> ()
(($paren ($($tail)*) -> $out $stack) $lp $init))
};
(@scan $paren:tt $life:tt $ret:ident (( $($inner:tt)* ) $($tail:tt)*) -> $out:tt ($stack:tt $lp:tt $init:tt)) => {
block!(@scan () $life $ret ($($inner)*) -> ()
(($paren ($($tail)*) -> $out $stack) $lp $init))
};
(@scan $paren:tt $life:tt $ret:ident ([ $($inner:tt)* ] $($tail:tt)*) -> $out:tt ($stack:tt $lp:tt $init:tt)) => {
block!(@scan [] $life $ret ($($inner)*) -> ()
(($paren ($($tail)*) -> $out $stack) $lp $init))
};
(@scan $paren:tt $life:tt $ret:ident ($head:tt $($tail:tt)*) -> ($($out:tt)*) $stack:tt) => {
block!(@scan $paren $life $ret ($($tail)*) -> ($($out)* $head) $stack)
};
(@up $life:tt $ret:ident $thing:tt (($paren:tt $tail:tt -> ($($out:tt)*) $stack:tt) $lp:tt $init:tt)) => {
block!(@scan $paren $life $ret $tail -> ($($out)* $thing) ($stack $lp $init))
};
($life:tt: { $($body:tt)* }) => {
block!(@scan {} $life _ret ($($body)*) -> () (() () ()))
};
($life:tt: loop { $($body:tt)* }) => {
block!(@scan {} $life _ret ($($body)*) -> () (() (loop) (= ())))
};
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let flag = true;
let x = block!('a: {
if flag { break 'a "early exit"; }
"normal exit"
});
assert_eq!(x, "early exit");
}
#[test]
fn shadowing() {
let flag = false;
let x = block!('b: {
if flag { break 'b "early exit"; }
let _y = block!('c: {
if flag { break 'b "inner early exit"; };
String::from("inner normal exit")
});
#[block(ignore)]
{
#[allow(dead_code)]
fn f() -> i32 {
block!('b: {
break 'b 42;
})
}
}
#[allow(dead_code)]
fn g() {
block!('b: {
break 'b 42;
});
while false {
break;
}
while false {
continue;
}
'b: while false {
continue 'b;
}
}
enum Foo { Bar(i32) }
let closure = move |Foo::Bar(x): Foo| -> i32 {
x + block!('d: {
break 'd 42;
})
};
assert_eq!(closure(Foo::Bar(0)), 42);
"normal exit"
});
assert_eq!(x, "normal exit");
'e: for i in 1..5 {
assert!(i >= 1 && i < 5);
block!('d: {
continue 'e;
});
}
}
#[test]
fn loops() {
assert_eq!(42, block!('a: loop { break 'a 42 }));
assert_eq!((), block!('a: {}));
let mut v = vec![];
let mut i = 0;
block!('a: loop {
i += 1;
if i == 5 {
continue 'a;
} else if i == 10 {
break 'a;
}
v.push(i);
});
assert_eq!(&*v, &[1, 2, 3, 4, 6, 7, 8, 9]);
}
}