Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add splice method to Bytes #6823

Merged
merged 13 commits into from
Jan 23, 2025
Merged
92 changes: 92 additions & 0 deletions sway-lib-std/src/bytes.sw
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,98 @@ impl Bytes {
// set capacity and length
self.len = both_len;
}

/// Removes and returns a range of elements from the `Bytes` (i.e. indices `[start, end)`),
/// then replaces that range with the contents of `replace_with`.
///
/// # Arguments
///
/// * `start`: [u64] - The starting index for the splice (inclusive).
/// * `end`: [u64] - The ending index for the splice (exclusive).
/// * `replace_with`: [Bytes] - The elements to insert in place of the removed range.
///
/// # Returns
///
/// * [Bytes] - A new `Bytes` containing all of the elements from `start` up to (but not including) `end`.
///
/// # Reverts
///
/// * When `start > end`.
/// * When `end > self.len`.
///
/// # Examples
///
/// ```sway
/// use std::bytes::Bytes;
///
/// fn foo() {
/// let mut bytes = Bytes::new();
/// bytes.push(5u8); // index 0
/// bytes.push(7u8); // index 1
/// bytes.push(9u8); // index 2
///
/// // Replace the middle item (index 1) with two new items
/// let mut replacement = Bytes::new();
/// replacement.push(42u8);
/// replacement.push(100u8);
///
/// // Splice out range [1..2) => removes the single element 7u8,
/// // then inserts [42, 100] there
/// let spliced = bytes.splice(1, 2, replacement);
///
/// // `spliced` has the element [7u8]
/// assert(spliced.len() == 1);
/// assert(spliced.get(0).unwrap() == 7u8);
///
/// // `bytes` is now [5u8, 42u8, 100u8, 9u8]
/// assert(bytes.len() == 4);
/// assert(bytes.get(0).unwrap() == 5u8);
/// assert(bytes.get(1).unwrap() == 42u8);
/// assert(bytes.get(2).unwrap() == 100u8);
/// assert(bytes.get(3).unwrap() == 9u8);
/// }
/// ```
pub fn splice(ref mut self, start: u64, end: u64, replace_with: Bytes) -> Bytes {
assert(start <= end);
assert(end <= self.len);

let splice_len = end - start;
let mut spliced = Bytes::with_capacity(splice_len);
if splice_len > 0 {
let start_ptr = self.buf.ptr().add_uint_offset(start);
start_ptr.copy_bytes_to(spliced.buf.ptr(), splice_len);
spliced.len = splice_len;
}

let new_len = self.len - splice_len + replace_with.len();
if new_len > self.buf.capacity() {
let new_ptr = realloc_bytes(self.buf.ptr(), self.buf.capacity(), new_len);
self.buf = RawBytes {
ptr: new_ptr,
cap: new_len,
};
}

let tail_len = self.len - end;
if splice_len != replace_with.len() && tail_len > 0 {
let src = self.buf.ptr().add_uint_offset(end);
let dst = self.buf.ptr().add_uint_offset(start + replace_with.len());
// Use temporary buffer to safely handle overlapping memory
let mut temp = Bytes::with_capacity(tail_len);
src.copy_bytes_to(temp.buf.ptr(), tail_len);
temp.buf.ptr().copy_bytes_to(dst, tail_len);
}

if replace_with.len() > 0 {
replace_with
.buf
.ptr()
.copy_bytes_to(self.buf.ptr().add_uint_offset(start), replace_with.len());
}

self.len = new_len;
spliced
}
}

impl core::ops::Eq for Bytes {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
library;

use std::bytes::Bytes;
pub mod utils;
pub mod splice;

fn setup() -> (Bytes, u8, u8, u8) {
let mut bytes = Bytes::new();
let a = 5u8;
let b = 7u8;
let c = 9u8;
bytes.push(a);
bytes.push(b);
bytes.push(c);
(bytes, a, b, c)
}
use utils::setup;
use std::bytes::Bytes;

#[test()]
fn bytes_new() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
library;

use std::bytes::Bytes;
use ::utils::setup;

#[test()]
fn bytes_splice() {
let (mut bytes, a, b, c) = setup();
bytes.push(11u8);
bytes.push(13u8);
// bytes = [5, 7, 9, 11, 13]

// Remove [1..4), replace with nothing
let spliced = bytes.splice(1, 4, Bytes::new());

// bytes => [5, 13]
assert(bytes.len() == 2);
assert(bytes.get(0).unwrap() == a);
assert(bytes.get(1).unwrap() == 13u8);

// spliced => [7, 9, 11]
assert(spliced.len() == 3);
assert(spliced.get(0).unwrap() == b);
assert(spliced.get(1).unwrap() == c);
assert(spliced.get(2).unwrap() == 11u8);
}

#[test()]
fn bytes_splice_front() {
let (mut bytes, a, b, c) = setup();
// Remove [0..2) => [5, 7], replace with nothing
let spliced = bytes.splice(0, 2, Bytes::new());

// bytes => [9]
assert(bytes.len() == 1);
assert(bytes.get(0).unwrap() == c);

// spliced => [5, 7]
assert(spliced.len() == 2);
assert(spliced.get(0).unwrap() == a);
assert(spliced.get(1).unwrap() == b);
}

#[test()]
fn bytes_splice_end() {
let (mut bytes, a, b, c) = setup();
// Remove [1..3) => [7, 9], replace with nothing
let spliced = bytes.splice(1, bytes.len(), Bytes::new());

// bytes => [5]
assert(bytes.len() == 1);
assert(bytes.get(0).unwrap() == a);

// spliced => [7, 9]
assert(spliced.len() == 2);
assert(spliced.get(0).unwrap() == b);
assert(spliced.get(1).unwrap() == c);
}

#[test()]
fn bytes_splice_empty_range() {
let (mut bytes, a, b, c) = setup();
// Remove [1..1) => nothing, replace with nothing
let spliced = bytes.splice(1, 1, Bytes::new());

assert(bytes.len() == 3);
assert(bytes.get(0).unwrap() == a);
assert(bytes.get(1).unwrap() == b);
assert(bytes.get(2).unwrap() == c);
assert(spliced.len() == 0);
}

#[test()]
fn bytes_splice_entire_range() {
let (mut bytes, a, b, c) = setup();
// Remove [0..3) => [5, 7, 9], replace with nothing
let spliced = bytes.splice(0, bytes.len(), Bytes::new());

assert(bytes.len() == 0);
assert(bytes.is_empty());
assert(spliced.len() == 3);
assert(spliced.get(0).unwrap() == a);
assert(spliced.get(1).unwrap() == b);
assert(spliced.get(2).unwrap() == c);
}

#[test(should_revert)]
fn revert_bytes_splice_start_greater_than_end() {
let (mut bytes, _, _, _) = setup();
let _spliced = bytes.splice(2, 1, Bytes::new());
}

#[test(should_revert)]
fn revert_bytes_splice_end_out_of_bounds() {
let (mut bytes, _, _, _) = setup();
let _spliced = bytes.splice(0, bytes.len() + 1, Bytes::new());
}

/// Additional tests for replacing a spliced range with different Byte lengths.
#[test()]
fn bytes_splice_replace_smaller() {
let (mut bytes, a, b, c) = setup();
bytes.push(11u8);
bytes.push(13u8);
// bytes = [5, 7, 9, 11, 13]
let mut replacement = Bytes::new();
replacement.push(42u8);
// Remove [1..4) => [7, 9, 11], replace with [42]
let spliced = bytes.splice(1, 4, replacement);

// bytes => [5, 42, 13]
assert(bytes.len() == 3);
assert(bytes.get(0).unwrap() == a);
assert(bytes.get(1).unwrap() == 42u8);
assert(bytes.get(2).unwrap() == 13u8);

// spliced => [7, 9, 11]
assert(spliced.len() == 3);
assert(spliced.get(0).unwrap() == b);
assert(spliced.get(1).unwrap() == c);
assert(spliced.get(2).unwrap() == 11u8);
}

#[test()]
fn bytes_splice_replace_larger() {
let (mut bytes, a, b, c) = setup();
// bytes = [5, 7, 9]
let mut replacement = Bytes::new();
replacement.push(42u8);
replacement.push(50u8);
replacement.push(60u8);
// Remove [1..2) => [7], replace with [42, 50, 60]
let spliced = bytes.splice(1, 2, replacement);

// bytes => [5, 42, 50, 60, 9]
assert(bytes.len() == 5);
assert(bytes.get(0).unwrap() == a);
assert(bytes.get(1).unwrap() == 42u8);
assert(bytes.get(2).unwrap() == 50u8);
assert(bytes.get(3).unwrap() == 60u8);
assert(bytes.get(4).unwrap() == c);

// spliced => [7]
assert(spliced.len() == 1);
assert(spliced.get(0).unwrap() == b);
}

#[test()]
fn bytes_splice_replace_same_length() {
let (mut bytes, a, b, c) = setup();
// bytes = [5, 7, 9]
let mut replacement = Bytes::new();
replacement.push(42u8);
replacement.push(50u8);
// Remove [1..3) => [7, 9], replace with [42, 50] (same length = 2)
let spliced = bytes.splice(1, 3, replacement);

// bytes => [5, 42, 50]
assert(bytes.len() == 3);
assert(bytes.get(0).unwrap() == a);
assert(bytes.get(1).unwrap() == 42u8);
assert(bytes.get(2).unwrap() == 50u8);

// spliced => [7, 9]
assert(spliced.len() == 2);
assert(spliced.get(0).unwrap() == b);
assert(spliced.get(1).unwrap() == c);
}

#[test()]
fn bytes_splice_replace_empty_bytes() {
let (mut bytes, a, b, c) = setup();
// bytes = [5, 7, 9]
let replacement = Bytes::new();
// Remove [0..1) => [5], replace with []
let spliced = bytes.splice(0, 1, replacement);

// bytes => [7, 9]
assert(bytes.len() == 2);
assert(bytes.get(0).unwrap() == b);
assert(bytes.get(1).unwrap() == c);

// spliced => [5]
assert(spliced.len() == 1);
assert(spliced.get(0).unwrap() == a);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
library;

use std::bytes::Bytes;

pub fn setup() -> (Bytes, u8, u8, u8) {
let mut bytes = Bytes::new();
let a = 5u8;
let b = 7u8;
let c = 9u8;
bytes.push(a);
bytes.push(b);
bytes.push(c);
(bytes, a, b, c)
}
Loading