mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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});
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user