Skip to content

Commit 19ce8b6

Browse files
author
Gilad Chase
committed
feat(byte_array): add at and index to ByteSpan
1 parent 20ea89a commit 19ce8b6

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

corelib/src/byte_array.cairo

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

45+
use crate::IndexView;
4546
use crate::array::{ArrayTrait, Span, SpanTrait};
4647
use crate::bytes_31::split_bytes31;
4748
#[allow(unused_imports)]
@@ -692,6 +693,27 @@ pub impl ByteSpanImpl of ByteSpanTrait {
692693
ba
693694
}
694695

696+
fn at(self: @ByteSpan, index: usize) -> Option<u8> {
697+
let actual_index = index.checked_add(upcast(self.first_char_start_offset))?;
698+
let (word_index, index_in_word) = DivRem::div_rem(actual_index, BYTES_IN_BYTES31_NONZERO);
699+
700+
match self.data.get(word_index) {
701+
Some(word) => {
702+
// index_in_word is from MSB, we need index from LSB.
703+
Some(word.at(BYTES_IN_BYTES31 - 1 - index_in_word))
704+
},
705+
None => {
706+
if word_index == self.data.len() && index_in_word < upcast(self.remainder_len) {
707+
// index_in_word is from MSB, we need index from LSB.
708+
let index_in_remainder = upcast(self.remainder_len) - 1 - index_in_word;
709+
Some(u8_at_u256((*self.remainder_word).into(), index_in_remainder))
710+
} else {
711+
None // Out of bounds.
712+
}
713+
},
714+
}
715+
}
716+
695717
/// Returns a slice of the ByteSpan from the given start position with the given length.
696718
fn slice(self: @ByteSpan, start: usize, len: usize) -> Option<ByteSpan> {
697719
if len == 0 {
@@ -733,6 +755,12 @@ impl ByteSpanDefault of Default<ByteSpan> {
733755
}
734756
}
735757

758+
impl ByteSpanIndex of IndexView<ByteSpan, usize, u8> {
759+
fn index(self: @ByteSpan, index: usize) -> u8 {
760+
self.at(index).expect('Index out of bounds')
761+
}
762+
}
763+
736764
pub trait ToByteSpanTrait<C> {
737765
#[must_use]
738766
fn span(self: @C) -> ByteSpan;

corelib/src/test/byte_array_test.cairo

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,3 +726,63 @@ fn test_span_multiple_start_offset_slicing() {
726726
assert_eq!(result3, "def", "third slice");
727727
}
728728

729+
#[test]
730+
fn test_span_at_and_index() {
731+
// Test simple access.
732+
let ba: ByteArray = "AB";
733+
let span = ba.span();
734+
assert_eq!(span.at(0), Some('A'));
735+
assert_eq!(span.at(1), Some('B'));
736+
assert_eq!(span.at(2), None);
737+
738+
// Test index operator on same span.
739+
assert_eq!(span[0], 'A');
740+
assert_eq!(span[1], 'B');
741+
742+
// Test with offset and two words.
743+
let ba_33: ByteArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg";
744+
let mut span = ba_33.span();
745+
span = span.slice(1, 32).unwrap();
746+
assert_eq!(span.at(0), Option::Some('B'));
747+
assert_eq!(span.at(30), Option::Some('f'));
748+
assert_eq!(span.at(31), Option::Some('g'));
749+
assert_eq!(span.at(32), Option::None);
750+
751+
// Test with offset and two words.
752+
// 64 bytes: 31 + 31 + 2.
753+
let ba_64: ByteArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#$";
754+
let mut span = ba_64.span();
755+
span = span.slice(1, 63).unwrap();
756+
assert_eq!(span.at(30), Some('f'), "byte 30 - last of 1nd word");
757+
assert_eq!(span.at(31), Some('g'), "byte 31 - first of 2nd word");
758+
assert_eq!(span.at(60), Some('9'), "byte 60 - last of 2nd word");
759+
assert_eq!(span.at(61), Some('#'), "byte 61 - first in last_word");
760+
assert_eq!(span.at(62), Some('$'), "byte 62 - last in last_word");
761+
assert_eq!(span.at(63), None);
762+
763+
// Test empty span.
764+
let empty: ByteArray = Default::default();
765+
let empty_span = empty.span();
766+
assert_eq!(empty_span.at(0), None);
767+
}
768+
769+
#[test]
770+
#[should_panic(expected: ('Index out of bounds',))]
771+
fn test_span_index_out_of_bounds() {
772+
let ba: ByteArray = "AB";
773+
let span = ba.span();
774+
let _x = span[2]; // Should panic
775+
}
776+
777+
#[test]
778+
fn test_span_at_overflows() {
779+
// Test overflow protection with large indices.
780+
let ba: ByteArray = "test";
781+
let span = ba.span();
782+
783+
assert_eq!(span.at(Bounded::<usize>::MAX), None);
784+
785+
let sliced = ba.span().slice(1, 3).unwrap();
786+
assert_eq!(sliced.at(Bounded::<usize>::MAX - 1), None);
787+
assert_eq!(sliced.at(Bounded::<usize>::MAX), None);
788+
}

0 commit comments

Comments
 (0)