Skip to content

Commit 76e8b98

Browse files
author
Gilad Chase
committed
feat(byte_array): add ByteArray::span and Span::len
1 parent 202c414 commit 76e8b98

File tree

2 files changed

+168
-1
lines changed

2 files changed

+168
-1
lines changed

corelib/src/byte_array.cairo

Lines changed: 120 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,18 +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::{self, BoundedInt, UnitInt, 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+
type BYTES_IN_BYTES31_TYPED = UnitInt<{ BYTES_IN_BYTES31.into() }>;
66+
6167
/// A magic constant for identifying serialization of `ByteArray` variables. An array of `felt252`
6268
/// with this magic value as one of the `felt252` indicates that you should expect right after it a
6369
/// serialized `ByteArray`. This is currently used mainly for prints and panics.
6470
pub const BYTE_ARRAY_MAGIC: felt252 =
6571
0x46a6158a16a947e5916b2a2ca68501a45e93d7110e81aa2d6438b1c57c879a3;
6672
const BYTES_IN_U128: usize = 16;
73+
const BYTES_IN_BYTES31_UNIT_INT: BYTES_IN_BYTES31_TYPED = downcast(BYTES_IN_BYTES31).unwrap();
6774
const BYTES_IN_BYTES31_MINUS_ONE: usize = BYTES_IN_BYTES31 - 1;
6875
const BYTES_IN_BYTES31_NONZERO: NonZero<usize> = BYTES_IN_BYTES31.try_into().unwrap();
6976

@@ -586,3 +593,115 @@ impl ByteArrayFromIterator of crate::iter::FromIterator<ByteArray, u8> {
586593
ba
587594
}
588595
}
596+
597+
/// A view into a contiguous collection of a string type.
598+
/// Currently implemented only for `ByteArray`, but will soon be implemented for other string types.
599+
/// `Span` implements the `Copy` and the `Drop` traits.
600+
#[derive(Copy, Drop)]
601+
pub struct ByteSpan {
602+
/// A span representing the array of all `bytes31` words in the byte-span, excluding the last
603+
/// bytes_31 word that is stored in [Self::last_word].
604+
/// Invariant: every byte stored in `data` is part of the span except for the bytes appearing
605+
/// before `first_char_start_offset` in the first word.
606+
data: Span<bytes31>,
607+
/// The offset of the first character in the first entry of [Self::data], for use in span
608+
/// slices. When data is empty, this offset applies to remainder_word instead.
609+
first_char_start_offset: WordBytes,
610+
/// Contains the final bytes of the span when the end is either not in memory or isn't aligned
611+
/// to a word boundary.
612+
/// It is represented as a `felt252` to improve performance of building the byte array, but
613+
/// represents a `bytes31`.
614+
/// The first byte is the most significant byte among the `pending_word_len` bytes in the word.
615+
remainder_word: felt252,
616+
/// The number of bytes in [Self::remainder_word].
617+
remainder_len: WordBytes,
618+
}
619+
620+
621+
#[generate_trait]
622+
pub impl ByteSpanImpl of ByteSpanTrait {
623+
/// Returns the length of the `ByteSpan`.
624+
///
625+
/// # Examples
626+
///
627+
/// ```
628+
/// let ba: ByteArray = "byte array";
629+
/// let span = ba.span();
630+
/// let len = span.len();
631+
/// assert!(len == 10);
632+
/// ```
633+
#[must_use]
634+
fn len(self: ByteSpan) -> usize {
635+
let data_bytes = bounded_int::mul(self.data.len(), BYTES_IN_BYTES31_UNIT_INT);
636+
let span_bytes_unadjusted = bounded_int::add(self.remainder_len, data_bytes);
637+
let span_bytes = bounded_int::sub(span_bytes_unadjusted, self.first_char_start_offset);
638+
639+
downcast(span_bytes).unwrap()
640+
}
641+
642+
/// Returns `true` if the `ByteSpan` has a length of 0.
643+
///
644+
/// # Examples
645+
///
646+
/// ```
647+
/// let ba: ByteArray = "";
648+
/// let span = ba.span();
649+
/// assert!(span.is_empty());
650+
///
651+
/// let ba2: ByteArray = "not empty";
652+
/// let span2 = ba2.span();
653+
/// assert!(!span2.is_empty());
654+
/// ```
655+
fn is_empty(self: ByteSpan) -> bool {
656+
self.remainder_len == 0 && self.data.len() == 0
657+
}
658+
}
659+
660+
pub trait ToByteSpanTrait<C> {
661+
#[must_use]
662+
fn span(self: @C) -> ByteSpan;
663+
}
664+
665+
impl ByteArrayToByteSpan of ToByteSpanTrait<ByteArray> {
666+
fn span(self: @ByteArray) -> ByteSpan {
667+
ByteSpan {
668+
data: self.data.span(),
669+
first_char_start_offset: 0,
670+
remainder_word: *self.pending_word,
671+
remainder_len: downcast(self.pending_word_len).expect('In [0,30] by assumption'),
672+
}
673+
}
674+
}
675+
676+
impl ByteSpanToByteSpan of ToByteSpanTrait<ByteSpan> {
677+
fn span(self: @ByteSpan) -> ByteSpan {
678+
*self
679+
}
680+
}
681+
682+
mod helpers {
683+
use core::num::traits::Bounded;
684+
use crate::bytes_31::BYTES_IN_BYTES31;
685+
#[feature("bounded-int-utils")]
686+
use crate::internal::bounded_int::{AddHelper, BoundedInt, MulHelper, SubHelper};
687+
use super::{BYTES_IN_BYTES31_MINUS_ONE, BYTES_IN_BYTES31_TYPED, WordBytes};
688+
689+
const U32_MAX_TIMES_B31: felt252 = Bounded::<u32>::MAX.into() * BYTES_IN_BYTES31.into();
690+
691+
pub impl U32ByB31 of MulHelper<u32, BYTES_IN_BYTES31_TYPED> {
692+
type Result = BoundedInt<0, { U32_MAX_TIMES_B31 }>;
693+
}
694+
695+
pub 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+
pub 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+
use helpers::{B30AddU32ByB31, B30AddU32ByB31SubB30, U32ByB31};

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)