jc/+internal/array.jai
2025-09-07 15:25:57 -06:00

245 lines
7 KiB
Text

/// ArrayAppend pushes values to the end of an array, resizing if necessary.
/// A pointer to the first value appended will be returned.
///
/// Note: If no allocator has been set, ArrayAppend will use the current context allocator.
/// Note: Calls to Append may invalidate pre-existing pointers.
ArrayAppend :: inline (arr: *[..]$T, values: ..T) -> *T {
TrySetAllocator(arr);
if values.count == 0 {
return basic.array_add(arr);
}
count := arr.count;
basic.array_add(arr, ..values);
return *arr.data[count];
}
/// ArrayGrow resizes the memory associated with an array to hold new_count values.
///
/// Note: If the array has enough allocated memory to accomodate new_count, ArrayGrow does nothing.
/// Note: ArrayGrow does not guarantee pointer stability.
ArrayGrow :: inline (arr: *[..]$T, new_count: int) {
if new_count <= arr.allocated
{ return; }
TrySetAllocator(arr);
basic.array_reserve(arr, new_count,, allocator = arr.allocator);
}
/// ArraySlice returns a subsection of an array.
ArraySlice :: (arr: []$T, start_idx: int, count := -1, loc := #caller_location) -> []T {
AssertCallsite(start_idx >= +0 && start_idx < arr.count, "jc: incorrect slice bounds");
AssertCallsite(count >= -1 && count < arr.count, "jc: incorrect slice length");
if count == -1
{ count = arr.count - start_idx; }
return .{ data = arr.data + start_idx, count = count };
}
/// ArrayReset sets an array's length to 0, but leaves its memory intact.
/// Note: To reset the associated memory as well, see ArrayClear.
ArrayReset :: (arr: *[]$T) {
arr.count = 0;
}
/// ArrayClear zeroes an array's memory and sets its length to 0.
/// Note: To leave the associated memory intact, see ArrayReset.
ArrayClear :: (arr: *[]$T) {
MemZero(arr.data, arr.count * size_of(T));
arr.count = 0;
}
/// ArrayEquals checks equality between two arrays.
ArrayEquals :: (lhs: []$T, rhs: []T) -> bool {
if lhs.count != rhs.count
{ return false; }
return MemEqual(lhs.data, rhs.data, lhs.count * size_of(T));
}
CheckBounds :: ($$index: $T, $$count: T, loc := #caller_location) #expand {
Message :: "bounds check failed!";
#if is_constant(index) && is_constant(count) {
if index < 0 || index >= count {
CompileError(Message, loc = loc);
}
}
else if index < 0 || index > count {
Panic(Message, loc = loc);
}
}
ArrayIndex :: struct(T: Type) {
value: T = ---;
index: int;
}
ArrayFindFlags :: enum_flags {
Last; // Return the last matching element.
FromEnd; // Search in reverse.
}
/// ArrayFind searches through an array, returning the first element that matches the given value.
ArrayFind :: (view: []$T, value: T, $flags: ArrayFindFlags = 0) -> (bool, ArrayIndex(T)) {
found: bool;
result: ArrayIndex(T);
result.index = -1;
REVERSE :: #run (flags & .FromEnd).(bool);
for #v2 <=REVERSE view if it == value {
found = true;
result.index = it_index;
result.value = it;
#if !(flags & .Last) break;
}
return found, result;
}
/// ArrayContains checks if the given value exists in an array.
ArrayContains :: (view: []$T, value: T) -> bool {
return ArrayFind(view, value);
}
ArrayTrimFlags :: enum_flags {
FromStart; // ArrayTrim the start of the array.
FromEnd; // ArrayTrim the end of the array.
MatchInFull; // Only trim when the cutset matches exactly.
}
/// ArrayTrim returns a subsection of an array with all leading/trailing values
/// from cutset removed.
ArrayTrim :: (view: []$T, cutset: []T, $flags: ArrayTrimFlags = .FromStart) -> []T {
result := view;
if cutset.count == 0 || cutset.count > view.count {
return result;
}
#if flags & .FromStart {
#if flags & .MatchInFull {
if ArrayEquals(ArraySlice(view, 0, cutset.count), cutset) {
result = ArraySlice(view, cutset.count, -1);
}
}
else {
while result.count > 0 {
if !ArrayContains(cutset, result[0]) {
break;
}
result.data += 1;
result.count -= 1;
}
}
}
#if flags & .FromEnd {
#if flags & .MatchInFull {
if ArrayEquals(ArraySlice(view, view.count - cutset.count), cutset) {
result.count -= cutset.count;
}
}
else {
while result.count > 0 {
if !ArrayContains(cutset, result[result.count - 1]) {
break;
}
result.count -= 1;
}
}
}
return result;
}
#scope_file
basic :: #import "Basic"; // @future
#if RunTests #run,stallable {
Test("slice", t => {
a1 := int.[ 1, 2, 3, 4, 5 ];
a2 := ArraySlice(a1, 2);
Expect(a2.count == 3);
Expect(ArrayEquals(a2, int.[ 3, 4, 5 ]));
b1 := int.[ 1, 2, 3, 4, 5 ];
b2 := ArraySlice(b1, 2, 0);
Expect(b2.count == 0);
Expect(b2.data == b1.data + 2);
c1 := int.[ 1, 2, 3, 4, 5 ];
c2 := ArraySlice(c1, 3, 1);
Expect(c2.count == 1);
Expect(ArrayEquals(c2, int.[ 4 ]));
d1 := int.[ 1, 2, 3 ];
d2 := ArraySlice(d1, 2);
Expect(d2.count == 1);
Expect(ArrayEquals(d2, int.[ 3 ]));
});
Test("find", t => {
a := int.[ 1, 2, 3, 4, 5 ];
ok, res := ArrayFind(a, 3);
Expect(ok && res.index == 2);
Expect(res.value == 3);
ok, res = ArrayFind(a, -1);
Expect(!ok && res.index == -1);
b := int.[ 1, 2, 2, 3, 4, 5, 2 ];
ok, res = ArrayFind(b, 2);
Expect(ok && res.index == 1);
ok, res = ArrayFind(b, 2, .FromEnd);
Expect(ok && res.index == 6);
c := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
ok, res = ArrayFind(c, 0, .Last);
Expect(ok && res.index == 8);
ok, res = ArrayFind(c, 0, .FromEnd | .Last);
Expect(ok && res.index == 0);
});
Test("contains", t => {
a := int.[ 1, 2, 3, 4, 5 ];
Expect(ArrayContains(a, 3));
Expect(!ArrayContains(a, -1));
});
Test("trim", t => {
a1 := int.[ 0, 0, 0, 1, 2, 3 ];
a2 := ArrayTrim(a1, .[ 0 ]);
Expect(ArrayEquals(a1, .[ 0, 0, 0, 1, 2, 3 ]));
Expect(ArrayEquals(a2, .[ 1, 2, 3 ]));
b1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
b2 := ArrayTrim(b1, .[ 0 ], .FromEnd);
Expect(ArrayEquals(b1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
Expect(ArrayEquals(b2, .[ 0, 0, 0, 1, 2, 3 ]));
c1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
c2 := ArrayTrim(c1, .[ 0 ], .FromStart | .FromEnd);
Expect(ArrayEquals(c1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
Expect(ArrayEquals(c2, .[ 1, 2, 3 ]));
d1 := int.[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ];
d2 := ArrayTrim(d1, .[ 0, 0, 0 ], .FromStart | .MatchInFull);
d3 := ArrayTrim(d1, .[ 0, 0, 0 ], .FromEnd | .MatchInFull);
d4 := ArrayTrim(d1, .[ 0, 0, 0 ], .FromStart | .FromEnd | .MatchInFull);
Expect(ArrayEquals(d1, .[ 0, 0, 0, 1, 2, 3, 0, 0, 0 ]));
Expect(ArrayEquals(d2, .[ 1, 2, 3, 0, 0, 0 ]));
Expect(ArrayEquals(d3, .[ 0, 0, 0, 1, 2, 3 ]));
Expect(ArrayEquals(d4, .[ 1, 2, 3 ]));
});
}