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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use crate::{Error, FRAME_SEPARATOR};
use core::{
    fmt::{self, Display, Formatter},
    str::FromStr,
};

/// A stack.
#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Stack {
    frames: Vec<Option<String>>,
}

impl Stack {
    /// Creates a stack.
    pub const fn new(frames: Vec<Option<String>>) -> Self {
        Self { frames }
    }

    /// Returns a stack.
    pub fn frames(&self) -> impl Iterator<Item = Option<&str>> {
        self.frames.iter().map(Option::as_deref)
    }

    /// Reverses frames.
    pub fn reverse_frames(&mut self) {
        self.frames.reverse();
    }

    /// Collapses frames.
    pub fn collapse_frames(&mut self) {
        let mut frames = vec![];

        for frame in self.frames.drain(..) {
            if frames.last() != Some(&frame) {
                frames.push(frame);
            }
        }

        self.frames = frames;
    }

    /// Displays a stack with a fixed local names.
    pub fn display_local<'a>(&'a self, local_name: &'a str) -> impl Display + '_ {
        StackDisplay::new(self, local_name)
    }
}

impl FromStr for Stack {
    type Err = Error;

    fn from_str(string: &str) -> Result<Self, Self::Err> {
        Ok(Self::new(
            string
                .split(FRAME_SEPARATOR)
                .map(|frame| (!frame.is_empty()).then_some(frame.to_owned()))
                .collect(),
        ))
    }
}

impl Display for Stack {
    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
        write!(formatter, "{}", self.display_local(""))
    }
}

pub struct StackDisplay<'a> {
    stack: &'a Stack,
    local_name: &'a str,
}

impl<'a> StackDisplay<'a> {
    pub const fn new(stack: &'a Stack, local_name: &'a str) -> Self {
        Self { stack, local_name }
    }
}

impl Display for StackDisplay<'_> {
    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
        let mut first = true;

        for frame in self.stack.frames() {
            if !first {
                write!(formatter, "{FRAME_SEPARATOR}")?;
            }

            first = false;

            write!(formatter, "{}", frame.unwrap_or(self.local_name))?;
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn parse() {
        let stack = Stack::new(vec![Some("foo".into()), Some("bar".into())]);

        assert_eq!(stack.to_string().parse::<Stack>().unwrap(), stack);
    }

    #[test]
    fn display_local() {
        let stack = Stack::new(vec![Some("foo".into()), None]);

        assert_eq!(stack.to_string(), "foo;");
    }

    mod collapse_frames {
        use super::*;
        use pretty_assertions::assert_eq;

        #[test]
        fn collapse_named_frames() {
            let mut stack = Stack::new(vec![
                Some("foo".into()),
                Some("foo".into()),
                Some("foo".into()),
            ]);

            stack.collapse_frames();

            assert_eq!(stack.to_string(), "foo");
        }

        #[test]
        fn collapse_anonymous_frames() {
            let mut stack = Stack::new(vec![None, None]);

            stack.collapse_frames();

            assert_eq!(stack.to_string(), "");
        }

        #[test]
        fn collapse_frames_in_middle() {
            let mut stack = Stack::new(vec![
                Some("baz".into()),
                Some("bar".into()),
                Some("bar".into()),
                Some("foo".into()),
            ]);

            stack.collapse_frames();

            assert_eq!(stack.to_string(), "baz;bar;foo");
        }
    }
}