Skip to content

Commit b3f9bb8

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

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

corelib/src/byte_array.cairo

Lines changed: 133 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 Bytes31Index = 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,130 @@ 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: Bytes31Index,
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: Bytes31Index,
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+
helpers::calc_bytespan_len(self)
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+
// No need to check offsets: when `slice` consumes the span it returns `Default::default()`.
651+
self.remainder_len == 0 && self.data.len() == 0
652+
}
653+
}
654+
655+
/// Trait for types that can be converted into a `ByteSpan`.
656+
#[unstable(feature: "byte_span")]
657+
pub trait ToByteSpanTrait<C> {
658+
#[must_use]
659+
/// Create a `ByteSpan` view object for the given type.
660+
fn span(self: @C) -> ByteSpan;
661+
}
662+
663+
#[feature("byte_span")]
664+
impl ByteArrayToByteSpan of ToByteSpanTrait<ByteArray> {
665+
fn span(self: @ByteArray) -> ByteSpan {
666+
ByteSpan {
667+
data: self.data.span(),
668+
first_char_start_offset: 0,
669+
remainder_word: *self.pending_word,
670+
remainder_len: downcast(self.pending_word_len).expect('In [0,30] by assumption'),
671+
}
672+
}
673+
}
674+
675+
#[feature("byte_span")]
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::{
687+
self, AddHelper, BoundedInt, MulHelper, SubHelper, UnitInt, downcast,
688+
};
689+
use super::{BYTES_IN_BYTES31_MINUS_ONE, ByteSpan, Bytes31Index};
690+
691+
type BytesInBytes31Typed = UnitInt<{ BYTES_IN_BYTES31.into() }>;
692+
693+
const U32_MAX_TIMES_B31: felt252 = Bounded::<u32>::MAX.into() * BYTES_IN_BYTES31.into();
694+
const BYTES_IN_BYTES31_UNIT_INT: BytesInBytes31Typed = downcast(BYTES_IN_BYTES31).unwrap();
695+
696+
impl U32ByB31 of MulHelper<u32, BytesInBytes31Typed> {
697+
type Result = BoundedInt<0, U32_MAX_TIMES_B31>;
698+
}
699+
700+
impl B30AddU32ByB31 of AddHelper<Bytes31Index, U32ByB31::Result> {
701+
type Result = BoundedInt<0, { BYTES_IN_BYTES31_MINUS_ONE.into() + U32_MAX_TIMES_B31 }>;
702+
}
703+
704+
impl B30AddU32ByB31SubB30 of SubHelper<B30AddU32ByB31::Result, Bytes31Index> {
705+
type Result =
706+
BoundedInt<
707+
{ -BYTES_IN_BYTES31_MINUS_ONE.into() },
708+
{ BYTES_IN_BYTES31_MINUS_ONE.into() + U32_MAX_TIMES_B31 },
709+
>;
710+
}
711+
712+
/// Calculates the length of a `ByteSpan` in bytes.
713+
pub fn calc_bytespan_len(span: ByteSpan) -> usize {
714+
let data_bytes = bounded_int::mul(span.data.len(), BYTES_IN_BYTES31_UNIT_INT);
715+
let span_bytes_unadjusted = bounded_int::add(span.remainder_len, data_bytes);
716+
let span_bytes = bounded_int::sub(span_bytes_unadjusted, span.first_char_start_offset);
717+
718+
downcast(span_bytes).unwrap()
719+
}
720+
}

corelib/src/test/byte_array_test.cairo

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[feature("byte_span")]
2+
use crate::byte_array::{ByteSpanTrait, ToByteSpanTrait};
13
use crate::test::test_utils::{assert_eq, assert_ne};
24

35
#[test]
@@ -505,3 +507,51 @@ fn test_from_collect() {
505507
let ba: ByteArray = array!['h', 'e', 'l', 'l', 'o'].into_iter().collect();
506508
assert_eq!(ba, "hello");
507509
}
510+
511+
// TODO(giladchase): add dedicated is_empty test once we have `slice`.
512+
#[test]
513+
fn test_span_len() {
514+
// Test simple happy flow --- value is included in the last word.
515+
// TODO(giladchase): add short string test here once supported cast into span.
516+
let ba: ByteArray = "A";
517+
let span = ba.span();
518+
assert_eq!(span.len(), 1);
519+
assert!(!span.is_empty());
520+
521+
// Test empty.
522+
let empty_ba: ByteArray = "";
523+
let empty_span = empty_ba.span();
524+
assert_eq!(empty_span.len(), 0);
525+
assert!(empty_span.is_empty());
526+
527+
// TODO(giladchase): Add start-offset using slice once supported.
528+
// First word in the array, second in last word.
529+
let two_byte31: ByteArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg";
530+
let mut single_span = two_byte31.span();
531+
assert_eq!(single_span.len(), 33, "len error with start offset");
532+
assert!(!single_span.is_empty());
533+
534+
// TODO(giladchase): Add start-offset using slice once supported.
535+
// First word in the array, second in the array, third in last word.
536+
let three_bytes31: ByteArray =
537+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$"; // 64 chars.
538+
let mut three_span = three_bytes31.span();
539+
assert_eq!(three_span.len(), 64, "len error with size-3 bytearray");
540+
assert!(!three_span.is_empty());
541+
// TODO(giladchase): use `ByteSpan::PartialEq` to check that a consuming slice == Default.
542+
}
543+
544+
#[test]
545+
fn test_span_copy() {
546+
let ba: ByteArray = "12";
547+
let span = ba.span();
548+
assert_eq!(span.len(), 2);
549+
550+
let other_span = span;
551+
assert_eq!(other_span.len(), 2);
552+
let span_again = span.span();
553+
assert_eq!(span_again.len(), span.len());
554+
let even_more_span_again = other_span.span();
555+
assert_eq!(even_more_span_again.len(), other_span.len());
556+
// TODO(giladchase): verify span content once we add `into` or `PartialEq`.
557+
}

0 commit comments

Comments
 (0)