1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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!(),
        }
    })
}