diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a6eaf..1d06d54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Pre-Release +- Introduce new cgp-field constructs [#36](https://github.com/contextgeneric/cgp/pull/36) + - Introduce the product type constructs `Cons` and `Nil`. + - Introduce the sum type constructs `Either` and `Void`. + - Introduce the `Field` type for tagged field value. + - Introduce the `Product!` macro for building product types. + - Introduce the `product!` macro for building product expressions. + - Introduce the `Sum!` macro for building sum types. + - Change the `symbol!` macro to generate product type of `Char` using `Cons` and `Nil`. + - Rename `HasField::Field` to `HasField::Value` - [#35](https://github.com/contextgeneric/cgp/pull/35) - Remove `Sized` constraint from `Async` trait - [#34](https://github.com/contextgeneric/cgp/pull/34) diff --git a/crates/cgp-core/src/prelude.rs b/crates/cgp-core/src/prelude.rs index 68320c1..f526796 100644 --- a/crates/cgp-core/src/prelude.rs +++ b/crates/cgp-core/src/prelude.rs @@ -3,4 +3,6 @@ pub use cgp_component::{ define_components, delegate_components, derive_component, DelegateComponent, HasComponents, }; pub use cgp_error::{CanRaiseError, HasErrorType}; -pub use cgp_field::{symbol, Char, HasField, HasFieldMut}; +pub use cgp_field::{ + product, symbol, Char, Cons, Either, HasField, HasFieldMut, Nil, Product, Sum, Void, +}; diff --git a/crates/cgp-field-macro-lib/src/field.rs b/crates/cgp-field-macro-lib/src/field.rs index e9abe70..602d44a 100644 --- a/crates/cgp-field-macro-lib/src/field.rs +++ b/crates/cgp-field-macro-lib/src/field.rs @@ -24,12 +24,12 @@ pub fn derive_has_field_impls(item_struct: &ItemStruct) -> Vec { for #struct_ident #ty_generics #where_clause { - type Field = #field_type; + type Value = #field_type; fn get_field( &self, key: ::core::marker::PhantomData< #field_symbol >, - ) -> &Self::Field + ) -> &Self::Value { &self. #field_ident } @@ -44,7 +44,7 @@ pub fn derive_has_field_impls(item_struct: &ItemStruct) -> Vec { fn get_field_mut( &mut self, key: ::core::marker::PhantomData< #field_symbol >, - ) -> &mut Self::Field + ) -> &mut Self::Value { &mut self. #field_ident } diff --git a/crates/cgp-field-macro-lib/src/lib.rs b/crates/cgp-field-macro-lib/src/lib.rs index 8a4e1cf..2e3fa92 100644 --- a/crates/cgp-field-macro-lib/src/lib.rs +++ b/crates/cgp-field-macro-lib/src/lib.rs @@ -6,10 +6,12 @@ */ pub mod field; +pub mod product; pub mod symbol; #[cfg(test)] mod tests; pub use field::derive_fields; +pub use product::{make_product_expr, make_product_type, make_sum_type}; pub use symbol::make_symbol; diff --git a/crates/cgp-field-macro-lib/src/product.rs b/crates/cgp-field-macro-lib/src/product.rs new file mode 100644 index 0000000..801ccef --- /dev/null +++ b/crates/cgp-field-macro-lib/src/product.rs @@ -0,0 +1,45 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{Expr, Type}; + +pub struct ParsePunctuated(pub Punctuated); + +impl Parse for ParsePunctuated { + fn parse(input: ParseStream) -> syn::Result { + let types = Punctuated::parse_terminated(input)?; + Ok(ParsePunctuated(types)) + } +} + +pub fn make_product_type(input: TokenStream) -> TokenStream { + let types: ParsePunctuated = syn::parse2(input).unwrap(); + + types.0.iter().rfold(quote! { Nil }, |res, item| { + quote! { + Cons< #item , #res > + } + }) +} + +pub fn make_sum_type(input: TokenStream) -> TokenStream { + let types: ParsePunctuated = syn::parse2(input).unwrap(); + + types.0.iter().rfold(quote! { Void }, |res, item| { + quote! { + Either< #item , #res > + } + }) +} + +pub fn make_product_expr(input: TokenStream) -> TokenStream { + let types: ParsePunctuated = syn::parse2(input).unwrap(); + + types.0.iter().rfold(quote! { Nil }, |res, item| { + quote! { + Cons( #item , #res ) + } + }) +} diff --git a/crates/cgp-field-macro-lib/src/symbol.rs b/crates/cgp-field-macro-lib/src/symbol.rs index 37fbb21..4cd5f31 100644 --- a/crates/cgp-field-macro-lib/src/symbol.rs +++ b/crates/cgp-field-macro-lib/src/symbol.rs @@ -1,17 +1,13 @@ use proc_macro2::TokenStream; use quote::ToTokens; -use syn::punctuated::Punctuated; -use syn::token::Comma; use syn::{parse_quote, LitStr, Type}; pub fn symbol_from_string(value: &str) -> Type { - let char_types = >::from_iter( - value - .chars() - .map(|c: char| -> Type { parse_quote!( Char< #c > ) }), - ); - - parse_quote!( ( #char_types ) ) + value + .chars() + .rfold(parse_quote! { Nil }, |tail, c: char| -> Type { + parse_quote!( Cons< Char< #c >, #tail > ) + }) } pub fn make_symbol(input: TokenStream) -> TokenStream { diff --git a/crates/cgp-field-macro-lib/src/tests/field.rs b/crates/cgp-field-macro-lib/src/tests/field.rs index 913e778..4afd5f5 100644 --- a/crates/cgp-field-macro-lib/src/tests/field.rs +++ b/crates/cgp-field-macro-lib/src/tests/field.rs @@ -13,42 +13,42 @@ fn test_basic_derive_fields() { }); let expected = quote! { - impl HasField<(Char<'b'>, Char<'a'>, Char<'r'>)> for Foo { - type Field = Bar; + impl HasField, Cons, Cons , Nil>>>> for Foo { + type Value = Bar; fn get_field( &self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'r'>)>, - ) -> &Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &Self::Value { &self.bar } } - impl HasFieldMut<(Char<'b'>, Char<'a'>, Char<'r'>)> for Foo { + impl HasFieldMut, Cons, Cons , Nil>>>> for Foo { fn get_field_mut( &mut self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'r'>)>, - ) -> &mut Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &mut Self::Value { &mut self.bar } } - impl HasField<(Char<'b'>, Char<'a'>, Char<'z'>)> for Foo { - type Field = Baz; + impl HasField, Cons, Cons , Nil>>>> for Foo { + type Value = Baz; fn get_field( &self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'z'>)>, - ) -> &Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &Self::Value { &self.baz } } - impl HasFieldMut<(Char<'b'>, Char<'a'>, Char<'z'>)> for Foo { + impl HasFieldMut, Cons, Cons , Nil>>>> for Foo { fn get_field_mut( &mut self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'z'>)>, - ) -> &mut Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &mut Self::Value { &mut self.baz } } @@ -70,58 +70,58 @@ fn test_generic_derive_fields() { }); let expected = quote! { - impl HasField<(Char<'b'>, Char<'a'>, Char<'r'>)> + impl HasField, Cons, Cons , Nil>>>> for Foo where FooParamA: Eq, { - type Field = Bar; + type Value = Bar; fn get_field( &self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'r'>)>, - ) -> &Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &Self::Value { &self.bar } } - impl HasFieldMut<(Char<'b'>, Char<'a'>, Char<'r'>)> + impl HasFieldMut, Cons, Cons , Nil>>>> for Foo where FooParamA: Eq, { fn get_field_mut( &mut self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'r'>)>, - ) -> &mut Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &mut Self::Value { &mut self.bar } } - impl HasField<(Char<'b'>, Char<'a'>, Char<'z'>)> + impl HasField, Cons, Cons , Nil>>>> for Foo where FooParamA: Eq, { - type Field = Baz; + type Value = Baz; fn get_field( &self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'z'>)>, - ) -> &Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &Self::Value { &self.baz } } - impl HasFieldMut<(Char<'b'>, Char<'a'>, Char<'z'>)> + impl HasFieldMut, Cons, Cons , Nil>>>> for Foo where FooParamA: Eq, { fn get_field_mut( &mut self, - key: ::core::marker::PhantomData<(Char<'b'>, Char<'a'>, Char<'z'>)>, - ) -> &mut Self::Field { + key: ::core::marker::PhantomData, Cons, Cons , Nil>>>>, + ) -> &mut Self::Value { &mut self.baz } } diff --git a/crates/cgp-field-macro-lib/src/tests/mod.rs b/crates/cgp-field-macro-lib/src/tests/mod.rs index e7c3ff4..2f331d1 100644 --- a/crates/cgp-field-macro-lib/src/tests/mod.rs +++ b/crates/cgp-field-macro-lib/src/tests/mod.rs @@ -1,3 +1,4 @@ pub mod field; pub mod helper; +pub mod product; pub mod symbol; diff --git a/crates/cgp-field-macro-lib/src/tests/product.rs b/crates/cgp-field-macro-lib/src/tests/product.rs new file mode 100644 index 0000000..9271f21 --- /dev/null +++ b/crates/cgp-field-macro-lib/src/tests/product.rs @@ -0,0 +1,87 @@ +use quote::quote; + +use crate::product::{make_product_expr, make_product_type, make_sum_type}; + +#[test] +fn test_product_type() { + let derived = make_product_type(quote! { + Foo, + Bar, + Baz, + }); + + let expected = quote! { + Cons< + Foo, + Cons< + Bar, + Cons< + Baz, + Nil> > > + }; + + assert_eq!(derived.to_string(), expected.to_string()); +} + +#[test] +fn test_product_ident() { + let derived = make_product_expr(quote! { + foo, + bar, + baz, + }); + + let expected = quote! { + Cons( + foo, + Cons( + bar, + Cons( + baz, + Nil ) ) ) + }; + + assert_eq!(derived.to_string(), expected.to_string()); +} + +#[test] +fn test_product_expr() { + let derived = make_product_expr(quote! { + foo.0, + Bar { bar }, + Baz::baz(), + }); + + let expected = quote! { + Cons( + foo.0, + Cons( + Bar { bar }, + Cons( + Baz::baz(), + Nil ) ) ) + }; + + assert_eq!(derived.to_string(), expected.to_string()); +} + +#[test] +fn test_sum_type() { + let derived = make_sum_type(quote! { + Foo, + Bar, + Baz, + }); + + let expected = quote! { + Either< + Foo, + Either< + Bar, + Either< + Baz, + Void> > > + }; + + assert_eq!(derived.to_string(), expected.to_string()); +} diff --git a/crates/cgp-field-macro-lib/src/tests/symbol.rs b/crates/cgp-field-macro-lib/src/tests/symbol.rs index 4198073..7ba87ec 100644 --- a/crates/cgp-field-macro-lib/src/tests/symbol.rs +++ b/crates/cgp-field-macro-lib/src/tests/symbol.rs @@ -5,26 +5,25 @@ use crate::tests::helper::equal::equal_token_stream; #[test] fn test_symbol_macro() { - let symbol = make_symbol(quote!("hello_world")); + let symbol = make_symbol(quote!("hello")); let derived = quote! { type Symbol = #symbol; }; let expected = quote! { - type Symbol = ( + type Symbol = Cons< Char<'h'>, - Char<'e'>, - Char<'l'>, - Char<'l'>, - Char<'o'>, - Char<'_'>, - Char<'w'>, - Char<'o'>, - Char<'r'>, - Char<'l'>, - Char<'d'>, - ); + Cons< + Char<'e'>, + Cons< + Char<'l'>, + Cons< + Char<'l'>, + Cons< + Char<'o'>, + Nil + >>>>>; }; assert!(equal_token_stream(&derived, &expected)); diff --git a/crates/cgp-field-macro/src/lib.rs b/crates/cgp-field-macro/src/lib.rs index d8446c4..5eb6858 100644 --- a/crates/cgp-field-macro/src/lib.rs +++ b/crates/cgp-field-macro/src/lib.rs @@ -11,3 +11,20 @@ pub fn derive_fields(item: TokenStream) -> TokenStream { pub fn symbol(body: TokenStream) -> TokenStream { cgp_field_macro_lib::make_symbol(body.into()).into() } + +#[proc_macro] +#[allow(non_snake_case)] +pub fn Product(body: TokenStream) -> TokenStream { + cgp_field_macro_lib::make_product_type(body.into()).into() +} + +#[proc_macro] +#[allow(non_snake_case)] +pub fn Sum(body: TokenStream) -> TokenStream { + cgp_field_macro_lib::make_sum_type(body.into()).into() +} + +#[proc_macro] +pub fn product(body: TokenStream) -> TokenStream { + cgp_field_macro_lib::make_product_expr(body.into()).into() +} diff --git a/crates/cgp-field/src/lib.rs b/crates/cgp-field/src/lib.rs index 7adc41a..1ebbee0 100644 --- a/crates/cgp-field/src/lib.rs +++ b/crates/cgp-field/src/lib.rs @@ -4,6 +4,6 @@ pub mod impls; pub mod traits; pub mod types; -pub use cgp_field_macro::{symbol, HasField}; +pub use cgp_field_macro::{product, symbol, HasField, Product, Sum}; pub use traits::{FieldGetter, HasField, HasFieldMut, MutFieldGetter}; -pub use types::Char; +pub use types::{Char, Cons, Either, Field, Index, Nil, Void}; diff --git a/crates/cgp-field/src/types.rs b/crates/cgp-field/src/types/char.rs similarity index 100% rename from crates/cgp-field/src/types.rs rename to crates/cgp-field/src/types/char.rs diff --git a/crates/cgp-field/src/types/field.rs b/crates/cgp-field/src/types/field.rs new file mode 100644 index 0000000..63052a9 --- /dev/null +++ b/crates/cgp-field/src/types/field.rs @@ -0,0 +1,15 @@ +use core::marker::PhantomData; + +pub struct Field { + pub value: Value, + pub phantom: PhantomData, +} + +impl From for Field { + fn from(value: Value) -> Self { + Self { + value, + phantom: PhantomData, + } + } +} diff --git a/crates/cgp-field/src/types/index.rs b/crates/cgp-field/src/types/index.rs new file mode 100644 index 0000000..a2938e7 --- /dev/null +++ b/crates/cgp-field/src/types/index.rs @@ -0,0 +1 @@ +pub struct Index; diff --git a/crates/cgp-field/src/types/mod.rs b/crates/cgp-field/src/types/mod.rs new file mode 100644 index 0000000..61d3f99 --- /dev/null +++ b/crates/cgp-field/src/types/mod.rs @@ -0,0 +1,11 @@ +pub mod char; +pub mod field; +pub mod index; +pub mod product; +pub mod sum; + +pub use char::*; +pub use field::*; +pub use index::*; +pub use product::*; +pub use sum::*; diff --git a/crates/cgp-field/src/types/product.rs b/crates/cgp-field/src/types/product.rs new file mode 100644 index 0000000..ba2cbef --- /dev/null +++ b/crates/cgp-field/src/types/product.rs @@ -0,0 +1,3 @@ +pub struct Cons(pub Head, pub Tail); + +pub struct Nil; diff --git a/crates/cgp-field/src/types/sum.rs b/crates/cgp-field/src/types/sum.rs new file mode 100644 index 0000000..89c067f --- /dev/null +++ b/crates/cgp-field/src/types/sum.rs @@ -0,0 +1,6 @@ +pub enum Either { + Left(Head), + Right(Tail), +} + +pub enum Void {}