6 releases
0.2.3 | Mar 15, 2024 |
---|---|
0.2.2 | Oct 28, 2023 |
0.2.0 | Sep 9, 2023 |
0.1.1 | May 16, 2023 |
#1806 in Procedural macros
122 downloads per month
Used in ssloc
53KB
823 lines
forr
Control flow and loops in compile time.
forr!
Aims to replace single use macro_rules!
for the purpose to repeating code.
For example it can reduce an implementation for multiple tuples:
use forr::forr;
trait Like { fn like (&self, other: &Self) -> bool; }
impl Like for i32 { fn like (&self, other: &Self) -> bool { self == other } }
forr! { $gens:inner in [(), (A,), (A, B), (A, B, C),
(A, B, C, D), (A, B, C, D, E)] $*
// $idx is special this would be like (idx, gen) = [...].enumerate()
forr! { $idx:idx, $gen:expr in [$gens] $:
impl<$($gen: Like,)*> Like for ($gens) {
fn like(&self, other: &Self) -> bool {
$(self.$idx.like(&other.$idx)&&)* true
}
}
}
}
assert!((1, 3).like(&(1, 3)));
assert!((1,).like(&(1,)));
assert!(().like(&()));
With macro-rules this would be:
macro_rules! impl_like_for_tuples {
[$(($gen:ident, $idx:tt), $(($gens:ident, $idxs:tt)),*)?$(,)?] => {
impl$(<$gen: Like, $($gens: Like),*>)? Like for ($($gen, $($gens),*)?) {
fn like(&self, other: &Self) -> bool {
$(self.$idx.like(&other.$idx) &&)?
$($(self.$idxs.like(&other.$idxs) &&)*)?
true
}
}
$(impl_like_for_tuples![$(($gens, $idxs)),*,];)?
}
}
impl_like_for_tuples![(E, 4), (D, 3), (C, 2), (B, 1), (A, 0)];
assert!((1, 3).like(&(1, 3)));
assert!((1,).like(&(1,)));
assert!(().like(&()));
Granted in this example it is not a lot more complicated, and adding more tuple variants actually requires less code. But it took me quite a few more trys getting it to work correctly. (If you don't count me implementing this whole library for the first one.)
Usage
The first part of the invocation is the pattern, similar to a normal for
loop in rust. Here you can use either a single
variable i.e. $name:type
or a tuple
binding ($name:type, $nbme:type, ...)
. There can
optionally be non consuming patterns specified
before or after, currently that includes only :idx
.
This is followed by the keyword in
, an array literal [...]
containing
the tokens to iterate and the body marked with either $*
or $:
.
Single variable binding
forr! { $val:expr in [1, 2 + 4, 20]
$val
will be 1
, 2 + 4
and 20
.
Tuple binding
(
$name:type
, ... )
forr! { ($val:expr, $vbl:ty) in [(1, i32), (Ok(2 + 4), Result<u8, ()>), (20.0, f32)]
$val
will be 1
, Ok(2 + 4)
and 20.0
.
$vbl
will be i32
, Result<u8, ()>
and f32
.
Non consuming patterns
Non consuming patterns can be specified before or after the consuming patterns or inside if using a tuple binding.
Currently, only :idx
is supported
forr! { $val:expr, $i:idx in [1, 2]
forr! { $i:idx, $val:expr in [1, 2]
forr! { $i:idx, ($val:expr, $vbl:ty) in [(1, i32), (2, i32)]
forr! { ($val:expr, $vbl:ty), $i:idx in [(1, i32), (2, i32)]
forr! { ($val:expr, $i:idx, $vbl:ty) in [(1, i32), (2, i32)]
$val
will be 1
and 2
$i
will be 0
and 1
$vbl
will be i32
Body
The body can be in two different modes. When it is initialized with $*
the
whole body is repeated similar to a normal for loop. Is it started with
$:
, the body will behave more like macro expansion using $()*
for
repetition. In both cases there is special handling for [optional
values](#optional values) when placed inside $()?
the innermost such group
is only added if the value is present.
$*
outer repetition
In the tokens following the $*
every occurrence of a $ident
where the
ident matches one of the declared variables is replaced with the
corresponding value.
forr! {$val:expr in [(1, "a", true)] $*
assert_eq!($val, $val);
}
will expand to
assert_eq!(1, 1);
assert_eq!("a", "a");
assert_eq!(true, true);
$:
inner repetition
$:
allows to have non repeated code surrounding the expansion, mainly
useful for cases where a macro would not be allowed.
forr! {($pat:expr, $res:expr) in [(0, true), (1, false), (2.., true)] $:
match 1u8 {
$($pat => $res,)*
}
}
Without the inner repetition this would not be possible, as macros are not
allowed as the body of a match
.
match 1 {
forr! {($pat:expr, $res:expr) in [(0, true), (1, false), (2.., true)] $*
$pat => $res
}
}
Names
Any valid rust idents including keywords can be used to name variables. Note
that shadowing does not work, i.e. an inner forr!
needs to use different
names.
Types
:expr
As this uses TokenParser::next_expression()
this will allow anything
that matches the ,
rules for expressions i.e. it cannot contain ;
and no
,
outside turbofishes for Types/Generics (HashMap::<A, B>
).
:ty
As this uses TokenParser::next_type()
this will allow anything
that matches the ,
rules for types i.e. it cannot contain ;
and no
,
outside <>
for Generics (HashMap<A, B>
) and unopened closing >
.
:tt
Currently matches exactly one proc_macro::TokenTree
, but the plan is to extend this to what macro_rule!
's :tt
matches.
:inner
The most versatile type, allowing arbitrary tokens wrapped by any bracket
[
, {
or (
... )}]
.
:idx
Non consuming pattern that will contain the current index.
iff!
Aims to be an alternative version of
#[cfg(...)]
able to conditionally enable any rust tokens and being able to, e.g. compare
tokens, but it is not able to be conditional other actual cfg
or features.
iff! { true && false $:
compile_error!("This is not expanded")
}
iff! { true || false $:
compile_error!("This is expanded")
}
On top of the basic boolean operations (!
, &&
, ||
) there are some
functions:
empty(<tokens>)
tests if<tokens>
is empty.equals(<lhs>)(<rhs>)
tests if<lhs>
is equal to<rhs>
.
iff! { empty() $:
compile_error!("This is expanded")
}
iff! { empty(something) $:
compile_error!("This is not expanded")
}
iff! { equals(something)(another thing) $:
compile_error!("Neither is this")
}
Dependencies
~235KB