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

#1998 in Procedural macros

Download history 2/week @ 2024-01-22 73/week @ 2024-01-29 61/week @ 2024-02-05 81/week @ 2024-02-12 90/week @ 2024-02-19 99/week @ 2024-02-26 86/week @ 2024-03-04 282/week @ 2024-03-11 153/week @ 2024-03-18 99/week @ 2024-03-25 153/week @ 2024-04-01 129/week @ 2024-04-08 32/week @ 2024-04-15

429 downloads per month
Used in ssloc

MIT/Apache

53KB
823 lines

forr

CI Status Crates.io Docs.rs Documentation for main

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

$ name : type

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

~245KB