/* Copyright ⓒ 2015 macro-attr contributors. Licensed under the MIT license (see LICENSE or ) or the Apache License, Version 2.0 (see LICENSE of ), at your option. All files in the project carrying such notice may not be copied, modified, or distributed except according to those terms. */ #[macro_use] extern crate macro_attr; extern crate rustc_serialize; macro_rules! StableEncodable { ( () $(pub)* enum $name:ident < $($tail:tt)* ) => { StableEncodable! { @extract_gen_args (enum $name), ($($tail)*) -> bounds(), ty_clss(where) } }; ( () $(pub)* enum $name:ident { $($body:tt)* } ) => { StableEncodable! { @impl enum $name, bounds(), ty_clss(), { $($body)* } } }; ( () $(pub)* struct $name:ident { $($body:tt)* } ) => { StableEncodable! { @impl struct $name, bounds(), ty_clss(), { $($body)* } } }; ( () $(pub)* struct $name:ident < $($tail:tt)* ) => { StableEncodable! { @extract_gen_args (struct $name), ($($tail)*) -> bounds(), ty_clss(where) } }; ( @impl enum $name:ident, bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*), { $($body:tt)* } ) => { StableEncodable! { @parse_variants (enum $name, bounds($($bounds)*), ty_clss($($ty_clss)*)), 0usize, ($($body)*,) -> () } }; ( @impl struct $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( &self, s: &mut StableEncodableEncoder ) -> Result<(), StableEncodableEncoder::Error> where StableEncodableEncoder: 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 ($kind:ident $name:ident), (> { $($tail:tt)* }) -> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*) ) => { StableEncodable! { @impl $kind $name, bounds($($bounds)*), ty_clss($($ty_clss)*), { $($tail)* } } }; ( @extract_gen_args $fixed:tt, ($ty_name:ident: $($tail)*) -> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*) ) => { StableEncodable! { @skip_inline_bound $fixed, ($($tail)*) -> bounds($($bounds)* $ty_name:), ty_clss($($ty_clss)* $ty_name: ::rustc_serialize::Encodable,) } }; ( @extract_gen_args $fixed:tt, ($ty_name:ident $($tail:tt)*) -> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*) ) => { StableEncodable! { @extract_gen_args $fixed, ($($tail)*) -> bounds($($bounds)* $ty_name), ty_clss($($ty_clss)* $ty_name: ::rustc_serialize::Encodable,) } }; ( @extract_gen_args $fixed:tt, (, $($tail:tt)*) -> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*) ) => { StableEncodable! { @extract_gen_args $fixed, ($($tail)*) -> bounds($($bounds)* ,), ty_clss($($ty_clss)*) } }; ( @extract_gen_args $fixed:tt, ($lt:tt $($tail:tt)*) -> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*) ) => { StableEncodable! { @extract_gen_args $fixed, ($($tail)*) -> bounds($($bounds)* $lt), ty_clss($($ty_clss)*) } }; ( @skip_inline_bound $fixed:tt, (, $($tail:tt)*) -> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*) ) => { StableEncodable! { @extract_gen_args $fixed, ($($tail)*) -> bounds($($bounds)* ,), ty_clss($($ty_clss)*) } }; ( @skip_inline_bound $fixed:tt, (> { $($tail:tt)* }) -> bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*) ) => { StableEncodable! { @impl $fixed, bounds($($bounds)*), ty_clss($($ty_clss)*), { $($tail)* } } }; ( @parse_variants (enum $name:ident, bounds($($bounds:tt)*), ty_clss($($ty_clss:tt)*)), $_id:expr, ($(,)*) -> ($($variants:tt)*) ) => { StableEncodable! { @as_item impl<$($bounds)*> rustc_serialize::Encodable for $name<$($bounds)*> $($ty_clss)* { fn encode( &self, s: &mut StableEncodableEncoder) -> Result<(), StableEncodableEncoder::Error> where StableEncodableEncoder: rustc_serialize::Encoder { s.emit_enum(stringify!($name), |s| { $( StableEncodable!(@encode_variant $name, $variants, self, s); )* unreachable!(); }) } } } }; ( @parse_variants $fixed:tt, $id:expr, ($var_name:ident, $($tail:tt)*) -> ($($variants:tt)*) ) => { StableEncodable! { @parse_variants $fixed, ($id + 1usize), ($($tail)*) -> ($($variants)* ($var_name, $id)) } }; ( @parse_variants $fixed:tt, $id:expr, ($var_name:ident($(,)*), $($tail:tt)*) -> ($($variants:tt)*) ) => { StableEncodable! { @parse_variants $fixed, ($id + 1usize), ($($tail)*) -> ($($variants)* ($var_name, $id)) } }; ( @parse_variants $fixed:tt, $id:expr, ($var_name:ident($_vta:ty), $($tail:tt)*) -> ($($variants:tt)*) ) => { StableEncodable! { @parse_variants $fixed, ($id + 1usize), ($($tail)*) -> ($($variants)* ($var_name, $id, (a))) } }; ( @parse_variants $fixed:tt, $id:expr, ($var_name:ident($_vta:ty, $_vtb:ty), $($tail:tt)*) -> ($($variants:tt)*) ) => { StableEncodable! { @parse_variants $fixed, ($id + 1usize), ($($tail)*) -> ($($variants)* ($var_name, $id, (a, b))) } }; ( @parse_variants $fixed:tt, $id:expr, ($var_name:ident($_vta:ty, $_vtb:ty, $_vtc:ty), $($tail:tt)*) -> ($($variants:tt)*) ) => { StableEncodable! { @parse_variants $fixed, ($id + 1usize), ($($tail)*) -> ($($variants)* ($var_name, $id, (a, b, c))) } }; ( @parse_variants $fixed:tt, $id:expr, ($var_name:ident { $($vfn:ident: $_vft:ty),* $(,)* }, $($tail:tt)*) -> ($($variants:tt)*) ) => { StableEncodable! { @parse_variants $fixed, ($id + 1usize), ($($tail)*) -> ($($variants)* ($var_name, $id, {$($vfn),*})) } }; ( @encode_variant $name:ident, ($var_name:ident, $var_id:expr), $self_:expr, $s:ident ) => { { if let $name::$var_name = *$self_ { return $s.emit_enum_variant(stringify!($var_name), $var_id, 0, |_| Ok(())); } } }; ( @encode_variant $name:ident, ($var_name:ident, $var_id:expr, ($($tup_elems:ident),*)), $self_:expr, $s:ident ) => { { if let $name::$var_name($(ref $tup_elems),*) = *$self_ { return $s.emit_enum_variant( stringify!($var_name), $var_id, StableEncodable!(@count_tts $($tup_elems)*), |s| { let mut idx = 0; $( try!(s.emit_enum_variant_arg(idx, |s| $tup_elems.encode(s))); idx += 1; )* let _ = idx; Ok(()) } ); } } }; ( @encode_variant $name:ident, ($var_name:ident, $var_id:expr, {$($str_fields:ident),*}), $self_:expr, $s:ident ) => { { if let $name::$var_name { $(ref $str_fields),* } = *$self_ { return $s.emit_enum_struct_variant( stringify!($var_name), $var_id, StableEncodable!(@count_tts $($str_fields)*), |s| { let mut idx = 0; $( try!(s.emit_enum_struct_variant_field( stringify!($str_fields), idx, |s| $str_fields.encode(s) )); idx += 1; )* let _ = idx; Ok(()) } ); } } }; (@count_tts) => {0usize}; (@count_tts $_tt:tt $($tail:tt)*) => {1usize + StableEncodable!(@count_tts $($tail)*)}; } macro_attr! { #[derive(Debug, StableEncodable!)] struct LazyEg { a: A, b: i32, c: (u8, u8, u8) } } macro_attr! { #[derive(Clone, StableEncodable!)] enum Wonky { Flim, Flam, Flom(i32), Bees { say: S } } } #[test] fn test_stable_encodable() { macro_rules! json { ($e:expr) => (rustc_serialize::json::encode(&$e).unwrap()); } let lazy_eg = LazyEg { a: String::from("Oh hai!"), b: 42, c: (1, 3, 0), }; assert_eq!(&*json!(lazy_eg), r#"{"a":"Oh hai!","b":42,"c":[1,3,0]}"#); assert_eq!(&*json!(Wonky::Flim::<()>), r#""Flim""#); assert_eq!(&*json!(Wonky::Flam::<()>), r#""Flam""#); assert_eq!(&*json!(Wonky::Flom::<()>(42)), r#"{"variant":"Flom","fields":[42]}"#); assert_eq!(&*json!(Wonky::Bees{say:"aaaaah!"}), r#"{"variant":"Bees","fields":["aaaaah!"]}"#); }