Skip to content

Commit

Permalink
Add splice method to Bytes (#6823)
Browse files Browse the repository at this point in the history
## Description
Adds a splice method to bytes that lets you select start and end
indices, and returns the range of bytes, removing them from the original
bytes.

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [ ] If my change requires substantial documentation changes, I have
[requested support from the DevRel
team](https://github.com/FuelLabs/devrel-requests/issues/new/choose)
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.

---------

Co-authored-by: K1-R1 <[email protected]>
  • Loading branch information
SwayStar123 and K1-R1 authored Jan 23, 2025
1 parent bdbe0b7 commit 986aee2
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 11 deletions.
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);
}

#[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)
}

0 comments on commit 986aee2

Please sign in to comment.