export module coral.stack; import coral; // Collections. export namespace coral { /** * Result codes used when pushing elements to a stack. * * [push_result::ok] indicates that the push operation was successful. * * [push_result::out_of_memory] alerts that the memory required to perform the push operation failed. */ enum class [[nodiscard]] push_result { ok, out_of_memory, }; /** * Last-in-first-out contiguous sequence of `element` values. * * *Note*: the [allocator] used by the stack must remain valid for the duration of the stack lifetime. */ template struct stack { stack(allocator & element_allocator) : element_allocator{element_allocator} { this->filled_elements = {}; } ~stack() { for (element & e : this->filled_elements) e.~element(); this->element_allocator.deallocate(this->filled_elements.pointer); } /** * Returns `true` if there are no elements in the stack, otherwise `false`. */ bool is_empty() const { return this->filled_elements.length == 0; } /** * Attempts to append `value` to the top of the stack. * * The returned [push_result] indicates whether the operation was successful or not. * * If the returned [push_result] is anything but [push_result::ok], the stack will be be left in an empty but * valid state. * * *Note* [push_all] is recommended when appending many values at once. */ push_result push(element const & value) { if (this->filled_elements.length == this->element_capacity) { constexpr usize min_size {2}; push_result const result {this->reserve(this->element_capacity + (static_cast(this->element_capacity == 0) * min_size))}; if (result != push_result::ok) return result; } this->filled_elements[this->filled_elements.length] = value; this->filled_elements.length += 1; return push_result::ok; } /** * Attempts to append `elements` to the top of the stack. * * The returned [push_result] indicates whether the operation was successful or not. * * If the returned [push_result] is anything but [push_result::ok], the stack will be left in an empty but valid * state. * * *Note* [push] is recommended when appending singular values. */ push_result push_all(slice const & values) { usize const updated_fill {this->filled + values.length}; if (updated_fill >= this->elements.length) { push_result const result {this->reserve(values.length)}; if (result != push_result::ok) return result; } for (usize i {0}; i < values.length; i += 1) this->elements[this->filled + i] = values[i]; this->filled = updated_fill; return push_result::ok; } /** * Attempts to reserve `requested_capacity` number of elements additional space on the stack. * * The returned [push_result] indicates whether the operation was successful or not. * * If the returned [push_result] is anything but [push_result::ok], the stack will be left in an empty but valid * state. */ push_result reserve(usize requested_capacity) { usize const total_capacity {this->elements_filled + requested_capacity}; u8 * const buffer {this->element_allocator.reallocate(reinterpret_cast( this->elements_filled.pointer), sizeof(element) * total_capacity)}; if (buffer == nullptr) { this->element_capacity = 0; this->filled_elements = {}; return push_result::out_of_memory; } this->filled_elements.pointer = reinterpret_cast(buffer); this->element_capacity = total_capacity; return push_result::ok; } private: allocator & element_allocator; usize element_capacity{0}; slice filled_elements; }; }