215 lines
6.4 KiB
C++
215 lines
6.4 KiB
C++
export module core.sequence;
|
|
|
|
import core;
|
|
|
|
export namespace core {
|
|
/**
|
|
* Result codes used by [sequence]-derived types when they are appended to in any way.
|
|
*
|
|
* [append_result::ok] indicates that an append operation was successful.
|
|
*
|
|
* [append_result::out_of_memory] alerts that the memory required to perform the append
|
|
* operation failed.
|
|
*/
|
|
enum class [[nodiscard]] append_result {
|
|
ok,
|
|
out_of_memory,
|
|
};
|
|
|
|
/**
|
|
* Base type for all sequence-like types.
|
|
*
|
|
* Sequences are any data structure which owns a linear, non-unique set of elements which may
|
|
* be queried and/or mutated.
|
|
*/
|
|
template<typename element> struct sequence {
|
|
virtual ~sequence() {};
|
|
|
|
/**
|
|
* Attempts to append `source_elements` to the sequence.
|
|
*
|
|
* The returned [append_result] indicates whether the operation was successful or not.
|
|
*
|
|
* If the returned [append_result] is anything but [append_result::ok], the [sequence] will
|
|
* be left in an implementation-defined state.
|
|
*/
|
|
virtual append_result append(slice<element const> const & source_elements) = 0;
|
|
};
|
|
|
|
/**
|
|
* Last-in-first-out linear sequence of `element` values.
|
|
*
|
|
* [stack] types will default to using an inline array of `init_capacity` at first. After all
|
|
* local storage has been exhausted, the [stack] will switch to a dynamic buffer. Because of
|
|
* this, it is recommended to use larger `init_capacity` values for data which has a known or
|
|
* approximate upper bound at compile-time. Otherwise, the `init_capacity` value may be left at
|
|
* its default.
|
|
*/
|
|
template<typename element, usize init_capacity = 1> struct stack : public sequence<element> {
|
|
stack(allocator * dynamic_allocator) : local_buffer{0} {
|
|
this->dynamic_allocator = dynamic_allocator;
|
|
this->filled = 0;
|
|
this->elements = this->local_buffer;
|
|
}
|
|
|
|
~stack() override {
|
|
if (this->is_dynamic()) this->dynamic_allocator->deallocate(this->elements.pointer);
|
|
}
|
|
|
|
/**
|
|
* Attempts to append `source_elements` to the top of the stack.
|
|
*
|
|
* The returned [append_result] indicates whether the operation was successful or not.
|
|
*
|
|
* If the returned [append_result] is anything but [append_result::ok], the stack will be
|
|
* be left in an empty but valid state.
|
|
*
|
|
* *Note* that [push] is recommended when appending singular values.
|
|
*/
|
|
append_result append(slice<element const> const & source_elements) override {
|
|
usize const updated_fill = this->filled + source_elements.length;
|
|
|
|
if (updated_fill >= this->elements.length) {
|
|
append_result const result =
|
|
this->reserve(max(this->elements.length, updated_fill));
|
|
|
|
if (result != append_result::ok) return result;
|
|
}
|
|
|
|
for (usize i = 0; i < source_elements.length; i += 1)
|
|
this->elements[this->filled + i] = source_elements[i];
|
|
|
|
this->filled = updated_fill;
|
|
|
|
return append_result::ok;
|
|
}
|
|
|
|
/**
|
|
* Returns the beginning of the elements as a mutable pointer.
|
|
*/
|
|
element * begin() {
|
|
return this->elements.pointer;
|
|
}
|
|
|
|
/**
|
|
* Returns the beginning of the elements as a const pointer.
|
|
*/
|
|
element const * begin() const {
|
|
return this->elements.pointer;
|
|
}
|
|
|
|
/**
|
|
* Returns the ending of the elements as a mutable pointer.
|
|
*/
|
|
element * end() {
|
|
return this->elements.pointer + this->filled;
|
|
}
|
|
|
|
/**
|
|
* Returns the ending of the elements as a const pointer.
|
|
*/
|
|
element const * end() const {
|
|
return this->elements.pointer + this->filled;
|
|
}
|
|
|
|
/**
|
|
* Returns `true` if the stack is backed by dynamic memory, otherwise `false`.
|
|
*/
|
|
bool is_dynamic() const {
|
|
return this->elements.pointer != this->local_buffer;
|
|
}
|
|
|
|
/**
|
|
* Attempts to append `source_element` to the top of the stack.
|
|
*
|
|
* The returned [append_result] indicates whether the operation was successful or not.
|
|
*
|
|
* If the returned [append_result] is anything but [append_result::ok], the stack will be
|
|
* be left in an empty but valid state.
|
|
*
|
|
* *Note* that [append] is recommended when appending many values at once.
|
|
*/
|
|
append_result push(element const & source_element) {
|
|
if (this->filled == this->elements.length) {
|
|
append_result const result = this->reserve(this->elements.length);
|
|
|
|
if (result != append_result::ok) return result;
|
|
}
|
|
|
|
this->elements[this->filled] = source_element;
|
|
this->filled += 1;
|
|
|
|
return append_result::ok;
|
|
}
|
|
|
|
/**
|
|
* Attempts to reserve `capacity` number of elements additional space on the stack, forcing
|
|
* it to use dynamic memory _even_ if it hasn't exhausted the local buffer yet.
|
|
*
|
|
* The returned [append_result] indicates whether the operation was successful or not.
|
|
*
|
|
* If the returned [append_result] is anything but [append_result::ok], the stack will be
|
|
* be left in an empty but valid state.
|
|
*
|
|
* *Note* that manual invocation is not recommended if the [stack] has a large
|
|
* `initial_capacity` argument.
|
|
*/
|
|
append_result reserve(usize capacity) {
|
|
usize const requested_capacity = this->elements.length + capacity;
|
|
|
|
if (this->is_dynamic()) {
|
|
// Grow dynamic buffer (bailing out if failed).
|
|
u8 * const buffer = this->dynamic_allocator->reallocate(
|
|
reinterpret_cast<u8 *>(this->elements.pointer),
|
|
sizeof(element) * requested_capacity);
|
|
|
|
if (buffer == nullptr) {
|
|
this->elements = {};
|
|
|
|
return append_result::out_of_memory;
|
|
}
|
|
|
|
this->elements = {reinterpret_cast<element *>(buffer), requested_capacity};
|
|
} else {
|
|
usize const buffer_size = sizeof(element) * requested_capacity;
|
|
u8 * const buffer = this->dynamic_allocator->reallocate(nullptr, buffer_size);
|
|
|
|
if (buffer == nullptr) {
|
|
this->elements = {};
|
|
|
|
return append_result::out_of_memory;
|
|
}
|
|
|
|
core::copy({buffer, buffer_size}, this->elements.as_bytes());
|
|
|
|
this->elements = {reinterpret_cast<element *>(buffer), requested_capacity};
|
|
}
|
|
|
|
return append_result::ok;
|
|
}
|
|
|
|
private:
|
|
allocator * dynamic_allocator;
|
|
|
|
usize filled;
|
|
|
|
slice<element> elements;
|
|
|
|
element local_buffer[init_capacity];
|
|
};
|
|
|
|
/**
|
|
* Writable type for appending data to a [sequence] containing [u8] values.
|
|
*/
|
|
struct sequence_writer : public writable {
|
|
sequence_writer(sequence<u8> * output_sequence) : writable{
|
|
[output_sequence](slice<u8 const> const & buffer) -> expected<usize, io_error> {
|
|
switch (output_sequence->append(buffer)) {
|
|
case append_result::ok: return buffer.length;
|
|
case append_result::out_of_memory: return io_error::unavailable;
|
|
default: unreachable();
|
|
}
|
|
}} {}
|
|
};
|
|
}
|