From 087c2bad9b610f2694ef8c414519c384ecd19e73 Mon Sep 17 00:00:00 2001 From: Matthew Gordon Date: Fri, 18 Oct 2024 20:14:52 -0300 Subject: [PATCH] Add Rope::iter_nodes and Rope::iter_chars --- Cargo.toml | 3 +- src/core/rope.rs | 251 +++++++++++++++++++++++++++++++---------------- 2 files changed, 169 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ef76fc..8929224 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ name = "ged" version = "0.1.0" edition = "2021" -[dependencies] +[dev-dependencies] +ntest = "0.9.3" diff --git a/src/core/rope.rs b/src/core/rope.rs index 4a83b8c..e348be4 100644 --- a/src/core/rope.rs +++ b/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, /// The root of the right subtree - right: Rc, + right: Option>, + }, + 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) -> Rc { - 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, right: Rc) -> Rc { - 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) -> CharIterator { + pub fn iter_chars(self: &Rc) -> CharIterator { CharIterator::new(self) } } @@ -105,16 +119,16 @@ struct NodeIterator { } impl NodeIterator { - fn new(rope: Rc) -> Self { + fn new(rope: Rc) -> Self { let mut result = NodeIterator { stack: vec![] }; result.push_tree(rope); result } - + fn push_tree(&mut self, node: Rc) { 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 { 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, - str_iterator: std::str::Chars<'a> + current_text: Option, + str_index: usize, } -impl<'a> CharIterator<'a> { - fn new(rope: Rc) -> Self{ - let mut node_iterator = NodeIterator::new(rope); - let str_iterator = node_iterator.next().contents +impl CharIterator { + fn new(rope: &Rc) -> 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 { + 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 { + 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); + } + } }