init commit
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | Cargo.toml | 8 | ||||
| -rw-r--r-- | src/impls.rs | 141 | ||||
| -rw-r--r-- | src/lib.rs | 283 | ||||
| -rw-r--r-- | src/protocol.rs | 214 | ||||
| -rw-r--r-- | src/protocols.rs | 59 | ||||
| -rw-r--r-- | tests/demo.rs | 14 |
8 files changed, 727 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d11c57b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "uniserde" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1cd5a70 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "uniserde" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/impls.rs b/src/impls.rs new file mode 100644 index 0000000..1c4d788 --- /dev/null +++ b/src/impls.rs @@ -0,0 +1,141 @@ +use crate::{ + lookup_hint, lookup_visit, + protocol::{AnyHint, AnyVisit, Protocol, ProtocolId}, + protocols::{self, str::Str}, + BasicError, Buildable, Error, Hint, HintGiven, Hints, Walkable, WalkableRef, +}; + +const _: () = { + pub struct Walker<'value>(&'value str); + + impl<'value> crate::Walker<'value> for Walker<'value> { + type Error = BasicError; + + fn hints(&mut self) -> &mut dyn Hints<'value, Self::Error> { + self + } + } + + impl<'value, Err: Error<'value>> Hints<'value, Err> for Walker<'value> { + fn protocol(&mut self, id: ProtocolId) -> Option<AnyHint<'_, 'value, Err>> { + match id { + id if id == ProtocolId::of::<Str>() => Some(AnyHint::new(self)), + _ => None, + } + } + } + + impl<'value, Err: Error<'value>> Hint<'value, Str, Err> for Walker<'value> { + fn hint( + &mut self, + visitor: &mut dyn crate::Visitor<'value, Err>, + _hint: <Str as crate::protocol::Protocol>::Hint, + ) -> Result<HintGiven, Err> { + lookup_visit::<Str, _, _>(visitor)? + .ok_or_else(Err::missing_visit::<Str>)? + .visit(protocols::str::Data::Value(self.0))?; + + Ok(HintGiven) + } + + fn known( + &mut self, + _hint: &<Str as crate::protocol::Protocol>::Hint, + ) -> Result<<Str as crate::protocol::Protocol>::Known, Err> { + Ok(protocols::str::Known { + len: Some(self.0.len()), + kind: Some(protocols::str::Kind::Value), + }) + } + } + + impl<'walking, 'value: 'walking> Walkable<'walking, 'value> for &'value str { + type Walker = Walker<'value>; + + fn walker(&'walking mut self) -> Self::Walker { + Walker(self) + } + } + + impl<'walking, 'value: 'walking> WalkableRef<'walking, 'value> for &'value str { + fn walker_ref(&'walking self) -> Self::Walker { + Walker(self) + } + } + + pub struct Visitor<'value>(Option<&'value str>); + + impl<'value, Err: Error<'value>> crate::Visitor<'value, Err> for Visitor<'value> { + fn request_hint( + &mut self, + hints: &mut dyn Hints<'value, Err>, + ) -> Result<Option<HintGiven>, Err> { + if let Some(hint) = lookup_hint::<Str, _, _>(hints)? { + Ok(Some(hint.hint( + self, + protocols::str::Hint { + kind: Some(protocols::str::Kind::Value), + min_len: None, + max_len: None, + }, + )?)) + } else { + Ok(None) + } + } + + fn protocol(&mut self, id: ProtocolId) -> Option<AnyVisit<'_, 'value, Err>> { + match id { + id if id == ProtocolId::of::<Str>() => Some(AnyVisit::new(self)), + _ => None, + } + } + } + + impl<'value, Err: Error<'value>> crate::Visit<'value, Str, Err> for Visitor<'value> { + fn visit<'walking>( + &'walking mut self, + accessor: <Str as Protocol<'value>>::Accessor<'walking, Err>, + ) -> Result<(), Err> { + match accessor { + protocols::str::Data::Value(str) | protocols::str::Data::Static(str) => { + self.0 = Some(str) + } + protocols::str::Data::Walking(_) => { + return Err(Err::custom("str value does not live long enough")) + } + } + Ok(()) + } + } + + impl<'value, Err: Error<'value>> crate::Builder<'value, Err> for Visitor<'value> { + type Value = &'value str; + + fn init() -> Self + where + Self: Sized, + { + Visitor(None) + } + + fn as_visitor(&mut self) -> &mut dyn crate::Visitor<'value, Err> { + self + } + + fn finish(self) -> Result<Self::Value, Err> + where + Self: Sized, + { + if let Some(str) = self.0 { + Ok(str) + } else { + Err(Err::custom_static_str("missing str")) + } + } + } + + impl<'value, Err: Error<'value>> Buildable<'value, Err> for &'value str { + type Builder = Visitor<'value>; + } +}; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0fdad32 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,283 @@ +#![no_std] + +pub mod impls; +pub mod protocol; +pub mod protocols; + +use core::fmt::Display; + +use protocol::{AnyHint, AnyVisit, Protocol, ProtocolId}; + +/// Trait for all walker error types. +/// +/// This gives an interface for visitors to generate their own errors. +/// `'value` is the lifetime of the source value which the error may borrow from. +pub trait Error<'value>: Sized + Display + 'value { + /// Create a custom error. + fn custom<T: Display>(message: T) -> Self; + + /// Create a custom error from a static string message. + /// + /// This is useful for error types that can't store arbitrary strings. + /// The default impl forwards to `Self::custom`. + fn custom_static_str(message: &'static str) -> Self { + Self::custom(message) + } + + /// Helper for making an error when a visitor is missing the + /// visit for a protocol it hinted. + fn missing_visit<P: Protocol<'value>>() -> Self { + Self::custom_static_str("visitor is missing expected protocol") + } +} + +#[derive(Debug)] +pub struct BasicError(pub &'static str); + +impl Display for BasicError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.0) + } +} + +impl<'value> Error<'value> for BasicError { + fn custom<T: Display>(_message: T) -> Self { + Self("basic error") + } + + fn custom_static_str(message: &'static str) -> Self { + Self(message) + } +} + +/// Token value showing a hint was given. +pub struct HintGiven; + +/// Status of a walker after walking using a visitor. +/// +/// Some walkers can be walked multiple times to extract multiple +/// values. +pub enum WalkStatus { + /// The walker is done. + /// + /// Attemping to call `walk` is likely to result in an error. + Done, + + /// The walker can continue. + Continue, +} + +/// Walker over a value with lifetime `'value`. +pub trait Walker<'value> { + /// Error type the walker generates. + type Error: Error<'value>; + + /// Walk the walker over the value. + /// + /// This is the main entrypoint for walking a value. + /// The walker should call [`Visit::visit`] on the provided visitor as it walks. + /// + /// The default impl calls [`Visitor::request_hint`] then returns an error if no hint + /// was given. Self describing formats can replace the default impl to fall back to + /// their own logic if no hint is given. It is recommended to keep the call to + /// [`Visitor::request_hint`] before using walker specific logic to pick a protocol. + fn walk( + &mut self, + visitor: &mut dyn Visitor<'value, Self::Error>, + ) -> Result<WalkStatus, Self::Error> { + // Request that the visitor give us a hint of what protocol to use. + match visitor.request_hint(self.hints())? { + Some(HintGiven) => Ok(WalkStatus::Done), + None => Err(<Self::Error as Error>::custom("walker needs a hint")), + } + } + + /// Get the hints the walker supports. + /// + /// The hint lookup is seperate from [`Walker`] so that [`Visitor::request_hint`] can't + /// easily cause a infinite loop be calling [`Walker::walk`]. + fn hints(&mut self) -> &mut dyn Hints<'value, Self::Error>; +} + +/// Hint lookup for a walker. +pub trait Hints<'value, Err> { + /// Query the walker for a given protocol. + /// + /// If the walker doesn't support the protocol then a `None` is returned. + /// Note, a walker can return `None` if it can't handle a hint of the protocol for the given + /// value. + fn protocol(&mut self, id: ProtocolId) -> Option<AnyHint<'_, 'value, Err>> { + let _ = id; + None + } +} + +/// Visitor over a value to be built. +pub trait Visitor<'value, Err> { + /// Request the visitor hint what protocol to use. + /// + /// It is not recommended to call this while in a protocol hint as a walker. + /// Calling this method when already processing a hint can cause a infinite loop. + /// + /// The visitor will hint the protocol by calling the [`Hint::hint`] method on the + /// the walker's returned hint instance for the protocol. + /// + /// A return value of `Ok(None)` means no hint was given to the walker. + fn request_hint( + &mut self, + hints: &mut dyn Hints<'value, Err>, + ) -> Result<Option<HintGiven>, Err> { + let _ = hints; + Ok(None) + } + + /// Query the visitor for a given protocol. + /// + /// If the visitor doesn't support the protocol then a `None` is returned. + fn protocol(&mut self, id: ProtocolId) -> Option<AnyVisit<'_, 'value, Err>> { + let _ = id; + None + } +} + +pub type HintOps<'walking, 'value, P, Err> = &'walking mut dyn Hint<'value, P, Err>; +pub type VisitOps<'walking, 'value, P, Err> = &'walking mut dyn Visit<'value, P, Err>; + +/// Protocol specific hint for a walker. +pub trait Hint<'value, P: Protocol<'value>, Err> { + /// Hint that protocol `P` should be used. + /// + /// After hinting a protocol, a walker should invoke only the same protocol on the visitor. + /// This is not forced though. + fn hint( + &mut self, + visitor: &mut dyn Visitor<'value, Err>, + hint: P::Hint, + ) -> Result<HintGiven, Err>; + + /// Any information the walker has for the protocol. + /// + /// This information should be easy to get inside the walker, and should + /// only be used when making a decision of what protocol to hint as a visitor. + /// This can be helpful for doing things like preallocating space in the visitor. + /// + /// Most protocols will allow returning a value representing no knowledge is known by the + /// walker. + fn known(&mut self, hint: &P::Hint) -> Result<P::Known, Err>; +} + +/// Protocol specific visit for a visitor. +pub trait Visit<'value, P: Protocol<'value>, Err: Error<'value>> { + /// Visit a value from the walker. + fn visit<'walking>(&'walking mut self, accessor: P::Accessor<'walking, Err>) + -> Result<(), Err>; +} + +/// A type buildable from a walker. +pub trait Buildable<'value, Err>: Sized { + /// The builder that can be used to build a value. + type Builder: Builder<'value, Err, Value = Self>; +} + +/// Build a [`Buildable`] type from a walker. +/// +/// This calls [`Walker::walk`] on the walker. +pub fn build<'value, T: Buildable<'value, Err>, Err, W: ?Sized + Walker<'value, Error = Err>>( + walker: &mut W, +) -> Result<(T, WalkStatus), W::Error> { + let mut builder = T::Builder::init(); + let status = walker.walk(builder.as_visitor())?; + Ok((builder.finish()?, status)) +} + +/// Extension to [`Visitor`] that allows constructing and finishing the visitor. +pub trait Builder<'value, Err> { + /// Type to be built. + type Value: Sized; + + /// Init a new builder. + fn init() -> Self + where + Self: Sized; + + /// As a visitor. + fn as_visitor(&mut self) -> &mut dyn Visitor<'value, Err>; + + /// Finish the value. + fn finish(self) -> Result<Self::Value, Err> + where + Self: Sized; +} + +/// A walkable type. +pub trait WalkableRef<'walking, 'value>: Walkable<'walking, 'value> { + /// Create a walker for a value. + fn walker_ref(&'walking self) -> Self::Walker; +} + +/// A walkable type. +pub trait Walkable<'walking, 'value> { + /// The walker for the type. + type Walker: Walker<'value>; + + /// Create a walker for a value. + /// + /// In general, calling this multiple times will result in different walks. + fn walker(&'walking mut self) -> Self::Walker; +} + +/// Helper to lookup a protocol's hint on a walker. +/// +/// A `Ok(None)` is returned if the walker doesn't support the protocol. +/// A `Err(...)` is returned if the walker returns a hint instance not for the protocol. +pub fn lookup_hint< + 'value, + P: Protocol<'value>, + Err: Error<'value>, + H: ?Sized + Hints<'value, Err>, +>( + hints: &mut H, +) -> Result<Option<HintOps<'_, 'value, P, Err>>, Err> { + match hints.protocol(ProtocolId::of::<P>()) { + Some(hint) => match hint.downcast::<P>() { + Ok(hint) => Ok(Some(hint)), + Err(_) => Err(Err::custom_static_str( + "unexpected protocol hint instance from walker", + )), + }, + None => Ok(None), + } +} + +/// Helper to lookup a protocol's visit on a visitor. +/// +/// A `Ok(None)` is returned if the visitor doesn't support the protocol. +/// A `Err(...)` is returned if the visitor returns a visit instance not for the protocol. +pub fn lookup_visit< + 'value, + P: Protocol<'value>, + Err: Error<'value>, + V: ?Sized + Visitor<'value, Err>, +>( + visitor: &mut V, +) -> Result<Option<VisitOps<'_, 'value, P, Err>>, Err> { + match visitor.protocol(ProtocolId::of::<P>()) { + Some(hint) => match hint.downcast::<P>() { + Ok(hint) => Ok(Some(hint)), + Err(_) => Err(Err::custom_static_str( + "unexpected protocol visit instance from visitor", + )), + }, + None => Ok(None), + } +} + +pub fn walking_clone<'walking, 'value, T>( + value: &'walking T, +) -> Result<T, <T::Walker as Walker<'value>>::Error> +where + T: Buildable<'value, <T::Walker as Walker<'value>>::Error> + WalkableRef<'walking, 'value>, +{ + let (value, _) = build(&mut value.walker_ref())?; + Ok(value) +} diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 0000000..081e569 --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,214 @@ +use core::any::Any; + +pub use any_hint::AnyHint; +pub use any_visit::AnyVisit; +pub use id::ProtocolId; + +use crate::Error; + +/// A protocol between a walker and visitor. +/// +/// On the walker side this takes the form of hints a visitor can give. +/// On the visitor side this takes the form of visits a walker can inject values into. +/// +/// When a visitor hints a walker should use a particular protocol, its expected +/// that the walker visits using that protocol. +/// +/// A protocol never needs to be a value, so it's recommended to use an uninhabited type +/// like an empty enum to represent them. +pub trait Protocol<'value>: Any { + /// Arbitrary hint metadata for the protocol. + /// + /// This allows a visitor to give extra information to a walker when hinting to + /// use the protocol. + type Hint; + + /// Data known about the protocol before hinting. + /// + /// This allows a walker to give extra information to a visitor to make a + /// better decision when selecting a hint. + type Known; + + /// The visit data the walker provides to the visitor. + /// + /// This may be actual data or another walker for a part of the bigger value. + /// The '`walking` lifetime is only alive while the walker is walking. + /// As such, a visitor cannot borrow from a `'walking` lifetime containing type + /// for it's output. + type Accessor<'walking, Err: Error<'value>> + where + 'value: 'walking; +} + +mod id { + use super::Protocol; + use core::any::TypeId; + + /// ID of a protocol. + /// + /// This can be used to query if a walker or visitor supports a protocol. + #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Debug, Copy, Clone)] + pub struct ProtocolId(TypeId); + + impl ProtocolId { + /// Get the ID of a protocol. + /// + /// The ID is unique per protocol. + pub fn of<'value, P: Protocol<'value>>() -> Self { + Self(TypeId::of::<P>()) + } + } +} + +mod any_hint { + use core::{any::Any, marker::PhantomData, mem::MaybeUninit}; + + use crate::Hint; + + use super::{Protocol, ProtocolId}; + + /// Form of `Hint` without `P`. + trait ErasedHint<'value, Err>: Any {} + + /// Get the size of pointers to trait objects for this target. + const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedHint<'static, ()>>(); + + /// Type erased form of `&'walking mut dyn Hint<'value, P, Err>` where `P` is erased. + pub struct AnyHint<'walking, 'value, Err> { + /// ID of `P`. + id: ProtocolId, + + /// This field stores a `&'walking mut dyn Hint<'value, P, Err>`. + fat_ptr: MaybeUninit<[u8; DYN_PTR_SIZE]>, + + /// Mimick what we actually store with a trait without `P`. + _marker: PhantomData<&'walking mut dyn ErasedHint<'value, Err>>, + } + + impl<'walking, 'value, Err> AnyHint<'walking, 'value, Err> { + /// Erase the `P` in a hint. + /// + /// This allows returning a hint from a object safe method. + pub fn new<P: Protocol<'value>>(visit: &'walking mut dyn Hint<'value, P, Err>) -> Self { + Self { + id: ProtocolId::of::<P>(), + // SAFETY: A maybe uninit array of bytes can hold any pointer. + // Additionally, transmute makes sure the size is correct. + fat_ptr: unsafe { core::mem::transmute(visit) }, + _marker: PhantomData, + } + } + + /// Try to downcast the hint for the given protocol. + /// + /// If the hint is of the wrong type then `None` is returned. + pub fn downcast<P: Protocol<'value>>( + self, + ) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> { + if self.id == ProtocolId::of::<P>() { + // SAFETY: Only `new` can make a value of this type, and it stores the ID of `P`. + // If the IDs are equal then we can act like any and downcast back to the real + // type. + // + // An important note is this method takes ownership. Which allows it to return + // the borrow with the `'walking` lifetime instead of a sub-borrow. + Ok(unsafe { core::mem::transmute(self.fat_ptr) }) + } else { + Err(self) + } + } + } +} + +mod any_visit { + use core::{any::Any, marker::PhantomData, mem::MaybeUninit}; + + use crate::Visit; + + use super::{Protocol, ProtocolId}; + + /// Form of `Visit` without `P`. + trait ErasedVisit<'value, Err>: Any {} + + /// Get the size of pointers to trait objects for this target. + const DYN_PTR_SIZE: usize = core::mem::size_of::<&mut dyn ErasedVisit<'static, ()>>(); + + /// Type erased form of `&'walking mut dyn Visit<'value, P, Err>` where `P` is erased. + pub struct AnyVisit<'walking, 'value, Err> { + /// ID of `P`. + id: ProtocolId, + + /// This field stores a `&'walking mut dyn Visit<'value, P, Err>`. + fat_ptr: MaybeUninit<[u8; DYN_PTR_SIZE]>, + + /// Mimick what we actually store with a trait without `P`. + _marker: PhantomData<&'walking mut dyn ErasedVisit<'value, Err>>, + } + + impl<'walking, 'value, Err> AnyVisit<'walking, 'value, Err> { + /// Erase the `P` in a visit. + /// + /// This allows returning a visit from a object safe method. + pub fn new<P: Protocol<'value>>(visit: &'walking mut dyn Visit<'value, P, Err>) -> Self { + Self { + id: ProtocolId::of::<P>(), + // SAFETY: A maybe uninit array of bytes can hold any pointer. + // Additionally, transmute makes sure the size is correct. + fat_ptr: unsafe { core::mem::transmute(visit) }, + _marker: PhantomData, + } + } + + /// Try to downcast the visit for the given protocol. + /// + /// If the visit is of the wrong type then `None` is returned. + pub fn downcast<P: Protocol<'value>>( + self, + ) -> Result<&'walking mut dyn Visit<'value, P, Err>, Self> { + if self.id == ProtocolId::of::<P>() { + // SAFETY: Only `new` can make a value of this type, and it stores the ID of `P`. + // If the IDs are equal then we can act like any and downcast back to the real + // type. + // + // An important note is this method takes ownership. Which allows it to return + // the borrow with the `'walking` lifetime instead of a sub-borrow. + Ok(unsafe { core::mem::transmute(self.fat_ptr) }) + } else { + Err(self) + } + } + } +} + +/// The following shows a safe form of the generic types in this module. +/// This shows how the lifetimes are correct. +#[cfg(test)] +#[allow(unused)] +mod generic_example { + use crate::Hint; + + use super::{Protocol, ProtocolId}; + + pub struct Generic<'walking, 'value, P, Err> { + id: ProtocolId, + fat_ptr: &'walking mut dyn Hint<'value, P, Err>, + } + + impl<'walking, 'value, P: Protocol<'value>, Err> Generic<'walking, 'value, P, Err> { + pub fn new(visit: &'walking mut dyn Hint<'value, P, Err>) -> Self { + Self { + id: ProtocolId::of::<P>(), + fat_ptr: visit, + } + } + + pub fn downcast(self) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> { + if self.id == ProtocolId::of::<P>() { + // Notice how this is valid. + Ok(self.fat_ptr) + } else { + Err(self) + } + } + } +} diff --git a/src/protocols.rs b/src/protocols.rs new file mode 100644 index 0000000..0da527f --- /dev/null +++ b/src/protocols.rs @@ -0,0 +1,59 @@ +pub mod recoverable { + use crate::*; + + pub trait Accessor<'value, Err> { + /// Each time this is called the walker resets. + fn walk_new(&mut self, visitor: &mut dyn Visitor<'value, Err>) -> Result<(), Err>; + } + + pub enum Recoverable {} + + impl<'value> Protocol<'value> for Recoverable { + type Hint = (); + + type Known = (); + + type Accessor<'walking, Err: Error<'value>> = &'walking dyn Accessor<'value, Err> + where + 'value: 'walking; + } +} + +pub mod str { + use crate::*; + + pub enum Kind { + Walking, + Value, + Static, + } + + pub struct Hint { + pub kind: Option<Kind>, + pub min_len: Option<usize>, + pub max_len: Option<usize>, + } + + pub struct Known { + pub kind: Option<Kind>, + pub len: Option<usize>, + } + + pub enum Data<'walking, 'value> { + Walking(&'walking str), + Value(&'value str), + Static(&'static str), + } + + pub enum Str {} + + impl<'value> Protocol<'value> for Str { + type Hint = Hint; + + type Known = Known; + + type Accessor<'walking, Err: Error<'value>> = Data<'walking, 'value> + where + 'value: 'walking; + } +} diff --git a/tests/demo.rs b/tests/demo.rs new file mode 100644 index 0000000..b954052 --- /dev/null +++ b/tests/demo.rs @@ -0,0 +1,14 @@ +use uniserde::walking_clone; + +#[test] +fn demo() { + let x = "test"; + let y = clone_str(&x); + dbg!(y); + todo!(); +} + +#[inline(never)] +pub fn clone_str(x: &str) -> &str { + walking_clone(&x).unwrap() +} |