Add Rope::iter_nodes and Rope::iter_chars
This commit is contained in:
parent
73168d3c0b
commit
087c2bad9b
|
|
@ -3,4 +3,5 @@ name = "ged"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
[dev-dependencies]
|
||||
ntest = "0.9.3"
|
||||
|
|
|
|||
247
src/core/rope.rs
247
src/core/rope.rs
|
|
@ -6,88 +6,102 @@ use std::rc::Rc;
|
|||
/// This is the main data structure used to store text file contents while they
|
||||
/// are being edited.
|
||||
#[derive(Debug)]
|
||||
pub struct Rope {
|
||||
/// 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.
|
||||
bytes_weight: usize,
|
||||
pub enum Rope {
|
||||
Branch {
|
||||
/// 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.
|
||||
bytes_weight: usize,
|
||||
|
||||
/// Total number of characters in the string contained in the left subtree,
|
||||
/// or the number of characters in the string if this is a leaf.
|
||||
chars_weight: usize,
|
||||
/// Total number of characters in the string contained in the left subtree,
|
||||
/// or the number of characters in the string if this is a leaf.
|
||||
chars_weight: usize,
|
||||
|
||||
/// Total number of line endings in the string contained in the left
|
||||
/// subtree, or the number of line endings in the string if this is a leaf.
|
||||
lines_weight: usize,
|
||||
/// Total number of line endings in the string contained in the left
|
||||
/// subtree, or the number of line endings in the string if this is a leaf.
|
||||
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
|
||||
left: Rc<Rope>,
|
||||
/// 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 {
|
||||
/// Create a new Rope containing the passed text in a single node.
|
||||
pub fn new(contents: impl Into<String>) -> Rc<Self> {
|
||||
let string = contents.into();
|
||||
let bytes_weight = string.len();
|
||||
let chars_weight = string.chars().count();
|
||||
let lines_weight = string.chars().filter(|&c| c == '\n').count();
|
||||
Rc::new(Rope {
|
||||
bytes_weight,
|
||||
chars_weight,
|
||||
lines_weight,
|
||||
contents: NodeContents::String(string),
|
||||
})
|
||||
let text = contents.into();
|
||||
Rc::new(Rope::Leaf { text })
|
||||
}
|
||||
|
||||
/// Return the total number of bytes in the text.
|
||||
pub fn total_bytes(&self) -> usize {
|
||||
match self {
|
||||
Rope::Branch {
|
||||
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,
|
||||
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,
|
||||
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> {
|
||||
Rc::new(Rope {
|
||||
Rc::new(Rope::Branch {
|
||||
bytes_weight: left.total_bytes(),
|
||||
chars_weight: left.total_chars(),
|
||||
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 {
|
||||
match self.contents {
|
||||
NodeContents::Children { .. } => false,
|
||||
NodeContents::String(_) => true,
|
||||
match self {
|
||||
Rope::Branch { .. } => false,
|
||||
Rope::Leaf { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +109,7 @@ impl Rope {
|
|||
NodeIterator::new(self)
|
||||
}
|
||||
|
||||
pub fn iter_chars(self: Rc<Self>) -> CharIterator {
|
||||
pub fn iter_chars(self: &Rc<Self>) -> CharIterator {
|
||||
CharIterator::new(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -114,7 +128,7 @@ impl NodeIterator {
|
|||
fn push_tree(&mut self, node: Rc<Rope>) {
|
||||
let mut curr = node;
|
||||
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());
|
||||
curr = left.clone();
|
||||
}
|
||||
|
|
@ -126,8 +140,10 @@ impl Iterator for NodeIterator {
|
|||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let curr = self.stack.pop()?;
|
||||
if let NodeContents::Children { right, .. } = &curr.contents {
|
||||
self.push_tree(right.clone());
|
||||
if let Rope::Branch { right, .. } = curr.as_ref() {
|
||||
if let Some(right) = right {
|
||||
self.push_tree(right.clone());
|
||||
}
|
||||
self.next()
|
||||
} else {
|
||||
Some(curr)
|
||||
|
|
@ -135,42 +151,84 @@ impl Iterator for NodeIterator {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct CharIterator<'a> {
|
||||
pub struct CharIterator {
|
||||
node_iterator: NodeIterator,
|
||||
current_node: Rc<Rope>,
|
||||
str_iterator: std::str::Chars<'a>
|
||||
current_text: Option<String>,
|
||||
str_index: usize,
|
||||
}
|
||||
|
||||
impl<'a> CharIterator<'a> {
|
||||
fn new(rope: Rc<Rope>) -> Self{
|
||||
let mut node_iterator = NodeIterator::new(rope);
|
||||
let str_iterator = node_iterator.next().contents
|
||||
impl CharIterator {
|
||||
fn new(rope: &Rc<Rope>) -> Self {
|
||||
let mut node_iterator = NodeIterator::new(rope.clone());
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ntest::timeout;
|
||||
|
||||
#[test]
|
||||
#[timeout(100)]
|
||||
fn node_iterator_for_single_node_returns_node_and_only_node() {
|
||||
let target = Rope::new("The");
|
||||
|
||||
let result: Vec<_> = target.iter_nodes().collect();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].bytes_weight, 3);
|
||||
assert_eq!(result[0].chars_weight, 3);
|
||||
assert_eq!(result[0].lines_weight, 0);
|
||||
match &result[0].contents {
|
||||
NodeContents::String(text) => assert_eq!(text, "The"),
|
||||
assert_eq!(result[0].total_bytes(), 3);
|
||||
assert_eq!(result[0].total_chars(), 3);
|
||||
assert_eq!(result[0].total_lines(), 1);
|
||||
match result[0].as_ref() {
|
||||
Rope::Leaf { text, .. } => assert_eq!(text, "The"),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(100)]
|
||||
fn node_iterator_returns_nodes_in_correct_order() {
|
||||
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(
|
||||
Rope::join(
|
||||
|
|
@ -182,17 +240,42 @@ mod tests {
|
|||
Rope::join(Rope::new("jklm"), Rope::new("n")),
|
||||
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);
|
||||
for (node, string) in result.iter().zip(strings) {
|
||||
match &node.contents {
|
||||
NodeContents::String(text) => assert_eq!(text, string),
|
||||
match &node.as_ref() {
|
||||
Rope::Leaf { text } => assert_eq!(text, string),
|
||||
_ => 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue