Skip to content

Commit d8c0e8b

Browse files
author
Gilad Chase
committed
feat(byte_array): add ByteArray::span and Span::len
Note: unlike `pending_word` in `ByteArray`, the remainder word can contains 31 bytes (rather than 30) for forward-compatibility with short-string spans, which can be 31 bytes and must be held in the remainder word.
1 parent 506e8cf commit d8c0e8b

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed

corelib/src/byte_array.cairo

Lines changed: 54 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,6 +52,8 @@ 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};
@@ -586,3 +588,54 @@ impl ByteArrayFromIterator of crate::iter::FromIterator<ByteArray, u8> {
586588
ba
587589
}
588590
}
591+
592+
/// A view into a contiguous collection of a string type.
593+
/// Currently implemented only for `ByteArray`, but will soon be implemented for other string types.
594+
/// `Span` implements the `Copy` and the `Drop` traits.
595+
#[derive(Copy, Drop)]
596+
pub struct ByteSpan {
597+
/// A span representing the array of all `bytes31` words in the byte-span, excluding the last
598+
/// bytes_31 word that is stored in [Self::last_word].
599+
/// Invariant: every byte stored in `data` is part of the span except for the bytes appearing
600+
/// before `first_char_start_offset` in the first word.
601+
data: Span<bytes31>,
602+
/// The offset of the first character in the first entry of [Self::data], for use in span
603+
/// slices.
604+
first_char_start_offset: BoundedInt<0, 30>,
605+
/// Contains the final bytes of the span when the end is either not in memory or isn't aligned
606+
/// to a word boundary.
607+
/// It is represented as a `felt252` to improve performance of building the byte array, but
608+
/// represents a `bytes31`.
609+
/// The first byte is the most significant byte among the `pending_word_len` bytes in the word.
610+
remainder_word: felt252,
611+
/// The number of bytes in [Self::remainder_word].
612+
remainder_len: BoundedInt<0, 31>,
613+
}
614+
615+
#[generate_trait]
616+
pub impl ByteArraySpanImpl of ByteSpanTrait {
617+
fn len(self: @ByteSpan) -> usize {
618+
let data_bytes = self.data.len() * BYTES_IN_BYTES31;
619+
data_bytes - upcast(self.first_char_start_offset) + upcast(self.remainder_len)
620+
}
621+
622+
fn is_empty(self: @ByteSpan) -> bool {
623+
self.len() == 0
624+
}
625+
}
626+
627+
pub trait ToByteSpanTrait<C> {
628+
#[must_use]
629+
fn span(self: @C) -> ByteSpan;
630+
}
631+
632+
impl ByteArrayToByteSpan of ToByteSpanTrait<ByteArray> {
633+
fn span(self: @ByteArray) -> ByteSpan {
634+
ByteSpan {
635+
data: self.data.span(),
636+
first_char_start_offset: downcast(0).unwrap(),
637+
remainder_word: *self.pending_word,
638+
remainder_len: downcast(self.pending_word_len).expect('In [0,30] by assumption'),
639+
}
640+
}
641+
}

corelib/src/test/byte_array_test.cairo

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

0 commit comments

Comments
 (0)