Skip to content

Instantly share code, notes, and snippets.

@durka
Last active September 11, 2015 23:50
Show Gist options
  • Save durka/f47d969e875b37ba305d to your computer and use it in GitHub Desktop.
Save durka/f47d969e875b37ba305d to your computer and use it in GitHub Desktop.
//! ```cargo
//! [dependencies]
//! custom_derive = "*"
//! ```
#[macro_use] extern crate custom_derive;
macro_rules! DiscriminantEnum {
// public entry points
// private enum with generics (proceed to generic parameter collection)
(($discr:ident) enum $name:ident < $($tail:tt)*) => {
DiscriminantEnum!(@collect_generics [] [<] [], $discr, $name, $($tail)*);
};
// public enum with generics (proceed to generic parameter collection)
(($discr:ident) pub enum $name:ident < $($tail:tt)*) => {
DiscriminantEnum!(@collect_generics [pub] [<] [], $discr, $name, $($tail)*);
};
// private enum with no generics (proceed to where-clause collection, just in case)
// (note: there shouldn't be a where clause to collect, since there are no parameters,
// but if there is one by mistake the error should come from the compiler, not from
// the macro)
(($discr:ident) enum $name:ident $($tail:tt)*) => {
DiscriminantEnum!(@collect_bounds [] [] [], $discr, $name, [] [] [], $($tail)*);
};
// public enum with no generics (proceed to where-clause collection, just in case)
(($discr:ident) pub enum $name:ident $($tail:tt)*) => {
DiscriminantEnum!(@collect_bounds [pub] [] [], $discr, $name, [] [] [], $($tail)*);
};
// private entry points
// macro capture name conventions:
// - $_var is a capture that is thrown away
// - $var_ is a capture that is simply passed through from the previous arm
// - $var is actually used or modified by this arm
// util: re-parse a series of items as such
(@as_items $($i:item)*) => { $($i)* };
// generic parameter collection
// end of generic parameters (proceed to where-clause collection)
(@collect_generics $v_:tt [$($generic:tt)*] $w_:tt, $d_:ident, $n_:ident, > $($tail:tt)*) => {
DiscriminantEnum!(@collect_bounds $v_ [$($generic)* >] $w_, $d_, $n_, [] [] [], $($tail)*);
};
// eat one tt of generic parameters (save it for later)
(@collect_generics $v_:tt [$($generic:tt)*] $w_:tt, $d_:ident, $n_:ident, $head:tt $($tail:tt)*) => {
DiscriminantEnum!(@collect_generics $v_ [$($generic)* $head] $w_, $d_, $n_, $($tail)*);
};
// where-clause collection
// beginning of the where clause (save it for later)
// (note: this $($bound:tt)* isn't really necessary, because there can't be anything
// before the where clause. but if there is something the error should come from
// the compiler, not from the macro)
(@collect_bounds $v_:tt $g_:tt [$($bound:tt)*], $d_:ident, $n_:ident, $bv_:tt $tv_:tt $sv_:tt, where $($tail:tt)*) => {
DiscriminantEnum!(@collect_bounds $v_ $g_ [$($bound)* where], $d_, $n_, $bv_ $tv_ $sv_, $($tail)*);
};
// end of the where clause (proceed to variant collection)
(@collect_bounds $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, $bv_:tt $tv_:tt $sv_:tt, { $($tail:tt)* }) => {
DiscriminantEnum!(@collect_variants $v_ $g_ $w_, $d_, $n_, $bv_ $tv_ $sv_, { $($tail)* });
};
// eat one tt of the where clause (save it for later)
(@collect_bounds $v_:tt $g_:tt [$($bound:tt)*], $d_:ident, $n_:ident, $bv_:tt $tv_:tt $sv_:tt, $head:tt $($tail:tt)*) => {
DiscriminantEnum!(@collect_bounds $v_ $g_ [$($bound)* $head], $d_, $n_, $bv_ $tv_ $sv_, $($tail)*);
};
// variant collection
// (need to detect the three tuples of enum variants and collect them separately)
// end of variants (proceed to output)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, $bv_:tt $tv_:tt $sv_:tt, { $(,)* }) => {
DiscriminantEnum!(@output $v_ $g_ $w_, $d_, $n_, $bv_ $tv_ $sv_);
};
// eat a meta attribute (throw it away)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, $b_v:tt $tv_:tt $sv_:tt, { #[$_attr:meta] $($tail:tt)* }) => {
DiscriminantEnum!(@collect_variants $v_ $g_ $w_, $d_, $n_, $bv_ $tv_ $sv_, { $($tail)* });
};
// eat a bare variant, with or without a C-style value, that isn't the last variant (save the variant name for later)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, [$($bare_variant:ident)*] $tv_:tt $sv_:tt, { $var:ident $(= $_val:expr)*, $($tail:tt)* }) => {
DiscriminantEnum!(@collect_variants $v_ $g_ $w_, $d_, $n_, [$($bare_variant)* $var] $tv_ $sv_, { $($tail)* });
};
// eat a bare variant, with or without a C-style value, that is the last variant (save the variant name for later)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, [$($bare_variant:ident)*] $tv_:tt $sv_:tt, { $var:ident $(= $_val:expr)* }) => {
DiscriminantEnum!(@output $v_ $g_ $w_, $d_, $n_, [$($bare_variant)* $var] $tv_ $sv_);
};
// eat a tuple variant, that isn't the last variant (save the variant name for later)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, $bv_:tt [$($tuple_variant:ident)*] $sv_:tt, { $var:ident ( $($_contents:tt)* ), $($tail:tt)* }) => {
DiscriminantEnum!(@collect_variants $v_ $g_ $w_, $d_, $n_, $bv_ [$($tuple_variant)* $var] $sv_, { $($tail)* });
};
// eat a tuple variant, that is the last variant (save the variant name for later)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, $bv_:tt [$($tuple_variant:ident)*] $sv_:tt, { $var:ident ( $($_contents:tt)* ) }) => {
DiscriminantEnum!(@output $v_ $g_ $w_, $d_, $n_, $bv_ [$($tuple_variant)* $var] $sv_);
};
// eat a struct variant, that isn't the last variant (save the variant name for later)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, $bv_:tt $ev_:tt [$($struct_variant:ident)*], { $var:ident { $($_contents:tt)* }, $($tail:tt)* }) => {
DiscriminantEnum!(@collect_variants $v_ $g_ $w_, $d_, $n_, $bv_ $ev_ [$($struct_variant)* $var], { $($tail)* });
};
// eat a struct variant, that is the last variant (save the variant name for later)
(@collect_variants $v_:tt $g_:tt $w_:tt, $d_:ident, $n_:ident, $bv_:tt $ev_:tt [$($struct_variant:ident)*], { $var:ident { $($_contents:tt)* } }) => {
DiscriminantEnum!(@output $v_ $g_ $w_, $d_, $n_, $bv_ $ev_ [$($struct_variant)* $var]);
};
// output!
(@output [$($visibility:ident)*] [$($generic:tt)*] [$($bound:tt)*], $discr:ident, $name:ident, [$($bare_variant:ident)*] [$($tuple_variant:ident)*] [$($struct_variant:ident)*]) => {
DiscriminantEnum! { @as_items // after the substitutions, need to re-parse as a series of items
// here's the new enum of just the variant names
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
$($visibility)* enum $discr {
$( $bare_variant, )*
$( $tuple_variant, )*
$( $struct_variant, )*
}
// add a method to the original enum that lowers it to the variant
impl $($generic)* $name $($generic)* $($bound)* {
$($visibility)* fn discriminant(&self) -> $discr {
match *self {
// this is the reason it's necessary to detect the three types of variants
// -- just to get this match syntax right
$( $name::$bare_variant => $discr::$bare_variant, )*
$( $name::$tuple_variant(..) => $discr::$tuple_variant, )*
$( $name::$struct_variant { .. } => $discr::$struct_variant, )*
}
}
}
}
}
}
// test case
custom_derive! {
#[derive(DiscriminantEnum(FooVariant), Debug)]
pub enum Foo<'a, T> where T: 'a { // note the where clause is required: the macro can't parse "enum Foo<'a, T: 'a>"
Bar1(u8),
Bar2(T),
Bar3,
Bar4 { a: u8, b: &'a T }
}
}
fn main() {
let foo = Foo::Bar1::<String>(42);
println!("{:?}\t\t\t{:?}", foo, foo.discriminant());
let foo = Foo::Bar2::<String>("hello".into());
println!("{:?}\t\t\t{:?}", foo, foo.discriminant());
let foo = Foo::Bar3::<String>;
println!("{:?}\t\t\t\t{:?}", foo, foo.discriminant());
let baz = Foo::Bar4::<String> { a: 42, b: &"hello".into() };
println!("{:?}\t{:?}", baz, baz.discriminant());
println!("{} {}", foo.discriminant() == baz.discriminant(), Foo::Bar1::<String>(42).discriminant() == Foo::Bar1::<String>(144).discriminant());
}
Bar1(42) Bar1
Bar2("hello") Bar2
Bar3 Bar3
Bar4 { a: 42, b: "hello" } Bar4
false true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment