dsa_rust/hierarchies/unsafe_linked_general_tree.rs
1#![allow(unused)]
2/*! An unsafe, linked, n-ary tree implementation
3
4# About
5Following classical DSA curricula, this implementation relies primarily on pointers for the structure's composition and navigation.
6
7See the module's companion [MD tree](`crate::hierarchies::unsafe_linked_general_tree::md_tree`) tool, which takes a Markdown document and prints a hierarchical tree diagram of its heading contents.
8
9# Design
10The base [GenTree] structure only contains basic operations for constructors and metadata retrieval. Most of the magic happens in the [CursorMut] struct. Both structs rely on a [Position] struct, which provides a safe handle to all the raw pointers required to make the tree go brrr.
11
12The design makes heavy use of `unsafe` code via raw pointers. The module represents a two-fold exercise: writing recursive traversal functions and understanding Rust's ownership model well enough to avoid falling back on tricks such as reference counting ([std::rc::Rc]/[std::sync::Arc]) or arena-like allocation with safe indexing ([Vec]) to manage the structure's operations safely.
13
14Notably, this structure enforces strict lifetime constraints on cursors and positions to lock them to the base tree allocation, utilizing Rust's variance rules to systematically prevent pointer dangling, cross-tree pointer smuggling, and use-after-free errors.
15
16In addition to leveraging lifetimes for safety bounds, the module also uses [Cell] for interior mutability on [CursorMut]. This allows cursor navigation methods to retain immutable `&self` borrows for a more ergonomic API.
17*/
18
19use std::marker::PhantomData;
20use std::ptr::NonNull;
21
22/// Represents the actual data structure. Currently this struct only
23/// has constructor methods to create a new tree and create new cursor
24/// handles which provide the lion's share of tree operations.
25///
26/// See [module-level documentation](`crate::hierarchies::unsafe_linked_general_tree`)
27/// for more details.
28pub struct GenTree<T> {
29 root: NonNull<Node<T>>, // Private for safety reasons
30}
31impl<T> Default for GenTree<T> {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36impl<T> GenTree<T> {
37 /// Instantiates a new Tree with a default root
38 pub fn new() -> GenTree<T> {
39 // Allocate the root node on the heap and get a raw pointer to it
40 // SAFETY: Box::into_raw is guaranteed to return a valid,
41 // non-null pointer because Box::new panics rather than
42 // returning null on allocation failure.
43 let root_node = unsafe {
44 NonNull::new_unchecked(Box::into_raw(Box::new(Node {
45 //parent: std::ptr::null_mut(),
46 parent: None,
47 children: Vec::new(),
48 data: None, // Root starts empty
49 })))
50 };
51
52 GenTree { root: root_node }
53 }
54
55 pub fn is_empty(&self) -> bool {
56 // SAFETY: Even empty trees have an initialized root
57 unsafe { (*self.root.as_ptr()).children.len() == 0 }
58 }
59
60 /// Creates a `CursorMut<T>` starting at the tree's root
61 /// NOTE: The lifetime 'a is implicitly tied from `&'a mut self`
62 /// to the returned `CursorMut<'a, T>`
63 pub fn cursor_mut(&mut self) -> CursorMut<'_, T> {
64 // Safety: self.root was allocated in GenTree::new() and is guaranteed not to be null
65 //let non_null_root = unsafe { NonNull::new_unchecked(self.root) };
66 let non_null_root = self.root;
67
68 CursorMut {
69 node: Cell::new(non_null_root),
70 //tree: self,
71 _marker: PhantomData,
72 }
73 }
74
75 // SAFETY: Allows critical use-after-free errors!!
76 //pub fn cursor_mut_from(&mut self, node: NonNull<Node<T>>) -> CursorMut<'_, T> {
77 // // Safety: self.root was allocated in GenTree::new() and is guaranteed not to be null
78 // //let non_null_root = unsafe { NonNull::new_unchecked(self.root) };
79 // let non_null_root = node;
80
81 // CursorMut {
82 // node: Cell::new(non_null_root),
83 // tree: self,
84 // _marker: PhantomData,
85 // }
86 //}
87
88 /** Exposes a read-only Position at the root node */
89 fn root(&self) -> Position<'_, T> {
90 // Safety: self.root is guaranteed valid
91 //let non_null_root = unsafe { NonNull::new_unchecked(self.root) };
92 let non_null_root = self.root;
93 Position {
94 node: non_null_root,
95 _marker: PhantomData,
96 }
97 }
98}
99// Required because Rust doesn't automatically drop heap allocations for
100// raw pointers (NonNull<Node<T>>)
101impl<T> Drop for GenTree<T> {
102 fn drop(&mut self) {
103 // self.root: NonNull<Node<T>>
104 let mut stack = vec![];
105 let mut node = unsafe { Box::from_raw(self.root.as_ptr()) };
106 stack.append(&mut node.children);
107
108 // self.root: *mut T
109 //if self.root.is_null() {
110 // return;
111 //}
112
113 //// Use an iterative stack-based teardown to prevent stack
114 //// overflows on deep trees
115 //let mut stack = vec![self.root];
116
117 while let Some(node_ptr) = stack.pop() {
118 unsafe {
119 // 1. Snatch the children vector out of the
120 // node before destroying it
121 //let mut current_node = Box::from_raw(node_ptr);
122 let mut current_node = Box::from_raw(node_ptr.as_ptr());
123
124 // 2. Push all child pointers onto our teardown stack
125 stack.append(&mut current_node.children);
126
127 // 3. current_node naturally goes out of scope
128 // here, freeing its allocation
129 // and dropping its inner data (T) safely.
130 }
131 }
132 }
133}
134
135/// Has no methods, but `Position` has methods to derive and create `Node`s.
136///
137/// See [module-level documentation](`crate::hierarchies::unsafe_linked_general_tree`)
138/// for more details.
139#[derive(Clone)]
140pub struct Node<T> {
141 parent: Option<NonNull<Node<T>>>,
142 children: Vec<NonNull<Node<T>>>,
143 data: Option<T>,
144}
145
146/// The `Position` struct serves as the module's safe public handle to
147/// individual nodes in the tree. This struct requires a shared
148/// lifetime 'a tied to the underlying tree.
149///
150/// See [module-level documentation](`crate::hierarchies::unsafe_linked_general_tree`)
151/// for more details.
152struct Position<'a, T> {
153 node: NonNull<Node<T>>,
154 _marker: PhantomData<&'a GenTree<T>>,
155}
156impl<'a, T> Position<'a, T> {
157 pub fn get_data(&self) -> Option<&T> {
158 unsafe { self.node.as_ref().data.as_ref() }
159 }
160
161 pub fn get_children(&self) -> Vec<Position<'a, T>> {
162 unsafe {
163 let v = &self.node.as_ref().children;
164 v.iter().map(|x| Position::from_ptr(*x)).collect()
165 }
166 }
167
168 // Internal utility for iterator
169 fn get_children_iter(&self) -> Vec<Position<'a, T>> {
170 unsafe {
171 let v = &self.node.as_ref().children;
172 v.iter().map(|x| Position::from_ptr(*x)).collect()
173 }
174 }
175
176 // Private for safety; aint nobody should have a NonNull ptr!
177 fn from_ptr(ptr: NonNull<Node<T>>) -> Position<'a, T> {
178 Position {
179 node: ptr,
180 _marker: PhantomData,
181 }
182 }
183
184 // SAFETY: Enables pointer smuggling
185 // A safe way to expose the underlying pointer for assert_eq!
186 //pub fn as_ptr(&self) -> NonNull<Node<T>> {
187 // self.node
188 //}
189}
190// Implement Clone so "let curr = cursor.current().clone();" works
191impl<'a, T> Clone for Position<'a, T> {
192 fn clone(&self) -> Self {
193 Position {
194 node: self.node,
195 _marker: PhantomData,
196 }
197 }
198}
199
200use std::cell::Cell;
201
202/// CursorMut takes 'a to tie it to the tree's lifetime
203/// which prevents dangling pointers by gating scopes
204///
205/// See [module-level documentation](`crate::hierarchies::unsafe_linked_general_tree`)
206/// for more details.
207pub struct CursorMut<'a, T> {
208 node: Cell<NonNull<Node<T>>>,
209 //tree: &'a mut GenTree<T>,
210 _marker: std::marker::PhantomData<&'a mut &'a ()>,
211}
212impl<'a, T> CursorMut<'a, T> {
213 // Utilities
214 ////////////
215
216 pub fn get_data(&self) -> Option<&T> {
217 //unsafe { self.node.as_ref().data.as_ref() }
218 unsafe { self.node.get().as_ref().data.as_ref() }
219 }
220
221 pub fn is_none(&self) -> bool {
222 //unsafe { self.node.as_ref().data.is_none() }
223 unsafe { self.node.get().as_ref().data.is_none() }
224 }
225
226 pub fn is_some(&self) -> bool {
227 !self.is_none()
228 }
229
230 pub fn num_children(&self) -> usize {
231 //unsafe { self.node.as_ref().children.len() }
232 unsafe { self.node.get().as_ref().children.len() }
233 }
234
235 fn current(&self) -> Position<'a, T> {
236 Position {
237 //node: self.node,
238 node: self.node.get(),
239 _marker: PhantomData,
240 }
241 }
242
243 //fn children(&self) -> Vec<Position<'a, T>> {
244 // // SAFETY: All nodes should have a valid node.children()
245 // unsafe {
246 // //self.node.as_ref().children.iter()
247 // self.node
248 // .get()
249 // .as_ref()
250 // .children
251 // .iter()
252 // .map(|&ptr| Position {
253 // //node: NonNull::new_unchecked(ptr),
254 // node: ptr,
255 // _marker: PhantomData,
256 // })
257 // .collect()
258 // }
259 //}
260
261 // SAFETY: Breaks uniqueness invariants
262 //pub fn get_tree(&mut self) -> &mut GenTree<T> {
263 // self.tree
264 //}
265
266 // Navigation
267 /////////////
268
269 // /// Uses Cell for interior mutability in order to retain signature semantics
270 // /// such that navigation methods retain &self borrows.
271 //pub fn jump(&self, pos: &Position<'a, T>) {
272 // //self.node = pos.node;
273 // self.node.update(|_| pos.node);
274 //}
275 // Updates to take mutable reference, for safety :)
276 //pub fn jump(&mut self, pos: &Position<'a, T>) {
277 // //self.node = pos.node;
278 // self.node.update(|_| pos.node);
279 //}
280
281 pub fn children_iter(&self) -> ChildIter<'a, T> {
282 ChildIter {
283 parent_ptr: self.node.get(),
284 index: 0,
285 _marker: PhantomData
286 }
287 }
288
289 /// Uses Cell for interior mutability in order to retain signature semantics
290 /// such that navigation methods retain &self borrows.
291 //pub fn ascend(&self) -> Result<(), &str> {
292 pub fn ascend(&mut self) -> Result<(), &str> {
293 unsafe {
294 //let parent_ptr = self.node.as_ref().parent;
295 let parent_ptr = self.node.get().as_ref().parent;
296 //if parent_ptr.is_none() {
297 // Err("Cannot ascend past root")
298 //} else {
299 // //self.node = NonNull::new_unchecked(parent_ptr);
300 // //self.node = parent_ptr.unwrap();
301 // self.node.update(|_| parent_ptr.unwrap());
302 // Ok(())
303 //}
304 if let Some(val) = parent_ptr {
305 self.node.update(|_| val);
306 Ok(())
307 } else {
308 Err("Cannot ascend past root")
309 }
310 }
311 }
312
313 /// Uses a closure to descend to the correct child node.
314 /// Ex:
315 /// ```rust
316 /// ```
317 pub fn descend<F>(&mut self, predicate: F) -> Result<(), &str>
318 where
319 F: Fn(&T) -> bool,
320 T: 'a
321 {
322 // 1. Get the iterator of children
323 // 2. Find the first child that satisfies the predicate
324 // 3. Update 'self' (the cursor) to point to that child
325
326 let target_child = self.children_iter()
327 .find(|&child| predicate(child));
328
329 if let Some(node) = target_child {
330 // Here you update your internal cursor state.
331 // Assuming your cursor stores a pointer or reference:
332 //self.node = node;
333 Ok(())
334 } else {
335 Err("No child found matching the criteria")
336 }
337 }
338 // Mutation
339 ///////////
340
341 /// Creates and adds a child `Node` to the position of the cursor,
342 /// then moves the cursor to the new child `Node`.
343 pub fn add_child(&mut self, data: T) {
344 unsafe {
345 //let current_ptr = self.node.as_ptr();
346 let current_ptr = self.node.get();
347 //let new_node = Box::into_raw(Box::new(Node {
348 let new_node = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
349 //parent: Some(current_ptr),
350 parent: Some(current_ptr),
351 children: Vec::new(),
352 data: Some(data),
353 })));
354 //self.node.as_mut().children.push(new_node);
355 self.node.get().as_mut().children.push(new_node);
356
357 // Sets the cursor to the new child node
358 self.node.set(new_node);
359 }
360 }
361
362 pub fn delete(&mut self) -> Option<T> {
363 unsafe {
364 //let current_ptr = self.node.as_ptr();
365 let current_ptr = self.node.get();
366 //let parent_ptr = self.node.as_ref().parent;
367 let parent_ptr = self.node.get().as_ref().parent;
368
369 //if parent_ptr.is_null() {
370 //if parent_ptr.is_none() {
371 // return None;
372 //}
373 parent_ptr?;
374
375 //let parent = &mut *parent_ptr;
376 let parent = parent_ptr.unwrap().as_mut();
377
378 if let Some(pos) = parent.children.iter().position(|&x| x == current_ptr) {
379
380 parent.children.remove(pos);
381
382 //let mut orphans = std::mem::take(&mut self.node.as_mut().children);
383 let mut orphans = std::mem::take(&mut self.node.get().as_mut().children);
384 for &orphan in &orphans {
385 //(*orphan).parent = parent_ptr;
386 (*orphan.as_ptr()).parent = parent_ptr;
387 }
388 parent.children.append(&mut orphans);
389 }
390
391 //self.node = NonNull::new_unchecked(parent_ptr);
392 //self.node = parent_ptr.unwrap();
393 self.node = Cell::new(parent_ptr.unwrap());
394
395 //let boxed_node = Box::from_raw(current_ptr);
396 let boxed_node = Box::from_raw(current_ptr.as_ptr());
397 boxed_node.data
398 }
399 }
400}
401pub struct ChildIter<'a, T> {
402 // pub struct Node<T> {
403 // parent: Option<NonNull<Node<T>>>,
404 // children: Vec<NonNull<Node<T>>>,
405 // data: Option<T>,
406 //}
407 parent_ptr: NonNull<Node<T>>,
408 index: usize,
409 // Ensures correct lifetime tracking
410 _marker: std::marker::PhantomData<&'a T>,
411}
412impl<'a, T> Iterator for ChildIter<'a, T> {
413
414 // Yields raw mutable pointers to the children so you can use them to move the cursor
415 type Item = &'a T;
416
417 fn next(&mut self) -> Option<Self::Item> {
418 unsafe {
419 let parent_ref = self.parent_ptr.as_ref();
420 let child = parent_ref.children.get(self.index).copied();
421 match child {
422 Some(val) => {
423 self.index += 1;
424 Some(val.as_ref().data.as_ref().unwrap())
425 }
426 None => None
427 }
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434
435 use super::*;
436
437 #[test]
438 /** Creates this tree to test properties
439 []
440 ├── Landlocked
441 │ ├── Switzerland
442 │ │ └── Geneva
443 │ │ └── Old Town
444 │ │ └── Cathédrale Saint-Pierre
445 │ └── Bolivia
446 │ └── []
447 │ └── []
448 │ ├── Puerta del Sol
449 │ └── Puerta de la Luna
450 └── Islands
451 ├── Marine
452 │ └── Australia
453 └── Fresh Water
454 */
455 fn basic() {
456 //use super::{md_tree, md_tree::Heading, GenTree, Position};
457 use crate::hierarchies::unsafe_linked_general_tree::{md_tree, md_tree::Heading};
458 use crate::hierarchies::unsafe_linked_general_tree::{GenTree, Position};
459 let tree_vec = vec![
460 Heading {
461 level: 2,
462 title: "Landlocked".to_string(),
463 },
464 Heading {
465 level: 3,
466 title: "Switzerland".to_string(),
467 },
468 Heading {
469 level: 4,
470 title: "Geneva".to_string(),
471 },
472 Heading {
473 level: 5,
474 title: "Old Town".to_string(),
475 },
476 Heading {
477 level: 6,
478 title: "Cathédrale Saint-Pierre".to_string(),
479 },
480 Heading {
481 level: 3,
482 title: "Bolivia".to_string(),
483 },
484 Heading {
485 level: 6,
486 title: "Puerta del Sol".to_string(),
487 },
488 Heading {
489 level: 6,
490 title: "Puerta de la Luna".to_string(),
491 },
492 Heading {
493 level: 2,
494 title: "Islands".to_string(),
495 },
496 Heading {
497 level: 3,
498 title: "Marine".to_string(),
499 },
500 Heading {
501 level: 4,
502 title: "Australia".to_string(),
503 },
504 Heading {
505 level: 3,
506 title: "Fresh Water".to_string(),
507 },
508 ];
509
510 // Constructs tree ignoring the first heading
511 let mut tree: GenTree<Heading> = md_tree::construct(1, tree_vec);
512
513 ////////////////////////////
514 // TESTS CURSOR FUNCTIONS //
515 ////////////////////////////
516
517// let mut cursor = tree.cursor_mut();
518// // Tests that root is empty with is_some() and is_none()
519// assert!(!cursor.is_some());
520// assert!(cursor.is_none());
521// // Tests root() -> Position<T>
522// assert_eq!(cursor.node.as_ptr().ok(), tree.root().as_ptr().ok());
523// assert_eq!(cursor.node.as_ptr().ok(), tree.root().as_ptr().ok());
524//
525// // Tests num_children()
526// assert_eq!(cursor.num_children(), 2); // Root has [Landlocked, Islands]
527//
528// // Tests children(), jump(), and get_data()
529// let kids = cursor.children();
530// let mut kids_iter = kids.iter();
531// let root: Option<&Heading> = cursor.children_iter().next();
532// assert_eq!(*root.unwrap().title, "Landlocked".to_string());
533//
534// //cursor.jump(kids_iter.next().unwrap()); // Moves to first child
535// let curr: Position<Heading> = cursor.current().clone(); // Passes the torch
536// let data = cursor.get_data().unwrap();
537// assert_eq!(*data.title, "Islands".to_string());
538//
539// // Jumps down a generation to [Marine, Fresh Water]
540// cursor.jump(&curr);
541// let new_kids = cursor.children();
542// let mut kids_iter = new_kids.iter();
543// cursor.jump(kids_iter.next().unwrap()); // Moves to first child
544// let data = cursor.get_data().unwrap();
545// assert_eq!(*data.title, "Marine".to_string());
546//
547// // Jumps down a generation, for fun
548// let new_kids = cursor.children(); // Gets cursor's chidlren
549// let mut kids_iter = new_kids.iter(); // Creates an iterator
550// cursor.jump(kids_iter.next().unwrap()); // Moves to first child
551// let data = cursor.get_data().unwrap();
552// assert_eq!(*data.title, "Australia".to_string());
553//
554// // Tests ascend()
555// assert!(cursor.ascend().is_ok()); // Marine
556// assert!(cursor.ascend().is_ok()); // Islands
557// let data = cursor.get_data().unwrap();
558// assert_eq!(*data.title, "Islands".to_string());
559// assert!(cursor.ascend().is_ok()); // []
560// assert!(cursor.ascend().is_err()); // Cannot ascend() past root
561// //assert!(cursor.is_root()); // Double checks, just in case
562//
563// // Descends to Islands to test delete()
564// let kids = cursor.children(); // Gets cursor's chidlren
565// let mut kids_iter = kids.iter(); // Creates an iterator
566// cursor.jump(kids_iter.next().unwrap()); // Moves to Landlocked
567// cursor.jump(kids_iter.next().unwrap()); // Moves to Islands
568// let data = cursor.get_data().unwrap();
569// assert_eq!(*data.title, "Islands".to_string());
570//
571// // Tests delete()
572// // Creates placeholder Heading
573// let mut deleted = Heading {
574// title: String::new(),
575// level: 0,
576// };
577// // Iterates through the child position's under the cursor
578// // looking for a matching Heading; Once found, jumps to that position,
579// // and deletes the Heading; The delete() operation automatically jumps
580// // the cursor to the parent of the deleted position
581// for position in cursor.children() {
582// if position.get_data().unwrap().title == "Marine" {
583// //cursor.jump(&position);
584// deleted = cursor.delete().unwrap();
585// }
586// }
587// // Tests that the correct Heading was deleted
588// assert_eq!(deleted.level, 3);
589// assert_eq!(deleted.title, "Marine".to_string());
590//
591// // Tests that the cursor got bumped up to Islands
592// let data = cursor.get_data().unwrap();
593// assert_eq!(data.title, "Islands".to_string());
594//
595// // Tests that the Islands node has the correct children
596// let mut kids = Vec::new();
597// assert_eq!(cursor.children().len(), 2);
598// for child in cursor.children() {
599// let title = child.get_data().unwrap().title.clone();
600// kids.push(title)
601// }
602// assert_eq!(kids, ["Fresh Water".to_string(), "Australia".to_string()]);
603
604 // Print debug, uncomment panic to print
605 md_tree::pretty_print("TEST", &tree);
606 //panic!();
607 }
608
609 // Pointer smuggling & (lifetime) covariance holes
610 //////////////////////////////////////////////////
611
612 #[test]
613 #[allow(unused)]
614 fn test_use_after_free() {
615
616 // 1)
617 // This test verifies that the `_marker: PhantomData<&'a mut &'a ()>`
618 // successfully enforces invariance on CursorMut.
619 // If your invariance fix works perfectly, THIS TEST MUST FAIL TO COMPILE.
620 use crate::hierarchies::unsafe_linked_general_tree::{md_tree, md_tree::Heading};
621 use crate::hierarchies::unsafe_linked_general_tree::{GenTree, Position};
622 let one = vec![
623 Heading {
624 level: 1,
625 title: "Landlocked".to_string(),
626 },
627 Heading {
628 level: 2,
629 title: "Switzerland".to_string(),
630 },
631 ];
632 let two = vec![
633 Heading {
634 level: 1,
635 title: "Bolivia".to_string(),
636 },
637 Heading {
638 level: 2,
639 title: "Zimbabwe".to_string(),
640 },
641 ];
642
643 let mut outer_tree: GenTree<Heading> = md_tree::construct(0, one);
644 let cursor = outer_tree.cursor_mut();
645 {
646 //let inner_tree: GenTree<String> = GenTree::new();
647 let inner_tree: GenTree<Heading> = md_tree::construct(0, two);
648 // COMPILER ERROR EXPECTED HERE:
649 // inner_tree does not live long enough. Invariance stops the compiler
650 // from shrinking `cursor`'s lifetime to match `inner_tree`.
651 let inner_pos = inner_tree.root();
652 //cursor.jump(&inner_pos); // Illegal with multiple mutable borrows
653 }
654 // If it compiled, this would be a use-after-free,
655 // but its illegal because CursorMut is invariant
656 cursor.get_data();
657
658 // CRITICAL ERROR: solved
659 let mut tree1: GenTree<&str> = GenTree::new();
660 let mut tree2: GenTree<&str> = GenTree::new();
661 let pos1: Position<'_, _> = tree1.cursor_mut().current();
662 //let ptr = pos1.as_ptr(); // Allows pointer smuggling
663 drop(tree1);
664 //let pos2 = tree2.cursor_mut_from(ptr); // Illegal pointer smuggling
665 let pos2 = tree2.cursor_mut(); // Legal
666 let _ = pos2.get_data(); // Avoided use-after-free
667
668 // CRITICAL ERROR: pending
669 let mut tree: GenTree<i32> = GenTree::new();
670 let mut cursor = tree.cursor_mut();
671 cursor.add_child(42);
672 // Navigate to child and capture its Position
673 //let child_pos = cursor.children()[0].clone();
674 //cursor.jump(&child_pos);
675 // Assert data is there
676 assert_eq!(cursor.get_data(), Some(&42));
677 //assert_eq!(child_pos.get_data(), Some(&42));
678 // Now drop/delete the current node using &mut self mutation
679 let deleted_data = cursor.delete();
680 assert_eq!(deleted_data, Some(42));
681 // cursor.delete() must internally reset cursor.node to a safe fallback
682 // such as the parent node or the tree's root. Otherwise this triggers UB
683 // by pointing to the freed node.
684 cursor.get_data();
685 // SAFETY: The borrow checker cannot protect child_pos from becoming stale
686 // because it has the same lifetime as the cursor and the tree itself.
687 // This is an inherent risk of keeping long-lived Positions.
688 // Consider removing Position or adding some reference counting?
689 //let _ = child_pos.get_data(); // Expected Miri error if unhandled
690 }
691
692 #[test]
693 fn test_ascend_past_deleted_parent() {
694 // Illegal out-of-bounds indexing!
695 //let i: usize = Vec::new()[0];
696 //assert_eq!(i, 0);
697
698 let mut tree: GenTree<&str> = GenTree::new();
699 let mut cursor = tree.cursor_mut();
700
701 cursor.add_child("parent");
702 //let parent_pos = cursor.children()[0].clone();
703 //cursor.jump(&parent_pos);
704
705 cursor.add_child("child");
706 //let child_pos = cursor.children()[0].clone();
707
708 // Delete parent node while cursor is aware
709 //cursor.jump(&parent_pos);
710 cursor.delete();
711
712 // Reposition cursor to the child (if it was preserved/re-linked)
713 // Verify that ascend safely handles situations where raw pointers are invalidated
714 //cursor.jump(&child_pos);
715 if let Err(e) = cursor.ascend() {
716 assert_eq!(e, "Cannot ascend past root"); // Or your custom orphan error handler
717 }
718 }
719
720 // Null pointer dereferences & bounds testing
721 /////////////////////////////////////////////
722
723 #[test]
724 fn test_ascend_past_root_error_handling() {
725 let mut tree: GenTree<f64> = GenTree::new();
726 let mut cursor = tree.cursor_mut();
727
728 // Verify root node has no parent and safely returns Err instead of null deref
729 let result = cursor.ascend(); // Illegal multiple mutable borrow
730 assert!(result.is_err());
731 assert_eq!(result.unwrap_err(), "Cannot ascend past root");
732 }
733
734 #[test]
735 fn test_empty_root_data_handling() {
736 let tree: GenTree<i32> = GenTree::new();
737 let root_pos = tree.root();
738
739 // Verify the un-initialized data option returns None safely without a null deref
740 assert_eq!(root_pos.get_data(), None);
741 }
742
743 // Memory aliasing & vector invalidations
744 /////////////////////////////////////////
745
746 #[test]
747 fn test_children_vector_reallocation_aliasing() {
748 let mut tree: GenTree<usize> = GenTree::new();
749 let mut cursor = tree.cursor_mut();
750
751 // Collect positions of children
752 cursor.add_child(1);
753 //let first_child_pos = cursor.children()[0].clone();
754
755 // Mass-push items to force the internal Vec<NonNull<Node<T>>> to reallocate
756 // its capacity, moving its backing buffer elsewhere in memory.
757 for i in 2..100 {
758 cursor.add_child(i);
759 }
760
761 // Verify that tracking pointers remain valid or that the tree layout
762 // does not break parental pointer linkage due to backing array growth.
763 //cursor.jump(&first_child_pos);
764 //assert_eq!(cursor.get_data(), Some(&1));
765 assert_eq!(cursor.get_data(), Some(&99));
766 }
767
768 #[test]
769 fn test_mut_exclusivity() {
770 // Enforces that structural adjustments cannot happen if read-only structures
771 // are actively interacting across restricted blocks.
772 let mut tree: GenTree<char> = GenTree::new();
773
774 {
775 let mut cursor = tree.cursor_mut();
776 cursor.add_child('A');
777 } // cursor drops here, relinquishing exclusive access to tree
778
779 let root_pos = tree.root();
780 assert_eq!(root_pos.get_data(), None);
781
782 // This line would fail to compile if root_pos held a mutable borrow,
783 // confirming that shared read-only states don't collide with subsequent allocations.
784 let mut _cursor_two = tree.cursor_mut();
785 }
786}
787
788pub mod md_tree {
789/*! A handy little tool to create tree diagrams from MD headings
790
791# About
792This module sits on top of the [GenTree](`crate::hierarchies::unsafe_linked_general_tree`) structure and contains five functions:
793- A top-level [navigator] function that takes a [Path] and a level setting to indicate the level that the output drawing should start at
794- A [parse] function that takes a [Path] and outputs a list of headings
795- A [construct] function that builds the `GenTree`
796- A [pretty_print] function that traverses the tree and prints the contents to terminal
797
798The overall output should look something like this,
799```text
800📄 /document.md
801 │
802 ├── Landlocked
803 │ ├── Switzerland
804 │ │ └── Geneva
805 │ │ └── Old Town
806 │ │ └── Cathédrale Saint-Pierre
807 │ └── Bolivia
808 │ └── []
809 │ └── []
810 │ ├── Puerta del Sol
811 │ └── Puerta de la Luna
812 └── Islands
813 ├── Fresh Water
814 └── Australia
815```
816
817# Design
818This is mostly just an excuse to write recursive tree traversal functions. All functions but the parsing function utilize recursion.
819
820**/
821
822 use regex::Regex;
823 use std::fs::File;
824 use std::io::{BufRead, BufReader};
825
826 //use crate::hierarchies::unsafe_linked_general_tree::{CursorMut, GenTree};
827 use crate::hierarchies::unsafe_linked_general_tree::{GenTree, Position};
828 use std::path::Path;
829
830 #[derive(Debug, PartialEq)]
831 pub struct Heading {
832 pub level: usize,
833 pub title: String,
834 }
835 impl Heading {
836 /** Just a humble Heading md_tree */
837 fn new(title: String, level: usize) -> Heading {
838 Heading { level, title }
839 }
840 }
841
842 /** Takes a path to a Markdown file, parses it for title and headings,
843 and returns a tuple containing the document title and a vector of
844 headings.
845
846 Note: The document title portion of the tuple is specifically
847 designed for the Astro-formatted frontmatter of each MD document. */
848 fn parse(root: &Path) -> (String, Vec<Heading>) {
849 // Regex for capturing the title from front matter
850 let t = Regex::new(r"(?ms)^---.*?^title:\s*(.+?)\s*$.*?^---").unwrap();
851 let mut doc_title = String::new();
852 // Regex for capturing headings H1-H6 as #-######
853 let h = Regex::new(r"^(#{1,6})\s+(.*)").unwrap();
854 let mut headings: Vec<Heading> = Vec::new();
855
856 // Read input
857 let file_path = root;
858 let file = File::open(file_path).unwrap(); // TODO: Fix lazy error handling
859 let reader = BufReader::new(file);
860
861 // Read the entire file into a single string
862 // Imperative style
863 let mut content = String::new();
864 for line_result in reader.lines() {
865 let line = line_result.unwrap();
866 if !content.is_empty() {
867 content.push('\n');
868 }
869 content.push_str(&line);
870 }
871 // Functional style
872 //let content: String = reader
873 // .lines()
874 // .map(|l| l.unwrap())
875 // .collect::<Vec<_>>()
876 // .join("\n");
877
878 // Extract the document title
879 if let Some(captures) = t.captures(&content) {
880 let title = captures.get(1).unwrap().as_str();
881 doc_title.push_str(title);
882 }
883
884 // Parse headings line by line
885 for line in content.lines() {
886 if let Some(captures) = h.captures(line) {
887 let level = captures.get(1).unwrap().as_str().len();
888 let text = captures.get(2).unwrap().as_str().to_string();
889 headings.push(Heading { level, title: text });
890 }
891 }
892
893 (doc_title, headings)
894 }
895
896 /** Constructs a tree of Heading types */
897 pub fn construct(mut cur_level: usize, data: Vec<Heading>) -> GenTree<Heading> {
898 let mut tree: GenTree<Heading> = GenTree::<Heading>::new();
899 let mut cursor = tree.cursor_mut();
900
901 for node in data {
902 let data_level = node.level;
903
904 // Case 1: Add child directly (level increases by 1)
905 if data_level == cur_level + 1 {
906 cursor.add_child(node);
907
908 // Move the cursor to the new child
909 //let kids = cursor.children();
910 //cursor.jump(kids.last().unwrap());
911 cur_level += 1;
912 }
913 // Case 2: Add child for multi-generational skip downwards
914 // (level increses by n)
915 else if data_level > cur_level {
916 let diff = data_level - cur_level;
917 for _ in 1..diff {
918 let empty = Heading::new("[]".to_string(), 0);
919 cursor.add_child(empty);
920
921 //let kids = cursor.children();
922 //cursor.jump(kids.last().unwrap());
923 cur_level += 1;
924 }
925 cursor.add_child(node);
926
927 //let kids = cursor.children();
928 //cursor.jump(kids.last().unwrap());
929 cur_level += 1;
930 }
931 // Case 3: Add sibling (level does not change)
932 else if data_level == cur_level {
933 cursor.ascend().ok(); // Back to parent
934 cursor.add_child(node);
935
936 // Move into new sibling to prepare for possible nested children
937 //let kids = cursor.children();
938 //cursor.jump(kids.last().unwrap());
939 }
940 // Case 4: Add child for multi-generational skip upwards
941 // (level decreases by n)
942 else {
943 let diff = cur_level - data_level;
944 // Ascend to the appropriate parent level (+1 for the current node itself)
945 for _ in 0..=diff {
946 cursor.ascend().ok();
947 cur_level -= 1;
948 }
949 cursor.add_child(node);
950
951 //let kids = cursor.children();
952 //cursor.jump(kids.last().unwrap());
953 cur_level += 1;
954 }
955 }
956 tree
957 }
958
959 // Dictates the print spacing for the tree drawing used in pretty_print
960 // and its recursive helper function
961 const SPACE: &str = " ";
962
963 /// A wrapper for a recursive preorder(ish) traversal function;
964 /// Contains logic to print [] on empty trees for more appealing presentation
965 /// Takes a reference to the GenTree and pretty-prints its contents.
966 pub fn pretty_print(title: &str, tree: &GenTree<Heading>) {
967 // Grab a shared reference to the Position at the root node
968 let root_pos = tree.root();
969
970 if tree.is_empty() {
971 println!("📄 {title}\n{SPACE}[]\n"); // Empty trees
972 } else {
973 println!("📄 {title}\n{SPACE}│");
974 // Recursive helper function call
975 preorder(&root_pos, "");
976 println!();
977 }
978 }
979 /// Modified preorder traversal function that walks the tree
980 /// recursively printing each node's title and children with
981 /// appropriate box drawing components.
982 fn preorder(pos: &Position<'_, Heading>, prefix: &str) {
983 // Safely collect child positions. Because Position is Clone,
984 // this creates an array of independent pointers bound to the
985 // same tree lifetime.
986 let children = pos.get_children();
987
988 for (index, child_pos) in pos.get_children().iter().enumerate() {
989 if let Some(child_data) = child_pos.get_data() {
990 let (marker, next_prefix) = if index == children.len() - 1 {
991 ("└── ", format!("{prefix}{SPACE}"))
992 } else {
993 ("├── ", format!("{prefix}│{SPACE}"))
994 };
995 // Print the current node layout
996 println!("{SPACE}{}{}{}", prefix, marker, child_data.title);
997 // Safely recurse! No mutable re-borrows, no raw pointer
998 // smuggling. The compiler tracks the shared tree
999 // lifetime across the entire call stack.
1000 preorder(child_pos, &next_prefix);
1001 }
1002 }
1003 }
1004
1005 /** A recursive function that chains the module's utility functions to
1006 pretty-print a table of contents for each Markdown file in the specified
1007 directory; The is_file() path contains logic to build a tree from filtered
1008 values, skipping headers above the user-supplied level argument;
1009 The function also substitues the file name (if any) for all MD files
1010 not formatted with Astro's frontmatter */
1011 pub fn navigator(level: usize, path: &Path) {
1012 if path.is_dir() {
1013 for component in path.read_dir().expect("read_dir call failed") {
1014 let entry = component.expect("failure to deconstruct value");
1015 navigator(level, &entry.path()); // Recursive call
1016 }
1017 } else if path.is_file() {
1018 if let Some(ext) = path.extension() {
1019 match ext.to_str() {
1020 Some("md") | Some("mdx") => {
1021 println!("{}", path.display());
1022 let parsed = parse(path);
1023 let mut name: String = parsed.0;
1024 if name.is_empty() {
1025 if let Some(n) = path
1026 .file_name()
1027 .expect("Error extracting file name")
1028 .to_str()
1029 {
1030 name = n.to_string()
1031 }
1032 }
1033 let filtered = parsed.1.into_iter().filter(|h| h.level > level).collect();
1034 let tree = construct(level, filtered);
1035 pretty_print(&name, &tree);
1036 }
1037 _ => (),
1038 }
1039 }
1040 }
1041 }
1042}