swh_graph_stdlib/collections/
paths.rs

1// Copyright (C) 2024  The Software Heritage developers
2// See the AUTHORS file at the top-level directory of this distribution
3// License: GNU General Public License version 3, or any later version
4// See top-level LICENSE file for more information
5
6use swh_graph::labels::LabelNameId;
7
8/// Value in the path_stack between two lists of path parts
9const PATH_SEPARATOR: LabelNameId = LabelNameId(u64::MAX);
10
11/// A stack that stores sequences of [`LabelNameId`] as a single array
12///
13/// Internally, it is a flattened array of paths. Each sequence is made of parts represented
14/// by an id, and sequences are separated by PATH_SEPARATOR.
15///
16/// Parts are in the reverse order of insertion, to avoid lazily popping without peeking.
17///
18/// ```
19/// use swh_graph::labels::LabelNameId;
20/// use swh_graph_stdlib::collections::PathStack;
21///
22/// let path1 = [LabelNameId(0), LabelNameId(10)];
23/// let path2 = [LabelNameId(1)];
24///
25/// let mut stack = PathStack::new();
26/// stack.push(path1);
27/// stack.push(path2);
28/// assert_eq!(stack.pop().unwrap().collect::<Vec<_>>(), path2);
29/// assert_eq!(stack.pop().unwrap().collect::<Vec<_>>(), path1);
30/// assert!(stack.pop().is_none());
31/// ```
32#[derive(Debug, Clone, Eq, PartialEq, Default)]
33pub struct PathStack(Vec<LabelNameId>);
34
35impl PathStack {
36    pub fn new() -> PathStack {
37        PathStack(Vec::new())
38    }
39
40    /// Creates a new empty [`PathStack`] pre-allocated for this many paths plus path parts.
41    pub fn with_capacity(capacity: usize) -> PathStack {
42        PathStack(Vec::with_capacity(capacity))
43    }
44
45    /// Adds a path to this stack
46    ///
47    /// # Panics
48    ///
49    /// If any of the [`LabelNameId`] path parts was manually built from `u64::MAX`.
50    #[inline]
51    pub fn push<Iter: IntoIterator<Item = LabelNameId>>(&mut self, path: Iter)
52    where
53        <Iter as IntoIterator>::IntoIter: DoubleEndedIterator,
54    {
55        self.0.push(PATH_SEPARATOR);
56        for item in path.into_iter().rev() {
57            assert_ne!(
58                item, PATH_SEPARATOR,
59                "u64::MAX may not be used as path part"
60            );
61            self.0.push(item);
62        }
63    }
64
65    /// Adds a path part to the last path on this stack
66    ///
67    /// # Panics
68    ///
69    /// If the [`LabelNameId`] path parts was manually built from `u64::MAX`.
70    #[inline]
71    pub fn push_filename(&mut self, item: LabelNameId) {
72        assert_ne!(
73            item, PATH_SEPARATOR,
74            "u64::MAX may not be used as path part"
75        );
76        self.0.push(item);
77    }
78
79    /// Removes the last path of this stack
80    ///
81    /// Returns an iterator that pops parts from the part as the iterator is consumed.
82    /// It is safe to drop the iterator without consuming it.
83    #[inline]
84    pub fn pop(&mut self) -> Option<PopPathStack<'_>> {
85        if self.0.is_empty() {
86            None
87        } else {
88            Some(PopPathStack(self))
89        }
90    }
91}
92
93/// Returned by [`PathStack::pop`]
94pub struct PopPathStack<'a>(&'a mut PathStack);
95
96impl Iterator for PopPathStack<'_> {
97    type Item = LabelNameId;
98
99    #[inline]
100    fn next(&mut self) -> Option<Self::Item> {
101        if *self
102            .0
103             .0
104            .last()
105            .expect("PopPathStack reached the bottom of the stack")
106            == PATH_SEPARATOR
107        {
108            None
109        } else {
110            Some(self.0 .0.pop().unwrap())
111        }
112    }
113}
114
115impl Drop for PopPathStack<'_> {
116    fn drop(&mut self) {
117        // Finish popping from the stack so a partial path does not remain on top of it
118        while self
119            .0
120             .0
121            .pop()
122            .expect("PopPathStack reached the bottom of the stack")
123            != PATH_SEPARATOR
124        {}
125    }
126}
127
128#[test]
129fn test_path_stack_empty() {
130    let mut stack = PathStack::new();
131    assert!(stack.pop().is_none());
132    assert!(stack.pop().is_none());
133}
134
135#[test]
136/// Checks that dropping PopPathStack does pop the rest of the path from the stack
137fn test_path_stack_drop() {
138    let path1 = [LabelNameId(0), LabelNameId(10)];
139    let path2 = [LabelNameId(1)];
140
141    let mut stack = PathStack::new();
142    stack.push(path1);
143    stack.push(path2);
144    stack.pop().unwrap(); // not consumed
145    assert_eq!(stack.pop().unwrap().collect::<Vec<_>>(), path1);
146    assert!(stack.pop().is_none());
147}