From 5e1c343be4aedcd18c14f7304a245019f8238f67 Mon Sep 17 00:00:00 2001 From: Zack Puhl Date: Fri, 23 Jan 2026 06:34:50 -0500 Subject: [PATCH] Deprecate builtin `EMPTY_MACRO_SLOT` for optional macro arguments (#2805) * add deprecations to macro slot builtins * refactor all stdlib uses of now-deprecated EMPTY_MACRO_SLOT; release notes * update incorrect releasenotes ref to this pr * remove leftover comments from refactoring * remove unnecessary `EmptySlot`-like type in countingsort; use private macro directly --- lib/std/core/builtin.c3 | 12 ++- lib/std/sort/binarysearch.c3 | 80 +++++++++--------- lib/std/sort/countingsort.c3 | 66 ++++++++++----- lib/std/sort/insertionsort.c3 | 17 ++-- lib/std/sort/quicksort.c3 | 40 +++++---- lib/std/sort/sort.c3 | 154 ++++++++++++++++++---------------- lib/std/sort/sorted.c3 | 100 +++++++--------------- releasenotes.md | 1 + 8 files changed, 237 insertions(+), 233 deletions(-) diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index b0754da91..92b2ac263 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -24,11 +24,15 @@ macro foo(a, #b = EMPTY_MACRO_SLOT) $endif } *> -const EmptySlot EMPTY_MACRO_SLOT @builtin = null; +const EmptySlot EMPTY_MACRO_SLOT @builtin @deprecated("Use `#arg = ...` instead.") = null; typedef EmptySlot = void*; -macro bool @is_empty_macro_slot(#arg) @const @builtin => $typeof(#arg) == EmptySlot; -macro bool @is_valid_macro_slot(#arg) @const @builtin => $typeof(#arg) != EmptySlot; +macro bool @is_empty_macro_slot(#arg) @const @builtin + @deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.") + => $typeof(#arg) == EmptySlot; +macro bool @is_valid_macro_slot(#arg) @const @builtin + @deprecated("Use `#arg = ...` to define an optional macro slot, and `$defined(#arg)` to detect whether the argument is set.") + => $typeof(#arg) != EmptySlot; <* Returns a random value at compile time. @@ -453,7 +457,7 @@ macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = <* Shuffle a vector by its index - + int[<4>] a = { 1, 2, 3, 4 }; assert(swizzle(a, 0, 1, 1, 3) == (int[<4>]) { 1, 2, 2, 4 }); *> diff --git a/lib/std/sort/binarysearch.c3 b/lib/std/sort/binarysearch.c3 index 6bbadfc30..35c86b6e0 100644 --- a/lib/std/sort/binarysearch.c3 +++ b/lib/std/sort/binarysearch.c3 @@ -6,14 +6,14 @@ in [0, array.len) where x would be inserted or cmp(i) is true and cmp(j) is true @require @list_is_by_ref(list) : "Expected a list passed by reference or a slice" @require @is_sortable(list) : "The list must be sortable" - @require @is_valid_cmp_fn(cmp, list, context) : "Expected a comparison function which compares values" - @require @is_valid_context(cmp, context) : "Expected a valid context" + @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "Expected a comparison function which compares values" + @require @is_valid_context(...cmp, ...context) : "Expected a valid context" *> -macro usz binarysearch(list, x, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin +macro usz binarysearch(list, x, cmp = ..., context = ...) @builtin { usz i; - var $no_cmp = @is_empty_macro_slot(cmp); - var $has_context = @is_valid_macro_slot(context); + var $no_cmp = !$defined(cmp); + var $has_context = $defined(context); $if $kindof(list) == SLICE: usz len = lengthof(list); @@ -52,41 +52,41 @@ macro usz binarysearch(list, x, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SL $endif } $else - usz len = lengthof(*list); - for (usz j = len; i < j;) - { - usz half = i + (j - i) / 2; - $if $no_cmp: - switch - { - case greater((*list)[half], x): j = half; - case less((*list)[half], x): i = half + 1; - default: return half; - } - $else + usz len = lengthof(*list); + for (usz j = len; i < j;) + { + usz half = i + (j - i) / 2; + $if $no_cmp: + switch + { + case greater((*list)[half], x): j = half; + case less((*list)[half], x): i = half + 1; + default: return half; + } + $else - $switch: - $case $defined(cmp((*list)[0], (*list)[0], context)): - int res = cmp(list[half], x, context); - $case $defined(cmp((*list)[0], (*list)[0])): - assert(!$has_context); - int res = cmp((*list)[half], x); - $case $defined(cmp(&(*list)[0], &(*list)[0], context)): - int res = cmp(&(*list)[half], &x, context); - $case $defined(cmp(&(*list)[0], &(*list)[0])): - assert(!$has_context); - int res = cmp(&(*list)[half], &x); - $default: - assert(false, "Invalid comparison function"); - $endswitch - switch - { - case res > 0: j = half; - case res < 0: i = half + 1; - default: return half; - } - $endif - } + $switch: + $case $defined(cmp((*list)[0], (*list)[0], context)): + int res = cmp(list[half], x, context); + $case $defined(cmp((*list)[0], (*list)[0])): + assert(!$has_context); + int res = cmp((*list)[half], x); + $case $defined(cmp(&(*list)[0], &(*list)[0], context)): + int res = cmp(&(*list)[half], &x, context); + $case $defined(cmp(&(*list)[0], &(*list)[0])): + assert(!$has_context); + int res = cmp(&(*list)[half], &x); + $default: + assert(false, "Invalid comparison function"); + $endswitch + switch + { + case res > 0: j = half; + case res < 0: i = half + 1; + default: return half; + } + $endif + } $endif - return i; + return i; } \ No newline at end of file diff --git a/lib/std/sort/countingsort.c3 b/lib/std/sort/countingsort.c3 index c1870cd42..6d178312b 100644 --- a/lib/std/sort/countingsort.c3 +++ b/lib/std/sort/countingsort.c3 @@ -1,41 +1,44 @@ module std::sort; import std::sort::is; -import std::sort::cs; +import std::sort::cs @public; import std::sort::qs; <* - Sort list using the counting sort algorithm. @require @list_is_by_ref(list) : "Expected the list to be passed by reference" @require @is_sortable(list) : "The list must be indexable and support .len or .len()" - @require @is_cmp_key_fn(key_fn, list) : "Expected a transformation function which returns an unsigned integer." + @require @is_cmp_key_fn(#key_fn: ...key_fn, #list: list) : "Expected a transformation function which returns an unsigned integer." *> -macro void countingsort(list, key_fn = EMPTY_MACRO_SLOT) @builtin +macro void countingsort(list, key_fn = ...) @builtin { - $if $kindof(list) == SLICE: - cs::csort{$typeof(list), $typeof(key_fn)}(list, 0, list.len, key_fn, ~((uint)0)); - $else - cs::csort{$typeof(*list), $typeof(key_fn)}(list, 0, lengthof(*list), key_fn, ~((uint)0)); - $endif + var list_length = $kindof(list) == SLICE ??? list.len : lengthof(*list); + var $ListType = $kindof(list) == SLICE ??? $typeof(list) : $typeof(*list); + cs::_csort{$ListType, $defined(key_fn) ??? $typeof(key_fn) : void*}(list, 0, list_length, ~(uint)0, ...key_fn); } -macro void insertionsort_indexed(list, start, end, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin +macro void insertionsort_indexed(list, start, end, cmp = ..., context = ...) @builtin { + // When the context or cmp functions are not defined, we can simply use a dummy/default type. + var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; + var $ContextType = $defined(context) ??? $typeof(context) : uint; $if $kindof(list) == SLICE: - is::isort{$typeof((list)), $typeof(cmp), $typeof(context)}(list, (usz)start, (usz)end, cmp, context); + is::isort{$typeof((list)), $CmpFnType, $ContextType}(list, (usz)start, (usz)end, ...cmp, ...context); $else - is::isort{$typeof((*list)), $typeof(cmp), $typeof(context)}(list, (usz)start, (usz)end, cmp, context); + is::isort{$typeof((*list)), $CmpFnType, $ContextType}(list, (usz)start, (usz)end, ...cmp, ...context); $endif } -macro void quicksort_indexed(list, start, end, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin +macro void quicksort_indexed(list, start, end, cmp = ..., context = ...) @builtin { + // When the context or cmp functions are not defined, we can simply use a dummy/default type. + var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; + var $ContextType = $defined(context) ??? $typeof(context) : uint; $if $kindof(list) == SLICE: - qs::qsort{$typeof((list)), $typeof(cmp), $typeof(context)}(list, (isz)start, (isz)(end-1), cmp, context); + qs::qsort{$typeof((list)), $CmpFnType, $ContextType}(list, (isz)start, (isz)(end-1), ...cmp, ...context); $else - qs::qsort{$typeof((*list)), $typeof(cmp), $typeof(context)}(list, (isz)start, (isz)(end-1), cmp, context); + qs::qsort{$typeof((*list)), $CmpFnType, $ContextType}(list, (isz)start, (isz)(end-1), ...cmp, ...context); $endif } @@ -46,7 +49,7 @@ alias Ranges @private = usz[257]; alias Indexs @private = char[256]; alias ElementType = $typeof((Type){}[0]); -const bool NO_KEY_FN @private = types::is_same(KeyFn, EmptySlot); +const bool NO_KEY_FN @private = KeyFn.kindof != FUNC; const bool KEY_BY_VALUE @private = NO_KEY_FN ||| $defined($typefrom(KeyFn.paramsof[0].type) x = (ElementType){}); const bool LIST_HAS_REF @private = $defined(&(Type){}[0]); @@ -64,7 +67,26 @@ macro list_get(ListType l, i) @if(IS_SLICE) => l[i]; macro list_get_ref(ListType l, i) @if(!IS_SLICE) => &(*l)[i]; macro list_get_ref(ListType l, i) @if(IS_SLICE) => &l[i]; -fn void csort(ListType list, usz low, usz high, KeyFn key_fn, uint byte_idx) +fn void csort(ListType list, usz low, usz high, KeyFn key_fn, uint byte_idx) @if (!NO_KEY_FN) +{ + _csort(list, low, high, byte_idx, key_fn); +} + +fn void csort(ListType list, usz low, usz high, uint byte_idx) @if (NO_KEY_FN) +{ + _csort(list, low, high, byte_idx); +} + +<* + COMPATIBILITY: With the deprecation of `EMPTY_MACRO_SLOT` in 0.7.9, this inner function was introduced + to prevent needing to further deprecate this module's downstream API. Since `key_fn` in `csort` appears + inline with other position-based parameters, it would be a hassle to require all callers of `csort` to + use named parameters when supplying `key_fn`. + + In other words, `csort(a_list, low, high, key_fn: some_fn, byte_idx: index_val)` breaks the existing API + since explicitly naming `byte_idx` becomes a requirement. +*> +macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn = ...) @private { if (high <= low) return; $if NO_KEY_FN: @@ -182,7 +204,7 @@ fn void csort(ListType list, usz low, usz high, KeyFn key_fn, uint byte_idx) usz start_offset = ranges[i]; usz end_offset = ranges[i + 1]; - insertionsort_indexed(list, low + start_offset, low + end_offset, compare_fn, key_fn); + insertionsort_indexed(list, low + start_offset, low + end_offset, compare_fn, ...key_fn); } for (usz p = 0; p < fallback1_count; p++) { @@ -191,7 +213,7 @@ fn void csort(ListType list, usz low, usz high, KeyFn key_fn, uint byte_idx) usz start_offset = ranges[i]; usz end_offset = ranges[i + 1]; - quicksort_indexed(list, low + start_offset, low + end_offset, compare_fn, key_fn); + quicksort_indexed(list, low + start_offset, low + end_offset, compare_fn, ...key_fn); } for (usz p = 0; p < recursion_count; p++) @@ -201,7 +223,11 @@ fn void csort(ListType list, usz low, usz high, KeyFn key_fn, uint byte_idx) usz start_offset = ranges[i]; usz end_offset = ranges[i + 1]; - csort(list, low + start_offset, low + end_offset, key_fn, byte_idx - 1); + $if $defined(key_fn): + csort(list, low + start_offset, low + end_offset, key_fn, byte_idx - 1); + $else + csort(list, low + start_offset, low + end_offset, byte_idx - 1); + $endif } } } diff --git a/lib/std/sort/insertionsort.c3 b/lib/std/sort/insertionsort.c3 index 3cb762975..a4ca4780d 100644 --- a/lib/std/sort/insertionsort.c3 +++ b/lib/std/sort/insertionsort.c3 @@ -6,14 +6,17 @@ import std::sort::is; @require @list_is_by_ref(list) : "Expected a list passed by reference, or slice passed by value" @require @is_sortable(list) : "The list must be indexable and support .len or .len()" - @require @is_valid_cmp_fn(cmp, list, context) : "Expected a comparison function which compares values" + @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "Expected a comparison function which compares values" *> -macro void insertionsort(list, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin @safemacro +macro void insertionsort(list, cmp = ..., context = ...) @builtin @safemacro { + // When the context or cmp functions are not defined, we can simply use a dummy/default type. + var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; + var $ContextType = $defined(context) ??? $typeof(context) : uint; $if $kindof(list) == SLICE: - is::isort{$typeof(list), $typeof(cmp), $typeof(context)}(list, 0, lengthof(list), cmp, context); + is::isort{$typeof(list), $CmpFnType, $ContextType}(list, 0, lengthof(list), ...cmp, ...context); $else - is::isort{$typeof(*list), $typeof(cmp), $typeof(context)}(list, 0, lengthof(*list), cmp, context); + is::isort{$typeof(*list), $CmpFnType, $ContextType}(list, 0, lengthof(*list), ...cmp, ...context); $endif } @@ -25,10 +28,10 @@ alias ListType = $typefrom(IS_SLICE ??? Type : Type*); macro ElementType list_get(ListType l, i) => IS_SLICE ??? l[i] : (*l)[i]; macro ElementType* list_get_ref(ListType l, i) => IS_SLICE ??? &l[i] : &(*l)[i]; -fn void isort(ListType list, usz low, usz high, CmpFn comp, Context context) +macro void isort(ListType list, usz low, usz high, CmpFn comp = ..., Context context = ...) { - var $has_cmp = @is_valid_macro_slot(comp); - var $has_context = @is_valid_macro_slot(context); + var $has_cmp = $defined(comp); + var $has_context = $defined(context); var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn.paramsof[0].type) p = list_get(list, 0)); var $has_get_ref = IS_SLICE ||| $defined(&(*list)[0]); for (usz i = low; i < high; ++i) diff --git a/lib/std/sort/quicksort.c3 b/lib/std/sort/quicksort.c3 index 9be163738..dcad5307d 100644 --- a/lib/std/sort/quicksort.c3 +++ b/lib/std/sort/quicksort.c3 @@ -6,15 +6,18 @@ import std::sort::qs; @require @list_is_by_ref(list) : "Expected a list passed by reference or be a slice" @require @is_sortable(list) : "The list must be indexable and support .len or .len()" - @require @is_valid_cmp_fn(cmp, list, context) : "Expected a comparison function which compares values" - @require @is_valid_context(cmp, context) : "Expected a valid context" + @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "Expected a comparison function which compares values" + @require @is_valid_context(...cmp, ...context) : "Expected a valid context" *> -macro void quicksort(list, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin +macro void quicksort(list, cmp = ..., context = ...) @builtin { + // When the context or cmp functions are not defined, we can simply use a dummy/default type. + var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; + var $ContextType = $defined(context) ??? $typeof(context) : uint; $if $kindof(list) == SLICE: - qs::qsort{$typeof(list), $typeof(cmp), $typeof(context)}(list, 0, (isz)list.len - 1, cmp, context); + qs::qsort{$typeof(list), $CmpFnType, $ContextType}(list, 0, (isz)list.len - 1, ...cmp, ...context); $else - qs::qsort{$typeof(*list), $typeof(cmp), $typeof(context)}(list, 0, (isz)lengthof(*list) - 1, cmp, context); + qs::qsort{$typeof(*list), $CmpFnType, $ContextType}(list, 0, (isz)lengthof(*list) - 1, ...cmp, ...context); $endif } @@ -25,15 +28,18 @@ macro void quicksort(list, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @ @require @list_is_by_ref(list) : "Expected a list passed by reference or be a slice" @require @is_sortable(list) : "The list must be indexable and support .len or .len()" - @require @is_valid_cmp_fn(cmp, list, context) : "expected a comparison function which compares values" - @require @is_valid_context(cmp, context) : "Expected a valid context" + @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "expected a comparison function which compares values" + @require @is_valid_context(...cmp, ...context) : "Expected a valid context" *> -macro quickselect(list, isz k, cmp = EMPTY_MACRO_SLOT, context = EMPTY_MACRO_SLOT) @builtin +macro quickselect(list, isz k, cmp = ..., context = ...) @builtin { + // When the context or cmp functions are not defined, we can simply use a dummy/default type. + var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; + var $ContextType = $defined(context) ??? $typeof(context) : uint; $if $kindof(list) == SLICE: - return qs::qselect{$typeof(list), $typeof(cmp), $typeof(context)}(list, 0, (isz)list.len - 1, k, cmp, context); + return qs::qselect{$typeof(list), $CmpFnType, $ContextType}(list, 0, (isz)list.len - 1, k, ...cmp, ...context); $else - return qs::qselect{$typeof(*list), $typeof(cmp), $typeof(context)}(list, 0, (isz)lengthof(*list) - 1, k, cmp, context); + return qs::qselect{$typeof(*list), $CmpFnType, $ContextType}(list, 0, (isz)lengthof(*list) - 1, k, ...cmp, ...context); $endif } @@ -59,7 +65,7 @@ alias Stack @private = StackElementItem[64]; // Based on https://alienryderflex.com/quicksort by Darel Rex Finley, Public Domain. -fn void qsort(ListType list, isz low, isz high, CmpFn cmp, Context context) +macro void qsort(ListType list, isz low, isz high, CmpFn cmp = ..., Context context = ...) { if (low >= 0 && high >= 0 && low < high) { @@ -76,7 +82,7 @@ fn void qsort(ListType list, isz low, isz high, CmpFn cmp, Context context) if (l < h) { - l = @partition(list, l, h, cmp, context); + l = @partition(list, l, h, ...cmp, ...context); stack[i + 1].low = l + 1; stack[i + 1].high = stack[i].high; stack[i++].high = l; @@ -97,7 +103,7 @@ fn void qsort(ListType list, isz low, isz high, CmpFn cmp, Context context) @require low <= k : "kth smallest element is smaller than lower bounds" @require k <= high : "kth smallest element is larger than upper bounds" *> -fn ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp, Context context) +macro ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp = ..., Context context = ...) { if (low >= 0 && high >= 0 && low < high) { @@ -108,7 +114,7 @@ fn ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp, Cont usz max_retries = 64; while (l <= h && max_retries--) { - pivot = @partition(list, l, h, cmp, context); + pivot = @partition(list, l, h, ...cmp, ...context); if (k == pivot) return list_get(list, k); if (k < pivot) { @@ -123,10 +129,10 @@ fn ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp, Cont return NOT_FOUND~; } -macro isz @partition(ListType list, isz l, isz h, CmpFn cmp, Context context) +macro isz @partition(ListType list, isz l, isz h, CmpFn cmp = ..., Context context = ...) { - var $has_cmp = @is_valid_macro_slot(cmp); - var $has_context = @is_valid_macro_slot(context); + var $has_cmp = $defined(cmp); + var $has_context = $defined(context); var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn.paramsof[0].type) v = list_get(list, 0)); ElementType pivot = list_get(list, l); diff --git a/lib/std/sort/sort.c3 b/lib/std/sort/sort.c3 index fc06ed7f3..6fdb3b36b 100644 --- a/lib/std/sort/sort.c3 +++ b/lib/std/sort/sort.c3 @@ -39,94 +39,102 @@ macro bool @is_any_sortable(#list) @const $endswitch } -macro bool @is_valid_context(#cmp, #context) +macro bool @is_valid_context(#cmp = ..., #context = ...) { - return @is_valid_macro_slot(#cmp) || @is_empty_macro_slot(#context); + return $defined(#cmp) || !$defined(#context); } <* @require @list_is_by_ref(#list) : "Expected the list to be passed by ref or be a slice" *> -macro bool @is_valid_cmp_fn(#cmp, #list, #context) @const +macro bool @is_valid_cmp_fn(#cmp = ..., #list = ..., #context = ...) @const { - var $Type = $typeof(#cmp); - var $no_context = @is_empty_macro_slot(#context); - $switch: - $case @is_empty_macro_slot(#cmp): return true; - $case $Type.kindof != FUNC ||| $Type.returns.kindof != SIGNED_INT: return false; - $default: - $if $kindof(#list) == SLICE: - $switch: - $case $defined(#cmp((#list)[0], (#list)[0], #context)): return true; - $case $defined(#cmp((#list)[0], (#list)[0])): return $no_context; - $case $defined(#cmp(&(#list)[0], &(#list)[0], #context)): return true; - $case $defined(#cmp(&(#list)[0], &(#list)[0])): return $no_context; - $default: return false; - $endswitch - $else - $switch: - $case $defined(#cmp((*#list)[0], (*#list)[0], #context)): return true; - $case $defined(#cmp((*#list)[0], (*#list)[0])): return $no_context; - $case $defined(#cmp(&(*#list)[0], &(*#list)[0], #context)): return true; - $case $defined(#cmp(&(*#list)[0], &(*#list)[0])): return $no_context; - $default: return false; - $endswitch - $endif - $endswitch + $if !$defined(#cmp): + return true; + $else + var $Type = $typeof(#cmp); + var $no_context = !$defined(#context); + $switch: + $case $Type.kindof != FUNC ||| $Type.returns.kindof != SIGNED_INT: return false; + $default: + $if $kindof(#list) == SLICE: + $switch: + $case $defined(#cmp((#list)[0], (#list)[0], #context)): return true; + $case $defined(#cmp((#list)[0], (#list)[0])): return $no_context; + $case $defined(#cmp(&(#list)[0], &(#list)[0], #context)): return true; + $case $defined(#cmp(&(#list)[0], &(#list)[0])): return $no_context; + $default: return false; + $endswitch + $else + $switch: + $case $defined(#cmp((*#list)[0], (*#list)[0], #context)): return true; + $case $defined(#cmp((*#list)[0], (*#list)[0])): return $no_context; + $case $defined(#cmp(&(*#list)[0], &(*#list)[0], #context)): return true; + $case $defined(#cmp(&(*#list)[0], &(*#list)[0])): return $no_context; + $default: return false; + $endswitch + $endif + $endswitch + $endif } -macro bool @is_any_valid_cmp_fn(#cmp, #list, #context) @const +macro bool @is_any_valid_cmp_fn(#cmp = ..., #list = ..., #context = ...) @const { - var $Type = $typeof(#cmp); - var $no_context = @is_empty_macro_slot(#context); - $switch: - $case @is_empty_macro_slot(#cmp): return true; - $case $Type.kindof != FUNC ||| $Type.returns.kindof != SIGNED_INT: return false; - $default: - $if $kindof(#list) != POINTER: - $switch: - $case $defined(#cmp((#list)[0], (#list)[0], #context)): return true; - $case $defined(#cmp((#list)[0], (#list)[0])): return $no_context; - $case $defined(#cmp(&(#list)[0], &(#list)[0], #context)): return true; - $case $defined(#cmp(&(#list)[0], &(#list)[0])): return $no_context; - $default: return false; - $endswitch - $else - $switch: - $case $defined(#cmp((*#list)[0], (*#list)[0], #context)): return true; - $case $defined(#cmp((*#list)[0], (*#list)[0])): return $no_context; - $case $defined(#cmp(&(*#list)[0], &(*#list)[0], #context)): return true; - $case $defined(#cmp(&(*#list)[0], &(*#list)[0])): return $no_context; - $default: return false; - $endswitch - $endif - $endswitch + $if !$defined(#cmp): + return true; + $else + var $Type = $typeof(#cmp); + var $no_context = !$defined(#context); + $switch: + $case $Type.kindof != FUNC ||| $Type.returns.kindof != SIGNED_INT: return false; + $default: + $if $kindof(#list) != POINTER: + $switch: + $case $defined(#cmp((#list)[0], (#list)[0], #context)): return true; + $case $defined(#cmp((#list)[0], (#list)[0])): return $no_context; + $case $defined(#cmp(&(#list)[0], &(#list)[0], #context)): return true; + $case $defined(#cmp(&(#list)[0], &(#list)[0])): return $no_context; + $default: return false; + $endswitch + $else + $switch: + $case $defined(#cmp((*#list)[0], (*#list)[0], #context)): return true; + $case $defined(#cmp((*#list)[0], (*#list)[0])): return $no_context; + $case $defined(#cmp(&(*#list)[0], &(*#list)[0], #context)): return true; + $case $defined(#cmp(&(*#list)[0], &(*#list)[0])): return $no_context; + $default: return false; + $endswitch + $endif + $endswitch + $endif } <* @require @list_is_by_ref(#list) : "Expected the list to be passed by ref or be a slice" *> -macro bool @is_cmp_key_fn(#key_fn, #list) @const +macro bool @is_cmp_key_fn(#key_fn = ..., #list = ...) @const { - $switch: - $case @is_empty_macro_slot(#key_fn): return true; - $case $kindof(#key_fn) != FUNC: return false; - $case $typeof(#key_fn).returns.kindof != UNSIGNED_INT: return false; - $default: - $if $kindof(#list) == SLICE: - $switch: - $case $defined(#key_fn((#list)[0])): return true; - $case $defined(#key_fn(&&((#list)[0]))): return true; - $default: return false; - $endswitch - $else - $switch: - $case $defined(#key_fn((*#list)[0])): return true; - $case $defined(#key_fn(&&((*#list)[0]))): return true; - $default: return false; - $endswitch - $endif - $endswitch - + $if !$defined(#key_fn): + return true; + $else + $switch: + $case $kindof(#key_fn) != FUNC: return false; + $case $typeof(#key_fn).returns.kindof != UNSIGNED_INT: return false; + $default: + $if $kindof(#list) == SLICE: + $switch: + $case $defined(#key_fn((#list)[0])): return true; + $case $defined(#key_fn(&&((#list)[0]))): return true; + $default: return false; + $endswitch + $else + $switch: + $case $defined(#key_fn((*#list)[0])): return true; + $case $defined(#key_fn(&&((*#list)[0]))): return true; + $default: return false; + $endswitch + $endif + $endswitch + $endif } \ No newline at end of file diff --git a/lib/std/sort/sorted.c3 b/lib/std/sort/sorted.c3 index dc6cc653a..e1bb33355 100644 --- a/lib/std/sort/sorted.c3 +++ b/lib/std/sort/sorted.c3 @@ -4,94 +4,50 @@ module std::sort; Returns true if list is sorted in either ascending or descending order. @require @is_any_sortable(list) : "The list must be indexable and support .len or .len()" - @require @is_any_valid_cmp_fn(cmp, list, ctx) : "Expected a comparison function which compares values" - @require @is_valid_context(cmp, ctx) : "Expected a valid context" + @require @is_any_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...ctx) : "Expected a comparison function which compares values" + @require @is_valid_context(...cmp, ...ctx) : "Expected a valid context" *> -macro bool is_sorted(list, cmp = EMPTY_MACRO_SLOT, ctx = EMPTY_MACRO_SLOT) @builtin +macro bool is_sorted(list, cmp = ..., ctx = ...) @builtin { - $if ($kindof(list) != POINTER): - var $Type = $typeof(list); - usz len = lengthof(list); + // When the context or cmp functions are not defined, we can simply use a dummy/default type. + const LIST_IS_REF = $kindof(list) == POINTER; + var $Type = !LIST_IS_REF ??? $typeof(list) : $typeof(*list); + + usz len = !LIST_IS_REF ??? lengthof(list) : lengthof(*list); if (len <= 1) return true; - var check_sort = fn bool($Type list, usz start, usz end, $typeof(cmp) cmp, $typeof(ctx) ctx) + + bool is_sorted = false; + if CHECK_SORT: (true) { - usz i; + usz i = 0; int sort_order; - // determine sort order (ascending or descending) - for (i = start; i < end && sort_order == 0; i++) + for (; i < (len - 1) && sort_order == 0; i++) { - sort_order = @sort_cmp_slice(list, i, cmp, ctx); + sort_order = @sort_cmp(list, i, ...cmp, ...ctx); } - // no sort order found, all elements are the same, consider list sorted - if (sort_order == 0) return true; - + if (sort_order == 0) + { + is_sorted = true; + break CHECK_SORT; + } // compare adjacent elements to the sort order - for (; i < end; i++) + for (; i < (len - 1); i++) { - if (sort_order * @sort_cmp_slice(list, i, cmp, ctx) < 0) return false; + if (sort_order * @sort_cmp(list, i, ...cmp, ...ctx) < 0) break CHECK_SORT; } - return true; - }; + is_sorted = true; + } - return check_sort(list, 0, len - 1, cmp, ctx); - $else - var $Type = $typeof(*list); - usz len = lengthof(*list); - if (len <= 1) return true; - var check_sort = fn bool($Type* list, usz start, usz end, $typeof(cmp) cmp, $typeof(ctx) ctx) - { - usz i; - int sort_order; - - // determine sort order (ascending or descending) - for (i = start; i < end && sort_order == 0; i++) - { - sort_order = @sort_cmp(list, i, cmp, ctx); - } - - // no sort order found, all elements are the same, consider list sorted - if (sort_order == 0) return true; - - // compare adjacent elements to the sort order - for (; i < end; i++) - { - if (sort_order * @sort_cmp(list, i, cmp, ctx) < 0) return false; - } - return true; - }; - return check_sort(list, 0, len - 1, cmp, ctx); - $endif + return is_sorted; } -macro int @sort_cmp(list, pos, cmp, ctx) @local +macro int @sort_cmp(list_maybe_ref, pos, cmp = ..., ctx = ...) @local { - var $has_cmp = @is_valid_macro_slot(cmp); - var $has_context = @is_valid_macro_slot(ctx); - var $cmp_by_value = $has_cmp &&& $defined($typefrom($typeof(cmp).paramsof[0].type) v = (*list)[0]); - - var a = (*list)[pos]; - var b = (*list)[pos+1]; - - $switch: - $case $cmp_by_value && $has_context: - return cmp(a, b); - $case $cmp_by_value: - return cmp(a, b); - $case $has_cmp && $has_context: - return cmp(&a, &b, ctx); - $case $has_cmp: - return cmp(&a, &b); - $default: - return compare_to(a,b); - $endswitch -} - -macro int @sort_cmp_slice(list, pos, cmp, ctx) @local -{ - var $has_cmp = @is_valid_macro_slot(cmp); - var $has_context = @is_valid_macro_slot(ctx); + var list = $kindof(list_maybe_ref) != POINTER ??? list_maybe_ref : *list_maybe_ref; + var $has_cmp = $defined(cmp); + var $has_context = $defined(ctx); var $cmp_by_value = $has_cmp &&& $defined($typefrom($typeof(cmp).paramsof[0].type) v = list[0]); var a = list[pos]; diff --git a/releasenotes.md b/releasenotes.md index 10310af6c..ab0fd10f2 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -137,6 +137,7 @@ - Use a `Printable` struct for ansi RGB formatting instead of explicit allocation and deprecate the old method. - HashSet.len() now returns usz instead of int. #2740 - Add `mem::store` and `mem::load` which may combine both aligned and volatile operations. +- Deprecated `EMPTY_MACRO_SLOT` and its related uses, in favor of `optional_param = ...` named macro arguments. #2805 ## 0.7.8 Change list