♻️ refactor rope implementation to use obikrope

- rename `obirope` → `obikroper`
- replace legacy rope with new in-place, Cell-based implementation
  - add ForwardCursor/Backward Cursor & SeekMode support (no more BytesMut)
- update all dependents:
  - obiread: switch to Rope + cursors, remove tape.rs
    • chunk iterator yields `Rope` instead of Vec<Bytes>
  - obiskbuilder: use ForwardCursor over Rope
- remove bytes dependency from affected crates
This commit is contained in:
Eric Coissac
2026-04-19 21:23:10 +02:00
parent 5fab59f92c
commit 0dcb5dd6c2
19 changed files with 790 additions and 1140 deletions
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "obikrope"
version = "0.1.0"
edition = "2024"
[dev-dependencies]
criterion2 = { version = "3", features = ["cargo_bench_support"] }
[dependencies]
bytes = "1.11.1"
+310
View File
@@ -0,0 +1,310 @@
use std::cell::Cell;
use crate::{Rope, RopeError};
#[derive(Clone, Copy)]
pub enum SeekMode {
Absolute,
Relative,
RelativeToEnd,
}
// ── shared state ──────────────────────────────────────────────────────────────
#[derive(Clone)]
struct CursorState<'a> {
block_idx: Cell<usize>,
block_start: Cell<usize>,
block_end: Cell<usize>,
block: Cell<&'a [Cell<u8>]>,
initialized: Cell<bool>,
current: Cell<Option<usize>>,
}
impl<'a> CursorState<'a> {
fn new() -> Self {
Self {
block_idx: Cell::new(0),
block_start: Cell::new(0),
block_end: Cell::new(0),
block: Cell::new(&[]),
initialized: Cell::new(false),
current: Cell::new(None),
}
}
fn get(&self, rope: &'a Rope, i: usize) -> Option<u8> {
if !self.initialized.get() || i < self.block_start.get() || i >= self.block_end.get() {
let (bi, bs, be) = rope.lookup(i)?;
self.block_idx.set(bi);
self.block_start.set(bs);
self.block_end.set(be);
self.block.set(rope.get_block(bi)?);
self.initialized.set(true);
}
Some(self.block.get()[i - self.block_start.get()].get())
}
fn set(&self, rope: &'a Rope, i: usize, value: u8) -> Result<(), RopeError> {
if !self.initialized.get() || i < self.block_start.get() || i >= self.block_end.get() {
let (bi, bs, be) = rope.lookup(i).ok_or(
RopeError::OutOfBounds(format!("index out of bounds: i={} > {}", i, rope.len())),
)?;
self.block_idx.set(bi);
self.block_start.set(bs);
self.block_end.set(be);
self.block.set(rope.get_block(bi).ok_or(RopeError::BlockNotFound(format!(
"Cannot find block for index {}",
i
)))?);
self.initialized.set(true);
}
self.block.get()[i - self.block_start.get()].set(value);
Ok(())
}
}
// ── trait ─────────────────────────────────────────────────────────────────────
pub trait RopeCursor {
fn get(&self, i: usize) -> Option<u8>;
fn set(&self, i: usize, value: u8) -> Result<(), RopeError>;
fn peek(&self) -> Option<u8>;
fn poke(&self, value: u8) -> Result<(), RopeError>;
fn tell(&self) -> Option<usize>;
fn seek(&self, pos: isize, mode: SeekMode) -> Result<usize, RopeError>;
fn rewind(&self, go_back_of: usize) -> Result<(), RopeError>;
fn len(&self) -> usize;
fn read_next(&self) -> Result<u8, RopeError>;
}
// ── ForwardCursor ─────────────────────────────────────────────────────────────
#[derive(Clone)]
pub struct ForwardCursor<'a> {
rope: &'a Rope,
state: CursorState<'a>,
}
impl<'a> ForwardCursor<'a> {
pub fn new(rope: &'a Rope) -> Self {
Self { rope, state: CursorState::new() }
}
pub fn read_ahead(&self, ahead: usize) -> Result<u8, RopeError> {
let pos = self.state.current.get().ok_or(RopeError::CurrentNotSet)?;
self.state
.get(self.rope, pos + ahead)
.ok_or(RopeError::OutOfBounds(format!(
"index out of bounds: i={} + {} > {}",
pos, ahead, self.rope.len()
)))
}
pub fn write(&self, value: u8) -> Result<(), RopeError> {
let pos = self.state.current.get().unwrap_or(0);
self.state.set(self.rope, pos, value)?;
self.state.current.set(Some(pos + 1));
Ok(())
}
pub fn iter(&self) -> ForwardIter<'a, '_> {
ForwardIter { cursor: self }
}
}
impl<'a> RopeCursor for ForwardCursor<'a> {
fn get(&self, i: usize) -> Option<u8> {
self.state.get(self.rope, i)
}
fn set(&self, i: usize, value: u8) -> Result<(), RopeError> {
self.state.set(self.rope, i, value)
}
fn peek(&self) -> Option<u8> {
self.state.get(self.rope, self.state.current.get()?)
}
fn poke(&self, value: u8) -> Result<(), RopeError> {
let pos = self.state.current.get().ok_or(RopeError::CurrentNotSet)?;
self.state.set(self.rope, pos, value)
}
fn tell(&self) -> Option<usize> {
self.state.current.get()
}
fn len(&self) -> usize {
self.rope.len()
}
fn read_next(&self) -> Result<u8, RopeError> {
let next_pos = match self.state.current.get() {
Some(i) => i + 1,
None => 0,
};
let value = self.state
.get(self.rope, next_pos)
.ok_or(RopeError::OutOfBounds(format!(
"index out of bounds: i={} > {}",
next_pos, self.rope.len()
)))?;
self.state.current.set(Some(next_pos));
Ok(value)
}
fn seek(&self, pos: isize, mode: SeekMode) -> Result<usize, RopeError> {
let pos = match mode {
SeekMode::Absolute => pos,
SeekMode::Relative => self.state.current.get().ok_or(RopeError::CurrentNotSet)? as isize + pos,
SeekMode::RelativeToEnd => self.rope.len() as isize - pos,
};
if pos < 0 {
return Err(RopeError::OutOfBounds(format!("index out of bounds: i={} < 0", pos)));
}
self.state.current.set(Some(pos as usize));
Ok(pos as usize)
}
fn rewind(&self, go_back_of: usize) -> Result<(), RopeError> {
self.seek(-(go_back_of as isize), SeekMode::Relative)?;
Ok(())
}
}
impl Iterator for ForwardCursor<'_> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
self.read_next().ok()
}
}
pub struct ForwardIter<'a, 'b> {
cursor: &'b ForwardCursor<'a>,
}
impl Iterator for ForwardIter<'_, '_> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
self.cursor.read_next().ok()
}
}
// ── BackwardCursor ────────────────────────────────────────────────────────────
#[derive(Clone)]
pub struct BackwardCursor<'a> {
rope: &'a Rope,
state: CursorState<'a>,
}
impl<'a> BackwardCursor<'a> {
pub fn new(rope: &'a Rope) -> Self {
Self { rope, state: CursorState::new() }
}
pub fn read_behind(&self, behind: usize) -> Result<u8, RopeError> {
let pos = self.state.current.get().ok_or(RopeError::CurrentNotSet)?;
let target = pos
.checked_add(behind)
.filter(|&t| t < self.rope.len())
.ok_or(RopeError::OutOfBounds(format!(
"index out of bounds: i={} + {} > {}",
pos, behind, self.rope.len()
)))?;
self.state
.get(self.rope, target)
.ok_or(RopeError::OutOfBounds(format!(
"index out of bounds: i={} + {} > {}",
pos, behind, self.rope.len()
)))
}
pub fn iter(&self) -> BackwardIter<'a, '_> {
BackwardIter { cursor: self }
}
}
impl<'a> RopeCursor for BackwardCursor<'a> {
fn get(&self, i: usize) -> Option<u8> {
self.state.get(self.rope, i)
}
fn set(&self, i: usize, value: u8) -> Result<(), RopeError> {
self.state.set(self.rope, i, value)
}
fn peek(&self) -> Option<u8> {
self.state.get(self.rope, self.state.current.get()?)
}
fn poke(&self, value: u8) -> Result<(), RopeError> {
let pos = self.state.current.get().ok_or(RopeError::CurrentNotSet)?;
self.state.set(self.rope, pos, value)
}
fn tell(&self) -> Option<usize> {
self.state.current.get()
}
fn len(&self) -> usize {
self.rope.len()
}
fn read_next(&self) -> Result<u8, RopeError> {
let next_pos = match self.state.current.get() {
None => self.rope.len().checked_sub(1).ok_or(RopeError::OutOfBounds(
"BackwardCursor: rope is empty".to_string(),
))?,
Some(0) => return Err(RopeError::OutOfBounds(
"BackwardCursor: already at beginning".to_string(),
)),
Some(i) => i - 1,
};
let value = self.state
.get(self.rope, next_pos)
.ok_or(RopeError::OutOfBounds(format!(
"BackwardCursor: index out of bounds at i={}",
next_pos
)))?;
self.state.current.set(Some(next_pos));
Ok(value)
}
fn seek(&self, pos: isize, mode: SeekMode) -> Result<usize, RopeError> {
let pos = match mode {
SeekMode::Absolute => pos,
SeekMode::Relative => self.state.current.get().ok_or(RopeError::CurrentNotSet)? as isize - pos,
SeekMode::RelativeToEnd => self.rope.len() as isize - pos,
};
if pos < 0 {
return Err(RopeError::OutOfBounds(format!("index out of bounds: i={} < 0", pos)));
}
self.state.current.set(Some(pos as usize));
Ok(pos as usize)
}
fn rewind(&self, go_back_of: usize) -> Result<(), RopeError> {
self.seek(-(go_back_of as isize), SeekMode::Relative)?;
Ok(())
}
}
impl Iterator for BackwardCursor<'_> {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
self.read_next().ok()
}
}
pub struct BackwardIter<'a, 'b> {
cursor: &'b BackwardCursor<'a>,
}
impl Iterator for BackwardIter<'_, '_> {
type Item = u8;
fn next(&mut self) -> Option<u8> {
self.cursor.read_next().ok()
}
}
+6
View File
@@ -0,0 +1,6 @@
#[derive(Debug)]
pub enum RopeError {
OutOfBounds(String),
BlockNotFound(String),
CurrentNotSet,
}
+8
View File
@@ -0,0 +1,8 @@
mod cursor;
mod error;
mod rope;
pub use {
cursor::BackwardCursor, cursor::ForwardCursor, cursor::RopeCursor, cursor::SeekMode,
error::RopeError, rope::Rope,
};
+114
View File
@@ -0,0 +1,114 @@
use crate::{BackwardCursor, ForwardCursor, RopeError};
use std::cell::Cell;
pub struct Rope {
pub(crate) blocks: Vec<Vec<Cell<u8>>>,
pub(crate) length: usize,
pub(crate) start_block_idx: Vec<usize>,
}
impl Rope {
pub fn new() -> Self {
Self {
blocks: Vec::new(),
length: 0,
start_block_idx: Vec::new(),
}
}
pub fn push(&mut self, block: Vec<u8>) {
let block_len = block.len();
self.start_block_idx.push(self.length);
// Safety: Cell<u8> has the same memory layout as u8 (guaranteed by the language)
let cell_block: Vec<Cell<u8>> = unsafe {
let mut v = std::mem::ManuallyDrop::new(block);
Vec::from_raw_parts(v.as_mut_ptr() as *mut Cell<u8>, v.len(), v.capacity())
};
self.blocks.push(cell_block);
self.length += block_len;
}
pub fn n_blocks(&self) -> usize {
self.blocks.len()
}
pub(crate) fn get_block(&self, block_idx: usize) -> Option<&[Cell<u8>]> {
self.blocks.get(block_idx).map(Vec::as_slice)
}
pub fn len(&self) -> usize {
self.length
}
pub(crate) fn lookup(&self, i: usize) -> Option<(usize, usize, usize)> {
if i >= self.length || self.blocks.is_empty() {
return None;
}
let block_idx = self.start_block_idx.partition_point(|&s| s <= i) - 1;
let from = self.start_block_idx[block_idx];
let to = if block_idx + 1 < self.blocks.len() {
self.start_block_idx[block_idx + 1]
} else {
self.length
};
Some((block_idx, from, to))
}
pub fn split_off(&mut self, pos: usize) -> Result<Rope, RopeError> {
if pos > self.length {
return Err(RopeError::OutOfBounds(format!(
"split_at: pos={} > length={}",
pos, self.length
)));
}
// pos == length: tail is empty.
if pos == self.length {
return Ok(Rope::new());
}
let (block_idx, from, _) = self.lookup(pos).ok_or_else(|| {
RopeError::OutOfBounds(format!("split_at: lookup failed at pos={}", pos))
})?;
let cut_offset = pos - from;
// Keep block_idx in self temporarily, split it, move remainder to tail.
let mut tail_blocks = self.blocks.split_off(block_idx + 1);
self.start_block_idx.truncate(block_idx + 1);
let tail_part = self.blocks[block_idx].split_off(cut_offset);
if !tail_part.is_empty() {
tail_blocks.insert(0, tail_part);
}
let mut tail_length = 0;
let tail_starts: Vec<usize> = tail_blocks
.iter()
.map(|b| {
let s = tail_length;
tail_length += b.len();
s
})
.collect();
self.length = pos;
Ok(Rope {
blocks: tail_blocks,
length: tail_length,
start_block_idx: tail_starts,
})
}
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
pub fn fw_cursor(&self) -> ForwardCursor<'_> {
ForwardCursor::new(self)
}
pub fn bw_cursor(&self) -> BackwardCursor<'_> {
BackwardCursor::new(self)
}
}