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
53 changes: 52 additions & 1 deletion lib/std/core/array.c3
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
module std::core::array;

import std::core::array::slice;
import std::math;


<*
Returns true if the array contains at least one element, else false
Expand Down Expand Up @@ -110,4 +113,52 @@ 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);


<*
@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);
}
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 @@ -43,6 +43,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::range` and compile-time `@range` builtins.
- Updated termios bindings to use bitstructs and fixed some constants with incorrect values #2372

## 0.7.4 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 @@ -37,3 +37,59 @@ fn void concat()
defer free(c);
assert (c == (int[]){ 2, 3, 1, 2, 3 });
}

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(@typeis(array::trange(2), isz[]));
assert(@typeis(array::trange(4, $OfType: char), char[]));
assert(@typeis(array::trange(8, step: 2, $OfType: uint128), uint128[]));
assert(@typeis(array::trange(4, 16, 3, true, long), long[]));
assert(@typeis(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 @@ -212,3 +212,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