Skip to content

Commit 02b3ccb

Browse files
author
Gilad Chase
committed
feat(byte_array): add ByteSpan::to_byte_array
When slicing bytespans (to-be-implemented), the end-offset is trimmed by shifting the word to the right. The left offset, however, will be trimmed lazily only if the `ByteSpan` is casted into a `ByteArray`. Note: Lazily removing the end-offset will require saving an additional field, `end_offset` in `ByteSpan`, due to how strings are represented inside felt252 (the first byte is the msb of the word). In other words, we cannot just reduce the remainder_len, because then it'd be impossible to know how much to trim off from the remainder word at `to_byte_array`.
1 parent 8e174c0 commit 02b3ccb

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

corelib/src/byte_array.cairo

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
//! ```
4444

4545
use crate::array::{ArrayTrait, Span, SpanTrait};
46+
use crate::bytes_31::split_bytes31;
4647
#[allow(unused_imports)]
4748
use crate::bytes_31::{
4849
BYTES_IN_BYTES31, Bytes31Trait, POW_2_128, POW_2_8, U128IntoBytes31, U8IntoBytes31,
@@ -53,7 +54,7 @@ use crate::cmp::min;
5354
#[allow(unused_imports)]
5455
use crate::integer::{U32TryIntoNonZero, u128_safe_divmod};
5556
#[feature("bounded-int-utils")]
56-
use crate::internal::bounded_int::{BoundedInt, downcast};
57+
use crate::internal::bounded_int::{BoundedInt, downcast, upcast};
5758
#[allow(unused_imports)]
5859
use crate::serde::Serde;
5960
use crate::traits::{Into, TryInto};
@@ -649,6 +650,53 @@ pub impl ByteSpanImpl of ByteSpanTrait {
649650
fn is_empty(self: ByteSpan) -> bool {
650651
self.remainder_len == 0 && self.data.len() == 0
651652
}
653+
654+
/// Converts a `ByteSpan` into a `ByteArray`.
655+
/// The cast includes trimming the start_offset of the first word of the span (which is created
656+
/// when slicing).
657+
///
658+
/// Note: creating `ByteArray.data` from `Span` requires allocating a new memory
659+
/// segment for the returned array, and *O*(*n*) operations to populate the array with the
660+
/// content of the span (see also `SpanIntoArray`).
661+
fn to_byte_array(mut self: ByteSpan) -> ByteArray {
662+
let remainder_len = upcast(self.remainder_len);
663+
let Some(first_word) = self.data.pop_front() else {
664+
// Slice is included entirely in the remainder word.
665+
let len_without_offset: usize = remainder_len - upcast(self.first_char_start_offset);
666+
let (start_offset_trimmed, _) = split_bytes31(
667+
self.remainder_word, remainder_len, len_without_offset,
668+
);
669+
return ByteArray {
670+
data: array![],
671+
pending_word: start_offset_trimmed,
672+
pending_word_len: upcast(len_without_offset),
673+
};
674+
};
675+
676+
let mut ba = Default::default();
677+
let n_bytes_to_append = BYTES_IN_BYTES31 - upcast(self.first_char_start_offset);
678+
let (first_word_no_offset, _) = split_bytes31(
679+
(*first_word).into(), BYTES_IN_BYTES31, n_bytes_to_append,
680+
);
681+
ba.append_word(first_word_no_offset, n_bytes_to_append);
682+
683+
let span_after_first_word = ByteArray {
684+
data: self.data.into(),
685+
pending_word: self.remainder_word,
686+
pending_word_len: remainder_len,
687+
};
688+
ba.append(@span_after_first_word);
689+
690+
ba
691+
}
692+
}
693+
694+
impl ByteSpanDefault of Default<ByteSpan> {
695+
fn default() -> ByteSpan {
696+
ByteSpan {
697+
data: [].span(), first_char_start_offset: 0, remainder_word: 0, remainder_len: 0,
698+
}
699+
}
652700
}
653701

654702
/// Trait for types that can be converted into a `ByteSpan`.

corelib/src/test/byte_array_test.cairo

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -547,9 +547,34 @@ fn test_span_copy() {
547547

548548
let other_span = span;
549549
assert_eq!(other_span.len(), 2);
550+
assert_eq!(ba, span.to_byte_array());
551+
550552
let span_again = span.span();
551-
assert_eq!(span_again.len(), span.len());
553+
assert_eq!(ba, span_again.to_byte_array());
554+
assert_eq!(ba, span.to_byte_array());
555+
552556
let even_more_span_again = other_span.span();
553-
assert_eq!(even_more_span_again.len(), other_span.len());
554-
// TODO(giladchase): verify span content once we add `into` or `PartialEq`.
557+
assert_eq!(ba, even_more_span_again.to_byte_array());
558+
assert_eq!(ba, other_span.to_byte_array());
559+
assert_eq!(ba, span.to_byte_array());
560+
}
561+
562+
#[test]
563+
fn test_span_to_bytearray() {
564+
let empty_ba: ByteArray = "";
565+
assert_eq!(empty_ba.span().to_byte_array(), empty_ba);
566+
567+
// Only remainder.
568+
let small_ba: ByteArray = "hello";
569+
assert_eq!(small_ba.span().to_byte_array(), small_ba);
570+
571+
// Data word and remainder.
572+
let large_ba: ByteArray = "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVW"; // 40 bytes
573+
assert_eq!(large_ba.span().to_byte_array(), large_ba);
574+
575+
// Two data words and remainder.
576+
let even_larger_ba: ByteArray =
577+
"abcdeFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$"; // 64 bytes
578+
assert_eq!(even_larger_ba.span().to_byte_array(), even_larger_ba);
579+
// TODO(giladchase): test with slice.
555580
}

0 commit comments

Comments
 (0)