Skip to content

Instantly share code, notes, and snippets.

@DanielKeep
Last active January 22, 2017 06:22
Show Gist options
  • Save DanielKeep/4bfb8a947f8e3f196bdd to your computer and use it in GitHub Desktop.
Save DanielKeep/4bfb8a947f8e3f196bdd to your computer and use it in GitHub Desktop.
custom_derive!
/*!
Provides a very bare-bones way of having custom `derive` support in stable Rust.
*/
// This example requires nightly for rustc_serialize because I don't want to involve Cargo.
#![feature(rustc_private)]
extern crate serialize as rustc_serialize;
// #![feature(trace_macros)]
macro_rules! as_item {
($($i:item)*) => {$($i)*};
}
macro_rules! custom_derive {
(
$(#[$($attrs:tt)*])*
enum $($it:tt)*
) => {
custom_derive! {
@split_attrs
($(#[$($attrs)*],)*), (), (),
(enum $($it)*)
}
};
(
$(#[$($attrs:tt)*])*
pub $($it:tt)*
) => {
custom_derive! {
@split_attrs
($(#[$($attrs)*],)*), (), (),
(pub $($it)*)
}
};
(
$(#[$($attrs:tt)*])*
struct $($it:tt)*
) => {
custom_derive! {
@split_attrs
($(#[$($attrs)*],)*), (), (),
(struct $($it)*)
}
};
(
@split_attrs
(),
$non_derives:tt,
$derives:tt,
$it:tt
) => {
custom_derive! {
@split_derive_attrs
{ $non_derives, $it },
$derives,
(),
()
}
};
(
@split_attrs
(#[derive($($new_drv:ident),*)], $(#[$($attrs:tt)*],)*),
$non_derives:tt,
($($derives:meta,)*),
$it:tt
) => {
custom_derive! {
@split_attrs
($(#[$($attrs)*],)*),
$non_derives,
($($derives,)* $(#[$new_drv],)*),
$it
}
};
(
@split_attrs
(#[$new_attr:meta], $(#[$($attrs:tt)*],)*),
($($non_derives:tt)*),
$derives:tt,
$it:tt
) => {
custom_derive! {
@split_attrs
($(#[$($attrs)*],)*),
($($non_derives)* #[$new_attr],),
$derives,
$it
}
};
/*
Built-in derives:
Clone, Hash, RustcEncodable, RustcDecodable, PartialEq, Eq, PartialOrd, Ord,
Debug, Default, Send, Sync, Copy,
*/
(@split_derive_attrs
{ ($(#[$($non_derives:tt)*],)*), ($($it:tt)*) },
(), ($($bi_drvs:ident,)*), ($($user_drvs:ident,)*)
) => {
as_item! {
#[derive($($bi_drvs,)*)]
$(#[$($non_derives)*])*
$($it)*
}
custom_derive! {
@expand_user_drvs
($($user_drvs,)*), ($($it)*)
}
};
(@split_derive_attrs
$fixed:tt,
(#[Clone], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Clone,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[RustcEncodable], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* RustcEncodable,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[RustcDecodable], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* RustcDecodable,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[PartialEq], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* PartialEq,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[Eq], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Eq,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[PartialOrd], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* PartialOrd,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[Ord], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Ord,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[Debug], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Debug,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[Default], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Default,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[Send], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Send,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[Sync], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Sync,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[Copy], $($tail:tt)*), ($($bi_drvs:meta,)*), $user_drvs:tt
) => {
custom_derive! {
@split_derive_attrs
$fixed,
($($tail)*), ($($bi_drvs,)* Copy,), $user_drvs
}
};
(@split_derive_attrs
$fixed:tt,
(#[$($new_user:tt)*], $($tail:tt)*), $bi_drvs:tt, ($($user_drvs:meta,)*)
) => {
custom_derive! {
@split_derive_attrs
$fixed, ($($tail)*), $bi_drvs, ($($user_drvs,)* $($new_user)*,)
}
};
(@expand_user_drvs
(), ($($it:tt)*)
) => {};
(@expand_user_drvs
($user_drv:ident, $($tail:ident,)*), ($($it:tt)*)
) => {
$user_drv! { $($it)* }
custom_derive! {
@expand_user_drvs
($($tail)*), ($($it)*)
}
};
}
// trace_macros!(true);
macro_rules! EnumIterator {
($(pub)* enum $name:ident { $($body:tt)* }) => {
EnumIterator! {
@collect_variants ($name),
($($body)*,) -> ()
}
};
(
@collect_variants ($name:ident),
($(,)*) -> ($($var_names:ident,)*)
) => {
type NameIter = ::std::vec::IntoIter<&'static str>;
type VariantIter = ::std::vec::IntoIter<$name>;
impl $name {
#[allow(dead_code)]
pub fn iter_variants() -> VariantIter {
vec![$($name::$var_names),*].into_iter()
}
#[allow(dead_code)]
pub fn iter_variant_names() -> NameIter {
vec![$(stringify!($var_names)),*].into_iter()
}
}
};
(
@collect_variants $fixed:tt,
($var:ident $(= $_val:expr)*, $($tail:tt)*) -> ($($var_names:tt)*)
) => {
EnumIterator! {
@collect_variants $fixed,
($($tail)*) -> ($($var_names)* $var,)
}
};
(
@collect_variants ($name:ident),
($var:ident $_struct:tt, $($tail:tt)*) -> ($($var_names:tt)*)
) => {
const _error: () = concat!(
"cannot derive EnumIterator for ",
stringify!($name),
", due to non-unitary variant ",
stringify!($var),
"."
);
};
}
macro_rules! StableEncodable {
(
$(pub)* struct $name:ident { $($body:tt)* }
) => {
StableEncodable! {
@impl $name,
bounds(),
ty_clss(),
{ $($body)* }
}
};
(
$(pub)* struct $name:ident < $($tail:tt)*
) => {
StableEncodable! {
@extract_gen_args $name,
($($tail)*)
-> bounds(), ty_clss(where)
}
};
(
@impl $name:ident,
bounds($($bounds:tt)*),
ty_clss($($ty_clss:tt)*),
{ $($fnames:ident: $_ftys:ty),* $(,)* }
) => {
StableEncodable! {
@as_item
impl<$($bounds)*> rustc_serialize::Encodable for $name<$($bounds)*>
$($ty_clss)* {
fn encode<S>(&self, s: &mut S) -> Result<(), S::Error>
where S: rustc_serialize::Encoder {
const NUM_FIELDS: usize = StableEncodable!(@count_tts $($fnames)*);
try!(s.emit_struct(stringify!($name), NUM_FIELDS, |s| {
// Poor man's enumerate!($($fnames)):
let mut idx = 0;
$(
try!(s.emit_struct_field(stringify!($fnames), idx, |s| {
self.$fnames.encode(s)
}));
idx += 1;
)*
let _ = idx;
Ok(())
}));
Ok(())
}
}
}
};
(@as_item $i:item) => {$i};
(
@extract_gen_args $name:ident,
(> { $($tail:tt)* })
-> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)
) => {
StableEncodable! {
@impl $name,
bounds($($bounds)*),
ty_clss($($ty_clss)*),
{ $($tail)* }
}
};
(
@extract_gen_args $name:ident,
($ty_name:ident: $($tail)*)
-> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)
) => {
StableEncodable! {
@skip_inline_bound $name,
($($tail)*)
-> bounds($($bounds)* $ty_name:),
ty_clss($($ty_clss)* $ty_name: ::rustc_serialize::Encodable,)
}
};
(
@extract_gen_args $name:ident,
($ty_name:ident $($tail:tt)*)
-> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)
) => {
StableEncodable! {
@extract_gen_args $name,
($($tail)*)
-> bounds($($bounds)* $ty_name),
ty_clss($($ty_clss)* $ty_name: ::rustc_serialize::Encodable,)
}
};
(
@extract_gen_args $name:ident,
(, $($tail:tt)*)
-> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)
) => {
StableEncodable! {
@extract_gen_args $name,
($($tail)*)
-> bounds($($bounds)* ,), ty_clss($($ty_clss)*)
}
};
(
@extract_gen_args $name:ident,
($lt:tt $($tail:tt)*)
-> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)
) => {
StableEncodable! {
@extract_gen_args $name,
($($tail)*)
-> bounds($($bounds)* $lt), ty_clss($($ty_clss)*)
}
};
(
@skip_inline_bound $name:ident,
(, $($tail:tt)*)
-> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)
) => {
StableEncodable! {
@extract_gen_args $name,
($($tail)*)
-> bounds($($bounds)* ,), ty_clss($($ty_clss)*)
}
};
(
@skip_inline_bound $name:ident,
(> { $($tail:tt)* })
-> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)
) => {
StableEncodable! {
@impl $name,
bounds($($bounds)*),
ty_clss($($ty_clss)*),
{ $($tail)* }
}
};
(@count_tts) => {0usize};
(@count_tts $_tt:tt $($tail:tt)*) => {1usize + StableEncodable!(@count_tts $($tail)*)};
}
custom_derive! {
#[derive(Debug, EnumIterator)]
enum Get { Up, Down, AllAround }
}
custom_derive! {
#[derive(Debug, StableEncodable)]
struct LazyEg<A> { a: A, b: i32, c: (u8, u8, u8) }
}
fn main() {
for (name, var) in Get::iter_variant_names().zip(Get::iter_variants()) {
println!("{:?}: {:?}", name, var)
}
let lazy_eg = LazyEg {
a: String::from("Muahahaha and you probably thought it couldn't be done!"),
b: 42,
c: (1, 3, 0),
};
println!("lazy_eg: {}", rustc_serialize::json::encode(&lazy_eg).unwrap());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment