added enum builder
Konnor Andrews 2024-06-09
parent b995c0e · commit 12e60f9
-rw-r--r--src/any.rs44
-rw-r--r--src/build/builders/core.rs1
-rw-r--r--src/build/builders/core/enum.rs277
-rw-r--r--src/build/builders/core/struct.rs7
-rw-r--r--src/build/builders/core/value2.mmd8
-rw-r--r--src/doc_macro.rs7
-rw-r--r--src/lib.rs5
-rw-r--r--src/protocol/visitor/tag.rs1
-rw-r--r--tests/builder_enum.rs193
9 files changed, 515 insertions, 28 deletions
diff --git a/src/any.rs b/src/any.rs
index 028f610..5c6a262 100644
--- a/src/any.rs
+++ b/src/any.rs
@@ -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&lt;Value&lt;OwnedStatic&lt;T&gt;&gt;&gt;()
- 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"
)
};
diff --git a/src/lib.rs b/src/lib.rs
index 88d550a..317acac 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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));
+}