From 0747fece03a95fd4c2d882a31c0cf293ea6dcab8 Mon Sep 17 00:00:00 2001 From: debris Date: Sat, 22 Jul 2017 02:17:23 +0200 Subject: [PATCH] serialization derive --- Cargo.lock | 9 ++++ Cargo.toml | 2 +- serialization_derive/Cargo.toml | 13 +++++ serialization_derive/src/de.rs | 64 ++++++++++++++++++++++++ serialization_derive/src/lib.rs | 28 +++++++++++ serialization_derive/src/ser.rs | 59 ++++++++++++++++++++++ serialization_derive/tests/raw.rs | 83 +++++++++++++++++++++++++++++++ 7 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 serialization_derive/Cargo.toml create mode 100644 serialization_derive/src/de.rs create mode 100644 serialization_derive/src/lib.rs create mode 100644 serialization_derive/src/ser.rs create mode 100644 serialization_derive/tests/raw.rs diff --git a/Cargo.lock b/Cargo.lock index f8c200e1..15877dd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,15 @@ dependencies = [ "primitives 0.1.0", ] +[[package]] +name = "serialization_derive" +version = "0.1.0" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serialization 0.1.0", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "shell32-sys" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index f417433b..a3ff8f08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,4 +45,4 @@ path = "pbtc/main.rs" name = "pbtc" [workspace] -members = ["bencher"] +members = ["bencher", "serialization_derive"] diff --git a/serialization_derive/Cargo.toml b/serialization_derive/Cargo.toml new file mode 100644 index 00000000..af2f8e15 --- /dev/null +++ b/serialization_derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "serialization_derive" +version = "0.1.0" +authors = ["debris "] + +[lib] +name = "serialization_derive" +proc-macro = true + +[dependencies] +syn = "0.11.11" +quote = "0.3.15" +serialization = { path = "../serialization" } diff --git a/serialization_derive/src/de.rs b/serialization_derive/src/de.rs new file mode 100644 index 00000000..635b6185 --- /dev/null +++ b/serialization_derive/src/de.rs @@ -0,0 +1,64 @@ +use {syn, quote}; + +pub fn impl_raw_deserialize(ast: &syn::DeriveInput) -> quote::Tokens { + let body = match ast.body { + syn::Body::Struct(ref s) => s, + _ => panic!("#[derive(RawDeserialize)] is only defined for structs."), + }; + + let stmts: Vec<_> = match *body { + syn::VariantData::Struct(ref fields) => fields.iter().enumerate().map(deserialize_field_map).collect(), + syn::VariantData::Tuple(ref fields) => fields.iter().enumerate().map(deserialize_field_map).collect(), + syn::VariantData::Unit => panic!("#[derive(RawDeserialize)] is not defined for Unit structs."), + }; + + let name = &ast.ident; + + let dummy_const = syn::Ident::new(format!("_IMPL_RAW_DESERIALIZE_FOR_{}", name)); + let impl_block = quote! { + impl serialization::Deserializable for #name { + fn deserialize(reader: &mut serialization::Reader) -> Result where T: io::Read { + let result = #name { + #(#stmts)* + }; + + Ok(result) + } + } + }; + + quote! { + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const #dummy_const: () = { + extern crate serialization; + use std::io; + #impl_block + }; + } +} + +fn deserialize_field_map(tuple: (usize, &syn::Field)) -> quote::Tokens { + deserialize_field(tuple.0, tuple.1) +} + +fn deserialize_field(index: usize, field: &syn::Field) -> quote::Tokens { + let ident = match field.ident { + Some(ref ident) => ident.to_string(), + None => index.to_string(), + }; + + let id = syn::Ident::new(ident.to_string()); + + match field.ty { + syn::Ty::Array(_, _) => quote! { #id: reader.read_list()?, }, + syn::Ty::Slice(_) => quote! { #id: reader.read_list()?, }, + syn::Ty::Path(_, ref path) => { + let ident = &path.segments.first().expect("there must be at least 1 segment").ident; + match &ident.to_string() as &str { + "Vec" => quote! { #id: reader.read_list()?, }, + _ => quote! { #id: reader.read()?, }, + } + }, + _ => quote! { #id: reader.read()?, }, + } +} diff --git a/serialization_derive/src/lib.rs b/serialization_derive/src/lib.rs new file mode 100644 index 00000000..3b1a42e0 --- /dev/null +++ b/serialization_derive/src/lib.rs @@ -0,0 +1,28 @@ +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +mod ser; +mod de; + +use proc_macro::TokenStream; +use ser::impl_raw_serialize; +use de::impl_raw_deserialize; + +#[proc_macro_derive(RawSerialize)] +pub fn raw_serialize(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + let gen = impl_raw_serialize(&ast); + gen.parse().unwrap() +} + +#[proc_macro_derive(RawDeserialize)] +pub fn raw_deserialize(input: TokenStream) -> TokenStream { + let s = input.to_string(); + let ast = syn::parse_derive_input(&s).unwrap(); + let gen = impl_raw_deserialize(&ast); + gen.parse().unwrap() +} + diff --git a/serialization_derive/src/ser.rs b/serialization_derive/src/ser.rs new file mode 100644 index 00000000..ad79415a --- /dev/null +++ b/serialization_derive/src/ser.rs @@ -0,0 +1,59 @@ +use {syn, quote}; + +pub fn impl_raw_serialize(ast: &syn::DeriveInput) -> quote::Tokens { + let body = match ast.body { + syn::Body::Struct(ref s) => s, + _ => panic!("#[derive(RawSerialize)] is only defined for structs."), + }; + + let stmts: Vec<_> = match *body { + syn::VariantData::Struct(ref fields) => fields.iter().enumerate().map(serialize_field_map).collect(), + syn::VariantData::Tuple(ref fields) => fields.iter().enumerate().map(serialize_field_map).collect(), + syn::VariantData::Unit => panic!("#[derive(RawSerialize)] is not defined for Unit structs."), + }; + + let name = &ast.ident; + + let dummy_const = syn::Ident::new(format!("_IMPL_RAW_SERIALIZE_FOR_{}", name)); + let impl_block = quote! { + impl serialization::Serializable for #name { + fn serialize(&self, stream: &mut serialization::Stream) { + #(#stmts)* + } + } + }; + + quote! { + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const #dummy_const: () = { + extern crate serialization; + #impl_block + }; + } +} + +fn serialize_field_map(tuple: (usize, &syn::Field)) -> quote::Tokens { + serialize_field(tuple.0, tuple.1) +} + +fn serialize_field(index: usize, field: &syn::Field) -> quote::Tokens { + let ident = match field.ident { + Some(ref ident) => ident.to_string(), + None => index.to_string(), + }; + + let id = syn::Ident::new(format!("self.{}", ident)); + + match field.ty { + syn::Ty::Array(_, _) => quote! { stream.append_list(&#id); }, + syn::Ty::Slice(_) => quote! { stream.append_list(#id); }, + syn::Ty::Path(_, ref path) => { + let ident = &path.segments.first().expect("there must be at least 1 segment").ident; + match &ident.to_string() as &str { + "Vec" => quote! { stream.append_list(&#id); }, + _ => quote! { stream.append(&#id); }, + } + }, + _ => quote! { stream.append(&#id); }, + } +} diff --git a/serialization_derive/tests/raw.rs b/serialization_derive/tests/raw.rs new file mode 100644 index 00000000..2a7fc92c --- /dev/null +++ b/serialization_derive/tests/raw.rs @@ -0,0 +1,83 @@ +extern crate serialization; +#[macro_use] +extern crate serialization_derive; + +use serialization::{serialize, deserialize}; + +#[derive(Debug, PartialEq, RawSerialize, RawDeserialize)] +struct Foo { + a: u8, + b: u16, + c: u32, + d: u64, +} + +#[derive(Debug, PartialEq, RawSerialize, RawDeserialize)] +struct Bar { + a: Vec, +} + +#[test] +fn test_foo_serialize() { + let foo = Foo { + a: 1, + b: 2, + c: 3, + d: 4, + }; + + let expected = vec![ + 1u8, + 2, 0, + 3, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, + ].into(); + + let result = serialize(&foo); + assert_eq!(result, expected); + + let d = deserialize(expected.as_ref()).unwrap(); + assert_eq!(foo, d); +} + +#[test] +fn test_bar_serialize() { + let foo = Foo { + a: 1, + b: 2, + c: 3, + d: 4, + }; + + let foo2 = Foo { + a: 5, + b: 6, + c: 7, + d: 8, + }; + + let expected = vec![ + // number of items + 2u8, + // first + 1, + 2, 0, + 3, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, + // second + 5, + 6, 0, + 7, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, 0, + ].into(); + + let bar = Bar { + a: vec![foo, foo2], + }; + + let result = serialize(&bar); + assert_eq!(result, expected); + + let d = deserialize(expected.as_ref()).unwrap(); + assert_eq!(bar, d); +}