Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 283 |
1 files changed, 283 insertions, 0 deletions
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) +} |