Diffstat (limited to 'src/macros.rs')
-rw-r--r--src/macros.rs120
1 files changed, 120 insertions, 0 deletions
diff --git a/src/macros.rs b/src/macros.rs
new file mode 100644
index 0000000..a946257
--- /dev/null
+++ b/src/macros.rs
@@ -0,0 +1,120 @@
+use crate::{
+ effect::{Effect, Future},
+ protocol::{
+ visitor::tag::{self, DynTag},
+ Visitor,
+ },
+ symbol::Symbol,
+ DynWalkerAdapter, DynWalkerError, Flow, Walker, WalkerTypes,
+};
+
+#[derive(Debug)]
+pub struct TagError {
+ symbol: Symbol,
+ msg: &'static str,
+}
+
+impl From<TagError> for &'static str {
+ fn from(value: TagError) -> Self {
+ value.msg
+ }
+}
+
+impl TagError {
+ fn new<const SYMBOL: u64>(msg: &'static str) -> Self {
+ Self {
+ symbol: Symbol::from_int(SYMBOL),
+ msg,
+ }
+ }
+
+ fn never_walked<const SYMBOL: u64>() -> Self {
+ Self::new::<SYMBOL>("visitor did not walk the walker")
+ }
+
+ fn walk_never_finished<const SYMBOL: u64>() -> Self {
+ Self::new::<SYMBOL>("walk did not finish")
+ }
+}
+
+pub fn visit_tag<
+ 'a,
+ 'ctx: 'a,
+ const SYMBOL: u64,
+ E: Effect<'ctx>,
+ W: Walker<'ctx, Effect = E> + 'a,
+>(
+ visitor: Visitor<'a, 'ctx>,
+ walker: W,
+) -> Future<'a, 'ctx, Flow<TagError>, E>
+where
+ W: WalkerTypes<Output = ()>,
+ <W as WalkerTypes>::Error: Into<&'static str>,
+{
+ E::wrap(async {
+ let result =
+ if let Some(object) = visitor.upcast_mut::<DynTag<'_, 'ctx, tag::Const<SYMBOL>, E>>() {
+ // The visitor knows about this tag at compile time.
+
+ // Wrap the walker to allow it to be passed to a dyn walker argument.
+ let mut name_walker = DynWalkerAdapter::new(walker);
+
+ // Visit the tag.
+ let flow = object.visit(tag::Const, &mut name_walker).await;
+
+ // Finish the dynamic walker to get any errors from it.
+ let result = name_walker.finish();
+
+ Some((flow, result))
+ } else if let Some(object) = visitor.upcast_mut::<DynTag<'_, 'ctx, tag::Dyn, E>>() {
+ // The visitor can handle dynamic tags.
+ // If the visitor can't handle the tag kind then it can call .skip on the walker
+ // to disable the error for not walking it.
+
+ // Wrap the walker to allow it to be passed to a dyn walker argument.
+ let mut name_walker = DynWalkerAdapter::new(walker);
+
+ // Visit the tag.
+ let flow = object
+ .visit(tag::Dyn(Symbol::from_int(SYMBOL)), &mut name_walker)
+ .await;
+
+ // Finish the dynamic walker to get any errors from it.
+ let result = name_walker.finish();
+
+ Some((flow, result))
+ } else {
+ None
+ };
+
+ let Some(result) = result else {
+ // The visitor doesn't know about this tag protocol so just continue as normal.
+ return Flow::Continue;
+ };
+
+ match result {
+ // The happy path.
+ (Flow::Continue, Ok(_)) => Flow::Continue,
+
+ // The visitor wants to stop the control flow for some reason.
+ (Flow::Break, Ok(_)) => Flow::Break,
+
+ // The walker had an error.
+ (_, Err(DynWalkerError::Walker(err))) => Flow::Err(TagError::new::<SYMBOL>(err.into())),
+
+ // The visitor never walked the dynamic walker. Aka it didn't call walk.
+ (_, Err(DynWalkerError::NeverWalked(_))) => {
+ Flow::Err(TagError::never_walked::<SYMBOL>())
+ }
+
+ // This is very hard to cause. The visitor must panic inside of .walk but then
+ // catch it in .visit.
+ (_, Err(DynWalkerError::WalkNeverFinished)) => {
+ Flow::Err(TagError::walk_never_finished::<SYMBOL>())
+ }
+
+ // This isn't possible because Err(!).
+ (Flow::Err(_), _) => unreachable!(),
+ }
+ })
+}