Diffstat (limited to 'src/protocol.rs')
-rw-r--r--src/protocol.rs225
1 files changed, 200 insertions, 25 deletions
diff --git a/src/protocol.rs b/src/protocol.rs
index 081e569..01336db 100644
--- a/src/protocol.rs
+++ b/src/protocol.rs
@@ -4,7 +4,7 @@ pub use any_hint::AnyHint;
pub use any_visit::AnyVisit;
pub use id::ProtocolId;
-use crate::Error;
+use crate::{error::VisitorError, HintGiven, UniError, Visitor, WalkerError, WalkerHints};
/// A protocol between a walker and visitor.
///
@@ -16,7 +16,7 @@ use crate::Error;
///
/// 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 {
+pub trait Protocol<'value, 'ctx: 'value>: Any {
/// Arbitrary hint metadata for the protocol.
///
/// This allows a visitor to give extra information to a walker when hinting to
@@ -35,9 +35,159 @@ pub trait Protocol<'value>: Any {
/// 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>>
+ type Accessor<'walking, WalkerErr: 'value, VisitorErr: 'value>
where
'value: 'walking;
+
+ fn description() -> Option<&'static str> {
+ None
+ }
+}
+
+pub struct ProtocolDescription {
+ id: fn() -> ProtocolId,
+ name: fn() -> &'static str,
+ description: fn() -> Option<&'static str>,
+}
+
+impl ProtocolDescription {
+ pub const fn of<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>>() -> Self {
+ Self {
+ id: || ProtocolId::of::<P>(),
+ name: || core::any::type_name::<P>(),
+ description: P::description,
+ }
+ }
+
+ pub fn id(&self) -> ProtocolId {
+ (self.id)()
+ }
+
+ pub fn name(&self) -> &'static str {
+ (self.name)()
+ }
+
+ pub fn description(&self) -> Option<&'static str> {
+ (self.description)()
+ }
+}
+
+impl core::fmt::Display for ProtocolDescription {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self.description() {
+ Some(description) => write!(f, "{} - {}", self.name(), description),
+ None => write!(f, "{}", self.name()),
+ }
+ }
+}
+
+impl core::fmt::Debug for ProtocolDescription {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.debug_struct("ProtocolDescription")
+ .field("id", &self.id())
+ .field("name", &self.name())
+ .field("description", &self.description())
+ .finish()
+ }
+}
+
+#[macro_export]
+#[doc(hidden)]
+macro_rules! protocol_list {
+ ($($protocol:ty),* $(,)?) => {{
+ [
+ $($crate::protocol::ProtocolDescription::of::<$protocol>()),*
+ ].into_iter()
+ }}
+}
+
+#[doc(inline)]
+pub use protocol_list;
+
+pub type HintOps<'walking, 'value, 'ctx, P, WalkerErr, VisitorErr> =
+ &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>;
+
+pub type VisitOps<'walking, 'value, 'ctx, P, WalkerErr, VisitorErr> =
+ &'walking mut dyn Visit<'value, 'ctx, P, WalkerErr, Error = VisitorErr>;
+
+/// Protocol specific hint for a walker.
+pub trait Hint<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>, VisitorErr> {
+ type Error;
+
+ /// 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, 'ctx, Self::Error, Error = VisitorErr>,
+ hint: P::Hint,
+ ) -> Result<HintGiven, UniError<Self::Error, VisitorErr>>;
+
+ /// 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, Self::Error>;
+}
+
+/// Protocol specific visit for a visitor.
+pub trait Visit<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>, WalkerErr> {
+ type Error;
+
+ /// Visit a value from the walker.
+ fn visit<'walking>(
+ &'walking mut self,
+ accessor: P::Accessor<'walking, WalkerErr, Self::Error>,
+ ) -> Result<(), UniError<WalkerErr, Self::Error>>;
+}
+
+/// 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,
+ 'ctx: 'value,
+ P: Protocol<'value, 'ctx>,
+ VisitorErr: VisitorError<'value, 'ctx>,
+ H: ?Sized + WalkerHints<'value, 'ctx, VisitorErr>,
+>(
+ hints: &mut H,
+) -> Result<Option<HintOps<'_, 'value, 'ctx, P, H::Error, VisitorErr>>, VisitorErr> {
+ match hints.protocol(ProtocolId::of::<P>()) {
+ Some(hint) => match hint.downcast::<P>() {
+ Ok(hint) => Ok(Some(hint)),
+ Err(_) => Err(VisitorErr::wrong_hint::<P>()),
+ },
+ 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,
+ 'ctx,
+ P: Protocol<'value, 'ctx>,
+ WalkerErr: WalkerError<'value, 'ctx>,
+ V: ?Sized + Visitor<'value, 'ctx, WalkerErr>,
+>(
+ visitor: &mut V,
+) -> Result<Option<VisitOps<'_, 'value, 'ctx, P, WalkerErr, V::Error>>, WalkerErr> {
+ match visitor.protocol(ProtocolId::of::<P>()) {
+ Some(visit) => match visit.downcast::<P>() {
+ Ok(visit) => Ok(Some(visit)),
+ Err(_) => Err(WalkerError::wrong_visit::<P>()),
+ },
+ None => Ok(None),
+ }
}
mod id {
@@ -54,7 +204,7 @@ mod id {
/// Get the ID of a protocol.
///
/// The ID is unique per protocol.
- pub fn of<'value, P: Protocol<'value>>() -> Self {
+ pub fn of<'value, 'ctx: 'value, P: Protocol<'value, 'ctx>>() -> Self {
Self(TypeId::of::<P>())
}
}
@@ -68,13 +218,14 @@ mod any_hint {
use super::{Protocol, ProtocolId};
/// Form of `Hint` without `P`.
- trait ErasedHint<'value, Err>: Any {}
+ trait ErasedHint<'value, 'ctx: 'value, WalkerErr, VisitorErr>: 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, ()>>();
+ const DYN_PTR_SIZE: usize =
+ core::mem::size_of::<&mut dyn ErasedHint<'static, 'static, (), ()>>();
/// Type erased form of `&'walking mut dyn Hint<'value, P, Err>` where `P` is erased.
- pub struct AnyHint<'walking, 'value, Err> {
+ pub struct AnyHint<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr> {
/// ID of `P`.
id: ProtocolId,
@@ -82,14 +233,18 @@ mod any_hint {
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>>,
+ _marker: PhantomData<&'walking mut dyn ErasedHint<'value, 'ctx, VisitorErr, WalkerErr>>,
}
- impl<'walking, 'value, Err> AnyHint<'walking, 'value, Err> {
+ impl<'walking, 'value, 'ctx: 'value, WalkerErr, VisitorErr>
+ AnyHint<'walking, 'value, 'ctx, WalkerErr, VisitorErr>
+ {
/// 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 {
+ pub fn new<P: Protocol<'value, 'ctx>>(
+ visit: &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>,
+ ) -> Self {
Self {
id: ProtocolId::of::<P>(),
// SAFETY: A maybe uninit array of bytes can hold any pointer.
@@ -102,9 +257,10 @@ mod any_hint {
/// 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>>(
+ pub fn downcast<P: Protocol<'value, 'ctx>>(
self,
- ) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> {
+ ) -> Result<&'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>, 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
@@ -128,13 +284,14 @@ mod any_visit {
use super::{Protocol, ProtocolId};
/// Form of `Visit` without `P`.
- trait ErasedVisit<'value, Err>: Any {}
+ trait ErasedVisit<'value, 'ctx: 'value, WalkerErr, VisitorErr>: 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, ()>>();
+ const DYN_PTR_SIZE: usize =
+ core::mem::size_of::<&mut dyn ErasedVisit<'static, 'static, (), ()>>();
/// Type erased form of `&'walking mut dyn Visit<'value, P, Err>` where `P` is erased.
- pub struct AnyVisit<'walking, 'value, Err> {
+ pub struct AnyVisit<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr> {
/// ID of `P`.
id: ProtocolId,
@@ -142,14 +299,18 @@ mod any_visit {
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>>,
+ _marker: PhantomData<&'walking mut dyn ErasedVisit<'value, 'ctx, WalkerErr, VisitorErr>>,
}
- impl<'walking, 'value, Err> AnyVisit<'walking, 'value, Err> {
+ impl<'walking, 'value: 'walking, 'ctx: 'value, WalkerErr, VisitorErr>
+ AnyVisit<'walking, 'value, 'ctx, WalkerErr, VisitorErr>
+ {
/// 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 {
+ pub fn new<P: Protocol<'value, 'ctx>>(
+ visit: &'walking mut dyn Visit<'value, 'ctx, P, WalkerErr, Error = VisitorErr>,
+ ) -> Self {
Self {
id: ProtocolId::of::<P>(),
// SAFETY: A maybe uninit array of bytes can hold any pointer.
@@ -162,9 +323,10 @@ mod any_visit {
/// 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>>(
+ pub fn downcast<P: Protocol<'value, 'ctx>>(
self,
- ) -> Result<&'walking mut dyn Visit<'value, P, Err>, Self> {
+ ) -> Result<&'walking mut dyn Visit<'value, 'ctx, P, WalkerErr, Error = VisitorErr>, 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
@@ -189,20 +351,33 @@ mod generic_example {
use super::{Protocol, ProtocolId};
- pub struct Generic<'walking, 'value, P, Err> {
+ pub struct Generic<'walking, 'value: 'walking, 'ctx: 'value, P, WalkerErr, VisitorErr> {
id: ProtocolId,
- fat_ptr: &'walking mut dyn Hint<'value, P, Err>,
+ fat_ptr: &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>,
}
- impl<'walking, 'value, P: Protocol<'value>, Err> Generic<'walking, 'value, P, Err> {
- pub fn new(visit: &'walking mut dyn Hint<'value, P, Err>) -> Self {
+ impl<
+ 'walking,
+ 'value: 'walking,
+ 'ctx: 'value,
+ P: Protocol<'value, 'ctx>,
+ WalkerErr,
+ VisitorErr,
+ > Generic<'walking, 'value, 'ctx, P, WalkerErr, VisitorErr>
+ {
+ pub fn new(
+ visit: &'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>,
+ ) -> Self {
Self {
id: ProtocolId::of::<P>(),
fat_ptr: visit,
}
}
- pub fn downcast(self) -> Result<&'walking mut dyn Hint<'value, P, Err>, Self> {
+ pub fn downcast(
+ self,
+ ) -> Result<&'walking mut dyn Hint<'value, 'ctx, P, VisitorErr, Error = WalkerErr>, Self>
+ {
if self.id == ProtocolId::of::<P>() {
// Notice how this is valid.
Ok(self.fat_ptr)