added enum builder
| -rw-r--r-- | src/any.rs | 44 | ||||
| -rw-r--r-- | src/build/builders/core.rs | 1 | ||||
| -rw-r--r-- | src/build/builders/core/enum.rs | 277 | ||||
| -rw-r--r-- | src/build/builders/core/struct.rs | 7 | ||||
| -rw-r--r-- | src/build/builders/core/value2.mmd | 8 | ||||
| -rw-r--r-- | src/doc_macro.rs | 7 | ||||
| -rw-r--r-- | src/lib.rs | 5 | ||||
| -rw-r--r-- | src/protocol/visitor/tag.rs | 1 | ||||
| -rw-r--r-- | tests/builder_enum.rs | 193 |
9 files changed, 515 insertions, 28 deletions
@@ -244,12 +244,18 @@ impl<'b, 'ctx: 'b> dyn AnyTrait<'ctx> + Send + Sync + 'b { macro_rules! any_trait { { impl[$lt:lifetime $($generic:tt)*] $name:ty = [$($protocol:ty),* $(,)?] + ref { + let ($if_this:ident, $if_id:ident); + $($if_fallback:tt)* + } else ref { - let ($this:ident, $id:ident); $($fallback:tt)* } + mut { + let ($if_mut_this:ident, $if_mut_id:ident); + $($if_mut_fallback:tt)* + } else mut { - let ($mut_this:ident, $mut_id:ident); $($mut_fallback:tt)* } $(where $($bound:tt)*)? @@ -269,14 +275,19 @@ macro_rules! any_trait { where $lt: '__ { + let ($if_this, $if_id) = (self, id); + + { + $($if_fallback)* + } + // This match should be optimized well by llvm. - match id { + match $if_id { $(id if id == $crate::any::TypeNameId::of::<$protocol>() => ::core::option::Option::Some($crate::any::AnyTraitObject::<'__, $lt, _>::new::< $crate::any::TypeName::T<'__, $lt, $protocol> - >(self as _)),)* - $id => { - let $this = self; + >($if_this as _)),)* + _ => { $($fallback)* } } @@ -290,14 +301,19 @@ macro_rules! any_trait { where $lt: '__ { + let ($if_mut_this, $if_mut_id) = (self, id); + + { + $($if_mut_fallback)* + } + // This match should be optimized well by llvm. - match id { + match $if_mut_id { $(id if id == $crate::any::TypeNameId::of::<$protocol>() => ::core::option::Option::Some($crate::any::AnyTraitObject::<'__, $lt, _>::new::< $crate::any::TypeName::T<'__, $lt, $protocol> - >(self as _)),)* - $mut_id => { - let $mut_this = self; + >($if_mut_this as _)),)* + _ => { $($mut_fallback)* } } @@ -310,13 +326,15 @@ macro_rules! any_trait { } => { $crate::any::any_trait! { impl[$lt $($generic)*] $name = [$($protocol),*] - else ref { - // Always answer no in the fallback branch if no fallback was given. + ref { let (_this, _id); + } else ref { + // Always answer no in the fallback branch if no fallback was given. ::core::option::Option::None + } mut { + let (_this, _id); } else mut { // Always answer no in the fallback branch if no fallback was given. - let (_this, _id); ::core::option::Option::None } $(where $($bound)*)? } diff --git a/src/build/builders/core.rs b/src/build/builders/core.rs index db7bc5d..4a7182d 100644 --- a/src/build/builders/core.rs +++ b/src/build/builders/core.rs @@ -11,6 +11,7 @@ pub mod value; // pub mod option; // pub mod variant; +pub mod r#enum; pub mod r#struct; pub mod tag_name; diff --git a/src/build/builders/core/enum.rs b/src/build/builders/core/enum.rs new file mode 100644 index 0000000..92e3149 --- /dev/null +++ b/src/build/builders/core/enum.rs @@ -0,0 +1,277 @@ +use core::fmt::{Debug, Display}; + +use crate::any::{OwnedStatic, TempBorrowedStatic, TempBorrowedStaticHrt}; +use crate::effect::{EffectExt, Effective, EffectiveExt}; +use crate::protocol::visitor::{DynRecoverableScope, Recoverable, RecoverableProto}; +use crate::{ + any::{AnyTraitObject, TypeName, TypeNameId}, + any_trait, + effect::{Effect, ErasedEffective, Ss}, + hkt::Invariant, + protocol::{ + visitor::{tags, Tag, TagProto, Value, ValueProto, VisitResult}, + DynVisitor, + }, + Builder, BuilderTypes, DynWalkerObjSafe, Flow, +}; + +pub struct EnumBuilder<'ctx, Info, Mode, E: Effect> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + inner: Inner<'ctx, Info, Mode, E>, +} + +enum Inner<'ctx, Info, Mode, E: Effect> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + Temp, + Seed(Info::Seed), + Builder { builder: Info::Builders }, + Value(Result<Info::T, Info::Error>), +} + +pub trait EnumBuildInfo<'ctx, Mode, E: Effect> { + type Builders: Ss; + + type Seed: Ss; + + type Error: Ss + Debug + Display; + + type ValueT: TypeName::MemberType; + + type T: Ss; + + type VariantMarker: Ss + Copy + Display; + + fn new_builder<'a>( + seed: Self::Seed, + variant: Self::VariantMarker, + ) -> ErasedEffective<'a, Self::Builders, E>; + + fn finish_builder<'a>( + builder: Self::Builders, + ) -> ErasedEffective<'a, Result<Self::T, Self::Error>, E>; + + fn from_value<'a>(value: TypeName::T<'a, 'ctx, Self::ValueT>) -> Self::T; + + fn as_visitor<'a>(builder: &'a mut Self::Builders) -> DynVisitor<'a, 'ctx>; + + fn marker_from_name(name: &str) -> Option<Self::VariantMarker>; + + fn marker_from_discriminant(discriminant: u32) -> Option<Self::VariantMarker>; + + fn guess_variant<'a>( + seed: Self::Seed, + scope: DynRecoverableScope<'a, 'ctx, E>, + ) -> ErasedEffective<'a, Result<Self::T, Self::Error>, E>; +} + +impl<'ctx, Info, Mode, E: Effect> BuilderTypes for EnumBuilder<'ctx, Info, Mode, E> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + type Seed = Info::Seed; + + type Error = Info::Error; + + type Value = Info::T; +} + +impl<'ctx, Info, Mode, E: Effect> Builder<'ctx, E> for EnumBuilder<'ctx, Info, Mode, E> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + fn from_seed<'a>(seed: Self::Seed) -> ErasedEffective<'a, Self, E> + where + Self: 'a, + { + E::ready(Self { + inner: Inner::Seed(seed), + }) + } + + fn build<'a>(self) -> ErasedEffective<'a, Result<Self::Value, Self::Error>, E> + where + Self: 'a, + { + match self.inner { + Inner::Temp => unreachable!(), + Inner::Seed(seed) => { + // what to do... + todo!() + } + Inner::Builder { builder } => Info::finish_builder(builder), + Inner::Value(value) => E::ready(value), + } + } + + fn as_visitor(&mut self) -> DynVisitor<'_, 'ctx> { + DynVisitor(self) + } +} + +any_trait! { + impl['ctx, Info, Mode, E] EnumBuilder<'ctx, Info, Mode, E> = [ + ValueProto<Info::ValueT, E>, + TagProto<tags::Variant, E>, + RecoverableProto<E> + ] ref { + let (_this, _id); + } else ref { + None + } mut { + let (this, id); + + // If a variant has been chosen, then forward everything to it's builder. + if matches!(this.inner, Inner::Builder { .. }) { + match &mut this.inner { + Inner::Builder { builder } => { + return Info::as_visitor(builder).0.upcast_to_id_mut(id) + } + _ => unreachable!(), + } + } + } else mut { + None + } where + E: Effect, + Info: EnumBuildInfo<'ctx, Mode, E>, +} + +impl<'ctx, Info, Mode, E: Effect> Recoverable<'ctx, E> for EnumBuilder<'ctx, Info, Mode, E> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + fn visit<'a>( + &'a mut self, + scope: DynRecoverableScope<'a, 'ctx, E>, + ) -> ErasedEffective<'a, VisitResult<DynRecoverableScope<'a, 'ctx, E>>, E> { + match core::mem::replace(&mut self.inner, Inner::Temp) { + Inner::Seed(seed) => Info::guess_variant(seed, scope).map(|result| { + self.inner = Inner::Value(result); + Flow::Done.into() + }), + inner => { + self.inner = inner; + E::ready(Flow::Continue.into()) + } + } + } +} + +impl<'ctx, Info, Mode, E: Effect> Value<'ctx, Info::ValueT, E> for EnumBuilder<'ctx, Info, Mode, E> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + fn visit<'a>( + &'a mut self, + value: TypeName::T<'a, 'ctx, Info::ValueT>, + ) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, Info::ValueT>>, E> + where + TypeName::T<'a, 'ctx, Info::ValueT>: Send + Sync + Sized, + 'ctx: 'a, + { + self.inner = Inner::Value(Ok(Info::from_value(value))); + E::ready(Flow::Done.into()) + } +} + +impl<'ctx, Info, Mode, E: Effect> Tag<'ctx, tags::Variant, E> for EnumBuilder<'ctx, Info, Mode, E> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + fn visit<'a: 'c, 'b: 'c, 'c>( + &'a mut self, + _kind: tags::Variant, + walker: DynWalkerObjSafe<'b, 'ctx, E>, + ) -> ErasedEffective<'c, VisitResult<DynWalkerObjSafe<'b, 'ctx, E>>, E> { + let visitor = VariantVisitor::<Info, Mode, E> { marker: None }; + + E::as_ctx((visitor, walker), |(visitor, walker)| { + walker.walk(DynVisitor(visitor)).cast() + }) + .then(|((visitor, _), result)| { + if let Some(variant) = visitor.marker { + match core::mem::replace(&mut self.inner, Inner::Temp) { + // A variant was given so we need to make the builder for + // it. + Inner::Seed(seed) => Info::new_builder(seed, variant).map(move |builder| { + self.inner = Inner::Builder { builder }; + result.to_done().into() + }), + inner => { + self.inner = inner; + E::ready(result.to_done().into()) + } + } + } else { + E::ready(result.to_done().into()) + } + }) + } +} + +struct VariantVisitor<'ctx, Info, Mode, E: Effect> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + marker: Option<Info::VariantMarker>, +} + +any_trait! { + impl['ctx, Info, Mode, E] VariantVisitor<'ctx, Info, Mode, E> = [ + ValueProto<TempBorrowedStaticHrt<str>, E>, + ValueProto<OwnedStatic<u32>, E>, + ] + where + E: Effect, + Info: EnumBuildInfo<'ctx, Mode, E>, +} + +impl<'ctx, Info, Mode, E: Effect> Value<'ctx, TempBorrowedStaticHrt<str>, E> + for VariantVisitor<'ctx, Info, Mode, E> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + fn visit<'a>( + &'a mut self, + TempBorrowedStatic(value): TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>, + ) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>>, E> + where + TypeName::T<'a, 'ctx, TempBorrowedStaticHrt<str>>: Send + Sync + Sized, + 'ctx: 'a, + { + if let Some(variant) = Info::marker_from_name(value) { + self.marker = Some(variant); + + E::ready(Flow::Done.into()) + } else { + E::ready(Flow::Continue.into()) + } + } +} + +impl<'ctx, Info, Mode, E: Effect> Value<'ctx, OwnedStatic<u32>, E> + for VariantVisitor<'ctx, Info, Mode, E> +where + Info: EnumBuildInfo<'ctx, Mode, E>, +{ + fn visit<'a>( + &'a mut self, + OwnedStatic(value): TypeName::T<'a, 'ctx, OwnedStatic<u32>>, + ) -> ErasedEffective<'a, VisitResult<TypeName::T<'a, 'ctx, OwnedStatic<u32>>>, E> + where + TypeName::T<'a, 'ctx, OwnedStatic<u32>>: Send + Sync + Sized, + 'ctx: 'a, + { + if let Some(variant) = Info::marker_from_discriminant(value) { + self.marker = Some(variant); + + E::ready(Flow::Done.into()) + } else { + E::ready(Flow::Continue.into()) + } + } +} diff --git a/src/build/builders/core/struct.rs b/src/build/builders/core/struct.rs index 9ee98aa..890ffda 100644 --- a/src/build/builders/core/struct.rs +++ b/src/build/builders/core/struct.rs @@ -457,12 +457,13 @@ struct FieldVisitor<'a, 'ctx, I: StructTypeInfo<'ctx, M, E>, M, E: Effect> { any_trait! { impl['ctx, 'a, I, M, E] FieldVisitor<'a, 'ctx, I, M, E> = [ TagProto<tags::Key, E>, - ] else ref { + ] ref { let (_this, _id); + } else ref { None - } else mut { + } mut { let (this, id); - + } else mut { this.marker.and_then(|marker| { I::as_visitor(marker, this.builders).0.upcast_to_id_mut(id) }) diff --git a/src/build/builders/core/value2.mmd b/src/build/builders/core/value2.mmd deleted file mode 100644 index b5572e2..0000000 --- a/src/build/builders/core/value2.mmd +++ /dev/null @@ -1,8 +0,0 @@ -sequenceDiagram - participant walker as Walker - participant request as RequestHint - participant value as Value\nT - alt Request Hint Flow - walker ->> request: request_hint() - request ->> walker: hint<Value<OwnedStatic<T>>>() - end diff --git a/src/doc_macro.rs b/src/doc_macro.rs index 6cf009a..ab4bb33 100644 --- a/src/doc_macro.rs +++ b/src/doc_macro.rs @@ -1,5 +1,5 @@ macro_rules! mermaid { - ($file:literal, $height:literal) => { + ($file:literal, $height:literal) => { concat!( "<pre class=\"mermaid\" style=\"padding:0;max-height:90vh;height:", stringify!($height), @@ -9,7 +9,10 @@ macro_rules! mermaid { "\n<div>\n", "</pre>\n\n", "<script type=\"module\">\n", - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc_mermaid_injector.js")), + include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/doc_mermaid_injector.js" + )), "</script>\n\n" ) }; @@ -9,6 +9,7 @@ extern crate alloc; pub mod any; mod build; +mod doc_macro; pub mod effect; pub mod hkt; pub mod macros; @@ -16,7 +17,6 @@ pub mod protocol; pub mod symbol; mod transform; mod walk; -mod doc_macro; use core::ops::ControlFlow; @@ -59,7 +59,8 @@ pub const TAG_ENUM: Symbol = Symbol::new("Enum"); pub enum DefaultMode {} -#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[must_use] +#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)] pub enum Status { Ok, Err, diff --git a/src/protocol/visitor/tag.rs b/src/protocol/visitor/tag.rs index ddf682d..ca32016 100644 --- a/src/protocol/visitor/tag.rs +++ b/src/protocol/visitor/tag.rs @@ -19,6 +19,7 @@ pub mod tags { pub type Struct = TagConst<{ Symbol::new("Struct").to_int() }>; pub type Map = TagConst<{ Symbol::new("Map").to_int() }>; + pub type Variant = TagConst<{ Symbol::new("Variant").to_int() }>; pub type Key = TagConst<{ Symbol::new("Key").to_int() }>; pub type Value = TagConst<{ Symbol::new("Value").to_int() }>; pub type FieldNames = TagConst<{ Symbol::new("Field Names").to_int() }>; diff --git a/tests/builder_enum.rs b/tests/builder_enum.rs new file mode 100644 index 0000000..dbdc60f --- /dev/null +++ b/tests/builder_enum.rs @@ -0,0 +1,193 @@ +use treaty::{ + any::{OwnedStatic, TypeName}, + builders::core::r#enum::{EnumBuildInfo, EnumBuilder}, + effect::{ + blocking::Blocking, Effect, EffectExt as _, Effective as _, EffectiveExt as _, + ErasedEffective, + }, + protocol::{ + visitor::{tags, visit_recoverable, visit_tag, visit_value, DynRecoverableScope, TagConst}, + DynVisitor, + }, + walkers::core::value::ValueWalker, + Build, BuildExt as _, Builder, Flow, Status, WalkExt as _, +}; + +use crate::common::protocol::{ + recoverable::MockRecoverableScopeVisitor, value::ValueVisitorExt as _, +}; + +mod common; + +#[derive(PartialEq, Debug)] +enum X { + A(f32), + B(bool), +} + +impl<'ctx, M, E: Effect> Build<'ctx, M, E> for X { + type Builder = EnumBuilder<'ctx, Info, M, E>; +} + +enum Info {} + +enum XBuilders<'ctx, M, E: Effect> { + A(<f32 as Build<'ctx, M, E>>::Builder), + B(<bool as Build<'ctx, M, E>>::Builder), +} + +#[derive(Copy, Clone, Debug)] +enum XMarker { + A, + B, +} + +impl core::fmt::Display for XMarker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +impl<'ctx, M, E: Effect> EnumBuildInfo<'ctx, M, E> for Info { + type Builders = XBuilders<'ctx, M, E>; + + type Seed = (); + + type Error = i32; + + type ValueT = OwnedStatic<X>; + + type T = X; + + type VariantMarker = XMarker; + + fn new_builder<'a>( + _seed: Self::Seed, + variant: Self::VariantMarker, + ) -> ErasedEffective<'a, Self::Builders, E> { + match variant { + XMarker::A => { + Builder::<E>::from_seed(Default::default()).map(|builder| XBuilders::A(builder)) + } + XMarker::B => { + Builder::<E>::from_seed(Default::default()).map(|builder| XBuilders::B(builder)) + } + } + } + + fn from_value<'a>(value: TypeName::T<'a, 'ctx, Self::ValueT>) -> Self::T { + value.0 + } + + fn as_visitor<'a>(builder: &'a mut Self::Builders) -> DynVisitor<'a, 'ctx> { + match builder { + XBuilders::A(builder) => builder.as_visitor(), + XBuilders::B(builder) => builder.as_visitor(), + } + } + + fn marker_from_name(name: &str) -> Option<Self::VariantMarker> { + match name { + "A" => Some(XMarker::A), + "B" => Some(XMarker::B), + _ => None, + } + } + + fn marker_from_discriminant(discriminant: u32) -> Option<Self::VariantMarker> { + match discriminant { + 0 => Some(XMarker::A), + 1 => Some(XMarker::B), + _ => None, + } + } + + fn finish_builder<'a>( + builder: Self::Builders, + ) -> ErasedEffective<'a, Result<Self::T, Self::Error>, E> { + match builder { + XBuilders::A(builder) => builder.build().map(|value| Ok(X::A(value.unwrap()))), + XBuilders::B(builder) => builder.build().map(|value| Ok(X::B(value.unwrap()))), + } + } + + fn guess_variant<'a>( + _seed: Self::Seed, + scope: DynRecoverableScope<'a, 'ctx, E>, + ) -> ErasedEffective<'a, Result<Self::T, Self::Error>, E> { + E::as_ctx(scope, |scope| { + <<f32 as Build<M, E>>::Builder as Builder<_>>::from_seed(Default::default()) + .map(|builder| (scope, builder)) + .as_ctx(|(scope, builder)| scope.new_walk(builder.as_visitor()).cast()) + .then(|((_, builder), result)| builder.build()) + .map(|result| result.map(X::A)) + .cast() + }) + .as_ctx(|(scope, result)| { + <<bool as Build<M, E>>::Builder as Builder<_>>::from_seed(Default::default()) + .map(|builder| (scope, builder)) + .as_ctx(|(scope, builder)| scope.new_walk(builder.as_visitor()).cast()) + .then(|((_, builder), result)| builder.build()) + .map(|result| result.map(X::B)) + .cast() + }) + .map(|((scope, _), result)| match result { + Ok(value) => Ok(value), + Err(err) => todo!("{}", err), + }) + } +} + +#[test] +fn demo() { + let mut builder = X::new_builder(); + + assert_eq!( + visit_tag::<tags::Variant, Blocking, _>( + TagConst, + builder.as_visitor(), + ValueWalker::new(0u32), + ) + .value() + .unwrap(), + Flow::Done.into() + ); + + builder + .as_visitor() + .visit_value_and_done(OwnedStatic(1.23f32)); + + assert_eq!(builder.build().value().unwrap(), X::A(1.23)); +} + +#[test] +fn demo2() { + let mut builder = X::new_builder(); + + // Use the recoverable flow. + { + let mut scope = MockRecoverableScopeVisitor::<Blocking>::new(); + + // The first builder for a f32 won't work. + // In a real walker the scope would always walk the same. + scope.expect_new_walk().once().return_const(Status::Ok); + + // The second builder will work. + scope.expect_new_walk().once().returning(|visitor| { + // The value for the B variant. + visitor.visit_value_and_done(OwnedStatic(true)); + + Status::Ok + }); + + // Visit a recoverable scope the enum builder can use to try and find + // a variant that can be built. + assert_eq!( + visit_recoverable(builder.as_visitor(), &mut scope).value(), + Flow::Done.into() + ); + } + + // The enum should have a value now. + assert_eq!(builder.build().value().unwrap(), X::B(true)); +} |