Get appending at the end of the rope to work

This commit is contained in:
Savanni D'Gerinel 2024-05-13 10:15:28 -04:00
parent 02af9e4ea7
commit 77bc77cd20
1 changed files with 126 additions and 81 deletions

View File

@ -2,7 +2,7 @@ use std::{mem, ops::Deref};
const CHUNK_SIZE: usize = 10; const CHUNK_SIZE: usize = 10;
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
struct NodeId(usize); struct NodeId(usize);
impl Deref for NodeId { impl Deref for NodeId {
@ -12,14 +12,15 @@ impl Deref for NodeId {
} }
} }
#[derive(Debug)]
enum Node { enum Node {
Interior { Interior {
// the number of characters to the left of this node // the number of characters to the left of this node
// the number of lines to the left of this node // the number of lines to the left of this node
char_count: usize, char_count: usize,
left: NodeId, left: Option<NodeId>,
right: NodeId, right: Option<NodeId>,
}, },
Leaf(LeafNode), Leaf(LeafNode),
} }
@ -33,6 +34,7 @@ impl Node {
} }
} }
#[derive(Debug)]
struct LeafNode(String); struct LeafNode(String);
impl LeafNode { impl LeafNode {
@ -56,88 +58,42 @@ impl From<String> for LeafNode {
} }
} }
// a Rope is a regular tree which adds on some extra behavior for dealing with a continuous data /// A Rope is a regular tree which adds on some extra behavior for dealing with a continuous data
// structure. /// structure. In this case, the nodes all contain strings, and the rope is arranged such that a
/// depth-first traversal will yield the entire contents of the rope in proper order.
#[derive(Debug)]
pub struct Rope { pub struct Rope {
node_count: usize, node_count: usize,
contents: Vec<Option<Node>>, contents: Vec<Option<Node>>,
} }
impl Rope { impl Rope {
/// Insert text at an index within the document. loc is the number of characters from the
/// beginning.
pub fn insert_at(&mut self, loc: usize, text: String) { pub fn insert_at(&mut self, loc: usize, text: String) {
unimplemented!(); match self.find_insertion_node_id(loc) {
} None => {
pub fn append(&mut self, text: String) {
if self.node_count == 0 {
let node = Node::Leaf(LeafNode::from(text)); let node = Node::Leaf(LeafNode::from(text));
self.node_count += 1; self.node_count += 1;
self.contents.push(Some(node)); self.contents.push(Some(node));
} else {
// Let's grab a node. If it's a leaf node, we need to convert it into a interior node.
// In doing so, we need to move the current leaf node into a left-hand child and create
// the right-hand child.
//
// If it's an interior node, we'll dig deeper. Not doing any tree rebalancing at the
// moment.
//
// First thing we need to know is what mode of node we have. That's going to determine
// which branch of operations we go into. We want to do this without borrowing the data
// for anything more than the match.
// let mut node = mem::replace(&mut self.contents[0], None);
match self.contents[0] {
Some(Node::Interior { ref left, ref right, .. }) => {
} }
Some(Node::Leaf(_)) => { Some(id) => {
let Some(Node::Leaf(mut ln)) = mem::replace(&mut self.contents[0], None) else { self.insert_at_node(id, text);
panic!("Should never leave an empty space in the node list")
};
let contents = ln.take();
let lnode = Node::Leaf(LeafNode::from(contents));
let rnode = Node::Leaf(LeafNode::from(text));
let lnode_id = self.node_count;
let rnode_id = self.node_count + 1;
let interior_node = Node::Interior {
char_count: lnode.len(),
left: NodeId(lnode_id),
right: NodeId(rnode_id),
};
let _ = mem::replace(&mut self.contents[0], Some(interior_node));
self.node_count += 2;
self.contents.push(Some(lnode));
self.contents.push(Some(rnode));
} }
None => panic!("Should never leave an empty space in the node list"),
}
/*
let mut node = &mut self.contents[0];
if let Node::Leaf(s) = node {
let contents = s.take();
let lnode = Node::Leaf(LeafNode::from(contents));
let rnode = Node::Leaf(LeafNode::from(text));
self.contents.push(lnode);
self.contents.push(rnode);
let lnode_id = self.node_count;
let rnode_id = self.node_count + 1;
self.node_count += 2;
*node = Node::Interior{
char_count: 0,
left: NodeId(lnode_id),
right: NodeId(rnode_id),
};
}
*/
} }
} }
/// Append text to the end of the document.
pub fn append(&mut self, text: String) {
self.insert_at(self.len(), text);
}
/// Convert the entire rope back to a continuous String.
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
if self.contents.is_empty() {
return "".to_owned();
}
let mut r = String::new(); let mut r = String::new();
let mut stack = vec![NodeId(0)]; let mut stack = vec![NodeId(0)];
@ -145,8 +101,12 @@ impl Rope {
let node = &self.contents[*current_id]; let node = &self.contents[*current_id];
match node { match node {
Some(Node::Interior { left, right, .. }) => { Some(Node::Interior { left, right, .. }) => {
stack.push(*right); if let Some(right_id) = *right {
stack.push(*left); stack.push(right_id);
}
if let Some(left_id) = *left {
stack.push(left_id);
}
} }
Some(Node::Leaf(ln)) => r.push_str(ln.as_str()), Some(Node::Leaf(ln)) => r.push_str(ln.as_str()),
None => panic!("Should never leave an empty space in the node list"), None => panic!("Should never leave an empty space in the node list"),
@ -156,6 +116,7 @@ impl Rope {
r r
} }
/// Calculate the length of the stored string.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
// This can be optimized later. Do a traversal of each right node. We already have // This can be optimized later. Do a traversal of each right node. We already have
// character counts of each left tree. Only count the length of the final right leaf. // character counts of each left tree. Only count the length of the final right leaf.
@ -177,6 +138,74 @@ impl Rope {
fn node_count(&self) -> usize { fn node_count(&self) -> usize {
self.node_count self.node_count
} }
// Find the node ID of the insertion point. This is not fully implemented, in that this
// function ignores the offset from the beginning. Because of that, it is also always inserting
// onto the right side, and never traversing down the left.
fn find_insertion_node_id(&self, _loc: usize) -> Option<NodeId> {
let mut current_id = NodeId(0);
loop {
match self.contents.get(*current_id) {
Some(Some(Node::Interior { ref right, .. })) => match right {
Some(id) => current_id = *id,
None => return Some(current_id),
},
Some(Some(Node::Leaf(_))) => return Some(current_id),
Some(None) => panic!("There should never be an empty node in the tree"),
// This only happens when the list is empty. Otherwise, we're detecting the None in
// advance.
None => return None,
}
}
}
// Insert text at a particular node location.
//
// This is not a self-balancing operation (yet). Once we know where text needs to be inserted,
// based on the offset from the beginning, we can grab that node and either replace it (if it
// is a Leaf node) or update it (if it is an Interior node).
//
// This function is currently naive, in that it will always assume that text needs to be added
// to the right side, which may not be correct.
fn insert_at_node(&mut self, id: NodeId, text: String) {
match self.contents[*id] {
Some(Node::Interior { ref mut right, .. }) => {
let new_node = Node::Leaf(LeafNode::from(text));
let new_node_id = NodeId(self.node_count + 1);
*right = Some(new_node_id);
self.contents.push(Some(new_node));
self.node_count += 1;
}
Some(Node::Leaf(_)) => {
let Some(Node::Leaf(mut ln)) = mem::replace(&mut self.contents[*id], None) else {
panic!("Should never leave an empty space in the node list")
};
let contents = ln.take();
let lnode = Node::Leaf(LeafNode::from(contents));
let rnode = Node::Leaf(LeafNode::from(text));
let lnode_id = self.node_count;
let rnode_id = self.node_count + 1;
let interior_node = Node::Interior {
char_count: lnode.len(),
left: Some(NodeId(lnode_id)),
right: Some(NodeId(rnode_id)),
};
let _ = mem::replace(&mut self.contents[*id], Some(interior_node));
self.node_count += 2;
self.contents.push(Some(lnode));
self.contents.push(Some(rnode));
}
None => panic!("Should never leave an empty space in the node list"),
}
}
} }
impl Default for Rope { impl Default for Rope {
@ -199,9 +228,9 @@ impl From<String> for Rope {
while lst.len() > CHUNK_SIZE { while lst.len() > CHUNK_SIZE {
(first, lst) = lst.split_at(CHUNK_SIZE); (first, lst) = lst.split_at(CHUNK_SIZE);
println!("[{}] [{}]", first, lst);
rope.append(first.to_owned()); rope.append(first.to_owned());
} }
rope.append(lst.to_owned());
rope rope
} }
} }
@ -210,15 +239,31 @@ impl From<String> for Rope {
mod test { mod test {
use super::*; use super::*;
struct TestCase {
content: String,
}
#[test] #[test]
fn it_creates_a_rope_from_a_string() { fn it_creates_a_rope_from_a_string() {
let content = let test_cases = vec![
TestCase{ content: "".to_owned() },
TestCase{ content: "This".to_owned() },
TestCase{ content: "This is some basic".to_owned() },
TestCase{ content: "This is some basic context which is much smaller".to_owned() },
TestCase{ content:
"This is some basic context which is much smaller than the rope is designed for." "This is some basic context which is much smaller than the rope is designed for."
.to_owned(); .to_owned()
},
];
let rope = Rope::from(content.clone()); for case in test_cases {
let rope = Rope::from(case.content.clone());
assert_eq!(rope.to_string(), content); for (idx, node) in rope.contents.iter().enumerate() {
assert_eq!(rope.len(), content.len()); }
assert_eq!(rope.len(), case.content.len(), "{}", case.content);
assert_eq!(rope.to_string(), case.content, "{:?}", case.content);
}
} }
} }