Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add splice method to Bytes #6823

Merged
merged 13 commits into from
Jan 23, 2025
Merged
97 changes: 97 additions & 0 deletions sway-lib-std/src/bytes.sw
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,103 @@ 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 replace_len = replace_with.len();

// Build the Bytes to return
let mut spliced = Bytes::with_capacity(splice_len);
if splice_len > 0 {
let old_ptr = self.buf.ptr().add_uint_offset(start);
old_ptr.copy_bytes_to(spliced.buf.ptr(), splice_len);
spliced.len = splice_len;
}

// New self
let new_len = self.len - splice_len + replace_len;
let mut new_buf = Bytes::with_capacity(new_len);

// Move head
if start > 0 {
let old_ptr = self.buf.ptr();
old_ptr.copy_bytes_to(new_buf.buf.ptr(), start);
}

// Move middle
if replace_len > 0 {
replace_with
.buf
.ptr()
.copy_bytes_to(new_buf.buf.ptr().add_uint_offset(start), replace_len);
}

// Move tail
let tail_len = self.len - end;
if tail_len > 0 {
let old_tail = self.buf.ptr().add_uint_offset(end);
let new_tail = new_buf.buf.ptr().add_uint_offset(start + replace_len);
old_tail.copy_bytes_to(new_tail, tail_len);
}

self.buf = new_buf.buf;
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,208 @@
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);
}
SwayStar123 marked this conversation as resolved.
Show resolved Hide resolved

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

// Expect spliced to contain the removed element [5]
assert(spliced.len() == 1);
assert(spliced.get(0).unwrap() == a);

// After replacement, bytes should now be [10, 12, 7, 9]
assert(bytes.len() == 4);
assert(bytes.get(0).unwrap() == 10u8);
assert(bytes.get(1).unwrap() == 12u8);
assert(bytes.get(2).unwrap() == b);
assert(bytes.get(3).unwrap() == c);
}
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