|
|
|
@ -5,9 +5,9 @@ import coral;
|
|
|
|
|
// Collections.
|
|
|
|
|
export namespace coral {
|
|
|
|
|
/**
|
|
|
|
|
* Result codes used by [contiguous_range]-derived types when they are appended to in any way.
|
|
|
|
|
* Result codes used when pushing elements to a stack.
|
|
|
|
|
*
|
|
|
|
|
* [push_result::ok] indicates that an push operation was successful.
|
|
|
|
|
* [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.
|
|
|
|
|
*/
|
|
|
|
@ -17,175 +17,72 @@ export namespace coral {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Base type for all stack types.
|
|
|
|
|
* Last-in-first-out contiguous sequence of `element` values.
|
|
|
|
|
*
|
|
|
|
|
* Sequences are any data structure which owns a linear, non-unique set of elements which may be queried and/or
|
|
|
|
|
* mutated.
|
|
|
|
|
* *Note*: the [allocator] used by the stack must remain valid for the duration of the stack lifetime.
|
|
|
|
|
*/
|
|
|
|
|
template<typename element> struct stack {
|
|
|
|
|
virtual ~stack() {};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns `true` if there are no elements in the stack, otherwise `false`.
|
|
|
|
|
*/
|
|
|
|
|
virtual bool is_empty() const = 0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
|
|
|
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
|
|
|
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
|
|
|
|
* evaluation.
|
|
|
|
|
*
|
|
|
|
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
|
|
|
|
* case. This may be leveraged to create conditional looping behavior.
|
|
|
|
|
*/
|
|
|
|
|
virtual bool every(closure<bool(element &)> apply) = 0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
|
|
|
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
|
|
|
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
|
|
|
|
* evaluation.
|
|
|
|
|
*
|
|
|
|
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
|
|
|
|
* case. This may be leveraged to create conditional looping behavior.
|
|
|
|
|
*/
|
|
|
|
|
virtual bool every(closure<bool(element const &)> apply) const = 0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to append `source_elements` to 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
|
|
|
|
|
* implementation-defined state.
|
|
|
|
|
*/
|
|
|
|
|
virtual push_result push_all(slice<element const> const & source_elements) = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Last-in-first-out contiguous sequence of `element` values optimized for small numbers of small-sized elements.
|
|
|
|
|
*
|
|
|
|
|
* [small_stack] types will default to using an inline array of `init_capacity` at first. After all local storage
|
|
|
|
|
* has been exhausted, the [small_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.
|
|
|
|
|
*
|
|
|
|
|
* *Note*: the [allocator] referenced in the stack must remain valid for the duration of the stack lifetime.
|
|
|
|
|
*/
|
|
|
|
|
template<typename element, usize init_capacity = 1> struct small_stack final : public stack<element> {
|
|
|
|
|
small_stack(allocator & dynamic_allocator) : dynamic_allocator{dynamic_allocator} {}
|
|
|
|
|
|
|
|
|
|
~small_stack() override {
|
|
|
|
|
if (this->is_dynamic()) {
|
|
|
|
|
for (element & e : this->elements) e.~element();
|
|
|
|
|
|
|
|
|
|
this->dynamic_allocator.deallocate(this->elements.pointer);
|
|
|
|
|
}
|
|
|
|
|
stack(allocator & element_allocator) : element_allocator{element_allocator} {
|
|
|
|
|
this->filled_elements = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a const [slice] of the current stack values.
|
|
|
|
|
*
|
|
|
|
|
* *Note*: the returned slice should be considered invalid if any mutable operation is performed on the source
|
|
|
|
|
* [stack] or it is no longer in scope.
|
|
|
|
|
*/
|
|
|
|
|
slice<element const> as_slice() const {
|
|
|
|
|
return this->elements.sliced(0, this->filled);
|
|
|
|
|
}
|
|
|
|
|
~stack() {
|
|
|
|
|
for (element & e : this->filled_elements) e.~element();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
|
|
|
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
|
|
|
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
|
|
|
|
* evaluation.
|
|
|
|
|
*
|
|
|
|
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
|
|
|
|
* case. This may be leveraged to create conditional looping behavior.
|
|
|
|
|
*/
|
|
|
|
|
bool every(closure<bool(element &)> apply) override {
|
|
|
|
|
for (usize index = 0; index < this->filled; index += 1) {
|
|
|
|
|
if (!apply(this->elements[index])) return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Invokes `apply` on each element in the stack and evaluating a user-defined condition which will return a
|
|
|
|
|
* `bool` at the end of each evaluation. After all elements have been evaluated, `true` is returned if every
|
|
|
|
|
* element elavuated `true`. Otherwise, `false` is returned to indicate that one of more elements failed
|
|
|
|
|
* evaluation.
|
|
|
|
|
*
|
|
|
|
|
* *Note*: This function uses short-circuit evaluation, so the enumeration will terminate upon the first failure
|
|
|
|
|
* case. This may be leveraged to create conditional looping behavior.
|
|
|
|
|
*/
|
|
|
|
|
bool every(closure<bool(element const &)> apply) const override {
|
|
|
|
|
for (usize index = 0; index < this->filled; index += 1) {
|
|
|
|
|
if (!apply(this->elements[index])) return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns `true` if the stack is backed by dynamic memory, otherwise `false`.
|
|
|
|
|
*/
|
|
|
|
|
bool is_dynamic() const {
|
|
|
|
|
return this->elements.pointer != reinterpret_cast<element const *>(this->local_buffer);
|
|
|
|
|
this->element_allocator.deallocate(this->filled_elements.pointer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns `true` if there are no elements in the stack, otherwise `false`.
|
|
|
|
|
*/
|
|
|
|
|
bool is_empty() const override {
|
|
|
|
|
return this->filled == 0;
|
|
|
|
|
bool is_empty() const {
|
|
|
|
|
return this->filled_elements.length == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to append `source_element` to the top of the stack.
|
|
|
|
|
* 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* that [push_all] is recommended when appending many values at once.
|
|
|
|
|
* *Note* [push_all] is recommended when appending many values at once.
|
|
|
|
|
*/
|
|
|
|
|
push_result push(element const & source_element) {
|
|
|
|
|
if (this->filled == this->elements.length) {
|
|
|
|
|
push_result const result = this->reserve(this->elements.length);
|
|
|
|
|
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<usize>(this->element_capacity == 0) * min_size))};
|
|
|
|
|
|
|
|
|
|
if (result != push_result::ok) return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->elements[this->filled] = source_element;
|
|
|
|
|
this->filled += 1;
|
|
|
|
|
this->filled_elements[this->filled_elements.length] = value;
|
|
|
|
|
this->filled_elements.length += 1;
|
|
|
|
|
|
|
|
|
|
return push_result::ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to append `source_elements` to the top of the stack.
|
|
|
|
|
* 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* that [push] is recommended when appending singular values.
|
|
|
|
|
* *Note* [push] is recommended when appending singular values.
|
|
|
|
|
*/
|
|
|
|
|
push_result push_all(slice<element const> const & source_elements) override {
|
|
|
|
|
usize const updated_fill = this->filled + source_elements.length;
|
|
|
|
|
push_result push_all(slice<element const> const & values) {
|
|
|
|
|
usize const updated_fill {this->filled + values.length};
|
|
|
|
|
|
|
|
|
|
if (updated_fill >= this->elements.length) {
|
|
|
|
|
push_result const result = this->reserve(updated_fill);
|
|
|
|
|
push_result const result {this->reserve(values.length)};
|
|
|
|
|
|
|
|
|
|
if (result != push_result::ok) return result;
|
|
|
|
|
if (result != push_result::ok) return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (usize i = 0; i < source_elements.length; i += 1)
|
|
|
|
|
this->elements[this->filled + i] = source_elements[i];
|
|
|
|
|
for (usize i {0}; i < values.length; i += 1) this->elements[this->filled + i] = values[i];
|
|
|
|
|
|
|
|
|
|
this->filled = updated_fill;
|
|
|
|
|
|
|
|
|
@ -193,115 +90,37 @@ export namespace coral {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
* *Note* that manual invocation is not recommended if the [stack] has a large `init_capacity` argument.
|
|
|
|
|
*/
|
|
|
|
|
push_result reserve(usize capacity) {
|
|
|
|
|
usize const requested_capacity = this->filled + capacity;
|
|
|
|
|
push_result reserve(usize requested_capacity) {
|
|
|
|
|
usize const total_capacity {this->elements_filled + requested_capacity};
|
|
|
|
|
|
|
|
|
|
if (this->is_dynamic()) {
|
|
|
|
|
u8 * const buffer = this->dynamic_allocator.reallocate(
|
|
|
|
|
reinterpret_cast<u8 *>(this->elements.pointer),
|
|
|
|
|
sizeof(element) * requested_capacity);
|
|
|
|
|
u8 * const buffer {this->element_allocator.reallocate(reinterpret_cast<u8 *>(
|
|
|
|
|
this->elements_filled.pointer), sizeof(element) * total_capacity)};
|
|
|
|
|
|
|
|
|
|
if (buffer == nullptr) {
|
|
|
|
|
this->elements = {};
|
|
|
|
|
if (buffer == nullptr) {
|
|
|
|
|
this->element_capacity = 0;
|
|
|
|
|
this->filled_elements = {};
|
|
|
|
|
|
|
|
|
|
return push_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 push_result::out_of_memory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
copy({buffer, buffer_size}, this->elements.as_bytes());
|
|
|
|
|
|
|
|
|
|
this->elements = {reinterpret_cast<element *>(buffer), requested_capacity};
|
|
|
|
|
return push_result::out_of_memory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->filled_elements.pointer = reinterpret_cast<element *>(buffer);
|
|
|
|
|
this->element_capacity = total_capacity;
|
|
|
|
|
|
|
|
|
|
return push_result::ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
allocator & dynamic_allocator;
|
|
|
|
|
allocator & element_allocator;
|
|
|
|
|
|
|
|
|
|
usize filled{0};
|
|
|
|
|
usize element_capacity{0};
|
|
|
|
|
|
|
|
|
|
slice<element> elements{reinterpret_cast<element *>(local_buffer), init_capacity};
|
|
|
|
|
|
|
|
|
|
u8 local_buffer[init_capacity]{0};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using byte_stack = coral::stack<coral::u8>;
|
|
|
|
|
|
|
|
|
|
// Reader / writers.
|
|
|
|
|
export namespace coral {
|
|
|
|
|
/**
|
|
|
|
|
* Readable type for streaming data from a [stack] containing [u8] values.
|
|
|
|
|
*/
|
|
|
|
|
struct stack_reader : public reader {
|
|
|
|
|
stack_reader(byte_stack const & stack) : stack{stack} {
|
|
|
|
|
this->cursor = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reads the data from the target stack into `buffer`, returning the number bytes read.
|
|
|
|
|
*/
|
|
|
|
|
expected<usize, io_error> read(slice<u8> const & buffer) override {
|
|
|
|
|
usize data_written = 0;
|
|
|
|
|
|
|
|
|
|
this->stack.every([&](u8 byte) -> bool {
|
|
|
|
|
buffer[data_written] = byte;
|
|
|
|
|
data_written += 1;
|
|
|
|
|
|
|
|
|
|
return data_written < buffer.length;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this->cursor += data_written;
|
|
|
|
|
|
|
|
|
|
return data_written;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
usize cursor {0};
|
|
|
|
|
|
|
|
|
|
byte_stack const & stack;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Writable type for appending data to a [contiguous_range] containing [u8] values.
|
|
|
|
|
*/
|
|
|
|
|
struct stack_writer : public writer {
|
|
|
|
|
stack_writer(byte_stack & stack) : stack{stack} {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Attempts to write `buffer` to the target stack, returning the number of bytes written or an [io_error] if it
|
|
|
|
|
* failed to commit `buffer` to the stack memory.
|
|
|
|
|
*/
|
|
|
|
|
expected<usize, io_error> write(slice<u8 const> const & buffer) override {
|
|
|
|
|
switch (this->stack.push_all(buffer)) {
|
|
|
|
|
case push_result::ok: return buffer.length;
|
|
|
|
|
case push_result::out_of_memory: return io_error::unavailable;
|
|
|
|
|
default: unreachable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
byte_stack & stack;
|
|
|
|
|
slice<element> filled_elements;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|