diff --git a/lib/std/collections/anylist.c3 b/lib/std/collections/anylist.c3 index e929e07a6..f497cf59d 100644 --- a/lib/std/collections/anylist.c3 +++ b/lib/std/collections/anylist.c3 @@ -2,10 +2,10 @@ // Use of self source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. module std::collections::anylist; -import std::io,std::math; +import std::collections::interfacelist; -alias AnyPredicate = fn bool(any value); -alias AnyTest = fn bool(any type, any context); +alias AnyPredicate = InterfacePredicate {any}; +alias AnyTest = InterfaceTest {any}; <* The AnyList contains a heterogenous set of types. Anything placed in the @@ -18,282 +18,7 @@ alias AnyTest = fn bool(any type, any context); If we're not doing pop, then things are easier, since we can just hand over the existing any. *> -struct AnyList (Printable) -{ - usz size; - usz capacity; - Allocator allocator; - any* entries; -} - - -<* - Initialize the list. If not initialized then it will use the temp allocator - when something is pushed to it. - - @param [&inout] allocator : "The allocator to use" - @param initial_capacity : "The initial capacity to reserve, defaults to 16" -*> -fn AnyList* AnyList.init(&self, Allocator allocator, usz initial_capacity = 16) -{ - self.allocator = allocator; - self.size = 0; - if (initial_capacity > 0) - { - initial_capacity = math::next_power_of_2(initial_capacity); - self.entries = allocator::alloc_array(allocator, any, initial_capacity); - } - else - { - self.entries = null; - } - self.capacity = initial_capacity; - return self; -} - -<* - Initialize the list using the temp allocator. - - @param initial_capacity : "The initial capacity to reserve" -*> -fn AnyList* AnyList.tinit(&self, usz initial_capacity = 16) -{ - return self.init(tmem, initial_capacity) @inline; -} - -fn bool AnyList.is_initialized(&self) @inline => self.allocator != null; - -<* - Push an element on the list by cloning it. -*> -macro void AnyList.push(&self, element) -{ - if (!self.allocator) self.allocator = tmem; - self._append(allocator::clone(self.allocator, element)); -} - -<* - Free a retained element removed using *_retained. -*> -fn void AnyList.free_element(&self, any element) @inline -{ - allocator::free(self.allocator, element.ptr); -} - -<* - Pop a value who's type is known. If the type is incorrect, this - will still pop the element. - - @param $Type : "The type we assume the value has" - @return "The last value as the type given" - @return? TYPE_MISMATCH, NO_MORE_ELEMENT -*> -macro AnyList.pop(&self, $Type) -{ - if (!self.size) return NO_MORE_ELEMENT?; - defer self.free_element(self.entries[self.size]); - return *anycast(self.entries[--self.size], $Type); -} - -<* - Copy the last value, pop it and return the copy of it. - - @param [&inout] allocator : "The allocator to use for copying" - @return "A copy of the last value if it exists" - @return? NO_MORE_ELEMENT -*> -fn any? AnyList.copy_pop(&self, Allocator allocator) -{ - if (!self.size) return NO_MORE_ELEMENT?; - defer self.free_element(self.entries[self.size]); - return allocator::clone_any(allocator, self.entries[--self.size]); -} - - -<* - Copy the last value, pop it and return the copy of it. - - @return "A temp copy of the last value if it exists" - @return? NO_MORE_ELEMENT -*> -fn any? AnyList.tcopy_pop(&self) => self.copy_pop(tmem); - - -<* - Pop the last value. It must later be released using `list.free_element()`. - - @return "The last value if it exists" - @return? NO_MORE_ELEMENT -*> -fn any? AnyList.pop_retained(&self) -{ - if (!self.size) return NO_MORE_ELEMENT?; - return self.entries[--self.size]; -} - -<* - Remove all elements in the list. -*> -fn void AnyList.clear(&self) -{ - for (usz i = 0; i < self.size; i++) - { - self.free_element(self.entries[i]); - } - self.size = 0; -} - -<* - Pop a value who's type is known. If the type is incorrect, this - will still pop the element. - - @param $Type : "The type we assume the value has" - @return "The first value as the type given" - @return? TYPE_MISMATCH, NO_MORE_ELEMENT -*> -macro AnyList.pop_first(&self, $Type) -{ - if (!self.size) return NO_MORE_ELEMENT?; - defer self.remove_at(0); - return *anycast(self.entries[0], $Type); -} - -<* - Pop the first value. It must later be released using `list.free_element()`. - - @return "The first value if it exists" - @return? NO_MORE_ELEMENT -*> -fn any? AnyList.pop_first_retained(&self) -{ - if (!self.size) return NO_MORE_ELEMENT?; - defer self.remove_at(0); - return self.entries[0]; -} - - -<* - Copy the first value, pop it and return the copy of it. - - @param [&inout] allocator : "The allocator to use for copying" - @return "A copy of the first value if it exists" - @return? NO_MORE_ELEMENT -*> -fn any? AnyList.copy_pop_first(&self, Allocator allocator) -{ - if (!self.size) return NO_MORE_ELEMENT?; - defer self.free_element(self.entries[self.size]); - defer self.remove_at(0); - return allocator::clone_any(allocator, self.entries[0]); -} - -<* - Copy the first value, pop it and return the temp copy of it. - - @return "A temp copy of the first value if it exists" - @return? NO_MORE_ELEMENT -*> -fn any? AnyList.tcopy_pop_first(&self) => self.copy_pop_first(tmem); - -<* - Remove the element at the particular index. - - @param index : "The index of the element to remove" - @require index < self.size -*> -fn void AnyList.remove_at(&self, usz index) -{ - if (!--self.size || index == self.size) return; - self.free_element(self.entries[index]); - self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size]; -} - -<* - Add all the elements in another AnyList. - - @param [&in] other_list : "The list to add" -*> -fn void AnyList.add_all(&self, AnyList* other_list) -{ - if (!other_list.size) return; - self.reserve(other_list.size); - foreach (value : other_list) - { - self.entries[self.size++] = allocator::clone_any(self.allocator, value); - } -} - -<* - Reverse the order of the elements in the list. -*> -fn void AnyList.reverse(&self) -{ - if (self.size < 2) return; - usz half = self.size / 2U; - usz end = self.size - 1; - for (usz i = 0; i < half; i++) - { - self.swap(i, end - i); - } -} - -<* - Return a view of the data as a slice. - - @return "The slice view" -*> -fn any[] AnyList.array_view(&self) -{ - return self.entries[:self.size]; -} - -<* - Push an element to the front of the list. - - @param value : "The value to push to the list" -*> -macro void AnyList.push_front(&self, value) -{ - self.insert_at(0, value); -} - -<* - Insert an element at a particular index. - - @param index : "the index where the element should be inserted" - @param type : "the value to insert" - @require index <= self.size : "The index is out of bounds" -*> -macro void AnyList.insert_at(&self, usz index, type) -{ - if (index == self.size) - { - self.push(type); - return; - } - any value = allocator::copy(self.allocator, type); - self._insert_at(self, index, value); -} - -<* - Remove the last element in the list. The list may not be empty. - - @require self.size > 0 : "The list was already empty" -*> -fn void AnyList.remove_last(&self) -{ - self.free_element(self.entries[--self.size]); -} - -<* - Remove the first element in the list, the list may not be empty. - - @require self.size > 0 -*> -fn void AnyList.remove_first(&self) -{ - self.remove_at(0); -} +typedef AnyList = inline InterfaceList {any}; <* Return the first element by value, assuming it is the given type. @@ -313,10 +38,7 @@ macro AnyList.first(&self, $Type) @return "The first element" @return? NO_MORE_ELEMENT *> -fn any? AnyList.first_any(&self) @inline -{ - return self.size ? self.entries[0] : NO_MORE_ELEMENT?; -} +fn any? AnyList.first_any(&self) @inline => InterfaceList {any}.first(self); <* Return the last element by value, assuming it is the given type. @@ -336,29 +58,36 @@ macro AnyList.last(&self, $Type) @return "The last element" @return? NO_MORE_ELEMENT *> -fn any? AnyList.last_any(&self) @inline +fn any? AnyList.last_any(&self) @inline => InterfaceList {any}.last(self); + +<* + Pop a value who's type is known. If the type is incorrect, this + will still pop the element. + + @param $Type : "The type we assume the value has" + @return "The last value as the type given" + @return? TYPE_MISMATCH, NO_MORE_ELEMENT +*> +macro AnyList.pop(&self, $Type) { - return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?; + if (!self.size) return NO_MORE_ELEMENT?; + defer self.free_element(self.entries[self.size]); + return *anycast(self.entries[--self.size], $Type); } <* - Return whether the list is empty. + Pop a value who's type is known. If the type is incorrect, this + will still pop the element. - @return "True if the list is empty" + @param $Type : "The type we assume the value has" + @return "The first value as the type given" + @return? TYPE_MISMATCH, NO_MORE_ELEMENT *> -fn bool AnyList.is_empty(&self) @inline +macro AnyList.pop_first(&self, $Type) { - return !self.size; -} - -<* - Return the length of the list. - - @return "The number of elements in the list" -*> -fn usz AnyList.len(&self) @operator(len) @inline -{ - return self.size; + if (!self.size) return NO_MORE_ELEMENT?; + defer self.remove_at(0); + return *anycast(self.entries[0], $Type); } <* @@ -383,222 +112,11 @@ macro AnyList.get(&self, usz index, $Type) @return? TYPE_MISMATCH, NO_MORE_ELEMENT @require index < self.size : "Index out of range" *> -fn any AnyList.get_any(&self, usz index) @inline @operator([]) -{ - return self.entries[index]; -} +fn any AnyList.get_any(&self, usz index) @inline @operator([]) => InterfaceList {any}.get(self, index); <* - Completely free and clear a list. + Return the length of the list. + + @return "The number of elements in the list" *> -fn void AnyList.free(&self) -{ - if (!self.allocator) return; - self.clear(); - allocator::free(self.allocator, self.entries); - self.capacity = 0; - self.entries = null; -} - -<* - Swap two elements in a list. - - @param i : "Index of one of the elements" - @param j : "Index of the other element" - @require i < self.size : "The first index is out of range" - @require j < self.size : "The second index is out of range" -*> -fn void AnyList.swap(&self, usz i, usz j) -{ - any temp = self.entries[i]; - self.entries[i] = self.entries[j]; - self.entries[j] = temp; -} - -<* - Print the list to a formatter. -*> -fn usz? AnyList.to_format(&self, Formatter* formatter) @dynamic -{ - switch (self.size) - { - case 0: - return formatter.print("[]")!; - case 1: - return formatter.printf("[%s]", self.entries[0])!; - default: - usz n = formatter.print("[")!; - foreach (i, element : self.entries[:self.size]) - { - if (i != 0) formatter.print(", ")!; - n += formatter.printf("%s", element)!; - } - n += formatter.print("]")!; - return n; - } -} - -<* - Remove any elements matching the predicate. - - @param filter : "The function to determine if it should be removed or not" - @return "the number of deleted elements" -*> -fn usz AnyList.remove_if(&self, AnyPredicate filter) -{ - return self._remove_if(filter, false); -} - -<* - Retain the elements matching the predicate. - - @param selection : "The function to determine if it should be kept or not" - @return "the number of deleted elements" -*> -fn usz AnyList.retain_if(&self, AnyPredicate selection) -{ - return self._remove_if(selection, true); -} - -<* - Remove any elements matching the predicate. - - @param filter : "The function to determine if it should be removed or not" - @param context : "The context to the function" - @return "the number of deleted elements" -*> -fn usz AnyList.remove_using_test(&self, AnyTest filter, any context) -{ - return self._remove_using_test(filter, false, context); -} - -<* - Retain any elements matching the predicate. - - @param selection : "The function to determine if it should be retained or not" - @param context : "The context to the function" - @return "the number of deleted elements" -*> -fn usz AnyList.retain_using_test(&self, AnyTest selection, any context) -{ - return self._remove_using_test(selection, true, context); -} - - -<* - Reserve memory so that at least the `min_capacity` exists. - - @param min_capacity : "The min capacity to hold" -*> -fn void AnyList.reserve(&self, usz min_capacity) -{ - if (!min_capacity) return; - if (self.capacity >= min_capacity) return; - if (!self.allocator) self.allocator = tmem; - min_capacity = math::next_power_of_2(min_capacity); - self.entries = allocator::realloc(self.allocator, self.entries, any.sizeof * min_capacity); - self.capacity = min_capacity; -} - -<* - Set the element at any index. - - @param index : "The index where to set the value." - @param value : "The value to set" - @require index <= self.size : "Index out of range" -*> -macro void AnyList.set(&self, usz index, value) -{ - if (index == self.size) - { - self.push(value); - return; - } - self.free_element(self.entries[index]); - self.entries[index] = allocator::copy(self.allocator, value); -} - -// -- private - -fn void AnyList.ensure_capacity(&self, usz added = 1) @inline @private -{ - usz new_size = self.size + added; - if (self.capacity >= new_size) return; - - assert(new_size < usz.max / 2U); - usz new_capacity = self.capacity ? 2U * self.capacity : 16U; - while (new_capacity < new_size) new_capacity *= 2U; - self.reserve(new_capacity); -} - -fn void AnyList._append(&self, any element) @local -{ - self.ensure_capacity(); - self.entries[self.size++] = element; -} - -<* - @require index < self.size -*> -fn void AnyList._insert_at(&self, usz index, any value) @local -{ - self.ensure_capacity(); - for (usz i = self.size; i > index; i--) - { - self.entries[i] = self.entries[i - 1]; - } - self.size++; - self.entries[index] = value; -} - -macro usz AnyList._remove_using_test(&self, AnyTest filter, bool $invert, ctx) @local -{ - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) - { - // Find last index of item to be deleted. - $if $invert: - while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--; - $else - while (i > 0 && filter(&self.entries[i - 1], ctx)) i--; - $endif - // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; - for (usz j = i; j < k; j++) self.free_element(self.entries[j]); - self.entries[i:n] = self.entries[k:n]; - self.size -= k - i; - // Find last index of item not to be deleted. - $if $invert: - while (i > 0 && filter(&self.entries[i - 1], ctx)) i--; - $else - while (i > 0 && !filter(&self.entries[i - 1], ctx)) i--; - $endif - } - return size - self.size; -} - -macro usz AnyList._remove_if(&self, AnyPredicate filter, bool $invert) @local -{ - usz size = self.size; - for (usz i = size, usz k = size; k > 0; k = i) - { - // Find last index of item to be deleted. - $if $invert: - while (i > 0 && !filter(&self.entries[i - 1])) i--; - $else - while (i > 0 && filter(&self.entries[i - 1])) i--; - $endif - // Remove the items from this index up to the one not to be deleted. - usz n = self.size - k; - for (usz j = i; j < k; j++) self.free_element(self.entries[j]); - self.entries[i:n] = self.entries[k:n]; - self.size -= k - i; - // Find last index of item not to be deleted. - $if $invert: - while (i > 0 && filter(&self.entries[i - 1])) i--; - $else - while (i > 0 && !filter(&self.entries[i - 1])) i--; - $endif - } - return size - self.size; -} +fn usz AnyList.len(&self) @operator(len) @inline => InterfaceList {any}.len(self); diff --git a/lib/std/collections/interfacelist.c3 b/lib/std/collections/interfacelist.c3 new file mode 100644 index 000000000..ffd51bd73 --- /dev/null +++ b/lib/std/collections/interfacelist.c3 @@ -0,0 +1,539 @@ +// Copyright (c) 2024-2025 Christoffer Lerno. All rights reserved. +// Use of self source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. +<* + @require Type.kindof == INTERFACE || Type.kindof == ANY : "The kind of an interfacelist must be an interface or `any`" +*> +module std::collections::interfacelist {Type}; +import std::io,std::math; + +alias InterfacePredicate = fn bool(Type value); +alias InterfaceTest = fn bool(Type type, Type context); + +<* + The InterfaceList contains a heterogenous set of types implementing an interface. anything placed in the + list will shallowly copied in order to be stored as the interface. This means + that the list will copy and free its elements. + + However, because we're getting interface values back when we pop, those operations + need to take an allocator, as we can only copy then pop then return the copy. + + If we're not doing pop, then things are easier, since we can just hand over + the existing value. +*> +struct InterfaceList (Printable) +{ + usz size; + usz capacity; + Allocator allocator; + Type* entries; +} + + +<* + Initialize the list. If not initialized then it will use the temp allocator + when something is pushed to it. + + @param [&inout] allocator : "The allocator to use" + @param initial_capacity : "The initial capacity to reserve, defaults to 16" +*> +fn InterfaceList* InterfaceList.init(&self, Allocator allocator, usz initial_capacity = 16) +{ + self.allocator = allocator; + self.size = 0; + if (initial_capacity > 0) + { + initial_capacity = math::next_power_of_2(initial_capacity); + self.entries = allocator::alloc_array(allocator, Type, initial_capacity); + } + else + { + self.entries = null; + } + self.capacity = initial_capacity; + return self; +} + +<* + Initialize the list using the temp allocator. + + @param initial_capacity : "The initial capacity to reserve" +*> +fn InterfaceList* InterfaceList.tinit(&self, usz initial_capacity = 16) +{ + return self.init(tmem, initial_capacity) @inline; +} + +fn bool InterfaceList.is_initialized(&self) @inline => self.allocator != null; + +<* + Push an element on the list by cloning it. + @require $defined(Type t = &element) : "Element must implement the interface" +*> +macro void InterfaceList.push(&self, element) +{ + if (!self.allocator) self.allocator = tmem; + self._append(allocator::clone(self.allocator, element)); +} + +<* + Free a retained element removed using *_retained. +*> +fn void InterfaceList.free_element(&self, Type element) @inline +{ + allocator::free(self.allocator, element.ptr); +} + +<* + Copy the last value, pop it and return the copy of it. + + @param [&inout] allocator : "The allocator to use for copying" + @return "A copy of the last value if it exists" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.copy_pop(&self, Allocator allocator) +{ + if (!self.size) return NO_MORE_ELEMENT?; + defer self.free_element(self.entries[self.size]); + return (Type)allocator::clone_any(allocator, self.entries[--self.size]); +} + +<* + Copy the last value, pop it and return the copy of it. + + @return "A temp copy of the last value if it exists" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.tcopy_pop(&self) => self.copy_pop(tmem); + +<* + Pop the last value. It must later be released using `list.free_element()`. + + @return "The last value if it exists" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.pop_retained(&self) +{ + if (!self.size) return NO_MORE_ELEMENT?; + return self.entries[--self.size]; +} + +<* + Remove all elements in the list. +*> +fn void InterfaceList.clear(&self) +{ + for (usz i = 0; i < self.size; i++) + { + self.free_element(self.entries[i]); + } + self.size = 0; +} + +<* + Pop the first value. It must later be released using `list.free_element()`. + + @return "The first value if it exists" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.pop_first_retained(&self) +{ + if (!self.size) return NO_MORE_ELEMENT?; + defer self.remove_at(0); + return self.entries[0]; +} + +<* + Copy the first value, pop it and return the copy of it. + + @param [&inout] allocator : "The allocator to use for copying" + @return "A copy of the first value if it exists" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.copy_pop_first(&self, Allocator allocator) +{ + if (!self.size) return NO_MORE_ELEMENT?; + defer self.free_element(self.entries[self.size]); + defer self.remove_at(0); + return (Type)allocator::clone_any(allocator, self.entries[0]); +} + +<* + Copy the first value, pop it and return the temp copy of it. + + @return "A temp copy of the first value if it exists" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.tcopy_pop_first(&self) => self.copy_pop_first(tmem); + +<* + Remove the element at the particular index. + + @param index : "The index of the element to remove" + @require index < self.size +*> +fn void InterfaceList.remove_at(&self, usz index) +{ + if (!--self.size || index == self.size) return; + self.free_element(self.entries[index]); + self.entries[index .. self.size - 1] = self.entries[index + 1 .. self.size]; +} + +<* + Add all the elements in another InterfaceList. + + @param [&in] other_list : "The list to add" +*> +fn void InterfaceList.add_all(&self, InterfaceList* other_list) +{ + if (!other_list.size) return; + self.reserve(other_list.size); + foreach (value : other_list) + { + self.entries[self.size++] = (Type)allocator::clone_any(self.allocator, value); + } +} + +<* + Reverse the order of the elements in the list. +*> +fn void InterfaceList.reverse(&self) +{ + if (self.size < 2) return; + usz half = self.size / 2U; + usz end = self.size - 1; + for (usz i = 0; i < half; i++) + { + self.swap(i, end - i); + } +} + +<* + Return a view of the data as a slice. + + @return "The slice view" +*> +fn Type[] InterfaceList.array_view(&self) +{ + return self.entries[:self.size]; +} + +<* + Push an element to the front of the list. + + @param value : "The value to push to the list" + @require $defined(Type t = &value) : "Value must implement the interface" +*> +macro void InterfaceList.push_front(&self, value) +{ + self.insert_at(0, value); +} + +<* + Insert an element at a particular index. + + @param index : "the index where the element should be inserted" + @param type : "the value to insert" + @require index <= self.size : "The index is out of bounds" + @require $defined(Type t = &type) : "Type must implement the interface" +*> +macro void InterfaceList.insert_at(&self, usz index, type) +{ + if (index == self.size) + { + self.push(type); + return; + } + Type value = allocator::clone(self.allocator, type); + self._insert_at(self, index, value); +} + +<* + Remove the last element in the list. The list may not be empty. + + @require self.size > 0 : "The list was already empty" +*> +fn void InterfaceList.remove_last(&self) +{ + self.free_element(self.entries[--self.size]); +} + +<* + Remove the first element in the list, the list may not be empty. + + @require self.size > 0 +*> +fn void InterfaceList.remove_first(&self) +{ + self.remove_at(0); +} + +<* + Return the first element + + @return "The first element" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.first(&self) @inline +{ + return self.size ? self.entries[0] : NO_MORE_ELEMENT?; +} + +<* + Return the last element + + @return "The last element" + @return? NO_MORE_ELEMENT +*> +fn Type? InterfaceList.last(&self) @inline +{ + return self.size ? self.entries[self.size - 1] : NO_MORE_ELEMENT?; +} + +<* + Return whether the list is empty. + + @return "True if the list is empty" +*> +fn bool InterfaceList.is_empty(&self) @inline +{ + return !self.size; +} + +<* + Return the length of the list. + + @return "The number of elements in the list" +*> +fn usz InterfaceList.len(&self) @operator(len) @inline +{ + return self.size; +} + +<* + Return an element in the list. + + @param index : "The index of the element to retrieve" + @return "The element at the index" + @return? TYPE_MISMATCH, NO_MORE_ELEMENT + @require index < self.size : "Index out of range" +*> +fn Type InterfaceList.get(&self, usz index) @inline @operator([]) +{ + return self.entries[index]; +} + +<* + Completely free and clear a list. +*> +fn void InterfaceList.free(&self) +{ + if (!self.allocator) return; + self.clear(); + allocator::free(self.allocator, self.entries); + self.capacity = 0; + self.entries = null; +} + +<* + Swap two elements in a list. + + @param i : "Index of one of the elements" + @param j : "Index of the other element" + @require i < self.size : "The first index is out of range" + @require j < self.size : "The second index is out of range" +*> +fn void InterfaceList.swap(&self, usz i, usz j) +{ + Type temp = self.entries[i]; + self.entries[i] = self.entries[j]; + self.entries[j] = temp; +} + +<* + Print the list to a formatter. +*> +fn usz? InterfaceList.to_format(&self, Formatter* formatter) @dynamic +{ + switch (self.size) + { + case 0: + return formatter.print("[]")!; + case 1: + return formatter.printf("[%s]", self.entries[0])!; + default: + usz n = formatter.print("[")!; + foreach (i, element : self.entries[:self.size]) + { + if (i != 0) formatter.print(", ")!; + n += formatter.printf("%s", element)!; + } + n += formatter.print("]")!; + return n; + } +} + +<* + Remove Type elements matching the predicate. + + @param filter : "The function to determine if it should be removed or not" + @return "the number of deleted elements" +*> +fn usz InterfaceList.remove_if(&self, InterfacePredicate filter) +{ + return self._remove_if(filter, false); +} + +<* + Retain the elements matching the predicate. + + @param selection : "The function to determine if it should be kept or not" + @return "the number of deleted elements" +*> +fn usz InterfaceList.retain_if(&self, InterfacePredicate selection) +{ + return self._remove_if(selection, true); +} + +<* + Remove Type elements matching the predicate. + + @param filter : "The function to determine if it should be removed or not" + @param context : "The context to the function" + @return "the number of deleted elements" +*> +fn usz InterfaceList.remove_using_test(&self, InterfaceTest filter, Type context) +{ + return self._remove_using_test(filter, false, context); +} + +<* + Retain Type elements matching the predicate. + + @param selection : "The function to determine if it should be retained or not" + @param context : "The context to the function" + @return "the number of deleted elements" +*> +fn usz InterfaceList.retain_using_test(&self, InterfaceTest selection, Type context) +{ + return self._remove_using_test(selection, true, context); +} + +<* + Reserve memory so that at least the `min_capacity` exists. + + @param min_capacity : "The min capacity to hold" +*> +fn void InterfaceList.reserve(&self, usz min_capacity) +{ + if (!min_capacity) return; + if (self.capacity >= min_capacity) return; + if (!self.allocator) self.allocator = tmem; + min_capacity = math::next_power_of_2(min_capacity); + self.entries = allocator::realloc(self.allocator, self.entries, Type.sizeof * min_capacity); + self.capacity = min_capacity; +} + +<* + Set the element at Type index. + + @param index : "The index where to set the value." + @param value : "The value to set" + @require index <= self.size : "Index out of range" + @require $defined(Type t = &value) : "Value must implement the interface" +*> +macro void InterfaceList.set(&self, usz index, value) +{ + if (index == self.size) + { + self.push(value); + return; + } + self.free_element(self.entries[index]); + self.entries[index] = allocator::clone(self.allocator, value); +} + +// -- private + +fn void InterfaceList.ensure_capacity(&self, usz added = 1) @inline @private +{ + usz new_size = self.size + added; + if (self.capacity >= new_size) return; + + assert(new_size < usz.max / 2U); + usz new_capacity = self.capacity ? 2U * self.capacity : 16U; + while (new_capacity < new_size) new_capacity *= 2U; + self.reserve(new_capacity); +} + +fn void InterfaceList._append(&self, Type element) @local +{ + self.ensure_capacity(); + self.entries[self.size++] = element; +} + +<* + @require index < self.size +*> +fn void InterfaceList._insert_at(&self, usz index, Type value) @local +{ + self.ensure_capacity(); + for (usz i = self.size; i > index; i--) + { + self.entries[i] = self.entries[i - 1]; + } + self.size++; + self.entries[index] = value; +} + +macro usz InterfaceList._remove_using_test(&self, InterfaceTest filter, bool $invert, ctx) @local +{ + usz size = self.size; + for (usz i = size, usz k = size; k > 0; k = i) + { + // Find last index of item to be deleted. + $if $invert: + while (i > 0 && !filter(self.entries[i - 1], ctx)) i--; + $else + while (i > 0 && filter(self.entries[i - 1], ctx)) i--; + $endif + // Remove the items from this index up to the one not to be deleted. + usz n = self.size - k; + for (usz j = i; j < k; j++) self.free_element(self.entries[j]); + self.entries[i:n] = self.entries[k:n]; + self.size -= k - i; + // Find last index of item not to be deleted. + $if $invert: + while (i > 0 && filter(self.entries[i - 1], ctx)) i--; + $else + while (i > 0 && !filter(self.entries[i - 1], ctx)) i--; + $endif + } + return size - self.size; +} + +macro usz InterfaceList._remove_if(&self, InterfacePredicate filter, bool $invert) @local +{ + usz size = self.size; + for (usz i = size, usz k = size; k > 0; k = i) + { + // Find last index of item to be deleted. + $if $invert: + while (i > 0 && !filter(self.entries[i - 1])) i--; + $else + while (i > 0 && filter(self.entries[i - 1])) i--; + $endif + // Remove the items from this index up to the one not to be deleted. + usz n = self.size - k; + for (usz j = i; j < k; j++) self.free_element(self.entries[j]); + self.entries[i:n] = self.entries[k:n]; + self.size -= k - i; + // Find last index of item not to be deleted. + $if $invert: + while (i > 0 && filter(self.entries[i - 1])) i--; + $else + while (i > 0 && !filter(self.entries[i - 1])) i--; + $endif + } + return size - self.size; +} diff --git a/releasenotes.md b/releasenotes.md index 8271c258c..877ab6949 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -11,6 +11,7 @@ - Returning pointer to index of slice stored in a struct from method taking self incorrectly detected as returning pointer to local variable #2455. ### Stdlib changes +- Added generic `InterfaceList` to store a list of values that implement a specific interface ## 0.7.5 Change list diff --git a/test/unit/stdlib/collections/anylist.c3 b/test/unit/stdlib/collections/anylist.c3 new file mode 100644 index 000000000..21c5186e0 --- /dev/null +++ b/test/unit/stdlib/collections/anylist.c3 @@ -0,0 +1,28 @@ +module anylist_test @test; +import std::collections::anylist; + +fn void pop() => @pool() +{ + AnyList l; + l.push(1.0); + l.push(1); + l.push("hello"); + assert(l.pop(String)!! == "hello"); + assert(l.pop(int)!! == 1); + assert(l.copy_pop(tmem)!!.type == double.typeid); +} + +fn void predicates() => @pool() +{ + AnyList l; + l.push(123u); + l.push(-1); + l.push("abc"); + l.push(5.0); + l.remove_using_test(fn (x, p) => x.type == p.type, &&456u); + assert(l[0].type == int.typeid); + assert(l.get(0, int)!! == -1); + l.retain_if(fn (x) => x.type == double.typeid); + assert(l.len() == 1); + assert(l.get(0, double)!! == 5.0); +} diff --git a/test/unit/stdlib/collections/interfacelist.c3 b/test/unit/stdlib/collections/interfacelist.c3 new file mode 100644 index 000000000..d00ba6032 --- /dev/null +++ b/test/unit/stdlib/collections/interfacelist.c3 @@ -0,0 +1,126 @@ +module interfacelist_test @test; +import std::collections::interfacelist; +interface Test +{ + fn int test(); +} + +alias TestL = InterfaceList {Test}; + +struct Test1 (Test) +{ + int a; +} +fn int Test1.test(&self) @dynamic => self.a; + +struct Test2 (Test) +{ + String b; +} +fn int Test2.test(&self) @dynamic => (int)self.b.len; + + +fn void initialized() => @pool() +{ + TestL l; + assert(!l.is_initialized()); + l.tinit(); + assert(l.is_initialized()); +} + +fn void basic_interation() => @pool() +{ + TestL l; + l.push((Test1){1}); + l.push((Test1){1234}); + assert(to_ints(l) == {1, 1234}); + assert(l.pop_retained().test()!! == 1234); + l.push((Test1){56789}); + assert(to_ints(l) == {1, 56789}); + l.set(2, (Test2){"abc"}); + assert(to_ints(l) == {1, 56789, 3}); +} + +fn void remove_at() => @pool() +{ + TestL l; + for (int i = 0; i < 5; i++) + { + l.push((Test1){i}); + } + assert(to_ints(l) == {0, 1, 2, 3, 4}); + l.remove_at(1); + assert(to_ints(l) == {0, 2, 3, 4}); + l.remove_at(3); + assert(to_ints(l) == {0, 2, 3}); +} + +fn void remove_with_predicate() => @pool() +{ + TestL l; + l.push((Test1){1}); + l.push((Test1){1234}); + l.push((Test2){"wefhewoifw"}); + l.push((Test1){-1290987}); + l.push((Test2){"abc"}); + assert(to_ints(l) == {1, 1234, 10, -1290987, 3}); + l.remove_if(fn (val) => val.test() < 5); + assert(to_ints(l) == {1234, 10}); + l.remove_if(fn (val) => val.type == Test2.typeid); + assert(to_ints(l) == {1234}); +} + +fn void retain_with_predicate() => @pool() +{ + TestL l; + l.push((Test1){1234}); + l.push((Test1){2345}); + l.push((Test1){3456}); + l.push((Test2){"abc"}); + l.push((Test2){"defg"}); + assert(to_ints(l) == {1234, 2345, 3456, 3, 4}); + l.retain_if(fn (val) => val.test() % 2 == 0); + assert(to_ints(l) == {1234, 3456, 4}); +} + +fn void remove_with_test() => @pool() +{ + TestL l; + l.push((Test1){532}); + l.push((Test2){"hello"}); + l.push((Test2){"abcdef"}); + l.push((Test1){765}); + assert(to_ints(l) == {532, 5, 6, 765}); + l.remove_using_test(fn (x, p) => x.type == p.type, &&(Test1){}); + assert(to_ints(l) == {5, 6}); + l.remove_using_test(fn (x, p) => x.test() == p.test(), &&(Test2){"abcdef"}); + assert(to_ints(l) == {5}); +} + +fn void retain_with_test() => @pool() +{ + TestL l; + l.push((Test1){345}); + l.push((Test1){3535}); + l.push((Test1){7654}); + l.push((Test2){"abdef"}); + l.push((Test1){6432}); + l.push((Test1){585868}); + assert(to_ints(l) == {345, 3535, 7654, 5, 6432, 585868}); + l.retain_using_test(fn (x, p) => x.test() < p.test(), &&(Test1){1000}); + assert(to_ints(l) == {345, 5}); + l.retain_using_test(fn (x, p) => x.type == p.type && x.test() == p.test(), &&(Test1){0}); + assert(to_ints(l) == {}); +} + +module interfacelist_test; + +fn int[] to_ints(TestL l) => @map(tmem, l.array_view(), fn int(Test x) => x.test()); + +import std::core::array @public; +macro @map(Allocator alloc, array, operation) +{ + var res = allocator::alloc_array(alloc, $typeof(operation).returns, array::find_len(array)); + foreach (i, val : array) res[i] = operation(val); + return res; +}