1
1
mirror of https://github.com/wez/wezterm.git synced 2025-01-03 19:21:57 +03:00

bintree: introduce a binary tree + zipper impl

The intent is for this to help manage panes, but it is reasonably
general purpose so it is broken out separately.
This commit is contained in:
Wez Furlong 2020-06-30 20:21:27 -07:00
parent 3d54a542bf
commit c2c788b41d
4 changed files with 503 additions and 0 deletions

5
Cargo.lock generated
View File

@ -317,6 +317,10 @@ checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
name = "base91"
version = "0.1.0"
[[package]]
name = "bintree"
version = "0.1.0"
[[package]]
name = "bitflags"
version = "0.9.1"
@ -3936,6 +3940,7 @@ dependencies = [
"async-trait",
"base64 0.10.1",
"base91",
"bintree",
"bitflags 1.2.1",
"bstr 0.2.13",
"cc",

View File

@ -22,6 +22,7 @@ allsorts = "0.4"
async-task = "1.2"
async-trait = "0.1"
anyhow = "1.0"
bintree = { path = "bintree" }
thiserror = "1.0"
base64 = "0.10"
base91 = { path = "base91" }

9
bintree/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "bintree"
version = "0.1.0"
authors = ["Wez Furlong <wez@wezfurlong.org>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

488
bintree/src/lib.rs Normal file
View File

@ -0,0 +1,488 @@
//! This crate implements a binary tree with a Zipper based Cursor implementation.
//!
//! For more details on the Zipper concept, check out these resources:
//! * <https://www.st.cs.uni-saarland.de//edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf>
//! * <https://donsbot.wordpress.com/2007/05/17/roll-your-own-window-manager-tracking-focus-with-a-zipper/>
//! * <https://stackoverflow.com/a/36168919/149111>
use std::cmp::PartialEq;
use std::fmt::Debug;
/// Represents a (mostly) "proper" binary tree; each Node has 0 or 2 children,
/// but there is a special case where the tree is rooted with a single leaf node.
/// Non-leaf nodes in the tree can be labelled with an optional node data type `N`,
/// which defaults to `()`.
/// Leaf nodes have a required leaf data type `L`.
pub enum Tree<L, N = ()> {
Empty,
Node {
left: Box<Self>,
right: Box<Self>,
data: Option<N>,
},
Leaf(L),
}
impl<L, N> PartialEq for Tree<L, N>
where
L: PartialEq,
N: PartialEq,
{
fn eq(&self, rhs: &Self) -> bool {
match (self, rhs) {
(Self::Empty, Self::Empty) => true,
(
Self::Node {
left: l_left,
right: l_right,
data: l_data,
},
Self::Node {
left: r_left,
right: r_right,
data: r_data,
},
) => (l_left == r_left) && (l_right == r_right) && (l_data == r_data),
(Self::Leaf(l), Self::Leaf(r)) => l == r,
_ => false,
}
}
}
impl<L, N> Debug for Tree<L, N>
where
L: Debug,
N: Debug,
{
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::Empty => fmt.write_str("Empty"),
Self::Node { left, right, data } => fmt
.debug_struct("Node")
.field("left", &left)
.field("right", &right)
.field("data", &data)
.finish(),
Self::Leaf(l) => fmt.debug_tuple("Leaf").field(&l).finish(),
}
}
}
/// Represents a location in the tree for the Zipper; the path contains directions
/// from the current position back towards the root of the tree.
enum Path<L, N> {
/// The current position is the top of the tree
Top,
/// The current position is the left hand side of its parent node;
/// Cursor::it holds the left node of the tree with the fields here
/// in Path::Left representing the partially constructed state of
/// the parent Tree::Node
Left {
right: Box<Tree<L, N>>,
data: Option<N>,
up: Box<Self>,
},
/// The current position is the right hand side of its parent node;
/// Cursor::it holds the right node of the tree with the fields here
/// in Path::Right representing the partially constructed state of
/// the parent Tree::Node
Right {
left: Box<Tree<L, N>>,
data: Option<N>,
up: Box<Self>,
},
}
impl<L, N> Debug for Path<L, N>
where
L: Debug,
N: Debug,
{
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
match self {
Self::Top => fmt.write_str("Top"),
Self::Left { right, data, up } => fmt
.debug_struct("Left")
.field("right", &right)
.field("data", &data)
.field("up", &up)
.finish(),
Self::Right { left, data, up } => fmt
.debug_struct("Right")
.field("left", &left)
.field("data", &data)
.field("up", &up)
.finish(),
}
}
}
/// The cursor is used to indicate the current position within the tree and enable
/// constant time mutation operations on that position as well as movement around
/// the tree.
/// The cursor isn't a reference to a location within the tree; it is an alternate
/// representation of the tree and thus requires ownership of the tree to create.
/// When you are done using the cursor you may wish to transform it back into
/// a tree.
pub struct Cursor<L, N> {
it: Box<Tree<L, N>>,
path: Box<Path<L, N>>,
}
impl<L, N> Debug for Cursor<L, N>
where
L: Debug,
N: Debug,
{
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
fmt.debug_struct("Cursor")
.field("it", &self.it)
.field("path", &self.path)
.finish()
}
}
impl<L, N> Tree<L, N> {
/// Construct a new empty tree
pub fn new() -> Self {
Self::Empty
}
/// Returns true if the tree is empty
pub fn is_empty(&self) -> bool {
match self {
Self::Empty => true,
_ => false,
}
}
/// Transform the tree into its Zipper based Cursor representation
pub fn cursor(self) -> Cursor<L, N> {
Cursor {
it: Box::new(self),
path: Box::new(Path::Top),
}
}
}
impl<L, N> Cursor<L, N> {
/// Construct a cursor representing a new empty tree
pub fn new() -> Self {
Self {
it: Box::new(Tree::Empty),
path: Box::new(Path::Top),
}
}
/// Returns true if the current position is a leaf node
pub fn is_leaf(&self) -> bool {
match &*self.it {
Tree::Leaf(_) => true,
_ => false,
}
}
/// If the current position is the root of the empty tree,
/// assign an initial leaf value.
/// Consumes the cursor and returns a new cursor representing
/// the mutated tree.
/// If the current position isn't the top of the empty tree,
/// yields `Err` containing the unchanged cursor.
pub fn assign_top(self, leaf: L) -> Result<Self, Self> {
match (&*self.it, &*self.path) {
(Tree::Empty, Path::Top) => Ok(Self {
it: Box::new(Tree::Leaf(leaf)),
path: self.path,
}),
_ => Err(self),
}
}
/// If the current position is a leaf node, return a mutable
/// reference to the leaf data, else `None`.
pub fn leaf_mut(&mut self) -> Option<&mut L> {
match &mut *self.it {
Tree::Leaf(l) => Some(l),
_ => None,
}
}
/// If the current position is not a leaf node, return a mutable
/// reference to the node data container, else yields `Err`.
pub fn node_mut(&mut self) -> Result<&mut Option<N>, ()> {
match &mut *self.it {
Tree::Node { data, .. } => Ok(data),
_ => Err(()),
}
}
/// If the current position is not a leaf node, assign the
/// node data to the supplied value.
/// Consumes the cursor and returns a new cursor representing the
/// mutated tree.
/// If the current position is a leaf node then yields `Err`
/// containing the unchanged cursor.
pub fn assign_node(mut self, value: Option<N>) -> Result<Self, Self> {
match &mut *self.it {
Tree::Node { data, .. } => {
*data = value;
Ok(self)
}
_ => Err(self),
}
}
/// If the current position is a leaf, split it into a Node where
/// the left side holds the current leaf value and the right side
/// holds the provided `right` value.
/// The cursor position remains unchanged.
/// Consumes the cursor and returns a new cursor representing the
/// mutated tree.
/// If the current position is not a leaf, yields `Err` containing
/// the unchanged cursor.
pub fn split_leaf_and_insert_right(self, right: L) -> Result<Self, Self> {
match *self.it {
Tree::Leaf(left) => Ok(Self {
it: Box::new(Tree::Node {
data: None,
left: Box::new(Tree::Leaf(left)),
right: Box::new(Tree::Leaf(right)),
}),
path: self.path,
}),
_ => Err(self),
}
}
/// If the current position is a leaf, split it into a Node where
/// the right side holds the current leaf value and the left side
/// holds the provided `left` value.
/// The cursor position remains unchanged.
/// Consumes the cursor and returns a new cursor representing the
/// mutated tree.
/// If the current position is not a leaf, yields `Err` containing
/// the unchanged cursor.
pub fn split_leaf_and_insert_left(self, left: L) -> Result<Self, Self> {
match *self.it {
Tree::Leaf(right) => Ok(Self {
it: Box::new(Tree::Node {
data: None,
left: Box::new(Tree::Leaf(left)),
right: Box::new(Tree::Leaf(right)),
}),
path: self.path,
}),
_ => Err(self),
}
}
/// If the current position is not a leaf, move the cursor to
/// its left child.
/// Consumes the cursor and returns a new cursor representing the
/// mutated tree.
/// If the current position is a Leaf, yields `Err` containing
/// the unchanged cursor.
pub fn go_left(self) -> Result<Self, Self> {
match *self.it {
Tree::Node { left, right, data } => Ok(Self {
it: left,
path: Box::new(Path::Left {
data,
right: right,
up: self.path,
}),
}),
_ => Err(self),
}
}
/// If the current position is not a leaf, move the cursor to
/// its right child.
/// Consumes the cursor and returns a new cursor representing the
/// mutated tree.
/// If the current position is a Leaf, yields `Err` containing
/// the unchanged cursor.
pub fn go_right(self) -> Result<Self, Self> {
match *self.it {
Tree::Node { left, right, data } => Ok(Self {
it: right,
path: Box::new(Path::Right {
data,
left: left,
up: self.path,
}),
}),
_ => Err(self),
}
}
/// If the current position is not at the root of the tree,
/// move up to the parent of the current position.
/// Consumes the cursor and returns a new cursor representing the
/// new location.
/// If the current position is the top of the tree,
/// yields `Err` containing the unchanged cursor.
pub fn go_up(self) -> Result<Self, Self> {
match *self.path {
Path::Top => Err(self),
Path::Right { left, data, up } => Ok(Self {
it: Box::new(Tree::Node {
left,
right: self.it,
data,
}),
path: up,
}),
Path::Left { right, data, up } => Ok(Self {
it: Box::new(Tree::Node {
right,
left: self.it,
data,
}),
path: up,
}),
}
}
/// Move the current position to the next in a preorder traversal.
/// Returns the modified cursor position.
///
/// In the case where there are no more nodes in the preorder traversal,
/// yields `Err` with the newly adjusted cursor; calling `preorder_next`
/// after it has yielded `Err` can potentially yield `Ok` with previously
/// visited nodes, so the caller must take care to stop iterating when
/// `Err` is received!
pub fn preorder_next(self) -> Result<Self, Self> {
// Since we are a "proper" binary tree, we know we cannot have
// difficult cases such as a left without a right or vice versa.
match (&*self.path, &*self.it) {
// On a leaf on the left branch, we need to go up and
// to the right
(Path::Left { .. }, Tree::Leaf(_)) => self.go_up()?.go_right(),
// On a leaf on the right branch we need to go up
// two levels and to the right
(Path::Right { .. }, Tree::Leaf(_)) => self.go_up()?.go_up()?.go_right(),
// In all other cases, the next down is down and to
// the left.
_ => self.go_left(),
}
}
/// Consume the cursor and return the root of the Tree
pub fn tree(mut self) -> Tree<L, N> {
loop {
self = match self.go_up() {
Ok(up) => up,
Err(top) => return *top.it,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn populate() {
let t: Tree<i32, i32> = Tree::new()
.cursor()
.assign_top(1)
.unwrap()
.split_leaf_and_insert_right(2)
.unwrap()
.tree();
assert_eq!(
t,
Tree::Node {
left: Box::new(Tree::Leaf(1)),
right: Box::new(Tree::Leaf(2)),
data: None
}
);
let t = t.cursor().assign_node(Some(100)).unwrap().tree();
assert_eq!(
t,
Tree::Node {
left: Box::new(Tree::Leaf(1)),
right: Box::new(Tree::Leaf(2)),
data: Some(100),
}
);
let t = t
.cursor()
.go_left()
.unwrap()
.split_leaf_and_insert_left(3)
.unwrap()
.assign_node(Some(101))
.unwrap()
.go_left()
.unwrap()
.split_leaf_and_insert_right(4)
.unwrap()
.assign_node(Some(102))
.unwrap()
.go_left()
.unwrap()
.split_leaf_and_insert_right(5)
.unwrap()
.assign_node(Some(103))
.unwrap()
.tree();
assert_eq!(
t,
Tree::Node {
left: Box::new(Tree::Node {
left: Box::new(Tree::Node {
left: Box::new(Tree::Node {
left: Box::new(Tree::Leaf(3)),
right: Box::new(Tree::Leaf(5)),
data: Some(103)
}),
right: Box::new(Tree::Leaf(4)),
data: Some(102)
}),
right: Box::new(Tree::Leaf(1)),
data: Some(101)
}),
right: Box::new(Tree::Leaf(2)),
data: Some(100),
}
);
let mut cursor = t.cursor();
assert_eq!(100, cursor.node_mut().unwrap().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(101, cursor.node_mut().unwrap().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(102, cursor.node_mut().unwrap().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(103, cursor.node_mut().unwrap().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(3, cursor.leaf_mut().copied().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(5, cursor.leaf_mut().copied().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(4, cursor.leaf_mut().copied().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(1, cursor.leaf_mut().copied().unwrap());
cursor = cursor.preorder_next().unwrap();
assert_eq!(2, cursor.leaf_mut().copied().unwrap());
assert!(cursor.preorder_next().is_err());
}
}