ftml/parsing/
depth.rs

1/*
2 * parsing/depth.rs
3 *
4 * ftml - Library to parse Wikidot text
5 * Copyright (C) 2019-2025 Wikijump Team
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21use crate::non_empty_vec::NonEmptyVec;
22use std::mem;
23
24pub type DepthList<L, T> = Vec<DepthItem<L, T>>;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum DepthItem<L, T> {
28    Item(T),
29    List(L, DepthList<L, T>),
30}
31
32#[derive(Debug)]
33struct DepthStack<L, T> {
34    finished: Vec<(L, DepthList<L, T>)>,
35    stack: NonEmptyVec<(L, Vec<DepthItem<L, T>>)>,
36}
37
38impl<L, T> DepthStack<L, T>
39where
40    L: Copy,
41{
42    #[inline]
43    pub fn new(top_ltype: L) -> Self {
44        DepthStack {
45            finished: Vec::new(),
46            stack: NonEmptyVec::new((top_ltype, Vec::new())),
47        }
48    }
49
50    pub fn increase_depth(&mut self, ltype: L) {
51        self.stack.push((ltype, Vec::new()));
52    }
53
54    pub fn decrease_depth(&mut self) {
55        let (ltype, list) = self.stack.pop().expect("No depth to pop off!");
56        self.push(DepthItem::List(ltype, list));
57    }
58
59    pub fn new_list(&mut self, ltype: L) {
60        if self.stack.is_single() {
61            // This is the last layer, so the pop/push trick doesn't work.
62            //
63            // Instead, output this entire thing as a finished list tree,
64            // then create a new one for the process to continue.
65            self.finish_depth_list(Some(ltype));
66        } else {
67            // We can just decrease and increase to make a new list
68            self.decrease_depth();
69            self.increase_depth(ltype);
70        }
71    }
72
73    fn push(&mut self, item: DepthItem<L, T>) {
74        self.stack.last_mut().1.push(item);
75    }
76
77    #[inline]
78    pub fn push_item(&mut self, item: T) {
79        self.push(DepthItem::Item(item));
80    }
81
82    #[inline]
83    pub fn last_type(&self) -> L {
84        self.stack.last().0
85    }
86
87    fn finish_depth_list(&mut self, new_ltype: Option<L>) {
88        // Wrap all opened layers
89        // Start at 1 since it's a non-empty vec
90        for _ in 1..self.stack.len() {
91            self.decrease_depth();
92        }
93
94        debug_assert!(
95            self.stack.is_single(),
96            "Open layers remain after collapsing",
97        );
98
99        // Return top-level layer
100        let (ltype, list) = {
101            // For into_trees(), we don't care what the new ltype is,
102            // so we just reuse the last one.
103            //
104            // But for new_list() we do, we want a new list layer.
105            let new_ltype = new_ltype.unwrap_or(self.stack.first().0);
106
107            mem::replace(self.stack.first_mut(), (new_ltype, Vec::new()))
108        };
109
110        // Only push if the list has elements
111        if !list.is_empty() {
112            self.finished.push((ltype, list));
113        }
114    }
115
116    pub fn into_trees(mut self) -> Vec<(L, DepthList<L, T>)> {
117        self.finish_depth_list(None);
118        self.finished
119    }
120}
121
122pub fn process_depths<I, L, T>(top_ltype: L, items: I) -> Vec<(L, DepthList<L, T>)>
123where
124    I: IntoIterator<Item = (usize, L, T)>,
125    L: Copy + PartialEq,
126{
127    let mut stack = DepthStack::new(top_ltype);
128
129    // The depth value for the previous item
130    let mut previous = 0;
131
132    // Iterate through each of the items
133    for (depth, ltype, item) in items {
134        // Add or remove new depth levels as appropriate,
135        // based on what our new depth value is compared
136        // to the value in the previous iteration.
137        //
138        // If previous == depth, then neither of these for loops will run
139        // If previous < depth, then only the first will run
140        // If previous > depth, then only the second will run
141
142        // Open new levels
143        for _ in previous..depth {
144            stack.increase_depth(ltype);
145        }
146
147        // Close existing levels
148        for _ in depth..previous {
149            stack.decrease_depth();
150        }
151
152        // Create new level if the type doesn't match
153        //
154        // Here we decrease and increase the depth to close
155        // the current layer, then make a new one with the
156        // type this item has.
157        //
158        // We'll keep appending to this remade layer until
159        // we hit a different depth or a different type.
160        if stack.last_type() != ltype {
161            stack.new_list(ltype);
162        }
163
164        // Push element and update state
165        stack.push_item(item);
166        previous = depth;
167    }
168
169    stack.into_trees()
170}
171
172#[test]
173fn depth() {
174    macro_rules! test {
175        ($depths:expr, $list:expr $(,)?) => {{
176            // Map to add unit as the level type
177            let depths: Vec<_> = $depths
178                .into_iter()
179                .map(|(depth, item)| (depth, (), item))
180                .collect();
181
182            // Get results
183            let list: Vec<DepthItem<(), char>> = $list;
184            let expected = ((), list);
185            let actual = process_depths((), depths);
186            assert_eq!(
187                actual.len(),
188                1,
189                "Actual produced finished list doesn't have exactly one element",
190            );
191
192            assert_eq!(
193                &actual[0], &expected,
194                "Actual produced depth list doesn't match expected",
195            );
196        }};
197    }
198
199    macro_rules! item {
200        ($item:expr) => {
201            DepthItem::Item($item)
202        };
203    }
204
205    macro_rules! list {
206        () => {
207            DepthItem::List((), vec![])
208        };
209        ($($x:expr),+ $(,)?) => {
210            DepthItem::List((), vec![$($x),+])
211        };
212    }
213
214    test!(
215        vec![(0, 'a')], //
216        vec![item!('a')],
217    );
218    test!(
219        vec![(0, 'a'), (0, 'b')], //
220        vec![item!('a'), item!('b')],
221    );
222    test!(
223        vec![(0, 'a'), (0, 'b'), (1, 'c')],
224        vec![item!('a'), item!('b'), list![item!('c')]]
225    );
226    test!(
227        vec![(0, 'a'), (0, 'b'), (2, 'c')],
228        vec![item!('a'), item!('b'), list![list![item!('c')]]],
229    );
230    test!(
231        vec![(1, 'a'), (1, 'b')],
232        vec![list![item!('a'), item!('b')]],
233    );
234    test!(
235        vec![(2, 'a'), (2, 'b')],
236        vec![list![list![item!('a'), item!('b')]]],
237    );
238    test!(
239        vec![(2, 'a'), (1, 'b')],
240        vec![list![list![item!('a')], item!('b')]],
241    );
242    test!(
243        vec![(5, 'a')],
244        vec![list![list![list![list![list![item!('a')]]]]]],
245    );
246    test!(
247        vec![(2, 'a'), (3, 'b'), (1, 'c'), (1, 'd'), (2, 'e'), (0, 'f')],
248        vec![
249            list![
250                list![item!('a'), list![item!('b')]],
251                item!('c'),
252                item!('d'),
253                list![item!('e')],
254            ],
255            item!('f'),
256        ],
257    );
258}
259
260#[test]
261fn depth_types() {
262    macro_rules! test {
263        ($depths:expr, $list:expr $(,)?) => {{
264            let expected: Vec<(char, Vec<DepthItem<char, char>>)> = $list;
265            let actual = process_depths(' ', $depths);
266
267            assert_eq!(
268                actual, expected,
269                "Actual produced depth list doesn't match expected",
270            );
271        }};
272    }
273
274    macro_rules! item {
275        ($item:expr) => {
276            DepthItem::Item($item)
277        };
278    }
279
280    macro_rules! list {
281        () => {
282            DepthItem::List((), vec![])
283        };
284        ($ltype:expr, $($x:expr),+ $(,)?) => {
285            DepthItem::List($ltype, vec![$($x),+])
286        };
287    }
288
289    test!(vec![], vec![]);
290    test!(
291        vec![(0, '*', 'a')], //
292        vec![('*', vec![item!('a')])],
293    );
294    test!(
295        vec![(0, '*', 'a'), (0, '*', 'b')], //
296        vec![('*', vec![item!('a'), item!('b')])],
297    );
298    test!(
299        vec![(0, '*', 'a'), (0, '#', 'b')], //
300        vec![('*', vec![item!('a')]), ('#', vec![item!('b')])],
301    );
302    test!(
303        vec![(1, '*', 'a'), (1, '#', 'b')],
304        vec![(' ', vec![list!['*', item!('a')], list!['#', item!('b')]])],
305    );
306    test!(
307        vec![(1, '*', 'a'), (1, '#', 'b'), (0, '*', 'c')],
308        vec![
309            (' ', vec![list!['*', item!('a')], list!['#', item!('b')]]),
310            ('*', vec![item!('c')]),
311        ],
312    );
313    test!(
314        vec![(2, '*', 'a'), (2, '#', 'b')],
315        vec![(
316            ' ',
317            vec![list!['*', list!['*', item!('a')], list!['#', item!('b')]]]
318        )],
319    );
320    test!(
321        vec![(0, '#', 'a'), (2, '*', 'b'), (2, '*', 'c'), (1, '*', 'd')],
322        vec![(
323            '#',
324            vec![
325                item!('a'),
326                list!['*', list!['*', item!('b'), item!('c')], item!('d')],
327            ],
328        )],
329    );
330    test!(
331        vec![(0, '#', 'a'), (0, '#', 'b'), (0, '*', 'c'), (1, '#', 'd')],
332        vec![
333            ('#', vec![item!('a'), item!('b')]),
334            ('*', vec![item!('c'), list!['#', item!('d')]]),
335        ],
336    );
337}
338
339#[cfg(test)]
340mod test {
341    use super::*;
342    use proptest::prelude::*;
343
344    macro_rules! test {
345        ($depths:expr) => {{
346            let depths_input = $depths;
347            let expected_length = depths_input.len();
348            let depth_list = process_depths(900, depths_input);
349
350            // Count all elements in the tree, recursively
351            fn count(list: &DepthList<i32, String>) -> usize {
352                list.iter()
353                    .map(|item| match item {
354                        DepthItem::Item(_) => 1,
355                        DepthItem::List(_, list) => count(&list),
356                    })
357                    .sum()
358            }
359
360            let actual_length: usize =
361                depth_list.iter().map(|(_, list)| count(list)).sum();
362
363            // Verify that it's the same as the number of elements in the original
364            assert_eq!(
365                actual_length, expected_length,
366                "Actual depth list output doesn't match input",
367            );
368        }};
369    }
370
371    fn arb_depth(max_depth: usize) -> impl Strategy<Value = Vec<(usize, i32, String)>> {
372        prop::collection::vec((0..max_depth, 901..902, "[a-z]{3}"), 0..100)
373    }
374
375    proptest! {
376        #![proptest_config(ProptestConfig::with_cases(4096))]
377
378        #[test]
379        #[ignore = "slow test"]
380        fn deep_depth_prop(depths in arb_depth(128)) {
381            test!(depths);
382        }
383
384        #[test]
385        #[ignore = "slow test"]
386        fn shallow_depth_prop(depths in arb_depth(4)) {
387            test!(depths);
388        }
389    }
390}