Compare commits

..

11 Commits

Author SHA1 Message Date
Matthew Gordon 78f8bd5fc4 Add Rope::delete_at_char_index() 2024-10-19 22:01:01 -03:00
Matthew Gordon c3ebef8fc1 Add Rope::insert_char_at_index() 2024-10-19 21:47:30 -03:00
Matthew Gordon 1164922ffe Rearrange rope.rs a little 2024-10-19 21:22:02 -03:00
Matthew Gordon 511f56872a Add Rope::rebalance() 2024-10-19 21:17:02 -03:00
Matthew Gordon b2ef8cc284 Add core::fibbonacci::fibbonacci() 2024-10-19 20:24:17 -03:00
Matthew Gordon 3873e5561f Add Rope::depth() 2024-10-19 15:00:57 -03:00
Matthew Gordon a22968456c Add Rope::split() and Rope::empty() 2024-10-19 15:00:23 -03:00
Matthew Gordon f85a771071 Add more documentation comments 2024-10-18 20:26:33 -03:00
Matthew Gordon 087c2bad9b Add Rope::iter_nodes and Rope::iter_chars 2024-10-18 20:14:52 -03:00
Matthew Gordon 73168d3c0b In-progress implementation of rope
Just the beginnings of a rope implementation but I want to refactor
already so saving what I've got so far.
2024-10-17 19:44:13 -03:00
Matthew Gordon d967dfe405 Add *~ to .gitignore 2024-10-17 19:44:13 -03:00
6 changed files with 639 additions and 0 deletions

2
.gitignore vendored
View File

@ -14,3 +14,5 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Backup files
*~

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "ged"
version = "0.1.0"
edition = "2021"
[dev-dependencies]
ntest = "0.9.3"

29
src/core/fibbonacci.rs Normal file
View File

@ -0,0 +1,29 @@
pub fn fibbonacci(index: usize) -> usize {
if index == 0 {
0
} else if index < 3 {
1
} else {
let mut values = vec![1;index+1];
for i in 3..index+1 {
values[i] = values[i - 1] + values[i - 2];
}
values[index]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_first_few_values() {
let expected_values = vec![
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,
6765, 10946,
];
for (i, &n) in expected_values.iter().enumerate() {
assert_eq!(fibbonacci(i), n);
}
}
}

3
src/core/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod rope;
mod fibbonacci;

593
src/core/rope.rs Normal file
View File

@ -0,0 +1,593 @@
use {super::fibbonacci::fibbonacci, std::rc::Rc};
/// [Rope](https://en.wikipedia.org/wiki/Rope_(data_structure)) data structure
/// implementation.
///
/// This is the main data structure used to store text file contents while they
/// are being edited.
#[derive(Debug)]
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 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 root of the left subtree
left: Rc<Rope>,
/// The root of the right subtree
right: Option<Rc<Rope>>,
},
Leaf {
text: 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 text = contents.into();
Rc::new(Rope::Leaf { text })
}
/// Create a new empty Rope
pub fn empty() -> Rc<Self> {
let text = "".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(),
}
}
/// Returns a new rope created from concatenating `other` onto the end of
/// this one.
pub fn concat(self: Rc<Rope>, other: Rc<Rope>) -> Rc<Rope> {
Rope::join(self, Some(other)).rebalance()
}
/// Insert new text into the rope at a given character index.
pub fn insert_at_char_index(
self: &Rc<Rope>,
index: usize,
text: impl Into<String>,
) -> Rc<Rope> {
let new_node = Rope::new(text);
let total_chars = self.total_chars();
if index == 0 {
new_node.concat(self.clone())
} else if index < total_chars {
let (before, after) = self.split_at_char_index(index);
before.concat(new_node).concat(after)
} else if index == total_chars {
self.clone().concat(new_node)
} else {
panic!("Attempt to insert past end of rope.")
}
}
/// Return a new rope with `length` characters removed after `start`.
pub fn delete_at_char_index(self: &Rc<Rope>, start: usize, length: usize) -> Rc<Rope> {
let (beginning, rest) = self.split_at_char_index(start);
let (_, end) = rest.split_at_char_index(length);
beginning.concat(end)
}
/// Split the rope in two at character `index`.
///
/// The result is two Ropes—one containing the first 'i' characters of the
/// text and the other containing the rest of the text.
pub fn split_at_char_index(self: &Rc<Self>, index: usize) -> (Rc<Self>, Rc<Self>) {
match *self.as_ref() {
Rope::Branch {
chars_weight,
ref left,
ref right,
..
} => {
if index < chars_weight {
let (first, second) = left.split_at_char_index(index);
(
first.rebalance(),
Rope::join(second, right.as_ref().map(|r| r.clone())).rebalance(),
)
} else if let Some(right) = right {
if index > chars_weight {
let (first, second) = right.split_at_char_index(index - chars_weight);
(
Rope::join(left.clone(), Some(first)).rebalance(),
second.rebalance(),
)
} else {
(left.clone(), right.clone())
}
} else {
(left.clone(), Rope::empty())
}
}
Rope::Leaf { ref text } => {
if let Some((byte_index, _)) = text.char_indices().nth(index) {
let (first, second) = text.split_at(byte_index);
(Rope::new(first), Rope::new(second))
} else {
(self.clone(), Rope::empty())
}
}
}
}
pub fn is_balanced(&self) -> bool {
match self {
Rope::Branch { bytes_weight, .. } => fibbonacci(self.depth() + 2) <= *bytes_weight,
Rope::Leaf { .. } => true,
}
}
pub fn rebalance(self: Rc<Rope>) -> Rc<Rope> {
if self.is_balanced() {
return self;
}
let leaf_nodes: Vec<_> = self.iter_nodes().collect();
merge(&leaf_nodes)
}
/// Number of steps between this node and its most distance descendant
///
/// Leaf nodes have a depth of 0 and each branch node has a depth one
/// greater than the depth of deepest child.
pub fn depth(&self) -> usize {
match self {
Rope::Branch { left, right, .. } => {
left.depth()
.max(right.as_ref().map(|r| r.depth()).unwrap_or(0))
+ 1
}
Rope::Leaf { .. } => 0,
}
}
/// Returns true if this node is a leaf node, false otherwise
pub fn is_leaf(&self) -> bool {
match &self {
Rope::Branch { .. } => false,
Rope::Leaf { .. } => true,
}
}
/// Returns an iterator over the chars of the text
pub fn iter_chars(self: &Rc<Self>) -> CharIterator {
CharIterator::new(self)
}
/// Concatenate two Ropes without rebalancing.
///
/// Combines to ropes by creating a new parent node and adding `left` and
/// `right` as children. If `right` is [None] then the resulting node will
/// only have one child.
fn join(left: Rc<Rope>, right: Option<Rc<Rope>>) -> Rc<Self> {
Rc::new(Rope::Branch {
bytes_weight: left.total_bytes(),
chars_weight: left.total_chars(),
lines_weight: left.total_lines(),
left,
right,
})
}
/// Returns an iterater over the leaf nodes
fn iter_nodes(self: Rc<Self>) -> NodeIterator {
NodeIterator::new(self)
}
}
fn merge(leaf_nodes: &[Rc<Rope>]) -> Rc<Rope> {
match leaf_nodes.len() {
0 => panic!("Attempt to merge empty list"),
1 => leaf_nodes[0].clone(),
2 => Rope::join(leaf_nodes[0].clone(), Some(leaf_nodes[1].clone())),
3.. => {
let mid = leaf_nodes.len() / 2;
Rope::join(merge(&leaf_nodes[..mid]), Some(merge(&leaf_nodes[mid..])))
}
}
}
struct NodeIterator {
stack: Vec<Rc<Rope>>,
}
impl NodeIterator {
fn new(rope: Rc<Rope>) -> Self {
let mut result = NodeIterator { stack: vec![] };
result.push_tree(rope);
result
}
fn push_tree(&mut self, node: Rc<Rope>) {
let mut curr = node;
self.stack.push(curr.clone());
while let Rope::Branch { left, .. } = curr.as_ref() {
self.stack.push(left.clone());
curr = left.clone();
}
}
}
impl Iterator for NodeIterator {
type Item = Rc<Rope>;
fn next(&mut self) -> Option<Self::Item> {
let curr = self.stack.pop()?;
if let Rope::Branch { right, .. } = curr.as_ref() {
if let Some(right) = right {
self.push_tree(right.clone());
}
self.next()
} else {
Some(curr)
}
}
}
pub struct CharIterator {
node_iterator: NodeIterator,
current_text: Option<String>,
str_index: usize,
}
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> {
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;
fn small_test_rope() -> Rc<Rope> {
Rope::join(
Rope::join(
Rope::join(Rope::new("abc"), Some(Rope::new("def"))),
Some(Rope::join(Rope::new("g"), Some(Rope::new("hi")))),
),
Some(Rope::join(
Rope::join(
Rope::join(Rope::new("jklm"), Some(Rope::new("n"))),
Some(Rope::join(Rope::new("op"), Some(Rope::new("qr")))),
),
Some(Rope::join(Rope::new("stuv"), Some(Rope::new("wxyz")))),
)),
)
}
fn small_test_rope_leaf_strings() -> Vec<&'static str> {
vec![
"abc", "def", "g", "hi", "jklm", "n", "op", "qr", "stuv", "wxyz",
]
}
fn small_test_rope_full_string() -> &'static str {
"abcdefghijklmnopqrstuvwxyz"
}
fn small_test_rope_with_multibyte_chars() -> Rc<Rope> {
Rope::join(
Rope::join(
Rope::join(Rope::new("aあbc"), Some(Rope::new("deえf"))),
Some(Rope::join(Rope::new("g"), Some(Rope::new("hiい")))),
),
Some(Rope::join(
Rope::join(
Rope::join(Rope::new("jklm"), Some(Rope::new("n"))),
Some(Rope::join(Rope::new("おop"), Some(Rope::new("qr")))),
),
Some(Rope::join(Rope::new("stuうv"), Some(Rope::new("wxyz")))),
)),
)
}
fn small_test_rope_with_multibyte_chars_leaf_strings() -> Vec<&'static str> {
vec![
"aあbc", "deえf", "g", "hiい", "jklm", "n", "おop", "qr", "stuうv", "wxyz",
]
}
fn small_test_rope_with_multibyte_chars_full_string() -> &'static str {
"aあbcdeえfghiいjklmnおopqrstuうvwxyz"
}
impl Rope {
fn unwrap_branch_left(&self) -> &Rc<Rope> {
match self {
Rope::Branch { left, .. } => left,
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
}
}
fn unwrap_branch_right(&self) -> Option<&Rc<Rope>> {
match self {
Rope::Branch { right, .. } => right.as_ref(),
Rope::Leaf { .. } => panic!("Expected branch node but found leaf"),
}
}
}
#[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].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 = small_test_rope_leaf_strings();
let target = small_test_rope();
let result: Vec<_> = target.iter_nodes().take(26).collect();
assert_eq!(result.len(), 10);
for (node, string) in result.iter().zip(strings) {
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 = small_test_rope();
let result: Vec<_> = target.iter_chars().collect();
assert_eq!(result.len(), 26);
for (&target_char, expected_char) in
result.iter().zip(small_test_rope_full_string().chars())
{
assert_eq!(target_char, expected_char);
}
}
#[test]
#[timeout(100)]
fn split_splits_at_correct_location() {
let target = small_test_rope();
let full_string = small_test_rope_full_string();
for i in 0..(full_string.chars().count()) {
let (first, second) = target.split_at_char_index(i);
let first_string: String = first.iter_chars().collect();
let second_string: String = second.iter_chars().collect();
assert_eq!(first_string, full_string[0..i]);
assert_eq!(second_string, full_string[i..]);
}
}
#[test]
#[timeout(100)]
fn split_splits_at_correct_location_with_multibyte_chars() {
let target = small_test_rope_with_multibyte_chars();
let expected_chars: Vec<_> = small_test_rope_with_multibyte_chars_full_string()
.chars()
.collect();
for i in 0..(expected_chars.len()) {
let (first, second) = target.split_at_char_index(i);
let string: String = first.iter_chars().collect();
let expected: String = expected_chars[0..i].iter().collect();
assert_eq!(string, expected);
let string: String = second.iter_chars().collect();
let expected: String = expected_chars[i..].iter().collect();
assert_eq!(string, expected);
}
}
#[test]
fn depths_have_correct_values() {
let target = small_test_rope_with_multibyte_chars();
assert_eq!(target.depth(), 4);
assert_eq!(target.unwrap_branch_left().depth(), 2);
assert_eq!(target.unwrap_branch_left().unwrap_branch_left().depth(), 1);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_left()
.unwrap_branch_left()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.depth(),
1
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.unwrap_branch_left()
.depth(),
0
);
assert_eq!(
target
.unwrap_branch_left()
.unwrap_branch_right()
.unwrap()
.unwrap_branch_right()
.unwrap()
.depth(),
0
);
assert_eq!(target.unwrap_branch_right().unwrap().depth(), 3);
assert_eq!(
target
.unwrap_branch_right()
.unwrap()
.unwrap_branch_left()
.depth(),
2
);
assert_eq!(
target
.unwrap_branch_right()
.unwrap()
.unwrap_branch_right()
.unwrap()
.depth(),
1
);
}
#[test]
fn insert_at_char_index() {
let target = Rope::new("The brown dog");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
let rope1 = target.insert_at_char_index(4, "quick");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
let rope2 = rope1.insert_at_char_index(9, " ");
assert_eq!(target.iter_chars().collect::<String>(), "The brown dog");
assert_eq!(rope1.iter_chars().collect::<String>(), "The quickbrown dog");
assert_eq!(
rope2.iter_chars().collect::<String>(),
"The quick brown dog"
);
let rope3 =
rope2.insert_at_char_index("The quick brown dog".len(), " jumps over the lazy fox.");
assert_eq!(
rope3.iter_chars().collect::<String>(),
"The quick brown dog jumps over the lazy fox."
);
}
#[test]
fn delete_at_char_index() {
let target = Rope::new("The quick brown fox jumps over the lazy dog.");
let test = target.delete_at_char_index(10, 6);
assert_eq!(target.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy dog.");
assert_eq!(test.iter_chars().collect::<String>(), "The quick fox jumps over the lazy dog.");
let test = target.delete_at_char_index(0, 4);
assert_eq!(target.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy dog.");
assert_eq!(test.iter_chars().collect::<String>(), "quick brown fox jumps over the lazy dog.");
let test = target.delete_at_char_index("The quick brown fox jumps over the lazy dog.".len()-5, 5);
assert_eq!(target.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy dog.");
assert_eq!(test.iter_chars().collect::<String>(), "The quick brown fox jumps over the lazy");
}
}

5
src/main.rs Normal file
View File

@ -0,0 +1,5 @@
mod core;
fn main() {
println!("Hello, world!");
}