Skip to content

Commit

Permalink
add checked accessors to Rope and RopeSlice
Browse files Browse the repository at this point in the history
- .get_byte(byte_index)
- .get_line(line_index)
- .get_line_slice(line_range)
  • Loading branch information
marcospb19 committed Jan 20, 2024
1 parent 37a0a78 commit 9690a14
Show file tree
Hide file tree
Showing 2 changed files with 273 additions and 50 deletions.
159 changes: 134 additions & 25 deletions src/rope/rope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub struct Rope {
}

impl Rope {
pub(super) const fn arity() -> usize {
ARITY
}

#[doc(hidden)]
pub fn assert_invariants(&self) {
self.tree.assert_invariants();
Expand Down Expand Up @@ -66,6 +70,8 @@ impl Rope {

/// Returns the byte at `byte_index`.
///
/// For a checked version, see [`get_byte`](Self::get_byte).
///
/// # Panics
///
/// Panics if the byte index is out of bounds (i.e. greater than or equal
Expand All @@ -85,14 +91,9 @@ impl Rope {
#[track_caller]
#[inline]
pub fn byte(&self, byte_index: usize) -> u8 {
if byte_index >= self.byte_len() {
panic::byte_index_out_of_bounds(byte_index, self.byte_len());
}

let (chunk, ByteMetric(chunk_byte_offset)) =
self.tree.leaf_at_measure(ByteMetric(byte_index + 1));

chunk.byte(byte_index - chunk_byte_offset)
self.get_byte(byte_index).unwrap_or_else(|| {
panic::byte_index_out_of_bounds(byte_index, self.line_len())
})
}

/// Returns the length of the `Rope` in bytes.
Expand Down Expand Up @@ -299,8 +300,120 @@ impl Rope {
self.replace(byte_range, "");
}

pub(super) const fn arity() -> usize {
ARITY
/// Returns the byte at `byte_index`, if it exists.
///
/// If `byte_index` is out of bounds, returns `None`.
///
/// For a panic'ing version, see [`byte`](Self::byte).
///
/// # Examples
///
/// ```
/// # use crop::Rope;
/// #
/// let r = Rope::from("bar");
///
/// assert_eq!(r.get_byte(0), Some(b'b'));
/// assert_eq!(r.get_byte(1), Some(b'a'));
/// assert_eq!(r.get_byte(2), Some(b'r'));
/// assert_eq!(r.get_byte(3), None);
/// ```
#[track_caller]
#[inline]
pub fn get_byte(&self, byte_index: usize) -> Option<u8> {
if byte_index >= self.byte_len() {
return None;
}

let (chunk, ByteMetric(chunk_byte_offset)) =
self.tree.leaf_at_measure(ByteMetric(byte_index + 1));

Some(chunk.byte(byte_index - chunk_byte_offset))
}

/// Returns the line at `line_index`, without its line terminator, if it exists.
///
/// If `line_index` is out of bounds, returns `None`.
///
/// If you want to include the line break consider calling
/// [`get_line_slice()`](Self::get_line_slice()) in the
/// `line_index..line_index + 1` range.
///
/// For a panic'ing version, see [`line_slice()`](Self::line_slice()).
///
/// # Examples
///
/// ```
/// # use crop::Rope;
/// #
/// let r = Rope::from("foo\nbar\r\nbaz");
///
/// assert_eq!(r.get_line(0).unwrap(), "foo");
/// assert_eq!(r.get_line(1).unwrap(), "bar");
/// assert_eq!(r.get_line(2).unwrap(), "baz");
/// assert_eq!(r.get_line(3), None);
/// ```
#[track_caller]
#[inline]
pub fn get_line(&self, line_index: usize) -> Option<RopeSlice<'_>> {
if line_index >= self.line_len() {
return None;
}

let tree_slice = self
.tree
.slice(RawLineMetric(line_index)..RawLineMetric(line_index + 1));

let mut line = RopeSlice { tree_slice, has_trailing_newline: false };

if line.tree_slice.summary().line_breaks() == 1 {
line.truncate_trailing_line_break();
}

Some(line)
}

/// Returns an immutable slice of the `Rope` in the specified line range,
/// if it exists, where the start and end of the range are interpreted as
/// offsets.
///
/// If `line_range` is out of bounds, returns `None`.
///
/// If you want a single line, see [`get_line()`](Self::get_line).
///
/// For a panic'ing version, see [`line_slice`](Self::line_slice).
///
/// # Examples
///
/// ```
/// # use crop::Rope;
/// #
/// let r = Rope::from("foo\nbar\r\nbaz\nfoobar\n");
///
/// assert_eq!(r.get_line_slice(..1).unwrap(), "foo\n");
/// assert_eq!(r.get_line_slice(1..3).unwrap(), "bar\r\nbaz\n");
/// assert_eq!(r.get_line_slice(3..).unwrap(), "foobar\n");
/// assert_eq!(r.get_line_slice(4..).unwrap(), "");
/// assert_eq!(r.get_line_slice(5..), None);
/// ```
#[track_caller]
#[inline]
pub fn get_line_slice<R>(&self, line_range: R) -> Option<RopeSlice<'_>>
where
R: RangeBounds<usize>,
{
let (start, end) =
range_bounds_to_start_end(line_range, 0, self.line_len());

if start > end {
return None;
}

if end > self.line_len() {
return None;
}

Some(self.tree.slice(RawLineMetric(start)..RawLineMetric(end)).into())
}

/// Returns an iterator over the extended grapheme clusters of this
Expand Down Expand Up @@ -446,6 +559,9 @@ impl Rope {
/// [`line_slice()`](Self::line_slice()) in the
/// `line_index..line_index + 1` range.
///
/// For a checked version, see
/// [`get_line_slice()`](Self::get_line_slice()).
///
/// # Panics
///
/// Panics if the line index is out of bounds (i.e. greater than or equal
Expand All @@ -465,21 +581,9 @@ impl Rope {
#[track_caller]
#[inline]
pub fn line(&self, line_index: usize) -> RopeSlice<'_> {
if line_index >= self.line_len() {
panic::line_index_out_of_bounds(line_index, self.line_len());
}

let tree_slice = self
.tree
.slice(RawLineMetric(line_index)..RawLineMetric(line_index + 1));

let mut line = RopeSlice { tree_slice, has_trailing_newline: false };

if line.tree_slice.summary().line_breaks() == 1 {
line.truncate_trailing_line_break();
}

line
self.get_line(line_index).unwrap_or_else(|| {
panic::line_index_out_of_bounds(line_index, self.line_len())
})
}

/// Returns the number of lines in the `Rope`.
Expand Down Expand Up @@ -551,6 +655,10 @@ impl Rope {
/// Returns an immutable slice of the `Rope` in the specified line range,
/// where the start and end of the range are interpreted as offsets.
///
/// If you want a single line, see [`line()`](Self::line).
///
/// If you want a checked version, see [`get_line_slice()`](Self::get_line_slice).
///
/// # Panics
///
/// Panics if the start is greater than the end or if the end is out of
Expand All @@ -566,6 +674,7 @@ impl Rope {
/// assert_eq!(r.line_slice(..1), "foo\n");
/// assert_eq!(r.line_slice(1..3), "bar\r\nbaz\n");
/// assert_eq!(r.line_slice(3..), "foobar\n");
/// assert_eq!(r.line_slice(4..), "");
/// ```
#[track_caller]
#[inline]
Expand Down
Loading

0 comments on commit 9690a14

Please sign in to comment.