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
// Copyright (C) 2024  The Software Heritage developers
// See the AUTHORS file at the top-level directory of this distribution
// License: GNU General Public License version 3, or any later version
// See top-level LICENSE file for more information

use crate::labels::FilenameId;

/// Value in the path_stack between two lists of path parts
const PATH_SEPARATOR: FilenameId = FilenameId(u64::MAX);

/// A stack that stores sequences of [`FilenameId`] as a single array
///
/// Internally, it is a flattened array of paths. Each sequence is made of parts represented
/// by an id, and sequences are separated by PATH_SEPARATOR.
///
/// Parts are in the reverse order of insertion, to avoid lazily popping without peeking.
///
/// ```
/// use swh_graph::collections::PathStack;
/// use swh_graph::labels::FilenameId;
///
/// let path1 = [FilenameId(0), FilenameId(10)];
/// let path2 = [FilenameId(1)];
///
/// let mut stack = PathStack::new();
/// stack.push(path1);
/// stack.push(path2);
/// assert_eq!(stack.pop().unwrap().collect::<Vec<_>>(), path2);
/// assert_eq!(stack.pop().unwrap().collect::<Vec<_>>(), path1);
/// assert!(stack.pop().is_none());
/// ```
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct PathStack(Vec<FilenameId>);

impl PathStack {
    pub fn new() -> PathStack {
        PathStack(Vec::new())
    }

    /// Creates a new empty [`PathStack`] pre-allocated for this many paths plus path parts.
    pub fn with_capacity(capacity: usize) -> PathStack {
        PathStack(Vec::with_capacity(capacity))
    }

    /// Adds a path to this stack
    ///
    /// # Panics
    ///
    /// If any of the [`FilenameId`] path parts was manually built from `u64::MAX`.
    #[inline]
    pub fn push<Iter: IntoIterator<Item = FilenameId>>(&mut self, path: Iter)
    where
        <Iter as IntoIterator>::IntoIter: DoubleEndedIterator,
    {
        self.0.push(PATH_SEPARATOR);
        for item in path.into_iter().rev() {
            assert_ne!(
                item, PATH_SEPARATOR,
                "u64::MAX may not be used as path part"
            );
            self.0.push(item);
        }
    }

    /// Adds a path part to the last path on this stack
    ///
    /// # Panics
    ///
    /// If the [`FilenameId`] path parts was manually built from `u64::MAX`.
    #[inline]
    pub fn push_filename(&mut self, item: FilenameId) {
        assert_ne!(
            item, PATH_SEPARATOR,
            "u64::MAX may not be used as path part"
        );
        self.0.push(item);
    }

    /// Removes the last path of this stack
    ///
    /// Returns an iterator that pops parts from the part as the iterator is consumed.
    /// It is safe to drop the iterator without consuming it.
    #[inline]
    pub fn pop(&mut self) -> Option<PopPathStack<'_>> {
        if self.0.is_empty() {
            None
        } else {
            Some(PopPathStack(self))
        }
    }
}

/// Returned by [`PathStack::pop`]
pub struct PopPathStack<'a>(&'a mut PathStack);

impl<'a> Iterator for PopPathStack<'a> {
    type Item = FilenameId;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        if *self
            .0
             .0
            .last()
            .expect("PopPathStack reached the bottom of the stack")
            == PATH_SEPARATOR
        {
            None
        } else {
            Some(self.0 .0.pop().unwrap())
        }
    }
}

impl<'a> Drop for PopPathStack<'a> {
    fn drop(&mut self) {
        // Finish popping from the stack so a partial path does not remain on top of it
        while self
            .0
             .0
            .pop()
            .expect("PopPathStack reached the bottom of the stack")
            != PATH_SEPARATOR
        {}
    }
}

#[test]
fn test_path_stack_empty() {
    let mut stack = PathStack::new();
    assert!(stack.pop().is_none());
    assert!(stack.pop().is_none());
}

#[test]
/// Checks that dropping PopPathStack does pop the rest of the path from the stack
fn test_path_stack_drop() {
    let path1 = [FilenameId(0), FilenameId(10)];
    let path2 = [FilenameId(1)];

    let mut stack = PathStack::new();
    stack.push(path1);
    stack.push(path2);
    stack.pop().unwrap(); // not consumed
    assert_eq!(stack.pop().unwrap().collect::<Vec<_>>(), path1);
    assert!(stack.pop().is_none());
}