Skip to content

Commit e89a587

Browse files
author
Gilad Chase
committed
feat(byte_array): add ByteArray::span and Span::len
1 parent cc917d2 commit e89a587

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

corelib/src/byte_array.cairo

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
//! assert!(first_byte == 0x41);
4343
//! ```
4444

45-
use crate::array::{ArrayTrait, SpanTrait};
45+
use crate::array::{ArrayTrait, Span, SpanTrait};
4646
#[allow(unused_imports)]
4747
use crate::bytes_31::{
4848
BYTES_IN_BYTES31, Bytes31Trait, POW_2_128, POW_2_8, U128IntoBytes31, U8IntoBytes31,
@@ -52,19 +52,25 @@ use crate::clone::Clone;
5252
use crate::cmp::min;
5353
#[allow(unused_imports)]
5454
use crate::integer::{U32TryIntoNonZero, u128_safe_divmod};
55+
#[feature("bounded-int-utils")]
56+
use crate::internal::bounded_int::{BoundedInt, downcast, upcast};
5557
#[allow(unused_imports)]
5658
use crate::serde::Serde;
5759
use crate::traits::{Into, TryInto};
5860
#[allow(unused_imports)]
5961
use crate::zeroable::NonZeroIntoImpl;
6062

63+
/// The number of bytes in
64+
pub type WordBytes = BoundedInt<0, BYTES_IN_BYTES31_MINUS_ONE_FELT>;
65+
6166
/// A magic constant for identifying serialization of `ByteArray` variables. An array of `felt252`
6267
/// with this magic value as one of the `felt252` indicates that you should expect right after it a
6368
/// serialized `ByteArray`. This is currently used mainly for prints and panics.
6469
pub const BYTE_ARRAY_MAGIC: felt252 =
6570
0x46a6158a16a947e5916b2a2ca68501a45e93d7110e81aa2d6438b1c57c879a3;
6671
const BYTES_IN_U128: usize = 16;
6772
const BYTES_IN_BYTES31_MINUS_ONE: usize = BYTES_IN_BYTES31 - 1;
73+
const BYTES_IN_BYTES31_MINUS_ONE_FELT: felt252 = BYTES_IN_BYTES31_MINUS_ONE.into();
6874
const BYTES_IN_BYTES31_NONZERO: NonZero<usize> = 31;
6975

7076
// TODO(yuval): don't allow creation of invalid ByteArray?
@@ -586,3 +592,84 @@ impl ByteArrayFromIterator of crate::iter::FromIterator<ByteArray, u8> {
586592
ba
587593
}
588594
}
595+
596+
/// A view into a contiguous collection of a string type.
597+
/// Currently implemented only for `ByteArray`, but will soon be implemented for other string types.
598+
/// `Span` implements the `Copy` and the `Drop` traits.
599+
#[derive(Copy, Drop)]
600+
pub struct ByteSpan {
601+
/// A span representing the array of all `bytes31` words in the byte-span, excluding the last
602+
/// bytes_31 word that is stored in [Self::last_word].
603+
/// Invariant: every byte stored in `data` is part of the span except for the bytes appearing
604+
/// before `first_char_start_offset` in the first word.
605+
data: Span<bytes31>,
606+
/// The offset of the first character in the first entry of [Self::data], for use in span
607+
/// slices. When data is empty, this offset applies to remainder_word instead.
608+
first_char_start_offset: WordBytes,
609+
/// Contains the final bytes of the span when the end is either not in memory or isn't aligned
610+
/// to a word boundary.
611+
/// It is represented as a `felt252` to improve performance of building the byte array, but
612+
/// represents a `bytes31`.
613+
/// The first byte is the most significant byte among the `pending_word_len` bytes in the word.
614+
remainder_word: felt252,
615+
/// The number of bytes in [Self::remainder_word].
616+
remainder_len: WordBytes,
617+
}
618+
619+
#[generate_trait]
620+
pub impl ByteArraySpanImpl of ByteSpanTrait {
621+
/// Returns the length of the `ByteSpan`.
622+
///
623+
/// # Examples
624+
///
625+
/// ```
626+
/// let ba: ByteArray = "byte array";
627+
/// let span = ba.span();
628+
/// let len = span.len();
629+
/// assert!(len == 10);
630+
/// ```
631+
#[must_use]
632+
fn len(self: @ByteSpan) -> usize {
633+
let data_bytes = self.data.len() * BYTES_IN_BYTES31;
634+
data_bytes + upcast(self.remainder_len) - upcast(self.first_char_start_offset)
635+
}
636+
637+
/// Returns `true` if the `ByteSpan` has a length of 0.
638+
///
639+
/// # Examples
640+
///
641+
/// ```
642+
/// let ba: ByteArray = "";
643+
/// let span = ba.span();
644+
/// assert!(span.is_empty());
645+
///
646+
/// let ba2: ByteArray = "not empty";
647+
/// let span2 = ba2.span();
648+
/// assert!(!span2.is_empty());
649+
/// ```
650+
fn is_empty(self: @ByteSpan) -> bool {
651+
self.data.len() == 0 && *self.remainder_len == 0
652+
}
653+
}
654+
655+
pub trait ToByteSpanTrait<C> {
656+
#[must_use]
657+
fn span(self: @C) -> ByteSpan;
658+
}
659+
660+
impl ByteArrayToByteSpan of ToByteSpanTrait<ByteArray> {
661+
fn span(self: @ByteArray) -> ByteSpan {
662+
ByteSpan {
663+
data: self.data.span(),
664+
first_char_start_offset: 0,
665+
remainder_word: *self.pending_word,
666+
remainder_len: downcast(self.pending_word_len).expect('In [0,30] by assumption'),
667+
}
668+
}
669+
}
670+
671+
impl ByteSpanToByteSpan of ToByteSpanTrait<ByteSpan> {
672+
fn span(self: @ByteSpan) -> ByteSpan {
673+
*self
674+
}
675+
}

corelib/src/test/byte_array_test.cairo

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::byte_array::{ByteSpanTrait, ToByteSpanTrait};
12
use crate::test::test_utils::{assert_eq, assert_ne};
23

34
#[test]
@@ -505,3 +506,50 @@ fn test_from_collect() {
505506
let ba: ByteArray = array!['h', 'e', 'l', 'l', 'o'].into_iter().collect();
506507
assert_eq!(ba, "hello");
507508
}
509+
510+
// TODO(giladchase): add dedicated is_empty test once we have `slice`.
511+
#[test]
512+
fn test_span_len() {
513+
// Test simple happy flow --- value is included in the last word.
514+
// TODO(giladchase): add short string test here once supported cast into span.
515+
let ba: ByteArray = "A";
516+
let span = ba.span();
517+
assert_eq!(span.len(), 1);
518+
assert!(!span.is_empty());
519+
520+
// Test empty.
521+
let empty_ba: ByteArray = "";
522+
let empty_span = empty_ba.span();
523+
assert_eq!(empty_span.len(), 0);
524+
assert!(empty_span.is_empty());
525+
526+
// TODO(giladchase): Add start-offset using slice once supported.
527+
// First word in the array, second in last word.
528+
let two_byte31: ByteArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg";
529+
let mut single_span = two_byte31.span();
530+
assert_eq!(single_span.len(), 33, "len error with start offset");
531+
assert!(!single_span.is_empty());
532+
533+
// TODO(giladchase): Add start-offset using slice once supported.
534+
// First word in the array, second in the array, third in last word.
535+
let three_bytes31: ByteArray =
536+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$"; // 64 chars.
537+
let mut three_span = three_bytes31.span();
538+
assert_eq!(three_span.len(), 64, "len error with size-3 bytearray");
539+
assert!(!three_span.is_empty());
540+
}
541+
542+
#[test]
543+
fn test_span_copy() {
544+
let ba: ByteArray = "12";
545+
let span = ba.span();
546+
assert_eq!(span.len(), 2);
547+
548+
let other_span = span;
549+
assert_eq!(other_span.len(), 2);
550+
let span_again = span.span();
551+
assert_eq!(span_again.len(), span.len());
552+
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`.
555+
}

0 commit comments

Comments
 (0)