Last active
September 11, 2015 23:50
-
-
Save durka/f47d969e875b37ba305d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! ```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()); | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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