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!(),
}
})
}