Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions lib/std/core/array.c3
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module std::core::array;

import std::collections::pair, std::io;
import std::math;

<*
Returns true if the array contains at least one element, else false
Expand Down Expand Up @@ -119,6 +121,54 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard
macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2);


<*
@param[&inout] allocator
@require types::is_int($typeof(start_stop))
@require types::is_int($OfType)
@require @is_empty_macro_slot(stop) ||| (types::is_int($typeof(stop)) && stop <= isz.max && stop >= isz.min)
@require @is_empty_macro_slot(step) ||| types::is_int($typeof(step))
*>
macro range(Allocator allocator, start_stop = 0, stop = EMPTY_MACRO_SLOT, step = EMPTY_MACRO_SLOT, bool inclusive = false, $OfType = isz)
{
// when no stop is provided, the range is expected to be 0..start_stop
int128 real_start = @is_valid_macro_slot(stop) ? start_stop : 0;
int128 real_stop = @is_valid_macro_slot(stop) ? (int128)stop : start_stop;
int128 real_step = @is_valid_macro_slot(step) ? (int128)step : 1;

if (!real_step) real_step = 1; // no zero-step values; force a 1

if ((real_start > real_stop && real_step > 0) || (real_start < real_stop && real_step < 0)) real_step *= -1;

int128 final_length = (int128)math::ceil((float)math::abs((float)(real_start - real_stop) / real_step));
if (inclusive && !((real_start - real_stop) % real_step)) ++final_length;

if (!final_length || final_length >= usz.max) return {};

$OfType[] result = allocator::new_array(allocator, $OfType, (usz)final_length);

for (
usz j = 0, int128 i = real_start;
real_stop > real_start
? (inclusive ? i <= real_stop : i < real_stop)
: (inclusive ? i >= real_stop : i > real_stop);
i += real_step, ++j
) result[j] = ($OfType)i;

return result;
}

<*
@require types::is_int($typeof(start_stop))
@require types::is_int($OfType)
@require @is_empty_macro_slot(stop) ||| (types::is_int($typeof(stop)) && stop <= isz.max && stop >= isz.min)
@require @is_empty_macro_slot(step) ||| types::is_int($typeof(step))
*>
macro trange(start_stop = 0, stop = EMPTY_MACRO_SLOT, step = EMPTY_MACRO_SLOT, bool inclusive = false, $OfType = isz)
{
return range(tmem, start_stop, stop, step, inclusive, $OfType);
}


<*
Apply a reduction/folding operation to an iterable type. This walks along the input array
and applies an `#operation` to each value, returning it to the `identity` (or "accumulator")
Expand Down
53 changes: 52 additions & 1 deletion lib/std/core/builtin.c3
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by the MIT license
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::builtin;
import libc, std::hash, std::io, std::os::backtrace;
import libc, std::hash, std::io, std::os::backtrace, std::math;


<*
Expand Down Expand Up @@ -535,6 +535,57 @@ macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin
return false;
}


<*
@require types::is_int($typeof($start_stop))
@require types::is_int($OfType)
@require @is_empty_macro_slot($stop) ||| (types::is_int($typeof($stop)) &&& $stop <= isz.max &&& $stop >= isz.min)
@require @is_empty_macro_slot($step) ||| (types::is_int($typeof($step)) &&& $step != 0)
*>
macro @range($start_stop = 0, $stop = EMPTY_MACRO_SLOT, $step = EMPTY_MACRO_SLOT, bool $inclusive = false, $OfType = isz) @builtin @const
{
// These are `int128` types because by default, we want to be able to accommodate the LARGEST computable values.
// Do NOT make these $OfType, as it disturbs the calculation of the resulting array's range based on the type.
int128 $real_start = @is_valid_macro_slot($stop) ? $start_stop : 0;
int128 $real_stop = @is_valid_macro_slot($stop) ? (int128)$stop : $start_stop;
int128 $real_step = @is_valid_macro_slot($step) ? (int128)$step : 1;

$if ($real_start > $real_stop &&& $real_step > 0) ||| ($real_start < $real_stop &&& $real_step < 0):
$real_step *= -1; // flip the sign of 'step' automatically if necessary
$endif

// a poor man's CT absolute value |max - min| ensures a positive length integer
var $interval = @max($real_start, $real_stop) - @min($real_start, $real_stop);
var $abs_step = ($real_step < 0) ? -$real_step : $real_step;

int128 $final_length = (int128)($interval / (float)$abs_step) + ($interval % $abs_step ? 1 : 0);
$if $final_length < 0: $final_length = -$final_length; $endif

// only add the extra slot if the end value is a multiple of the step
$if $inclusive && !($interval % $abs_step): ++$final_length; $endif

$assert $final_length > 0
: "A compile-time range cannot return a zero-length array (start %s, stop %s, step %s).", $real_start, $real_stop, $real_step;

$assert $final_length < isz.max
: "A compile-time range should never be larger than isz.max. You need to use something more efficient.";

$OfType[(usz)$final_length] $result;

$for
var $j = 0, var $i = $real_start;
$real_stop > $real_start
? ($inclusive ? $i <= $real_stop : $i < $real_stop)
: ($inclusive ? $i >= $real_stop : $i > $real_stop);
++$j, $i += $real_step
:
$result[$j] = ($OfType)$i;
$endfor

return $result;
}


<*
@require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array"
*>
Expand Down
1 change: 1 addition & 0 deletions releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

### Stdlib changes
- Sorting functions correctly took slices by value, but also other types by value. Now, only slices are accepted by value, other containers are always by ref.
- Added `array::range` and compile-time `@range` builtins.

## 0.7.6 Change list

Expand Down
56 changes: 56 additions & 0 deletions test/unit/stdlib/core/array.c3
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,59 @@ fn void zip_into_linked_list() => @pool()
test::eq(l.len(), 4);
foreach (i, c : l.array_view()) test::@check(c == expected[i], "Mismatch on index %d: %s (actual) != %s (expected)", i, c, expected[i]);
}

fn void range() => @pool()
{
// quick alloc/free
isz[] t = array::range(mem, 3);
assert(t.len == 3);
mem::free(t.ptr);

// empty
assert({} == array::trange(0));
assert({} == array::trange());
assert({} == array::trange(-10_000, -10_000));

// ascending
assert({0, 1, 2, 3, 4} == array::trange(5));
assert({0, 1, 2, 3, 4, 5} == array::trange(5, inclusive: true));
assert({0, 2, 4} == array::trange(5, step: 2));
assert({0, 2, 4} == array::trange(5, step: 2, inclusive: true));
assert({2, 3, 4, 5} == array::trange(2, 5, inclusive: true));

// descending
assert({2, 1, 0, -1, -2, -3, -4} == array::trange(2, -5));
assert({0, -1, -2, -3, -4, -5} == array::trange(-5, inclusive: true));
assert({-2, -3, -4, -5} == array::trange(-2, -5, inclusive: true));
// -- purposefully using the wrong sign for 'step' here to show it will work regardless
assert({0, -2, -4} == array::trange(-5, step: 2));
assert({0, -2, -4} == array::trange(-5, step: 2, inclusive: true));

// misc
assert({0, 1, 2} == array::trange(stop: 3));
assert({0, 1, 2, 3} == array::trange(stop: 3, inclusive: true));
assert({-2, -1, 0, 1} == array::trange(-2, 2));
assert({-2, -1, 0, 1, 2} == array::trange(-2, 2, inclusive: true));
assert({-18, -8, 2, 12, 22} == array::trange(-18, 25, 10));
assert({-18, -8, 2, 12, 22} == array::trange(-18, 25, 10, inclusive: true));
assert({2, 4} == array::trange(2, 6, 2));

// type-based wraparound
assert({254, 255, 0, 1, 2} == array::trange(254, 258, inclusive: true, $OfType: char)); // overflow
assert({2, 1, 0, 255, 254} == array::trange(258, 254, inclusive: true, $OfType: char)); // underflow
assert({126, 127, -128, -127, -126} == array::trange(126, 130, inclusive: true, $OfType: ichar)); // signed overflow
assert({-126, -127, -128, 127, 126} == array::trange(130, 126, inclusive: true, $OfType: ichar)); // signed underflow
assert({-126, -128, 126} == array::trange(130, 126, -2, inclusive: true, $OfType: ichar)); // signed stepped underflow

// iterate
isz z;
foreach (i : array::trange(200, step: 25, inclusive: true)) z += i;
assert(z == 0 + 25 + 50 + 75 + 100 + 125 + 150 + 175 + 200);

// typed returns
assert($typeof(array::trange(2)) == isz[]);
assert($typeof(array::trange(4, $OfType: char)) == char[]);
assert($typeof(array::trange(8, step: 2, $OfType: uint128)) == uint128[]);
assert($typeof(array::trange(4, 16, 3, true, long)) == long[]);
assert($typeof(array::trange(stop: 1200, inclusive: true, $OfType: char)) == char[]);
}
46 changes: 46 additions & 0 deletions test/unit/stdlib/core/builtintests.c3
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,49 @@ fn void test_ct_min_max()
assert(@max(0, 0, 1.234, 1.2345, 0.2) == 1.2345);
assert(@max(127.9999999, 45 + 46, bitsizeof(uint128)) == 128);
}

fn void test_ct_range()
{
// ascending
assert({0, 1, 2, 3, 4} == @range(5));
assert({0, 1, 2, 3, 4, 5} == @range(5, $inclusive: true));
assert({0, 2, 4} == @range(5, $step: 2));
assert({0, 2, 4} == @range(5, $step: 2, $inclusive: true));
assert({2, 3, 4, 5} == @range(2, 5, $inclusive: true));

// descending
assert({2, 1, 0, -1, -2, -3, -4} == @range(2, -5));
assert({0, -1, -2, -3, -4, -5} == @range(-5, $inclusive: true));
assert({-2, -3, -4, -5} == @range(-2, -5, $inclusive: true));
// -- purposefully using the wrong sign for 'step' here to show it will work regardless
assert({0, -2, -4} == @range(-5, $step: 2));
assert({0, -2, -4} == @range(-5, $step: 2, $inclusive: true));

// misc
assert({0, 1, 2} == @range($stop: 3));
assert({0, 1, 2, 3} == @range($stop: 3, $inclusive: true));
assert({-2, -1, 0, 1} == @range(-2, 2));
assert({-2, -1, 0, 1, 2} == @range(-2, 2, $inclusive: true));
assert({-18, -8, 2, 12, 22} == @range(-18, 25, 10));
assert({-18, -8, 2, 12, 22} == @range(-18, 25, 10, $inclusive: true));
assert({2, 4} == @range(2, 6, 2));

// type-based wraparound
assert({254, 255, 0, 1, 2} == @range(254, 258, $inclusive: true, $OfType: char)); // overflow
assert({2, 1, 0, 255, 254} == @range(258, 254, $inclusive: true, $OfType: char)); // underflow
assert({126, 127, -128, -127, -126} == @range(126, 130, $inclusive: true, $OfType: ichar)); // signed overflow
assert({-126, -127, -128, 127, 126} == @range(130, 126, $inclusive: true, $OfType: ichar)); // signed underflow
assert({-126, -128, 126} == @range(130, 126, -2, $inclusive: true, $OfType: ichar)); // signed stepped underflow

// iterate
isz z;
foreach (i : @range(200, $step: 25, $inclusive: true)) z += i;
assert(z == 0 + 25 + 50 + 75 + 100 + 125 + 150 + 175 + 200);

// typed returns
assert(@typeis(@range(2), isz[2]));
assert(@typeis(@range(4, $OfType: char), char[4]));
assert(@typeis(@range(8, $step: 2, $OfType: uint128), uint128[4]));
assert(@typeis(@range(4, 16, 3, true, long), long[5]));
assert(@typeis(@range($stop: 1200, $inclusive: true, $OfType: char), char[1201]));
}
Loading