From fa4e847e725780d2a3eb1af1ebf46eb91101622f Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 3 Oct 2022 12:00:01 +0200 Subject: [PATCH] Add derive macro for structs Signed-off-by: trivernis --- .gitignore | 2 + Cargo.toml | 20 +++++++++ derive/Cargo.toml | 14 +++++++ derive/src/lib.rs | 84 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 9 ++++ src/value.rs | 71 +++++++++++++++++++++++++++++++ src/value_trait.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++ tests/structs.rs | 68 ++++++++++++++++++++++++++++++ 8 files changed, 369 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 derive/Cargo.toml create mode 100644 derive/src/lib.rs create mode 100644 src/lib.rs create mode 100644 src/value.rs create mode 100644 src/value_trait.rs create mode 100644 tests/structs.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0c8c1ea --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +members = [".", "derive"] + +[package] +name = "rusty-value" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.rusty-value-derive] +path = "./derive" +optional = true + +[features] +default = [] +derive = ["rusty-value-derive"] + +[dev-dependencies.rusty-value-derive] +path = "./derive" diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..66a1b75 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rusty-value-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = "1.0.46" +quote = "1.0.21" +syn = "1.0.101" diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..92c172a --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,84 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DataStruct, DeriveInput, FieldsNamed, FieldsUnnamed}; + +#[proc_macro_derive(RustyValue)] +pub fn derive_value(input: TokenStream) -> TokenStream { + derive(parse_macro_input!(input as DeriveInput)) +} + +fn derive(input: DeriveInput) -> TokenStream { + match &input.data { + syn::Data::Struct(s) => derive_struct(&input, s), + syn::Data::Enum(_) => todo!(), + syn::Data::Union(_) => panic!("unions are currently unsupported"), + } +} + +fn derive_struct(input: &DeriveInput, struct_data: &DataStruct) -> TokenStream { + let ident = &input.ident; + let name = ident.to_string(); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + match &struct_data.fields { + syn::Fields::Named(FieldsNamed { named, .. }) => { + let field_idents = named.iter().map(|f| f.ident.as_ref()).collect::>(); + let field_names = named + .iter() + .map(|f| f.ident.as_ref().unwrap().to_string()) + .collect::>(); + let field_count = named.len(); + + TokenStream::from(quote! { + impl #impl_generics rusty_value::RustyValue for #ident #ty_generics #where_clause { + fn into_rusty_value(self) -> rusty_value::Value { + use rusty_value::*; + let mut values = std::collections::HashMap::with_capacity(#field_count); + + #( + values.insert(#field_names.to_string(), self.#field_idents.into_rusty_value()); + )* + + Value::Struct(Struct{ + name: #name.to_string(), + fields: StructFields::Named(values), + }) + } + } + }) + } + syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => { + let field_indices = unnamed + .iter() + .enumerate() + .map(|(i, _)| syn::Index::from(i)) + .collect::>(); + let field_count = unnamed.len(); + + TokenStream::from(quote! { + impl #impl_generics rusty_value::RustyValue for #ident #ty_generics #where_clause { + fn into_rusty_value(self) -> rusty_value::Value { + use rusty_value::*; + let mut values = Vec::with_capacity(#field_count); + + #( + values.push(self.#field_indices.into_rusty_value()); + )* + + Value::Struct(Struct{ + name: #name.to_string(), + fields: StructFields::Unnamed(values), + }) + } + } + }) + } + syn::Fields::Unit => TokenStream::from(quote! { + impl #impl_generics rusty_value::RustyValue for #ident #ty_generics #where_clause { + fn into_rusty_value(self) -> rusty_value::Value { + Value::Unit(#name.to_string()) + } + } + }), + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..710f362 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +pub(crate) mod value; +pub(crate) mod value_trait; +pub use value::*; +pub use value_trait::*; + +#[doc(inline)] +#[cfg(feature = "derive")] +#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] +pub use rusty_value_derive::*; diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..92c43fa --- /dev/null +++ b/src/value.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; + +#[derive(Clone, Debug, PartialEq)] +pub enum Value { + Primitive(Primitive), + Struct(Struct), + Enum(Enum), + Map(HashMap), + List(Vec), + Unit(String), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Enum { + pub name: String, + pub variant: String, + pub value: Box, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Struct { + pub name: String, + pub fields: StructFields, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum StructFields { + Named(HashMap), + Unnamed(Vec), +} + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub enum Primitive { + Integer(Integer), + Float(Float), + String(String), + Char(char), + Bool(bool), +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub enum Integer { + U8(u8), + I8(i8), + U16(u16), + I16(i16), + U32(u32), + I32(i32), + U64(u64), + I64(i64), + U128(u128), + I128(i128), +} + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub enum Float { + F32(f32), + F64(f64), +} + +pub enum HashableValue { + Primitive(HashablePrimitive), +} + +#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub enum HashablePrimitive { + Integer(Integer), + String(String), + Char(char), + Bool(bool), +} diff --git a/src/value_trait.rs b/src/value_trait.rs new file mode 100644 index 0000000..4643466 --- /dev/null +++ b/src/value_trait.rs @@ -0,0 +1,101 @@ +use std::collections::HashMap; + +use crate::{HashablePrimitive, Primitive, Value}; + +pub trait RustyValue { + fn into_rusty_value(self) -> Value; +} + +impl RustyValue for u8 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::U8(self))) + } +} + +impl RustyValue for i8 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::I8(self))) + } +} + +impl RustyValue for u16 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::U16(self))) + } +} + +impl RustyValue for i16 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::I16(self))) + } +} + +impl RustyValue for u32 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::U32(self))) + } +} + +impl RustyValue for i32 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::I32(self))) + } +} + +impl RustyValue for u64 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::U64(self))) + } +} + +impl RustyValue for i64 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::I64(self))) + } +} + +impl RustyValue for u128 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::U128(self))) + } +} + +impl RustyValue for i128 { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Integer(crate::Integer::I128(self))) + } +} + +impl RustyValue for String { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::String(self)) + } +} + +impl RustyValue for bool { + fn into_rusty_value(self) -> Value { + Value::Primitive(Primitive::Bool(self)) + } +} + +impl RustyValue for Vec { + fn into_rusty_value(self) -> Value { + let value_vec = self + .into_iter() + .map(|v| v.into_rusty_value()) + .collect::>(); + + Value::List(value_vec) + } +} + +impl RustyValue for HashMap { + fn into_rusty_value(self) -> Value { + let map = self + .into_iter() + .map(|(k, v)| (HashablePrimitive::String(k), v.into_rusty_value())) + .collect::>(); + + Value::Map(map) + } +} diff --git a/tests/structs.rs b/tests/structs.rs new file mode 100644 index 0000000..b0f6715 --- /dev/null +++ b/tests/structs.rs @@ -0,0 +1,68 @@ +use rusty_value::{RustyValue, StructFields, Value}; +use rusty_value_derive::*; + +#[derive(RustyValue)] +struct TestStructNamed { + foo: String, + bar: u64, +} + +#[test] +fn it_handles_named_fields() { + let test_struct = TestStructNamed { + foo: String::from("Hello World"), + bar: 12, + }; + let value = test_struct.into_rusty_value(); + dbg!(&value); + + if let Value::Struct(s) = value { + assert_eq!(&s.name, "TestStructNamed"); + + if let StructFields::Named(fields) = s.fields { + assert_eq!(fields.len(), 2); + } else { + panic!("Struct wasn't serialized as named struct") + } + } else { + panic!("Struct wasn't serialized as struct"); + } +} + +#[derive(RustyValue)] +struct TestStructUnnamed(String, u64); + +#[test] +fn it_handles_unnamed_fields() { + let test_struct = TestStructUnnamed(String::from("Hello World"), 12); + let value = test_struct.into_rusty_value(); + dbg!(&value); + + if let Value::Struct(s) = value { + assert_eq!(&s.name, "TestStructUnnamed"); + + if let StructFields::Unnamed(fields) = s.fields { + assert_eq!(fields.len(), 2); + } else { + panic!("Struct wasn't serialized as unnamed struct") + } + } else { + panic!("Struct wasn't serialized as struct"); + } +} + +#[derive(RustyValue)] +struct TestStructUnit; + +#[test] +fn it_handles_unit_structs() { + let test_struct = TestStructUnit; + let value = test_struct.into_rusty_value(); + dbg!(&value); + + if let Value::Unit(s) = value { + assert_eq!(&s, "TestStructUnit"); + } else { + panic!("Struct wasn't serialized as struct"); + } +}