Add Rope::iter_nodes and Rope::iter_chars

This commit is contained in:
Matthew Gordon 2024-10-18 20:14:52 -03:00
parent 73168d3c0b
commit 087c2bad9b
2 changed files with 169 additions and 85 deletions

View File

@ -3,4 +3,5 @@ name = "ged"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dev-dependencies]
ntest = "0.9.3"

View File

@ -6,7 +6,8 @@ use std::rc::Rc;
/// This is the main data structure used to store text file contents while they /// This is the main data structure used to store text file contents while they
/// are being edited. /// are being edited.
#[derive(Debug)] #[derive(Debug)]
pub struct Rope { pub enum Rope {
Branch {
/// Total number of bytes in the string contained in the left subtree, or /// Total number of bytes in the string contained in the left subtree, or
/// the number of bytes in the string if this is a leaf. /// the number of bytes in the string if this is a leaf.
bytes_weight: usize, bytes_weight: usize,
@ -19,75 +20,88 @@ pub struct Rope {
/// subtree, or the number of line endings in the string if this is a leaf. /// subtree, or the number of line endings in the string if this is a leaf.
lines_weight: usize, lines_weight: usize,
/// The children (subtrees) if this is a branch node, or the string fragment
/// if this is a leaf.
contents: NodeContents,
}
#[derive(Debug)]
pub enum NodeContents {
/// The children of a branch Rope node
Children {
/// The root of the left subtree /// The root of the left subtree
left: Rc<Rope>, left: Rc<Rope>,
/// The root of the right subtree /// The root of the right subtree
right: Rc<Rope>, right: Option<Rc<Rope>>,
},
Leaf {
text: String,
}, },
/// The string fragment contained in a Rope leaf node
String(String),
} }
impl Rope { impl Rope {
/// Create a new Rope containing the passed text in a single node.
pub fn new(contents: impl Into<String>) -> Rc<Self> { pub fn new(contents: impl Into<String>) -> Rc<Self> {
let string = contents.into(); let text = contents.into();
let bytes_weight = string.len(); Rc::new(Rope::Leaf { text })
let chars_weight = string.chars().count(); }
let lines_weight = string.chars().filter(|&c| c == '\n').count();
Rc::new(Rope { /// Return the total number of bytes in the text.
pub fn total_bytes(&self) -> usize {
match self {
Rope::Branch {
bytes_weight, bytes_weight,
right: None,
..
} => *bytes_weight,
Rope::Branch {
bytes_weight,
right: Some(right),
..
} => bytes_weight + right.total_bytes(),
Rope::Leaf { text } => text.len(),
}
}
/// Return the total number of characters in the text.
pub fn total_chars(&self) -> usize {
match self {
Rope::Branch {
chars_weight, chars_weight,
right: None,
..
} => *chars_weight,
Rope::Branch {
chars_weight,
right: Some(right),
..
} => chars_weight + right.total_chars(),
Rope::Leaf { text } => text.chars().count(),
}
}
// Return the total number of lines in the text
pub fn total_lines(&self) -> usize {
match self {
Rope::Branch {
lines_weight, lines_weight,
contents: NodeContents::String(string), right: None,
}) ..
} => *lines_weight,
Rope::Branch {
lines_weight,
right: Some(right),
..
} => lines_weight + right.total_lines(),
Rope::Leaf { text } => text.lines().count(),
}
} }
pub fn join(left: Rc<Rope>, right: Rc<Rope>) -> Rc<Self> { pub fn join(left: Rc<Rope>, right: Rc<Rope>) -> Rc<Self> {
Rc::new(Rope { Rc::new(Rope::Branch {
bytes_weight: left.total_bytes(), bytes_weight: left.total_bytes(),
chars_weight: left.total_chars(), chars_weight: left.total_chars(),
lines_weight: left.total_lines(), lines_weight: left.total_lines(),
contents: NodeContents::Children { left, right }, left,
right: Some(right),
}) })
} }
pub fn total_bytes(&self) -> usize {
if let NodeContents::Children { left, right } = &self.contents {
left.bytes_weight + right.bytes_weight
} else {
self.bytes_weight
}
}
pub fn total_chars(&self) -> usize {
if let NodeContents::Children { left, right } = &self.contents {
left.chars_weight + right.chars_weight
} else {
self.chars_weight
}
}
pub fn total_lines(&self) -> usize {
if let NodeContents::Children { left, right } = &self.contents {
left.lines_weight + right.lines_weight
} else {
self.lines_weight
}
}
pub fn is_leaf(&self) -> bool { pub fn is_leaf(&self) -> bool {
match self.contents { match self {
NodeContents::Children { .. } => false, Rope::Branch { .. } => false,
NodeContents::String(_) => true, Rope::Leaf { .. } => true,
} }
} }
@ -95,7 +109,7 @@ impl Rope {
NodeIterator::new(self) NodeIterator::new(self)
} }
pub fn iter_chars(self: Rc<Self>) -> CharIterator { pub fn iter_chars(self: &Rc<Self>) -> CharIterator {
CharIterator::new(self) CharIterator::new(self)
} }
} }
@ -114,7 +128,7 @@ impl NodeIterator {
fn push_tree(&mut self, node: Rc<Rope>) { fn push_tree(&mut self, node: Rc<Rope>) {
let mut curr = node; let mut curr = node;
self.stack.push(curr.clone()); self.stack.push(curr.clone());
while let NodeContents::Children { left, .. } = &curr.contents { while let Rope::Branch { left, .. } = curr.as_ref() {
self.stack.push(left.clone()); self.stack.push(left.clone());
curr = left.clone(); curr = left.clone();
} }
@ -126,8 +140,10 @@ impl Iterator for NodeIterator {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let curr = self.stack.pop()?; let curr = self.stack.pop()?;
if let NodeContents::Children { right, .. } = &curr.contents { if let Rope::Branch { right, .. } = curr.as_ref() {
if let Some(right) = right {
self.push_tree(right.clone()); self.push_tree(right.clone());
}
self.next() self.next()
} else { } else {
Some(curr) Some(curr)
@ -135,42 +151,84 @@ impl Iterator for NodeIterator {
} }
} }
pub struct CharIterator<'a> { pub struct CharIterator {
node_iterator: NodeIterator, node_iterator: NodeIterator,
current_node: Rc<Rope>, current_text: Option<String>,
str_iterator: std::str::Chars<'a> str_index: usize,
} }
impl<'a> CharIterator<'a> { impl CharIterator {
fn new(rope: Rc<Rope>) -> Self{ fn new(rope: &Rc<Rope>) -> Self {
let mut node_iterator = NodeIterator::new(rope); let mut node_iterator = NodeIterator::new(rope.clone());
let str_iterator = node_iterator.next().contents let current_text = Self::next_string(&mut node_iterator);
CharIterator {
node_iterator,
current_text,
str_index: 0,
}
}
fn next_string(node_iterator: &mut NodeIterator) -> Option<String> {
node_iterator.next().map(|rope| {
if let Rope::Leaf { text } = rope.as_ref() {
text.clone()
} else {
panic!("Rope NodeIterator yielded non-leaf node.")
}
})
}
}
impl Iterator for CharIterator {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
dbg!(self
.current_text
.as_ref()
.and_then(|text| {
let c = text.chars().nth(self.str_index);
self.str_index += 1;
c
})
.or_else(|| {
self.current_text = Self::next_string(&mut self.node_iterator);
if self.current_text.is_some() {
self.str_index = 0;
self.next()
} else {
None
}
}))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use ntest::timeout;
#[test] #[test]
#[timeout(100)]
fn node_iterator_for_single_node_returns_node_and_only_node() { fn node_iterator_for_single_node_returns_node_and_only_node() {
let target = Rope::new("The"); let target = Rope::new("The");
let result: Vec<_> = target.iter_nodes().collect(); let result: Vec<_> = target.iter_nodes().collect();
assert_eq!(result.len(), 1); assert_eq!(result.len(), 1);
assert_eq!(result[0].bytes_weight, 3); assert_eq!(result[0].total_bytes(), 3);
assert_eq!(result[0].chars_weight, 3); assert_eq!(result[0].total_chars(), 3);
assert_eq!(result[0].lines_weight, 0); assert_eq!(result[0].total_lines(), 1);
match &result[0].contents { match result[0].as_ref() {
NodeContents::String(text) => assert_eq!(text, "The"), Rope::Leaf { text, .. } => assert_eq!(text, "The"),
_ => panic!(), _ => panic!(),
} }
} }
#[test] #[test]
#[timeout(100)]
fn node_iterator_returns_nodes_in_correct_order() { fn node_iterator_returns_nodes_in_correct_order() {
let strings = vec![ let strings = vec![
"abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "xyz", "abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz",
]; ];
let target = Rope::join( let target = Rope::join(
Rope::join( Rope::join(
@ -182,17 +240,42 @@ mod tests {
Rope::join(Rope::new("jklm"), Rope::new("n")), Rope::join(Rope::new("jklm"), Rope::new("n")),
Rope::join(Rope::new("op"), Rope::new("qr")), Rope::join(Rope::new("op"), Rope::new("qr")),
), ),
Rope::join(Rope::new("stuv"), Rope::new("xyz")), Rope::join(Rope::new("stuv"), Rope::new("wxyz")),
), ),
); );
let result: Vec<_> = target.iter_nodes().collect(); let result: Vec<_> = target.iter_nodes().take(26).collect();
assert_eq!(result.len(), 10); assert_eq!(result.len(), 10);
for (node, string) in result.iter().zip(strings) { for (node, string) in result.iter().zip(strings) {
match &node.contents { match &node.as_ref() {
NodeContents::String(text) => assert_eq!(text, string), Rope::Leaf { text } => assert_eq!(text, string),
_ => panic!(), _ => panic!(),
} }
} }
} }
#[test]
#[timeout(100)]
fn char_iterator_returns_chars_in_correct_order() {
let target = Rope::join(
Rope::join(
Rope::join(Rope::new("abc"), Rope::new("def")),
Rope::join(Rope::new("g"), Rope::new("hi")),
),
Rope::join(
Rope::join(
Rope::join(Rope::new("jklm"), Rope::new("n")),
Rope::join(Rope::new("op"), Rope::new("qr")),
),
Rope::join(Rope::new("stuv"), Rope::new("wxyz")),
),
);
let result: Vec<_> = target.iter_chars().collect();
assert_eq!(result.len(), 26);
for (&target_char, expected_char) in result.iter().zip("abcdefghijklmnopqrstuvwxyz".chars())
{
assert_eq!(target_char, expected_char);
}
}
} }