Add array::zip and Related Macros (#2370)

* zip / zip_into
* Deprecate `add_array` in favour of `push_all` on lists.
* Add support for generic lists for zip.

---------

Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
This commit is contained in:
Zack Puhl
2025-08-16 07:30:24 -04:00
committed by GitHub
parent e35dbd29fb
commit 702b63ddb7
16 changed files with 567 additions and 45 deletions

View File

@@ -121,7 +121,24 @@ fn usz ElasticArray.add_all_to_limit(&self, ElasticArray* other_list)
@param [in] array
*>
fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
fn usz ElasticArray.add_array_to_limit(&self, Type[] array) @deprecated("Use push_all_to_limit")
{
if (!array.len) return 0;
foreach (i, &value : array)
{
if (self.size == MAX_SIZE) return array.len - i;
self.entries[self.size++] = *value;
}
return 0;
}
<*
Add as many values from this array as possible, returning the
number of elements that didn't fit.
@param [in] array
*>
fn usz ElasticArray.push_all_to_limit(&self, Type[] array)
{
if (!array.len) return 0;
foreach (i, &value : array)
@@ -139,7 +156,7 @@ fn usz ElasticArray.add_array_to_limit(&self, Type[] array)
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.add_array(&self, Type[] array)
fn void ElasticArray.add_array(&self, Type[] array) @deprecated("Use push_all")
{
if (!array.len) return;
foreach (&value : array)
@@ -148,6 +165,21 @@ fn void ElasticArray.add_array(&self, Type[] array)
}
}
<*
Add the values of an array to this list.
@param [in] array
@require array.len + self.size <= MAX_SIZE : `Size would exceed max.`
@ensure self.size >= array.len
*>
fn void ElasticArray.push_all(&self, Type[] array)
{
if (!array.len) return;
foreach (&value : array)
{
self.entries[self.size++] = *value;
}
}
<*

View File

@@ -57,7 +57,7 @@ fn List* List.tinit(&self, usz initial_capacity = 16)
fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
{
self.init(allocator, values.len) @inline;
self.add_array(values) @inline;
self.push_all(values) @inline;
return self;
}
@@ -70,7 +70,7 @@ fn List* List.init_with_array(&self, Allocator allocator, Type[] values)
fn List* List.tinit_with_array(&self, Type[] values)
{
self.tinit(values.len) @inline;
self.add_array(values) @inline;
self.push_all(values) @inline;
return self;
}
@@ -199,7 +199,21 @@ fn Type[] List.array_view(&self)
@param [in] array
@ensure self.size >= array.len
*>
fn void List.add_array(&self, Type[] array)
fn void List.add_array(&self, Type[] array) @deprecated("Use push_all")
{
if (!array.len) return;
self.reserve(array.len);
usz index = self.set_size(self.size + array.len);
self.entries[index : array.len] = array[..];
}
<*
Add the values of an array to this list.
@param [in] array
@ensure self.size >= array.len
*>
fn void List.push_all(&self, Type[] array)
{
if (!array.len) return;
self.reserve(array.len);

View File

@@ -1,5 +1,7 @@
module std::core::array;
import std::core::array::slice;
import std::collections::pair;
<*
Returns true if the array contains at least one element, else false
@@ -18,6 +20,7 @@ macro bool contains(array, element)
return false;
}
<*
Return the first index of element found in the array, searching from the start.
@@ -35,6 +38,7 @@ macro index_of(array, element)
return NOT_FOUND?;
}
<*
Slice a 2d array and create a Slice2d from it.
@@ -74,6 +78,7 @@ macro rindex_of(array, element)
return NOT_FOUND?;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them.
@@ -99,6 +104,7 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
}
return result;
}
<*
Concatenate two arrays or slices, returning a slice containing the concatenation of them,
allocated using the temp allocator.
@@ -110,4 +116,225 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
@require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type"
@ensure return.len == arr1.len + arr2.len
*>
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);
<*
Zip together two separate arrays/slices into a single array of Pairs or return values. Values will
be collected up to the length of the shorter array if `fill_with` is left undefined; otherwise, they
will be collected up to the length of the LONGER array, with missing values in the shorter array being
assigned to the value of `fill_with`. Return array elements do not have to be of the same type.
For example:
```c3
uint[] chosen_session_ids = server::get_random_sessions(instance)[:128];
String[200] refreshed_session_keys = prng::new_keys_batch();
Pair { uint, String }[] sessions_meta = array::zip(mem, chosen_session_ids, refreshed_session_keys);
// The resulting Pair{}[] slice is then length of the shortest of the two arrays, so 128.
foreach (i, &sess : sessions:meta) {
// distribute new session keys to associated instance IDs
}
```
Or:
```c3
String[] client_names = server::online_usernames(instance);
uint128[] session_ids = server::user_keys();
// in this example, we 'know' ahead of time that 'session_ids' can only ever be SHORTER
// than 'client_names', but never longer, because it's possible new users have logged
// in without getting whatever this 'session ID' is delegated to them.
Pair { String, uint128 }[] zipped = array::tzip(client_names, session_ids, fill_with: uint128.max);
server::refresh_session_keys_by_pair(zipped)!;
```
### When an `operation` is supplied...
Apply an operation to each element of two slices or arrays and return the results of
each operation into a newly allocated array.
This essentially combines Iterable1 with Iterable2 using the `operation` functor.
See the functional `zipWith` construct, which has a more appropriate name than, e.g., `map`;
a la: https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelude.html#v:zipWith
Similar to "normal" `zip`, this macro pads the shorter input array with a given `fill_with`, or
an empty value if one isn't supplied. This `fill_with` is supplied to the `operation` functor
_BEFORE_ calculating its result while zipping.
For example: a functor of `fn char (char a, char b) => a + b` with a `fill_with` of 7,
where the `left` array is the shorter iterable, will put 7 into that lambda in each place
where `left` is being filled in during the zip operation.
@param [&inout] allocator : "The allocator to use; default is the heap allocator."
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
@require @is_valid_operation(#operation, left, right) : "The operator must take two parameters matching the elements of the left and right side"
@require @is_valid_fill(left, right, fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
*>
macro @zip(Allocator allocator, left, right, #operation = EMPTY_MACRO_SLOT, fill_with = EMPTY_MACRO_SLOT) @nodiscard
{
var $LeftType = $typeof(left[0]);
var $RightType = $typeof(right[0]);
var $Type = Pair { $LeftType, $RightType };
bool $is_op = @is_valid_macro_slot(#operation);
$if $is_op:
$Type = $typeof(#operation).returns;
$endif
usz left_len = find_len(left);
usz right_len = find_len(right);
$LeftType left_fill;
$RightType right_fill;
usz result_len = min(left_len, right_len);
bool $has_fill = @is_valid_macro_slot(fill_with);
$if $has_fill:
switch
{
case left_len > right_len:
$if !$defined(($RightType)fill_with):
unreachable();
$else
right_fill = ($RightType)fill_with;
result_len = left_len;
$endif
case left_len < right_len:
$if !$defined(($LeftType)fill_with):
unreachable();
$else
left_fill = ($LeftType)fill_with;
result_len = right_len;
$endif
}
$endif
if (result_len == 0) return ($Type[]){};
$Type[] result = allocator::alloc_array(allocator, $Type, result_len);
foreach (idx, &item : result)
{
$if $is_op:
var $LambdaType = $typeof(fn $Type ($LeftType a, $RightType b) => ($Type){});
$LambdaType $operation = ($LambdaType)#operation;
$LeftType lval = idx >= left_len ? left_fill : left[idx];
$RightType rval = idx >= right_len ? right_fill : right[idx];
*item = $operation(lval, rval);
$else
*item = {
idx >= left_len ? left_fill : left[idx],
idx >= right_len ? right_fill : right[idx]
};
$endif
}
return result;
}
<*
Array 'zip' using the temp allocator.
@param [in] left : "The left-side array. These items will be placed as the First in each Pair"
@param [in] right : "The right-side array. These items will be placed as the Second in each Pair"
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
@param fill_with : "The value used to fill or pad the shorter iterable to the length of the longer one while zipping."
@require @is_valid_list(left) &&& @is_valid_list(right) : "Left and right sides must be integer indexable"
@require @is_valid_operation(#operation, left, right) : "The operator must take two parameters matching the elements of the left and right side"
@require @is_valid_fill(left, right, fill_with) : "The specified fill value does not match either the left or the right array's underlying type."
*>
macro @tzip(left, right, #operation = EMPTY_MACRO_SLOT, fill_with = EMPTY_MACRO_SLOT) @nodiscard
{
return @zip(tmem, left, right, #operation, fill_with);
}
<*
Apply an operation to each element of two slices or arrays and store the results of
each operation into the 'left' value.
This is useful because no memory allocations are required in order to perform the operation.
A good example of using this might be using algorithmic transformations on data in-place:
```
char[] partial_cipher = get_next_plaintext_block();
array::@zip_into(
partial_cipher[ENCRYPT_OFFSET:BASE_KEY.len],
BASE_KEY,
fn char (char a, char b) => a ^ (b * 5) % 37
);
```
This parameterizes the lambda function with left (`partial_cipher`) and right (`BASE_KEY`) slice
elements and stores the end result in-place within the left slice. This is in contrast to a
regular `zip_with` which will create a cloned final result and return it.
@param [inout] left : `Slice to store results of applied functor/operation.`
@param [in] right : `Slice to apply in the functor/operation.`
@param #operation : "The function to apply. Must have a signature of `$typeof(a) (a, b)`, where the type of 'a' and 'b' is the element type of left/right respectively."
@require @is_valid_list(left) : "Expected a valid list"
@require @is_valid_list(right) : "Expected a valid list"
@require find_len(right) >= find_len(left) : `Right side length must be >= the destination (left) side length; consider using a sub-array of data for the assignment.`
@require @assignable_to(#operation, $typefrom(@zip_into_fn(left, right))) : "The functor must use the same types as the `left` and `right` inputs, and return a value of the `left` type."
*>
macro @zip_into(left, right, #operation)
{
$typefrom(@zip_into_fn(left, right)) $operation = #operation;
foreach (i, &v : left) *v = $operation(left[i], right[i]);
}
// --- helper functions
macro typeid @zip_into_fn(#left, #right) @private @const
{
return @typeid(fn $typeof(#left[0]) ($typeof(#left[0]) l, $typeof(#right[0]) r) => l);
}
macro bool @is_valid_operation(#operation, #left, #right) @const @private
{
$switch:
$case @is_empty_macro_slot(#operation):
return true;
$case @typekind(#operation) != FUNC:
return false;
$default:
return $defined(#operation(#left[0], #right[0]));
$endswitch
}
macro bool @is_valid_list(#expr) @const @private
{
return $defined(#expr[0]) &&& ($defined(#expr.len) ||| $defined(#expr.len()));
}
macro bool @is_valid_fill(left, right, fill_with) @private
{
if (@is_empty_macro_slot(fill_with)) return true;
usz left_len = @select($defined(left.len()), left.len(), left.len);
usz right_len = @select($defined(right.len()), right.len(), right.len);
if (left_len == right_len) return true;
return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with);
}
macro usz find_len(list)
{
$if $defined(list.len()):
return list.len();
$else
return list.len;
$endif
}

View File

@@ -684,6 +684,12 @@ macro @clone(value) @builtin @nodiscard
return allocator::clone(mem, value);
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro @clone_slice(value) @builtin @nodiscard => allocator::clone_slice(mem, value);
<*
@param value : "The value to clone"
@return "A pointer to the cloned value, which must be released using free_aligned"
@@ -706,6 +712,12 @@ macro @tclone(value) @builtin @nodiscard
$endif
}
<*
@param value : "The value to clone"
@return "A pointer to the cloned value"
*>
macro @tclone_slice(value) @builtin @nodiscard => allocator::clone_slice(tmem, value);
fn void* malloc(usz size) @builtin @inline @nodiscard
{
return allocator::malloc(mem, size);

View File

@@ -317,6 +317,23 @@ macro clone(Allocator allocator, value) @nodiscard
return new(allocator, $typeof(value), value);
}
<*
@param [&inout] allocator : "The allocator used to clone"
@param slice : "The slice to clone"
@return "A pointer to the cloned slice"
@require @typekind(slice) == SLICE || @typekind(slice) == ARRAY
*>
macro clone_slice(Allocator allocator, slice) @nodiscard
{
var $Type = $typeof(slice[0]);
$Type[] new_arr = new_array(allocator, $Type, slice.len);
mem::copy(new_arr.ptr, &slice[0], slice.len * $Type.sizeof);
return new_arr;
}
<*
Clone overaligned values. Must be released using free_aligned.

View File

@@ -51,6 +51,7 @@ macro @select(bool $bool, #value_1, #value_2) @builtin
return #value_2;
$endif
}
macro promote_int_same(x, y)
{
$if @is_int(x):

View File

@@ -12,6 +12,8 @@
- Switch available for types implementing `@operator(==)`.
- `Type.is_eq` is now true for types with `==` overload.
- Methods ignore visibility settings.
- Allow inout etc on untyped macro parameters even if they are not pointers.
- Deprecate `add_array` in favour of `push_all` on lists.
### Fixes
- List.remove_at would incorrectly trigger ASAN.
@@ -49,6 +51,7 @@
- Updated hash functions in default hash methods.
- Added `FixedBlockPool` which is a memory pool for fixed size blocks.
- Added the experimental `std::core::log` for logging.
- Added array `@zip` and `@zip_into` macros. #2370
- Updated termios bindings to use bitstructs and fixed some constants with incorrect values #2372
- Added libloaderapi to `std::os::win32`.

View File

@@ -2578,7 +2578,6 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s
goto SKIP_LINK;
}
Decl *func = context->call_env.current_function;
ASSERT_SPAN(func, func);
ASSERT_SPAN(func, func->resolved_attributes);
if (!func->attrs_resolved)
{
@@ -2773,7 +2772,7 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s
if (param->var.init_expr)
{
Type *param_type = param->type;
if (param_type && (param->var.out_param || param->var.not_null))
if (param_type && param->var.type_info && (param->var.out_param || param->var.not_null))
{
param_type = type_flatten(param_type);
if (param_type->type_kind != TYPE_POINTER && param_type->type_kind != TYPE_SLICE && param_type->type_kind != TYPE_INTERFACE && param_type->type_kind != TYPE_ANY)
@@ -2782,7 +2781,7 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s
goto EXIT_FAIL;
}
}
if (param->var.not_null)
if (param->var.not_null && (param_type->type_kind == TYPE_POINTER || param_type->type_kind == TYPE_SLICE || param_type->type_kind == TYPE_INTERFACE || param_type->type_kind == TYPE_ANY))
{
Expr *expr = expr_variable(param);
Expr *binary = expr_new_expr(EXPR_BINARY, expr);

View File

@@ -7,6 +7,6 @@ macro foo(x) => *x = 1;
fn int main()
{
foo(0); // #error: Expected a pointer
foo(0); // #error: x must be
return 0;
}

View File

@@ -7,7 +7,7 @@ alias PtrList = ElasticArray{void*, 10};
fn void delete_contains_index()
{
IntList test;
test.add_array({ 1, 2 });
test.push_all({ 1, 2 });
assert(test.contains(1));
assert(test.contains(2));
assert(!test.contains(0));
@@ -36,7 +36,7 @@ fn void delete_contains_index()
fn void compact()
{
PtrList test;
test.add_array({ null, &test });
test.push_all({ null, &test });
assert(test.compact_count() == 1);
test.push(null);
assert(test.compact_count() == 1);
@@ -50,7 +50,7 @@ fn void reverse()
{
IntList test;
test.reverse();
test.add_array({ 1, 2 });
test.push_all({ 1, 2 });
test.push(3);
assert(test.array_view() == (int[]) { 1, 2, 3});
test.reverse();
@@ -66,13 +66,13 @@ fn void remove_if()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_if(&filter);
assert(removed == 3);
assert(test.array_view() == (int[]){1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_if(&select);
assert(removed == 2);
assert(test.array_view() == (int[]){11, 10, 20});
@@ -84,13 +84,13 @@ fn void remove_using_test()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10);
assert(removed == 3);
assert(test.array_view() == (int[]){1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10);
assert(removed == 2);
assert(test.array_view() == (int[]){11, 10, 20});
@@ -101,13 +101,13 @@ fn void retain_if()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.retain_if(&select);
assert(removed == 3);
assert(test.array_view() == (int[]){1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.retain_if(&filter);
assert(removed == 2);
assert(test.array_view() == (int[]){11, 10, 20});
@@ -118,13 +118,13 @@ fn void retain_using_test()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10);
assert(removed == 3);
assert(test.array_view() == (int[]){1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10);
assert(removed == 2);
assert(test.array_view() == (int[]){11, 10, 20});

View File

@@ -25,7 +25,7 @@ fn void remove_at()
IntList test;
test.init(mem);
defer test.free();
test.add_array({ 1, 2, 3, 4 });
test.push_all({ 1, 2, 3, 4 });
test::eq(test.array_view(), (int[]){ 1, 2, 3, 4 });
test.remove_at(0);
test::eq(test.array_view(), (int[]){ 2, 3, 4 });
@@ -41,7 +41,7 @@ fn void delete_contains_index()
{
IntList test;
test.add_array({ 1, 2 });
test.push_all({ 1, 2 });
assert(test.contains(1));
assert(test.contains(2));
assert(!test.contains(0));
@@ -70,7 +70,7 @@ fn void delete_contains_index()
fn void compact()
{
PtrList test;
test.add_array({ null, &test });
test.push_all({ null, &test });
assert(test.compact_count() == 1);
test.push(null);
assert(test.compact_count() == 1);
@@ -85,7 +85,7 @@ fn void reverse()
IntList test;
test.reverse();
test.add_array({ 1, 2 });
test.push_all({ 1, 2 });
test.push(3);
assert(test.array_view() == { 1, 2, 3});
test.reverse();
@@ -101,13 +101,13 @@ fn void remove_if()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_if(&filter);
assert(removed == 3);
assert(test.array_view() == {1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_if(&select);
assert(removed == 2);
assert(test.array_view() == {11, 10, 20});
@@ -135,13 +135,13 @@ fn void remove_using_test()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10);
assert(removed == 3);
assert(test.array_view() == {1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10);
assert(removed == 2);
assert(test.array_view() == {11, 10, 20});
@@ -152,13 +152,13 @@ fn void retain_if()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.retain_if(&select);
assert(removed == 3);
assert(test.array_view() == {1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.retain_if(&filter);
assert(removed == 2);
assert(test.array_view() == {11, 10, 20});
@@ -169,13 +169,13 @@ fn void retain_using_test()
IntList test;
usz removed;
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i >= *(int*)ctx, &&10);
assert(removed == 3);
assert(test.array_view() == {1, 2});
test.clear();
test.add_array({ 1, 11, 2, 10, 20 });
test.push_all({ 1, 11, 2, 10, 20 });
removed = test.remove_using_test(fn bool(i, ctx) => *i < *(int*)ctx, &&10);
assert(removed == 2);
assert(test.array_view() == {11, 10, 20});

View File

@@ -1,19 +1,47 @@
module arraytests @test;
module array_test;
import std::io;
struct TestStructZip (Printable)
{
int a;
int b;
}
fn TestStructZip TestStructZip.mult(self, TestStructZip other) @operator(*)
{
self.a *= other.a;
self.b *= other.b;
return self;
}
fn bool TestStructZip.eq(self, TestStructZip other) @operator(==)
{
return self.a == other.a && self.b == other.b;
}
fn usz? TestStructZip.to_format(&self, Formatter* f) @dynamic
{
return f.printf("{ %d, %d }", self.a, self.b);
}
module array_test @test;
import std::collections::pair, std::collections::list;
fn void contains()
{
int[3] a = { 1, 2, 3 };
assert(array::contains(a, 2) == true);
assert(array::contains(a, 15) == false);
assert(array::contains(a, 2));
assert(!array::contains(a, 15));
}
fn void find()
{
int[3] a = { 1, 2, 3 };
assert(array::index_of(a, 2)!! == 1);
assert(array::index_of(a, 1)!! == 0);
assert(array::index_of(a, 3)!! == 2);
assert(@catch(array::index_of(a, 4)) == NOT_FOUND);
test::eq(array::index_of(a, 2)!!, 1);
test::eq(array::index_of(a, 1)!!, 0);
test::eq(array::index_of(a, 3)!!, 2);
test::@error(array::index_of(a, 4), NOT_FOUND);
}
fn void find_subarray()
@@ -35,5 +63,194 @@ fn void concat()
free(array::concat(mem, a[:0], (int[2]) { 1, 2 }));
int[] c = array::concat(mem, a[1..2], a);
defer free(c);
assert (c == (int[]){ 2, 3, 1, 2, 3 });
assert(c == (int[]){ 2, 3, 1, 2, 3 });
}
fn void zip() => @pool()
{
char[] left = "abcde";
long[] right = { -1, 0x8000, 0 };
Pair{char, long}[] expected = { {'a', -1}, {'b', 0x8000}, {'c', 0} };
Pair{char, long}[] zipped = array::@tzip(left, right);
test::eq(zipped.len, 3);
foreach (i, c : zipped) assert(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_list() => @pool()
{
char[] left = "abcde";
List{long} l;
l.push(-1);
l.push(0x8000);
l.push(0);
Pair{char, long}[] expected = { {'a', -1}, {'b', 0x8000}, {'c', 0} };
Pair{char, long}[] zipped = array::@tzip(left, l);
test::eq(zipped.len, 3);
foreach (i, c : zipped) assert(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_fill_with_default() => @pool()
{
char[] left = "abcde";
long[] right = { -1, 0x8000, 0 };
Pair{char, long}[] expected = { {'a', -1}, {'b', 0x8000}, {'c', 0}, {'d', 0}, {'e', 0} };
Pair{char, long}[] zipped = array::@tzip(left, right, fill_with: 0);
test::eq(zipped.len, 5);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_fill_with_char() => @pool()
{
char[] left = "abcde";
long[] right = { -1, 0x8000, 0 };
Pair{char, long}[] expected = { {'a', -1}, {'b', 0x8000}, {'c', 0}, {'d', 0x40}, {'e', 0x40} };
Pair{char, long}[] zipped = array::@tzip(left, right, fill_with: 0x40);
test::eq(zipped.len, 5);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_fill_with_string() => @pool()
{
String[] left = { "abcde", "123456" };
long[] right = { -1, 0x8000, 20, 30, 40 };
Pair{String, long}[] expected = { {"abcde", -1}, {"123456", 0x8000}, {"aaa", 20}, {"aaa", 30}, {"aaa", 40} };
Pair{String, long}[] zipped = array::@tzip(left, right, fill_with: "aaa");
test::eq(zipped.len, 5);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_fill_with_struct() => @pool()
{
String[] left = { "abcde", "123456", "zzz" };
TestStructZip[] right = { {1, 2} };
Pair{String, TestStructZip}[] expected = { {"abcde", {1, 2}}, {"123456", {100, 200}}, {"zzz", {100, 200}} };
Pair{String, TestStructZip}[] zipped = array::@tzip(left, right, fill_with: (TestStructZip){100, 200});
test::eq(zipped.len, 3);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_with() => @pool()
{
char[] left = "abcde";
char[4] right = { 0x05, 0x04, 0x03, 0x00 };
char[] expected = "fffd";
char[] zipped = array::@tzip(left, right, fn char (char a, char b) => a + b);
test::eq(zipped.len, 4);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_with_fill_with_default() => @pool()
{
char[] left = "abcde";
char[] right = { 0x05, 0x04 };
char[] expected = "ffcde";
char[] zipped = array::@tzip(left, right, fn char (char a, char b) => a + b, 0);
test::eq(zipped.len, 5);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_with_fill_with_char() => @pool()
{
char[] left = "abcde";
char[] right = { 0x05, 0x04 };
char[] expected = "ffghi";
char[] zipped = array::@tzip(left, right, fn char (char a, char b) => a + b, 0x04);
test::eq(zipped.len, 5);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_with_fill_with_pointers() => @pool()
{
ZString field = "0123456789abcdefghijklmnopqrstuvwxyz-_=!";
char*[] left = { &field[3], &field[1] };
char[] right = { 0x05, 0x04, 0x0A, 0x10, 0x11 };
char[] expected = "85agh";
char[] zipped = array::@tzip(left, right, fn char (char* a, char b) => a[b], &field[0]);
test::eq(zipped.len, 5);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_with_fill_with_string() => @pool()
{
String[] left = { "Hello", "World", "Foo", "Bazzy" };
String[] right = { " there", "!" };
String[] expected = { "Hello there", "World!", "FooBar", "BazzyBar" };
String[] zipped = array::@tzip(left, right, fn String (String a, String b) => a.tconcat(b), "Bar");
test::eq(zipped.len, 4);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_with_fill_with_struct() => @pool()
{
TestStructZip[] left = { {1, 2}, {300, 400} };
TestStructZip[] right = { {-1, -1} };
TestStructZip[] expected = { {-1, -2}, {600, 1200} };
TestStructZip[] zipped = array::@tzip(left, right, fn TestStructZip (TestStructZip a, TestStructZip b) => a * b, (TestStructZip){2, 3});
test::eq(zipped.len, 2);
foreach (i, c : zipped) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_into()
{
char[] left = { '1', '2', '3', '4' };
String[6] right = { "one", "two", "three", "four", "five", "six" };
char[] expected = { '4', '5', '8', '8' };
array::@zip_into(left, right, fn (a, b) => a + (char)b.len);
test::eq(left.len, 4);
foreach (i, c : left) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}
fn void zip_into_list() => @pool()
{
List{char} l;
l.push_all({ '1', '2', '3', '4' });
String[6] right = { "one", "two", "three", "four", "five", "six" };
char[] expected = { '4', '5', '8', '8' };
array::@zip_into(l, right, fn (a, b) => a + (char)b.len);
test::eq(l.len(), 4);
foreach (i, c : l) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}

View File

@@ -78,7 +78,7 @@ alias CountingSortTestList = List{int};
fn void countingsort_list()
{
CountingSortTestList list;
list.add_array({ 2, 1, 3});
list.push_all({ 2, 1, 3});
sort::countingsort(list, &sort::key_int_value);
assert(check::int_ascending_sort(list.array_view()));
}

View File

@@ -83,7 +83,7 @@ alias InsertionSortTestList = List{int};
fn void insertionsort_list()
{
InsertionSortTestList list;
list.add_array({ 2, 1, 3});
list.push_all({ 2, 1, 3});
sort::insertionsort(list, &sort::cmp_int_value);
assert(check::int_ascending_sort(list.array_view()));
}

View File

@@ -83,7 +83,7 @@ alias List = List{int};
fn void quicksort_list()
{
List list;
list.add_array({ 2, 1, 3});
list.push_all({ 2, 1, 3});
sort::quicksort(list, &sort::cmp_int_value);
assert(check::int_sort(list.array_view()));
}

View File

@@ -68,7 +68,7 @@ fn void sorted()
// with list
List{int} list;
list.tinit();
list.add_array(tc.input);
list.push_all(tc.input);
got = is_sorted(list);
assert(got == tc.want, "list: %s, got: %s, want: %s",