Skip to content

Commit 19dc717

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

File tree

2 files changed

+175
-1
lines changed

2 files changed

+175
-1
lines changed

corelib/src/byte_array.cairo

Lines changed: 127 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,12 +52,17 @@ 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};
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 [`ByteArray::pending_word`].
64+
type WordBytes = BoundedInt<0, { BYTES_IN_BYTES31_MINUS_ONE.into() }>;
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.
@@ -586,3 +591,124 @@ impl ByteArrayFromIterator of crate::iter::FromIterator<ByteArray, u8> {
586591
ba
587592
}
588593
}
594+
595+
/// A view into a contiguous collection of a string type.
596+
/// Currently implemented only for `ByteArray`, but will soon be implemented for other string types.
597+
/// `Span` implements the `Copy` and the `Drop` traits.
598+
#[derive(Copy, Drop)]
599+
pub struct ByteSpan {
600+
/// A span representing the array of all `bytes31` words in the byte-span, excluding the last
601+
/// bytes_31 word that is stored in [Self::last_word].
602+
/// Invariant: every byte stored in `data` is part of the span except for the bytes appearing
603+
/// before `first_char_start_offset` in the first word.
604+
data: Span<bytes31>,
605+
/// The offset of the first character in the first entry of [Self::data], for use in span
606+
/// slices. When data is empty, this offset applies to remainder_word instead.
607+
first_char_start_offset: WordBytes,
608+
/// Contains the final bytes of the span when the end is either not in memory or isn't aligned
609+
/// to a word boundary.
610+
/// It is represented as a `felt252` to improve performance of building the byte array, but
611+
/// represents a `bytes31`.
612+
/// The first byte is the most significant byte among the `pending_word_len` bytes in the word.
613+
remainder_word: felt252,
614+
/// The number of bytes in [Self::remainder_word].
615+
remainder_len: WordBytes,
616+
}
617+
618+
619+
#[generate_trait]
620+
pub impl ByteSpanImpl 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+
downcast(calc_bytespan_len(self)).unwrap()
634+
}
635+
636+
/// Returns `true` if the `ByteSpan` has a length of 0.
637+
///
638+
/// # Examples
639+
///
640+
/// ```
641+
/// let ba: ByteArray = "";
642+
/// let span = ba.span();
643+
/// assert!(span.is_empty());
644+
///
645+
/// let ba2: ByteArray = "not empty";
646+
/// let span2 = ba2.span();
647+
/// assert!(!span2.is_empty());
648+
/// ```
649+
fn is_empty(self: ByteSpan) -> bool {
650+
self.remainder_len == 0 && self.data.len() == 0
651+
}
652+
}
653+
654+
/// Trait for types that can be converted into a `ByteSpan`.
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+
}
676+
677+
mod helpers {
678+
use core::num::traits::Bounded;
679+
use crate::bytes_31::BYTES_IN_BYTES31;
680+
#[feature("bounded-int-utils")]
681+
use crate::internal::bounded_int::{
682+
self, AddHelper, BoundedInt, MulHelper, SubHelper, UnitInt, downcast,
683+
};
684+
use super::{BYTES_IN_BYTES31_MINUS_ONE, ByteSpan, WordBytes};
685+
686+
type BytesInBytes31Typed = UnitInt<{ BYTES_IN_BYTES31.into() }>;
687+
688+
const U32_MAX_TIMES_B31: felt252 = Bounded::<u32>::MAX.into() * BYTES_IN_BYTES31.into();
689+
const BYTES_IN_BYTES31_UNIT_INT: BytesInBytes31Typed = downcast(BYTES_IN_BYTES31).unwrap();
690+
691+
impl U32ByB31 of MulHelper<u32, BytesInBytes31Typed> {
692+
type Result = BoundedInt<0, { U32_MAX_TIMES_B31 }>;
693+
}
694+
695+
impl B30AddU32ByB31 of AddHelper<WordBytes, U32ByB31::Result> {
696+
type Result = BoundedInt<0, { BYTES_IN_BYTES31_MINUS_ONE.into() + U32_MAX_TIMES_B31 }>;
697+
}
698+
699+
impl B30AddU32ByB31SubB30 of SubHelper<B30AddU32ByB31::Result, WordBytes> {
700+
type Result =
701+
BoundedInt<
702+
{ -BYTES_IN_BYTES31_MINUS_ONE.into() },
703+
{ BYTES_IN_BYTES31_MINUS_ONE.into() + U32_MAX_TIMES_B31 },
704+
>;
705+
}
706+
707+
pub fn calc_bytespan_len(span: ByteSpan) -> B30AddU32ByB31SubB30::Result {
708+
let data_bytes = bounded_int::mul(span.data.len(), BYTES_IN_BYTES31_UNIT_INT);
709+
let span_bytes_unadjusted = bounded_int::add(span.remainder_len, data_bytes);
710+
let span_bytes = bounded_int::sub(span_bytes_unadjusted, span.first_char_start_offset);
711+
span_bytes
712+
}
713+
}
714+
use helpers::calc_bytespan_len;

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)