use crate::{
effect::{Effect, Future},
protocol::{
visitor::tag::{self, DynTag},
Visitor,
},
symbol::Symbol,
DynWalkerAdapter, DynWalkerError, Flow, Walker, WalkerTypes,
};
#[derive(Debug)]
pub enum TagErrorKind<E> {
NeverWalked,
/// This can only happen if a panic happens furing the walk and is then caught before calling
/// finish..
WalkNeverFinished,
Walker(E),
}
#[derive(Debug)]
pub struct TagError<E> {
symbol: Symbol,
err: TagErrorKind<E>,
}
impl<E> TagError<E> {
fn new<const SYMBOL: u64>(err: E) -> Self {
Self {
symbol: Symbol::from_int(SYMBOL),
err: TagErrorKind::Walker(err),
}
}
fn never_walked<const SYMBOL: u64>() -> Self {
Self {
symbol: Symbol::from_int(SYMBOL),
err: TagErrorKind::NeverWalked,
}
}
fn walk_never_finished<const SYMBOL: u64>() -> Self {
Self {
symbol: Symbol::from_int(SYMBOL),
err: TagErrorKind::WalkNeverFinished,
}
}
}
pub fn visit_tag<'a, 'ctx: 'a, const SYMBOL: u64, E: Effect<'ctx>, W: Walker<'ctx, E> + 'a>(
visitor: Visitor<'a, 'ctx>,
walker: W,
) -> Future<'a, 'ctx, Result<Flow, TagError<W::Error>>, E>
where
W: WalkerTypes<Output = ()>,
{
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 Ok(Flow::Continue);
};
match result {
// The happy path.
(Flow::Continue, Ok(_)) => Ok(Flow::Continue),
// The visitor wants to stop the control flow for some reason.
(Flow::Break, Ok(_)) => Ok(Flow::Break),
(Flow::Done, Ok(_)) => Ok(Flow::Done),
// The walker had an error.
(_, Err(DynWalkerError::Walker(err))) => Err(TagError::new::<SYMBOL>(err)),
// The visitor never walked the dynamic walker. Aka it didn't call walk.
(_, Err(DynWalkerError::NeverWalked(_))) => 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)) => {
Err(TagError::walk_never_finished::<SYMBOL>())
}
}
})
}