forked from vitrine/wmaker
This is a bit of a red herring, since WMTree is only used in wmmenugen, which I don't think I've ever directly used. See notes at the top of tree.rs for more musings on whether this should be in wutil-rs at all.
253 lines
7.9 KiB
Rust
253 lines
7.9 KiB
Rust
//! N-ary tree structure.
|
|
//!
|
|
//! ## Rust rewrite notes
|
|
//!
|
|
//! This is only used in one place (`util/wmmenugen.c`), and it should probably
|
|
//! be moved out of wutil-rs (if not deleted entirely) once we migrate that
|
|
//! utility.
|
|
//!
|
|
//! The FFI functions provided here assume that they will be used as they are in
|
|
//! `wmmenugen.c`. (For example, the C interface may allow for some callback
|
|
//! parameters to be null, but they aren't in practice, so the Rust layer
|
|
//! doesn't check for that.)
|
|
//!
|
|
//! The original C library had a larger surface area and tracked parent
|
|
//! pointers, but the Rust version only has the API used by wmmenugen. Case in
|
|
//! point: `WMTreeNode` originally had an optional destructor function pointer
|
|
//! for freeing its `data` field, but wmmenugen never actually deleted a tree
|
|
//! node, so the destructor was never called. This Rust rewrite doesn't track a
|
|
//! `data` destructor and doesn't even provide a way to delete tree nodes.
|
|
//!
|
|
//! If a generic tree really becomes necessary, [`Tree`] may be adapted, but a
|
|
//! different design is probably warranted. (At the very least, `Tree` should be
|
|
//! generic over the type of `data` so that we're not using `c_void`. It is also
|
|
//! probably better to index into an owned `Vec` or use an external
|
|
//! arena. `GhostCell` or one of its ilk may even make sense.)
|
|
|
|
use std::ffi::c_void;
|
|
|
|
pub struct Tree {
|
|
depth: u32,
|
|
children: Vec<Box<Tree>>,
|
|
data: *mut c_void,
|
|
}
|
|
|
|
pub mod ffi {
|
|
use super::Tree;
|
|
|
|
use std::{
|
|
ffi::{c_int, c_void},
|
|
ptr,
|
|
};
|
|
|
|
/// Creates the root of a new tree with the data `data`.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMCreateTreeNode(data: *mut c_void) -> *mut Tree {
|
|
Box::leak(Box::new(Tree {
|
|
depth: 0,
|
|
children: Vec::new(),
|
|
data,
|
|
}))
|
|
}
|
|
|
|
/// Creates a tree node as the `index`th child of `parent`, with the data
|
|
/// `item`. If `index` is negative, the node is added as the last child of
|
|
/// `parent`.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMInsertItemInTree(
|
|
parent: *mut Tree,
|
|
index: c_int,
|
|
item: *mut c_void,
|
|
) -> *mut Tree {
|
|
if parent.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let parent = unsafe { &mut *parent };
|
|
|
|
let child = Tree {
|
|
depth: parent.depth + 1,
|
|
children: Vec::new(),
|
|
data: item,
|
|
};
|
|
|
|
if index < 0 {
|
|
parent.children.push(Box::new(child));
|
|
parent.children.last_mut().unwrap().as_mut() as *mut _
|
|
} else {
|
|
let index = index as usize;
|
|
parent.children.insert(index, Box::new(child));
|
|
parent.children[index].as_mut() as *mut _
|
|
}
|
|
}
|
|
|
|
/// Inserts `tree` as the `index`th child of `parent`. If `index` is
|
|
/// negative, `tree` is added as the last child of `parent`.
|
|
///
|
|
/// This eagerly updates the depth of the entire subtree rooted at `tree`,
|
|
/// so it has a O(N) cost rather than O(1).
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMInsertNodeInTree(
|
|
parent: *mut Tree,
|
|
index: c_int,
|
|
tree: *mut Tree,
|
|
) -> *mut Tree {
|
|
if parent.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let parent = unsafe { &mut *parent };
|
|
|
|
if tree.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
|
|
let mut stack = vec![(unsafe { &mut *tree }, parent.depth + 1)];
|
|
while let Some((tree, depth)) = stack.pop() {
|
|
tree.depth = depth;
|
|
for child in &mut tree.children {
|
|
stack.push((child, depth + 1));
|
|
}
|
|
}
|
|
|
|
if index < 0 {
|
|
parent.children.push(unsafe { Box::from_raw(tree) });
|
|
tree
|
|
} else {
|
|
let index = index as usize;
|
|
parent
|
|
.children
|
|
.insert(index, unsafe { Box::from_raw(tree) });
|
|
tree
|
|
}
|
|
}
|
|
|
|
/// Returns the data field of `tree`.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetDataForTreeNode(tree: *mut Tree) -> *mut c_void {
|
|
if tree.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
unsafe { (*tree).data }
|
|
}
|
|
|
|
/// Returns the depth of `tree` (0 if `tree` is a root, 1 if it is an
|
|
/// immediate child of the root, etc.). This is an `O(1)` operation.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMGetTreeNodeDepth(tree: *mut Tree) -> c_int {
|
|
if tree.is_null() {
|
|
return 0;
|
|
}
|
|
unsafe { (*tree).depth as c_int }
|
|
}
|
|
|
|
/// Recursively sorts the children of each node of the subtree rooted at
|
|
/// `tree`, according to `comparer`.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMSortTree(
|
|
tree: *mut Tree,
|
|
comparer: unsafe extern "C" fn(*const Tree, *const Tree) -> c_int,
|
|
) {
|
|
use std::cmp::Ordering;
|
|
|
|
if tree.is_null() {
|
|
return;
|
|
}
|
|
|
|
let comparer = |a: &Box<Tree>, b: &Box<Tree>| {
|
|
let a = a.as_ref() as *const Tree as *const _;
|
|
let b = b.as_ref() as *const Tree as *const _;
|
|
match unsafe { comparer(a, b) }.signum() {
|
|
-1 => Ordering::Less,
|
|
0 => Ordering::Equal,
|
|
1 => Ordering::Greater,
|
|
_ => unreachable!(),
|
|
}
|
|
};
|
|
|
|
let mut stack = vec![unsafe { &mut *tree }];
|
|
|
|
while let Some(tree) = stack.pop() {
|
|
tree.children.sort_by(comparer);
|
|
stack.extend(tree.children.iter_mut().map(|c| c.as_mut()));
|
|
}
|
|
}
|
|
|
|
/// Returns the first tree node in the subtree rooted at `tree` that matches
|
|
/// `match_p`, or null if none is found. If `match_p` is `None`, returns
|
|
/// the first node whose `data` field is equal to `cdata`. Search is
|
|
/// recursive, up to `limit` depth (which must be non-negative).
|
|
///
|
|
/// ## Rust rewrite notes
|
|
///
|
|
/// This was originally a DFS with a `depthFirst` parameter which actually
|
|
/// controlled whether tree traversal was pre- or post-order (and not
|
|
/// whether the search was depth-first or breadth-first, which the name
|
|
/// might seem to suggest). Since this was only ever called with a non-zero
|
|
/// `depthFirst`, the Rust version is hard-coded as a pre-order DFS.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMFindInTreeWithDepthLimit(
|
|
tree: *mut Tree,
|
|
match_p: Option<unsafe extern "C" fn(*const Tree, *const c_void) -> c_int>,
|
|
cdata: *mut c_void,
|
|
limit: c_int,
|
|
) -> *mut Tree {
|
|
if tree.is_null() {
|
|
return ptr::null_mut();
|
|
}
|
|
let safe_tree = unsafe { &mut *tree };
|
|
|
|
let match_p = |t: &Tree| -> bool {
|
|
if let Some(p) = match_p {
|
|
(unsafe { (p)(t, cdata) }) != 0
|
|
} else {
|
|
t.data == cdata
|
|
}
|
|
};
|
|
|
|
let mut stack = vec![(safe_tree, 0)];
|
|
|
|
while let Some((tree, depth)) = stack.pop() {
|
|
if depth > limit {
|
|
continue;
|
|
}
|
|
|
|
if match_p(tree) {
|
|
return tree as *mut Tree;
|
|
}
|
|
|
|
for child in tree.children.iter_mut() {
|
|
stack.push((child.as_mut(), depth + 1));
|
|
}
|
|
}
|
|
|
|
ptr::null_mut()
|
|
}
|
|
|
|
/// Traverses `tree` recursively, invoking `walk` on each tree node with
|
|
/// `cdata` passed in to provide a side channel.
|
|
#[unsafe(no_mangle)]
|
|
pub unsafe extern "C" fn WMTreeWalk(
|
|
tree: *mut Tree,
|
|
walk: unsafe extern "C" fn(*mut Tree, *mut c_void),
|
|
cdata: *mut c_void,
|
|
) {
|
|
if tree.is_null() {
|
|
return;
|
|
}
|
|
let tree = unsafe { &mut *tree };
|
|
|
|
let walk_fn = |t: &mut Tree| unsafe {
|
|
walk(t as *mut Tree, cdata);
|
|
};
|
|
|
|
let mut stack = vec![tree];
|
|
|
|
while let Some(tree) = stack.pop() {
|
|
walk_fn(tree);
|
|
|
|
for child in &mut tree.children {
|
|
stack.push(child);
|
|
}
|
|
}
|
|
}
|
|
}
|