Compare commits
17 Commits
78ae403f61
...
13fffacd98
Author | SHA1 | Date | |
---|---|---|---|
13fffacd98 | |||
98fb389ff0 | |||
3c44a6c0f3 | |||
6a1eb71ba0 | |||
a2c033107c | |||
3f84b0ce0b | |||
dc39fedf0c | |||
e379d1d9da | |||
868297dfb7 | |||
38e4572352 | |||
ff4d60a1b3 | |||
02e202cb3b | |||
4cd950a613 | |||
e2f4ab77b6 | |||
ea75a4f96e | |||
7a6731df8e | |||
329fca3929 |
39
.vscode/settings.json
vendored
39
.vscode/settings.json
vendored
@ -8,9 +8,46 @@
|
||||
"array": "cpp",
|
||||
"functional": "cpp",
|
||||
"tuple": "cpp",
|
||||
"utility": "cpp"
|
||||
"utility": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"iterator": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"random": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"limits": "cpp",
|
||||
"new": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ostream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"typeinfo": "cpp"
|
||||
},
|
||||
"editor.minimap.maxColumn": 120,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.insertSpaces": false,
|
||||
"C_Cpp.errorSquiggles": "disabled",
|
||||
"editor.rulers": [120],
|
||||
}
|
||||
|
25
.vscode/tasks.json
vendored
Normal file
25
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "process",
|
||||
"command": "python3",
|
||||
"args": ["${workspaceFolder}/build.py"],
|
||||
"problemMatcher": "$gcc",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "silent",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": true,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
317
source/coral.cpp
317
source/coral.cpp
@ -3,6 +3,7 @@ module;
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <concepts>
|
||||
|
||||
export module coral;
|
||||
|
||||
@ -11,8 +12,8 @@ export namespace coral {
|
||||
/**
|
||||
* Triggers safety-checked behavior in debug mode.
|
||||
*
|
||||
* In release mode, the compiler can use this function as a marker to optimize out safety-
|
||||
* checked logic branches that should never be executed.
|
||||
* In release mode, the compiler can use this function as a marker to optimize out safety-checked logic branches
|
||||
* that should never be executed.
|
||||
*/
|
||||
[[noreturn]] void unreachable() {
|
||||
__builtin_unreachable();
|
||||
@ -39,6 +40,8 @@ export namespace coral {
|
||||
|
||||
using u32 = uint32_t;
|
||||
|
||||
usize const u32_max = 0xffffffff;
|
||||
|
||||
using i32 = int32_t;
|
||||
|
||||
usize const i32_max = 0xffffffff;
|
||||
@ -58,37 +61,38 @@ export namespace coral {
|
||||
virtual ~allocator() {};
|
||||
|
||||
/**
|
||||
* If `allocation` is `nullptr`, the allocator will attempt to allocate a new memory block
|
||||
* of `requested_size` bytes. Otherwise, the allocator will attempt to reallocate
|
||||
* `allocation` to be `request_size` bytes in size.
|
||||
* If `allocation` is `nullptr`, the allocator will attempt to allocate a new memory block of `requested_size`
|
||||
* bytes. Otherwise, the allocator will attempt to reallocate `allocation` to be `request_size` bytes in size.
|
||||
*
|
||||
* The returned address will point to a dynamically allocated buffer of `requested_size` if
|
||||
* the operation was successful, otherwise `nullptr`.
|
||||
* The returned address will point to a dynamically allocated buffer of `requested_size` if the operation was
|
||||
* successful, otherwise `nullptr`.
|
||||
*
|
||||
* *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to
|
||||
* program exit. This may be achieved through either [deallocate] or implementation-
|
||||
* specific allocator functionality.
|
||||
* *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program exit. This may
|
||||
* be achieved through either [deallocate] or implementation-specific allocator functionality.
|
||||
*
|
||||
* *Note*: Attempting to pass a non-`nullptr` `allocation` address not allocated by the
|
||||
* allocator *will* result in erroneous implementation-behavior.
|
||||
* *Note*: Attempting to pass a non-`nullptr` `allocation` address not allocated by the allocator *will* result
|
||||
* in erroneous implementation-behavior.
|
||||
*
|
||||
* *Note*: After invocation, `allocation` should be considered an invalid memory address.
|
||||
*/
|
||||
[[nodiscard]] virtual u8 * reallocate(u8 * allocation, usize requested_size) = 0;
|
||||
|
||||
/**
|
||||
* If `allocation` points to a non-`nullptr` address, the allocator will deallocate it.
|
||||
* Otherwise, the function has no side-effects.
|
||||
* If `allocation` points to a non-`nullptr` address, the allocator will deallocate it. Otherwise, the function
|
||||
* has no side-effects.
|
||||
*
|
||||
* *Note* that attempting to pass a non-`nullptr` `allocation` address not allocated by the
|
||||
* allocator *will* result in erroneous implementation-behavior.
|
||||
* *Note* that attempting to pass a non-`nullptr` `allocation` address not allocated by the allocator *will*
|
||||
* result in erroneous implementation-behavior.
|
||||
*/
|
||||
virtual void deallocate(void * allocation) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Length-signed pointer type that describes how many elements of `type` it references,
|
||||
* providing a type-safe wrapper for passing arrays and zero-terminated strings to functions.
|
||||
* Length-signed pointer type that describes how many elements of `type` it references, providing a type-safe
|
||||
* wrapper for passing arrays and zero-terminated strings to functions.
|
||||
*
|
||||
* **Note**: slices take no ownership of their data, making it the responsibility of the caller to manage the
|
||||
* lifetime of any data referenced by it.
|
||||
*/
|
||||
template<typename type> struct slice {
|
||||
/**
|
||||
@ -139,8 +143,7 @@ export namespace coral {
|
||||
*
|
||||
* The returned view is constant to protect against inadvertant memory corruption.
|
||||
*
|
||||
* *Note* the returned value has no guarantees about the validity of any specific character
|
||||
* encoding set.
|
||||
* *Note* the returned value has no guarantees about the validity of any specific character encoding set.
|
||||
*/
|
||||
slice<char const> as_chars() const {
|
||||
return {reinterpret_cast<char const *>(this->pointer), this->length * sizeof(type)};
|
||||
@ -161,11 +164,11 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new slice with the base-pointer offset by `index` elements and a length of
|
||||
* `range` elements from `index`.
|
||||
* Returns a new slice with the base-pointer offset by `index` elements and a length of `range` elements from
|
||||
* `index`.
|
||||
*
|
||||
* *Note* that attempting to slice with an `index` or `range` outside of the existing slice
|
||||
* bounds will result in safety-checked behavior.
|
||||
* *Note* that attempting to slice with an `index` or `range` outside of the existing slice bounds will result
|
||||
* in safety-checked behavior.
|
||||
*/
|
||||
constexpr slice sliced(usize index, usize range) const {
|
||||
if ((this->length <= index) || ((range + index) > this->length)) unreachable();
|
||||
@ -204,7 +207,9 @@ export namespace coral {
|
||||
/**
|
||||
* Returns `value` clamped between the range of `min_value` and `max_value` (inclusive).
|
||||
*/
|
||||
template<typename scalar> constexpr scalar clamp(scalar const & value, scalar const & min_value, scalar const & max_value) {
|
||||
template<typename scalar> constexpr scalar clamp(scalar const & value,
|
||||
scalar const & min_value, scalar const & max_value) {
|
||||
|
||||
return max(min_value, min(max_value, value));
|
||||
}
|
||||
|
||||
@ -217,11 +222,10 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates and initializes a type of `requested_size` in `buffer`, returning its base pointer. As
|
||||
* a result of accepting a pre-allocated buffer, invocation does not allocate any dynamic memory.
|
||||
* Allocates and initializes a type of `requested_size` in `buffer`, returning its base pointer. As a result of
|
||||
* accepting a pre-allocated buffer, invocation does not allocate any dynamic memory.
|
||||
*
|
||||
* *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked
|
||||
* behavior.
|
||||
* *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked behavior.
|
||||
*/
|
||||
export void * operator new(coral::usize requested_size, coral::slice<coral::u8> const & buffer) {
|
||||
if (buffer.length < requested_size) coral::unreachable();
|
||||
@ -230,12 +234,10 @@ export void * operator new(coral::usize requested_size, coral::slice<coral::u8>
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates and initializes a series of types at `requested_size` in `buffer`, returning the base
|
||||
* pointer. As a result of accepting a pre-allocated buffer, invocation does not allocate any
|
||||
* dynamic memory.
|
||||
* Allocates and initializes a series of types at `requested_size` in `buffer`, returning the base pointer. As a result
|
||||
* of accepting a pre-allocated buffer, invocation does not allocate any dynamic memory.
|
||||
*
|
||||
* *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked
|
||||
* behavior.
|
||||
* *Note*: passing an `buffer` smaller than `requested_size` will result in safety-checked behavior.
|
||||
*/
|
||||
export void * operator new[](coral::usize requested_size, coral::slice<coral::u8> const & buffer) {
|
||||
if (buffer.length < requested_size) coral::unreachable();
|
||||
@ -246,9 +248,8 @@ export void * operator new[](coral::usize requested_size, coral::slice<coral::u8
|
||||
/**
|
||||
* Attempts to allocate and initialize a type of `requested_size` using `allocator`.
|
||||
*
|
||||
* *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program
|
||||
* exit. This may be achieved through either [coral::allocator::deallocate] or implementation-
|
||||
* specific allocator functionality.
|
||||
* *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program exit. This may be
|
||||
* achieved through either [coral::allocator::deallocate] or implementation-specific allocator functionality.
|
||||
*/
|
||||
export [[nodiscard]] void * operator new(coral::usize requested_size, coral::allocator & allocator) {
|
||||
return allocator.reallocate(nullptr, requested_size);
|
||||
@ -257,27 +258,30 @@ export [[nodiscard]] void * operator new(coral::usize requested_size, coral::all
|
||||
/**
|
||||
* Attempts to allocate and initialize a series of types of `requested_size` using `allocator`.
|
||||
*
|
||||
* *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program
|
||||
* exit. This may be achieved through either [coral::allocator::deallocate] or implementation-
|
||||
* specific allocator functionality.
|
||||
* *Note*: If the returned address is a non-`nullptr`, it should be deallocated prior to program exit. This may be
|
||||
* achieved through either [coral::allocator::deallocate] or implementation-specific allocator functionality.
|
||||
*/
|
||||
export [[nodiscard]] void * operator new[](coral::usize requested_size, coral::allocator & allocator) {
|
||||
return allocator.reallocate(nullptr, requested_size);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `pointer` is a non-`nullptr` value, the referenced memory will be deallocated using
|
||||
* `allocator`. Otherwise, the function has no side-effects.
|
||||
* If `pointer` is a non-`nullptr` value, the referenced memory will be deallocated using `allocator`. Otherwise, the
|
||||
* function has no side-effects.
|
||||
*
|
||||
* *Note*: passing a `pointer` value that was not allocated by `allocator` will result in erroneous
|
||||
* behavior defined by the [coral::allocator] implementation.
|
||||
* *Note*: passing a `pointer` value that was not allocated by `allocator` will result in erroneous behavior defined by
|
||||
* the [coral::allocator] implementation.
|
||||
*/
|
||||
export void operator delete(void * pointer, coral::allocator & allocator) {
|
||||
return allocator.deallocate(pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* If `pointer` is a non-`nullptr` value, the referenced memory block will be deallocated using `allocator`. Otherwise,
|
||||
* the function has no side-effects.
|
||||
*
|
||||
* *Note*: passing a `pointer` value that was not allocated by `allocator` will result in erroneous behavior defined by
|
||||
* the [coral::allocator] implementation.
|
||||
*/
|
||||
export void operator delete[](void * pointer, coral::allocator & allocator) {
|
||||
return allocator.deallocate(pointer);
|
||||
@ -285,66 +289,91 @@ export void operator delete[](void * pointer, coral::allocator & allocator) {
|
||||
|
||||
// Wrapper types.
|
||||
export namespace coral {
|
||||
template<typename callable, typename... arguments> concept function_pointer =
|
||||
requires (callable callable_value, arguments... value_arguments) {
|
||||
{*callable_value};
|
||||
{callable_value(value_arguments...)};
|
||||
};
|
||||
|
||||
template<typename callable, typename... arguments> concept functor =
|
||||
requires (callable callable_value, arguments... value_arguments) {
|
||||
{callable_value.operator()(value_arguments...)};
|
||||
};
|
||||
|
||||
template<typename> struct closure;
|
||||
|
||||
/**
|
||||
* Type-erasing view wrapper for both function and functor types that have a call operator with
|
||||
* a return value matching `return_value` and arguments matching `argument_values`.
|
||||
* Type-erasing view wrapper for both function and functor types that have a call operator with a return value
|
||||
* matching `result` and arguments matching `arguments`.
|
||||
*
|
||||
* **Note**: closures take no ownership of allocated memory, making it the responsibility of
|
||||
* the caller to manage the lifetime of any functor assigned to it.
|
||||
* A closure may be constructed from either of the following inputs:
|
||||
*
|
||||
* * A function pointer that uses arguments and returns which are implicitly convertible to `arguments` and
|
||||
* `returns`.
|
||||
*
|
||||
* * An L or R-value functor reference.
|
||||
*
|
||||
* **Note**: closures take no ownership of their data, making it the responsibility of the caller to manage the
|
||||
* lifetime of any functor assigned to it.
|
||||
*/
|
||||
template<typename returns, typename... arguments> struct closure<returns(arguments...)> {
|
||||
using function = returns(*)(arguments...);
|
||||
|
||||
closure(function callable_function) {
|
||||
this->dispatch = [](void const * context, arguments... dispatch_arguments) -> returns {
|
||||
return (reinterpret_cast<function const *>(context))(dispatch_arguments...);
|
||||
template<typename result, typename... arguments> struct closure<result(arguments...)> {
|
||||
template<typename callable> closure(callable call) requires function_pointer<callable, arguments...> {
|
||||
this->dispatch = [](void * context, arguments... dispatch_arguments) -> result {
|
||||
return (reinterpret_cast<callable>(context))(dispatch_arguments...);
|
||||
};
|
||||
|
||||
this->context = callable_function;
|
||||
this->context = reinterpret_cast<void *>(call);
|
||||
}
|
||||
|
||||
template<typename functor> closure(functor * callable_functor) {
|
||||
this->dispatch = [](void const * context, arguments... dispatch_arguments) -> returns {
|
||||
return (*reinterpret_cast<functor const*>(context))(dispatch_arguments...);
|
||||
template<typename callable> closure(callable && call) requires functor<callable, arguments...> {
|
||||
this->dispatch = [](void * context, arguments... dispatch_arguments) -> result {
|
||||
return (*reinterpret_cast<callable *>(context))(dispatch_arguments...);
|
||||
};
|
||||
|
||||
this->context = callable_functor;
|
||||
this->context = &call;
|
||||
}
|
||||
|
||||
template<typename callable> closure(callable & call) requires functor<callable, arguments...> {
|
||||
this->dispatch = [](void * context, arguments... dispatch_arguments) -> result {
|
||||
return (*reinterpret_cast<callable *>(context))(dispatch_arguments...);
|
||||
};
|
||||
|
||||
this->context = call;
|
||||
}
|
||||
|
||||
closure(closure const &) = delete;
|
||||
|
||||
template<typename functor> closure(functor && callable_functor) {
|
||||
this->dispatch = [](void const * context, arguments... dispatch_arguments) -> returns {
|
||||
return (*reinterpret_cast<functor const*>(context))(dispatch_arguments...);
|
||||
};
|
||||
|
||||
this->context = &callable_functor;
|
||||
}
|
||||
|
||||
returns operator()(arguments const &... call_arguments) const {
|
||||
result operator()(arguments const &... call_arguments) const {
|
||||
return this->dispatch(this->context, call_arguments...);
|
||||
}
|
||||
|
||||
private:
|
||||
void const * context;
|
||||
void * context;
|
||||
|
||||
returns(* dispatch)(void const *, arguments...);
|
||||
result(* dispatch)(void *, arguments...);
|
||||
};
|
||||
|
||||
/**
|
||||
* Monadic container for a single-`element` value or nothing.
|
||||
*/
|
||||
template<typename element> struct [[nodiscard]] optional {
|
||||
optional() : buffer{0} {}
|
||||
/**
|
||||
* Constructs an empty [optional].
|
||||
*/
|
||||
constexpr optional() = default;
|
||||
|
||||
optional(element const & value) : buffer{0} {
|
||||
/**
|
||||
* Constructs an [optional] that contains `value`.
|
||||
*/
|
||||
constexpr optional(element const & value) {
|
||||
(*reinterpret_cast<element *>(this->buffer)) = value;
|
||||
this->buffer[sizeof(element)] = 1;
|
||||
}
|
||||
|
||||
optional(optional const & that) : buffer{0} {
|
||||
/**
|
||||
* Constructs an [optional] from `that`, copying over its data.
|
||||
*/
|
||||
constexpr optional(optional const & that) {
|
||||
if (that.has_value()) {
|
||||
(*reinterpret_cast<element *>(this->buffer)) = *that;
|
||||
this->buffer[sizeof(element)] = 1;
|
||||
@ -361,12 +390,10 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to call `apply` on the contained value, returning a new [optional] of whatever type `apply` returns.
|
||||
*
|
||||
* If the optional is empty, an empty optional will always be returned.
|
||||
* Monadically maps `apply` to the value if it exists, otherwise doing nothing.
|
||||
*/
|
||||
template<typename functor> std::invoke_result_t<functor, element> map(functor const & apply) const {
|
||||
if (this->has_value()) return apply(**this);
|
||||
template<typename result> optional<result> map(closure<result(element const &)> const & apply) const {
|
||||
if (this->has_value()) return apply(this->value());
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -374,16 +401,26 @@ export namespace coral {
|
||||
/**
|
||||
* Returns the contained value or `fallback` if the optional is empty.
|
||||
*/
|
||||
element const & value_or(element const & fallback) const {
|
||||
element const & or_value(element const & fallback) const {
|
||||
return this->has_value() ? *reinterpret_cast<element const *>(this->buffer) : fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the contained value.
|
||||
*
|
||||
* *Note*: attempting to access the value of an empty optional will trigger safety-checked behavior.
|
||||
*/
|
||||
element & operator *() {
|
||||
if (!this->has_value()) unreachable();
|
||||
|
||||
return *reinterpret_cast<element *>(this->buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a const reference to the contained value.
|
||||
*
|
||||
* *Note*: attempting to access the value of an empty optional will trigger safety-checked behavior.
|
||||
*/
|
||||
element const & operator *() const {
|
||||
if (!this->has_value()) unreachable();
|
||||
|
||||
@ -391,97 +428,89 @@ export namespace coral {
|
||||
}
|
||||
|
||||
private:
|
||||
u8 buffer[sizeof(element) + 1];
|
||||
u8 buffer[sizeof(element) + 1] {0};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Monadic container for a descriminating union of either `value_element` or `error_element`.
|
||||
* Monadic container for a descriminating union of either `expects` or `errors`.
|
||||
*/
|
||||
template<typename value_element, typename error_element> struct [[nodiscard]] expected {
|
||||
expected(value_element const & value) : buffer{0} {
|
||||
(*reinterpret_cast<value_element *>(this->buffer)) = value;
|
||||
template<typename expects, typename errors> struct [[nodiscard]] expected {
|
||||
template<typename value> using rebound = expected<value, errors>;
|
||||
|
||||
/**
|
||||
* Constructs from `value`, creating an [expected] that contains the expected type.
|
||||
*/
|
||||
expected(expects const & value) {
|
||||
(*reinterpret_cast<expects *>(this->buffer)) = value;
|
||||
this->buffer[buffer_size] = 1;
|
||||
}
|
||||
|
||||
expected(error_element const & error) : buffer{0} {
|
||||
(*reinterpret_cast<error_element *>(this->buffer)) = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monadic function for calling `predicate` conditionally based on whether the expected is
|
||||
* ok. If ok, the result of `predicate` is returned, otherwise `false` is always returned.
|
||||
*
|
||||
* This function may be used to chain conditional checks that depend on the expected being
|
||||
* ok without creating a new local variable.
|
||||
* Constructs from `error`, creating an [expected] that does not contain the expected type.
|
||||
*/
|
||||
bool and_test(closure<bool(value_element const &)> const & predicate) const {
|
||||
return this->is_ok() && predicate(this->value());
|
||||
expected(errors const & error) {
|
||||
(*reinterpret_cast<errors *>(this->buffer)) = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the optional contains a value, otherwise `false` if it holds an error.
|
||||
* Returns the contained error as an [optional].
|
||||
*/
|
||||
optional<errors> error() const {
|
||||
if (this->is_error()) return *reinterpret_cast<errors const *>(this->buffer);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the optional holds an error, otherwise `false` if it is ok.
|
||||
*/
|
||||
bool is_error() const {
|
||||
return this->buffer[buffer_size] == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the optional contains the expected value, otherwise `false` if it holds an error.
|
||||
*/
|
||||
bool is_ok() const {
|
||||
return this->buffer[buffer_size];
|
||||
return this->buffer[buffer_size] == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the contained value.
|
||||
*
|
||||
* *Note*: attempting to access the value of an erroneous expected will trigger safety-
|
||||
* checked behavior.
|
||||
* Monadically maps `apply` to the value if it exists, otherwise doing nothing.
|
||||
*/
|
||||
value_element & value() {
|
||||
if (!this->is_ok()) unreachable();
|
||||
template<typename result> rebound<result> map(closure<result(expects const &)> const & apply) const {
|
||||
if (this->is_ok()) return apply(*this->ok());
|
||||
|
||||
return *reinterpret_cast<value_element *>(this->buffer);
|
||||
return *this->error();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contained value.
|
||||
*
|
||||
* *Note*: attempting to access the value of an erroneous expected will trigger safety-
|
||||
* checked behavior.
|
||||
* Returns the contained ok value as an [optional].
|
||||
*/
|
||||
value_element const & value() const {
|
||||
if (!this->is_ok()) unreachable();
|
||||
optional<expects> ok() const {
|
||||
if (this->is_ok()) return *reinterpret_cast<expects const *>(this->buffer);
|
||||
|
||||
return *reinterpret_cast<value_element const *>(this->buffer);
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the contained error.
|
||||
*
|
||||
* *Note*: attempting to access the error of a non-erroneous expected will trigger safety-
|
||||
* checked behavior.
|
||||
* Returns the contained value or `value` if it is not ok.
|
||||
*/
|
||||
error_element & error() {
|
||||
if (this->is_ok()) unreachable();
|
||||
expects ok_or(expects value) const {
|
||||
if (this->is_ok()) return *this->ok();
|
||||
|
||||
return *reinterpret_cast<error_element *>(this->buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contained error.
|
||||
*
|
||||
* *Note*: attempting to access the error of a non-erroneous expected will trigger safety-
|
||||
* checked behavior.
|
||||
*/
|
||||
error_element const & error() const {
|
||||
if (this->is_ok()) unreachable();
|
||||
|
||||
return *reinterpret_cast<error_element const *>(this->buffer);
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr usize buffer_size = max(sizeof(value_element), sizeof(error_element));
|
||||
static constexpr usize buffer_size = max(sizeof(expects), sizeof(errors));
|
||||
|
||||
u8 buffer[buffer_size + 1];
|
||||
u8 buffer[buffer_size + 1] {0};
|
||||
};
|
||||
|
||||
/**
|
||||
* Errors that may occur while executing an opaque I/O operation via the `readable` and
|
||||
* `writable` type aliases.
|
||||
* Errors that may occur while executing an opaque I/O operation via the `readable` and `writable` type aliases.
|
||||
*/
|
||||
enum class io_error {
|
||||
unavailable,
|
||||
@ -494,8 +523,7 @@ export namespace coral {
|
||||
virtual ~reader() {}
|
||||
|
||||
/**
|
||||
* Attempts to fill `data` with whatever data the reader has to offer, returning the number
|
||||
* of bytes actually read.
|
||||
* Attempts to fill `data` with whatever the reader has to offer, returning the number of bytes actually read.
|
||||
*
|
||||
* Should the read operation fail for any reason, a [io_error] is returned instead.
|
||||
*/
|
||||
@ -509,8 +537,7 @@ export namespace coral {
|
||||
virtual ~writer() {}
|
||||
|
||||
/**
|
||||
* Attempts to write `data` out to the writer, returning the number of bytes actually
|
||||
* written.
|
||||
* Attempts to write `data` out to the writer, returning the number of bytes actually written.
|
||||
*
|
||||
* Should the write operation fail for any reason, a [io_error] is returned instead.
|
||||
*/
|
||||
@ -561,17 +588,25 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the equality of `a` against `b`, returning `true` if they contain identical bytes,
|
||||
* otherwise `false`.
|
||||
* Tests the equality of `a` against `b`, returning `true` if they contain identical bytes, otherwise `false`.
|
||||
*/
|
||||
constexpr bool equals(slice<u8 const> const & a, slice<u8 const> const & b) {
|
||||
if (a.length != b.length) return false;
|
||||
|
||||
for (size_t i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false;
|
||||
for (usize i = 0; i < a.length; i += 1) if (a[i] != b[i]) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a linear search from the back of
|
||||
*/
|
||||
constexpr optional<usize> find_last(slice<u8 const> const & bytes, u8 byte) {
|
||||
for (usize i = bytes.length; i >= 0; i -= 1) if (bytes[i] == byte) return i;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a hash code generated from the values in `bytes`.
|
||||
*
|
||||
|
@ -4,7 +4,7 @@ import coral;
|
||||
|
||||
export namespace coral {
|
||||
/**
|
||||
* Platform-generalized identifier for a resource in a [file_store].
|
||||
* Platform-generalized identifier for a resource in a [fs].
|
||||
*/
|
||||
struct path {
|
||||
/**
|
||||
@ -17,11 +17,11 @@ export namespace coral {
|
||||
*/
|
||||
static char const seperator = '/';
|
||||
|
||||
constexpr path() : buffer{0} {
|
||||
constexpr path() {
|
||||
this->buffer[max] = max;
|
||||
}
|
||||
|
||||
template<usize text_size> constexpr path(char const(&text)[text_size]) : path{} {
|
||||
template<usize text_size> constexpr path(char const(&text)[text_size]) {
|
||||
static_assert(text_size <= max);
|
||||
|
||||
for (usize i = 0; i < text_size; i += 1) this->buffer[i] = text[i];
|
||||
@ -53,8 +53,7 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the path to `that`, returning the difference between the two paths or `0` if
|
||||
* they are identical.
|
||||
* Compares the path to `that`, returning the difference between the two paths or `0` if they are identical.
|
||||
*/
|
||||
constexpr size compare(path const & that) const {
|
||||
return coral::compare(this->as_slice().as_bytes(), that.as_slice().as_bytes());
|
||||
@ -68,8 +67,7 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the path against `that` for equality, returning `true` if they are identical,
|
||||
* otherwise `false`.
|
||||
* Tests the path against `that` for equality, returning `true` if they are identical, otherwise `false`.
|
||||
*/
|
||||
constexpr bool equals(path const & that) const {
|
||||
return coral::equals(this->as_slice().as_bytes(), that.as_slice().as_bytes());
|
||||
@ -88,6 +86,8 @@ export namespace coral {
|
||||
* Returns a new [path] composed of the current path joined with `text`.
|
||||
*
|
||||
* *Note:* should the new path exceed [max] bytes in size, an empty [path] is returned instead.
|
||||
*
|
||||
* *Note:* should the new path exceed [max] bytes in size, an empty [path] is returned instead.
|
||||
*/
|
||||
constexpr path joined(slice<char const> const & text) const {
|
||||
if (text.length > this->buffer[max]) return path{};
|
||||
@ -103,19 +103,41 @@ export namespace coral {
|
||||
}
|
||||
|
||||
private:
|
||||
char buffer[max + 1];
|
||||
char buffer[max + 1]{0};
|
||||
};
|
||||
|
||||
/**
|
||||
* [reader] that has a known range of data and may attempt to traverse it freely.
|
||||
*/
|
||||
struct file_reader : public reader {
|
||||
virtual ~file_reader() {}
|
||||
|
||||
/**
|
||||
* Attempts to seek to the position in the file defined by `offset`, returning the literal absolute position
|
||||
* that was actually sought to or a [io_error] if the operation failed for whatever reason.
|
||||
*/
|
||||
virtual expected<u64, io_error> seek(u64 offset) = 0;
|
||||
|
||||
/**
|
||||
* Attempts to get the current cursor position in the file, returning the literal absolute position of it or a
|
||||
* [io_error] if the operation failed for whatever reason.
|
||||
*/
|
||||
virtual expected<u64, io_error> tell() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* [writer] that has a known range of data and may attempt to traverse it freely.
|
||||
*/
|
||||
struct file_writer : public writer {
|
||||
virtual ~file_writer() {}
|
||||
/**
|
||||
* Attempts to seek to the position in the file defined by `offset`, returning the literal absolute position
|
||||
* that was actually sought to or a [io_error] if the operation failed for whatever reason.
|
||||
*/
|
||||
virtual expected<u64, io_error> seek(u64 offset) = 0;
|
||||
|
||||
/**
|
||||
* Attempts to get the current cursor position in the file, returning the literal absolute position of it or a
|
||||
* [io_error] if the operation failed for whatever reason.
|
||||
*/
|
||||
virtual expected<u64, io_error> tell() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -131,27 +153,34 @@ export namespace coral {
|
||||
bool can_write;
|
||||
};
|
||||
|
||||
enum class [[nodiscard]] walk_result {
|
||||
ok,
|
||||
not_implemented,
|
||||
access_denied,
|
||||
not_found,
|
||||
io_error,
|
||||
};
|
||||
|
||||
virtual ~fs() {};
|
||||
|
||||
virtual walk_result walk_files(path const & target_path, closure<bool(path const &)> const & apply) {
|
||||
return walk_result::not_implemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the file-system for its global [access_rules], returning them.
|
||||
*/
|
||||
virtual access_rules query_access() = 0;
|
||||
|
||||
/**
|
||||
* Attempts to read the file in the file system located at `file_path` relative, calling
|
||||
* `then` if it was successfully opened for reading.
|
||||
*
|
||||
* Once `then` returns, access to the file is closed automatically.
|
||||
* Attempts to read the file in `file_path`, calling `then` if it was successfully opened for reading.
|
||||
*/
|
||||
virtual void read_file(path const & file_path, closure<void(file_reader &)> const & then) = 0;
|
||||
virtual void read_file(path const & target_path, closure<void(file_reader &)> const & then) {}
|
||||
|
||||
/**
|
||||
* Attempts to write the file in the file system located at `file_path` relative, calling
|
||||
* `then` if it was successfully opened for writing.
|
||||
*
|
||||
* Once `then` returns, access to the file is closed automatically.
|
||||
* Attempts to write the file in the file system located at `file_path`, calling `then` if it was successfully
|
||||
* opened for writing.
|
||||
*/
|
||||
virtual void write_file(path const & file_path, closure<void(file_writer &)> const & then) = 0;
|
||||
virtual void write_file(path const & target_path, closure<void(file_writer &)> const & then) {}
|
||||
};
|
||||
}
|
||||
|
15
source/coral/functional.cpp
Normal file
15
source/coral/functional.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
export module coral.functional;
|
||||
|
||||
import coral;
|
||||
|
||||
export namespace coral {
|
||||
/**
|
||||
* Helpful wrapper utility for using in chainable conditionals like [coral::expected::map] to check if a `bool`
|
||||
* is `true` without writing a lambda.
|
||||
*/
|
||||
template<typename value> constexpr auto equality_predicate(value reference_value) {
|
||||
return [reference_value](value comparing_value) -> bool {
|
||||
return comparing_value == reference_value;
|
||||
};
|
||||
}
|
||||
}
|
@ -4,8 +4,8 @@ import coral;
|
||||
|
||||
export namespace coral {
|
||||
/**
|
||||
* Multiplexing byte-based ring buffer of `capacity` size that may be used for memory-backed
|
||||
* I/O operations and lightweight data construction.
|
||||
* Multiplexing byte-based ring buffer of `capacity` size that may be used for memory-backed I/O operations and
|
||||
* lightweight data construction.
|
||||
*/
|
||||
template<usize capacity> struct fixed_buffer : public writer, public reader {
|
||||
fixed_buffer() = default;
|
||||
@ -13,8 +13,8 @@ export namespace coral {
|
||||
/**
|
||||
* Returns a mutable [slice] ranging from the head to the last-filled element.
|
||||
*
|
||||
* *Note*: The lifetime and validity of the returned slice is only guaranteed for as long
|
||||
* as the source [fixed_buffer] is not mutated or out-of-scope.
|
||||
* *Note*: The lifetime and validity of the returned slice is only guaranteed for as long as the source
|
||||
* [fixed_buffer] is not mutated or out-of-scope.
|
||||
*/
|
||||
slice<u8 const> as_slice() const {
|
||||
return {0, this->filled};
|
||||
@ -56,8 +56,8 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to write the single value `data` into the buffer, returning `true` if
|
||||
* successful, otherwise `false` if the buffer is full.
|
||||
* Attempts to write the single value `data` into the buffer, returning `true` if successful, otherwise `false`
|
||||
* if the buffer is full.
|
||||
*/
|
||||
bool put(u8 data) {
|
||||
if (this->is_full()) return false;
|
||||
@ -70,8 +70,7 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads whatever data is in the buffer into `data`, returning the number of bytes read
|
||||
* from the buffer.
|
||||
* Reads whatever data is in the buffer into `data`, returning the number of bytes read from the buffer.
|
||||
*/
|
||||
expected<usize, io_error> read(slice<u8> const & data) override {
|
||||
slice const readable_data{this->data, min(this->filled, data.length)};
|
||||
@ -87,9 +86,8 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to write `data` to the buffer, returning the number of bytes written or
|
||||
* [io_error::unavailable] if it has been completely filled and no more bytes can be
|
||||
* written.
|
||||
* Attempts to write `data` to the buffer, returning the number of bytes written or [io_error::unavailable] if
|
||||
* it has been completely filled and no more bytes can be written.
|
||||
*/
|
||||
expected<usize, io_error> write(slice<u8 const> const & data) override {
|
||||
if (this->is_full()) return io_error::unavailable;
|
||||
@ -119,31 +117,31 @@ export namespace coral {
|
||||
/**
|
||||
* Streams the data from `input` to `output`, using `buffer` as temporary transfer space.
|
||||
*
|
||||
* The returned [expected] can be used to introspect if `input` or `output` encountered any
|
||||
* issues during streaming, otherwise it will contain the number of bytes streamed.
|
||||
* The returned [expected] can be used to introspect if `input` or `output` encountered any issues during streaming,
|
||||
* otherwise it will contain the number of bytes streamed.
|
||||
*
|
||||
* *Note*: if `buffer` has a length of `0`, no data will be streamed as there is nowhere to
|
||||
* temporarily place data during streaming.
|
||||
* *Note*: if `buffer` has a length of `0`, no data will be streamed as there is nowhere to temporarily place data
|
||||
* during streaming.
|
||||
*/
|
||||
expected<usize, io_error> stream(writer & output, reader & input, slice<u8> const & buffer) {
|
||||
usize total_bytes_written = 0;
|
||||
expected bytes_read = input.read(buffer);
|
||||
expected<u64, io_error> stream(writer & output, reader & input, slice<u8> const & buffer) {
|
||||
u64 total_bytes_written {0};
|
||||
expected bytes_read {input.read(buffer)};
|
||||
|
||||
if (!bytes_read.is_ok()) return bytes_read.error();
|
||||
if (bytes_read.is_error()) return *bytes_read.error();
|
||||
|
||||
usize read = bytes_read.value();
|
||||
usize read {*bytes_read.ok()};
|
||||
|
||||
while (read != 0) {
|
||||
expected const bytes_written = output.write(buffer.sliced(0, read));
|
||||
|
||||
if (!bytes_written.is_ok()) return bytes_read.error();
|
||||
if (bytes_written.is_error()) return *bytes_read.error();
|
||||
|
||||
total_bytes_written += bytes_written.value();
|
||||
total_bytes_written += *bytes_written.ok();
|
||||
bytes_read = input.read(buffer);
|
||||
|
||||
if (!bytes_read.is_ok()) return bytes_read.error();
|
||||
if (bytes_read.is_error()) return *bytes_read.error();
|
||||
|
||||
read = bytes_read.value();
|
||||
read = *bytes_read.ok();
|
||||
}
|
||||
|
||||
return total_bytes_written;
|
||||
@ -172,8 +170,8 @@ export namespace coral {
|
||||
/**
|
||||
* Attempts to format and print `value` as an unsigned integer out to `output`.
|
||||
*
|
||||
* The returned [expected] can be used to introspect if `output` encountered any issues during
|
||||
* printing, otherwise it will contain the number of characters used to print `value` as text.
|
||||
* The returned [expected] can be used to introspect if `output` encountered any issues during printing, otherwise
|
||||
* it will contain the number of characters used to print `value` as text.
|
||||
*/
|
||||
expected<usize, io_error> print_unsigned(writer & output, u64 value) {
|
||||
if (value == 0) return output.write(slice{"0"}.as_bytes());
|
||||
|
@ -9,8 +9,7 @@ export namespace coral {
|
||||
*
|
||||
* [push_result::ok] indicates that an push operation was successful.
|
||||
*
|
||||
* [push_result::out_of_memory] alerts that the memory required to perform the push operation
|
||||
* failed.
|
||||
* [push_result::out_of_memory] alerts that the memory required to perform the push operation failed.
|
||||
*/
|
||||
enum class [[nodiscard]] push_result {
|
||||
ok,
|
||||
@ -20,67 +19,115 @@ export namespace coral {
|
||||
/**
|
||||
* Base type for all stack types.
|
||||
*
|
||||
* Sequences are any data structure which owns a linear, non-unique set of elements which may
|
||||
* be queried and/or mutated.
|
||||
* 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 stack {
|
||||
virtual ~stack() {};
|
||||
|
||||
/**
|
||||
* Returns a read-only [slice] of the current range values.
|
||||
*
|
||||
* *Note*: the behavior of retaining the returned value past the scope of the source
|
||||
* [stack] or any subsequent modifications to it is implementation-defined.
|
||||
* Returns `true` if there are no elements in the stack, otherwise `false`.
|
||||
*/
|
||||
virtual slice<element const> as_slice() const = 0;
|
||||
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.
|
||||
* 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.
|
||||
* 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.
|
||||
* [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.
|
||||
* *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 : public stack<element> {
|
||||
small_stack(allocator * dynamic_allocator) {
|
||||
this->dynamic_allocator = dynamic_allocator;
|
||||
}
|
||||
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);
|
||||
this->dynamic_allocator.deallocate(this->elements.pointer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read-only [slice] of the current stack values.
|
||||
* 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.
|
||||
* *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 override {
|
||||
slice<element const> as_slice() const {
|
||||
return this->elements.sliced(0, this->filled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
@ -88,13 +135,20 @@ export namespace coral {
|
||||
return this->elements.pointer != reinterpret_cast<element const *>(this->local_buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if there are no elements in the stack, otherwise `false`.
|
||||
*/
|
||||
bool is_empty() const override {
|
||||
return this->filled == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to append `source_element` 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.
|
||||
* 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.
|
||||
*/
|
||||
@ -116,8 +170,8 @@ export namespace coral {
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
@ -139,22 +193,21 @@ 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 `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 [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.
|
||||
* 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
|
||||
* `initial_capacity` argument.
|
||||
* *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;
|
||||
|
||||
if (this->is_dynamic()) {
|
||||
u8 * const buffer = this->dynamic_allocator->reallocate(
|
||||
u8 * const buffer = this->dynamic_allocator.reallocate(
|
||||
reinterpret_cast<u8 *>(this->elements.pointer),
|
||||
sizeof(element) * requested_capacity);
|
||||
|
||||
@ -167,7 +220,7 @@ export namespace coral {
|
||||
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);
|
||||
u8 * const buffer = this->dynamic_allocator.reallocate(nullptr, buffer_size);
|
||||
|
||||
if (buffer == nullptr) {
|
||||
this->elements = {};
|
||||
@ -184,7 +237,7 @@ export namespace coral {
|
||||
}
|
||||
|
||||
private:
|
||||
allocator * dynamic_allocator{nullptr};
|
||||
allocator & dynamic_allocator;
|
||||
|
||||
usize filled{0};
|
||||
|
||||
@ -211,14 +264,18 @@ export namespace coral {
|
||||
* Reads the data from the target stack into `buffer`, returning the number bytes read.
|
||||
*/
|
||||
expected<usize, io_error> read(slice<u8> const & buffer) override {
|
||||
slice const stack_elements {this->stack->as_slice()};
|
||||
usize const read {min(buffer.length, stack_elements.length - this->cursor)};
|
||||
usize data_written = 0;
|
||||
|
||||
copy(buffer, stack_elements.sliced(cursor, read));
|
||||
this->stack->every([&](u8 byte) -> bool {
|
||||
buffer[data_written] = byte;
|
||||
data_written += 1;
|
||||
|
||||
this->cursor += read;
|
||||
return data_written < buffer.length;
|
||||
});
|
||||
|
||||
return read;
|
||||
this->cursor += data_written;
|
||||
|
||||
return data_written;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -236,8 +293,8 @@ export namespace coral {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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)) {
|
||||
|
384
source/oar.cpp
384
source/oar.cpp
@ -2,137 +2,151 @@ export module oar;
|
||||
|
||||
import coral;
|
||||
import coral.files;
|
||||
import coral.functional;
|
||||
import coral.io;
|
||||
import coral.stack;
|
||||
|
||||
using coral::closure;
|
||||
using coral::expected;
|
||||
using coral::file_reader;
|
||||
using coral::fs;
|
||||
using coral::io_error;
|
||||
using coral::path;
|
||||
using coral::u8;
|
||||
using coral::u64;
|
||||
using coral::usize;
|
||||
|
||||
/**
|
||||
* Length of the full magic signature at the beginning of an Oar file.
|
||||
*/
|
||||
constexpr coral::usize signature_length {4};
|
||||
|
||||
/**
|
||||
* Length of the magic signature at the beginning of an Oar file without the version indicator
|
||||
* byte.
|
||||
*/
|
||||
constexpr coral::usize signature_identifier_length {signature_length - 1};
|
||||
constexpr static usize signature_length {4};
|
||||
|
||||
/**
|
||||
* Hardcoded signature magic value that this implementation of Oar expects when reading archives.
|
||||
*/
|
||||
constexpr coral::u8 signature_magic[signature_length] {'o', 'a', 'r', 0};
|
||||
constexpr static u8 signature_magic[signature_length] {'o', 'a', 'r', 1};
|
||||
|
||||
/**
|
||||
* Oar file header format.
|
||||
*/
|
||||
struct header {
|
||||
coral::u8 signature_magic[signature_length];
|
||||
union header {
|
||||
struct {
|
||||
u8 signature[signature_length];
|
||||
|
||||
coral::u32 entry_count;
|
||||
coral::u32 entry_count;
|
||||
} layout;
|
||||
|
||||
coral::u8 padding[504];
|
||||
u8 bytes[512];
|
||||
|
||||
static constexpr bool is_sizeof(usize value) {
|
||||
return value == sizeof(header);
|
||||
}
|
||||
|
||||
expected<bool, io_error> read(coral::reader & archive_reader) {
|
||||
return archive_reader.read(this->bytes).map<bool>(is_sizeof).map<bool>([&](bool is_valid) -> bool {
|
||||
return is_valid && coral::equals(this->layout.signature, signature_magic);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(header) == 512);
|
||||
static_assert(header::is_sizeof(512));
|
||||
|
||||
/**
|
||||
* Oar file header format.
|
||||
*/
|
||||
struct entry {
|
||||
coral::path path;
|
||||
|
||||
coral::u64 data_offset;
|
||||
|
||||
coral::u64 data_length;
|
||||
|
||||
coral::u8 padding[240];
|
||||
enum class entry_kind {
|
||||
file,
|
||||
directory,
|
||||
};
|
||||
|
||||
static_assert(sizeof(entry) == 512);
|
||||
/**
|
||||
* Oar entry block format.
|
||||
*/
|
||||
union block {
|
||||
struct {
|
||||
path path;
|
||||
|
||||
u64 data_offset;
|
||||
|
||||
u64 data_length;
|
||||
|
||||
entry_kind kind;
|
||||
} layout;
|
||||
|
||||
u8 bytes[512];
|
||||
|
||||
static constexpr bool is_sizeof(usize value) {
|
||||
return value == sizeof(block);
|
||||
}
|
||||
|
||||
expected<bool, io_error> read(coral::reader & archive_reader) {
|
||||
return archive_reader.read(this->bytes).map<bool>(is_sizeof);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(block::is_sizeof(512));
|
||||
|
||||
/**
|
||||
* Archive file access interface.
|
||||
* Archive entry access interface.
|
||||
*/
|
||||
struct archive_file : public coral::file_reader {
|
||||
struct entry : public file_reader {
|
||||
/**
|
||||
* Results of a find operation performed on an [archive_file].
|
||||
*
|
||||
* [find_result::ok] means that the find operation was successful.
|
||||
*
|
||||
* [find_result::io_unavailable] signals a failure to communicate with the underlying
|
||||
* [coral::file_reader] for whatever reason.
|
||||
* [find_result::io_unavailable] signals a failure to communicate with the underlying [file_reader] for whatever
|
||||
* reason.
|
||||
*
|
||||
* [find_result::archive_invalid] signals that data was read but it does not match the format
|
||||
* of an Oar archive. This is typically because the underlying [coral::file_reader] is not
|
||||
* reading from an Oar archive file.
|
||||
* [find_result::archive_invalid] signals that data was read but it does not match the format of an Oar archive.
|
||||
* This is typically because the underlying [file_reader] is not reading from an Oar archive file or the archive does
|
||||
* not match the supported version.
|
||||
*
|
||||
* [find_result::archive_unsupported] signals that data was read and was formatted as expected
|
||||
* for an Oar archive, however, it is from an unsupported version of the archive format.
|
||||
*
|
||||
* [find_result::not_found] indicates that no entry in the archive could be found that matches
|
||||
* the given query.
|
||||
* [find_result::not_found] indicates that no entry in the archive could be found that matches the given query.
|
||||
*/
|
||||
enum class [[nodiscard]] find_result {
|
||||
ok,
|
||||
io_unavailable,
|
||||
archive_invalid,
|
||||
archive_unsupported,
|
||||
not_found,
|
||||
};
|
||||
|
||||
archive_file(coral::file_reader * archive_reader) {
|
||||
entry(file_reader * archive_reader) {
|
||||
this->archive_reader = archive_reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a lookup for a file entry matching the path `file_path` in the archive, returning
|
||||
* [find_result] to indicate the result of the operation.
|
||||
* Performs a lookup for a file entry matching the path `file_path` in the archive, returning [find_result] to
|
||||
* indicate the result of the operation.
|
||||
*/
|
||||
find_result find(coral::path const & file_path) {
|
||||
find_result find(entry_kind kind, path const & entry_path) {
|
||||
this->data_offset = 0;
|
||||
this->data_length = 0;
|
||||
this->data_cursor = 0;
|
||||
|
||||
if (!this->archive_reader->seek(0).is_ok()) return find_result::io_unavailable;
|
||||
|
||||
constexpr coral::usize header_size {sizeof(header)};
|
||||
coral::u8 archive_header_buffer[header_size] {0};
|
||||
header archive_header {};
|
||||
|
||||
if (!this->archive_reader->read(archive_header_buffer).and_test(
|
||||
[](coral::usize value) -> bool { return value == header_size; }))
|
||||
return find_result::archive_invalid;
|
||||
|
||||
header const * const archive_header {
|
||||
reinterpret_cast<header const *>(archive_header_buffer)};
|
||||
|
||||
if (!coral::equals({archive_header->signature_magic, signature_identifier_length},
|
||||
{signature_magic, signature_identifier_length})) return find_result::archive_invalid;
|
||||
|
||||
if (archive_header->signature_magic[signature_identifier_length] !=
|
||||
signature_magic[signature_identifier_length]) return find_result::archive_unsupported;
|
||||
if (!archive_header.read(*this->archive_reader).map<bool>(coral::equality_predicate(true)).is_ok())
|
||||
return find_result::archive_invalid;
|
||||
|
||||
// Read file table.
|
||||
coral::u64 head {0};
|
||||
coral::u64 tail {archive_header->entry_count - 1};
|
||||
constexpr coral::usize entry_size {sizeof(entry)};
|
||||
coral::u8 archive_entry_buffer[entry_size] {0};
|
||||
u64 head {0};
|
||||
u64 tail {archive_header.layout.entry_count - 1};
|
||||
block archive_block {};
|
||||
|
||||
while (head <= tail) {
|
||||
coral::u64 const midpoint {head + ((tail - head) / 2)};
|
||||
u64 const midpoint {head + ((tail - head) / 2)};
|
||||
|
||||
if (!this->archive_reader->seek(header_size + (entry_size * midpoint)).is_ok())
|
||||
if (!archive_block.read(*this->archive_reader).map<bool>(coral::equality_predicate(true)).is_ok())
|
||||
return find_result::archive_invalid;
|
||||
|
||||
if (!this->archive_reader->read(archive_entry_buffer).and_test(
|
||||
[](coral::usize value) -> bool { return value == entry_size; }))
|
||||
return find_result::archive_invalid;
|
||||
if (archive_block.layout.kind == kind) return find_result::not_found;
|
||||
|
||||
entry const * const archive_entry {
|
||||
reinterpret_cast<entry const *>(archive_entry_buffer)};
|
||||
|
||||
coral::size const comparison {file_path.compare(archive_entry->path)};
|
||||
coral::size const comparison {entry_path.compare(archive_block.layout.path)};
|
||||
|
||||
if (comparison == 0) {
|
||||
this->data_offset = archive_entry->data_offset;
|
||||
this->data_length = archive_entry->data_length;
|
||||
this->data_cursor = archive_entry->data_offset;
|
||||
this->data_offset = archive_block.layout.data_offset;
|
||||
this->data_length = archive_block.layout.data_length;
|
||||
this->data_cursor = archive_block.layout.data_offset;
|
||||
|
||||
return find_result::ok;
|
||||
}
|
||||
@ -149,65 +163,115 @@ struct archive_file : public coral::file_reader {
|
||||
|
||||
/**
|
||||
* Attempts to read `data.length` bytes from the file and fill `data` with it, returning the
|
||||
* number of bytes actually read or a [coral::io_error] value to indicate an error occured.
|
||||
* number of bytes actually read or a [io_error] value to indicate an error occured.
|
||||
*/
|
||||
coral::expected<coral::usize, coral::io_error> read(coral::slice<coral::u8> const & data) override {
|
||||
if (this->data_offset < sizeof(header)) return coral::io_error::unavailable;
|
||||
expected<usize, io_error> read(coral::slice<u8> const & data) override {
|
||||
if (this->data_offset < sizeof(header)) return io_error::unavailable;
|
||||
|
||||
coral::usize const data_tail {this->data_offset + this->data_length};
|
||||
usize const data_tail {this->data_offset + this->data_length};
|
||||
|
||||
if (!this->archive_reader->seek(coral::clamp(this->data_offset + this->data_cursor,
|
||||
this->data_offset, data_tail)).is_ok()) return coral::io_error::unavailable;
|
||||
this->data_offset, data_tail)).is_ok()) return io_error::unavailable;
|
||||
|
||||
coral::expected const data_read {this->archive_reader->read(
|
||||
expected const data_read {this->archive_reader->read(
|
||||
data.sliced(0, coral::min(data.length, data_tail - this->data_cursor)))};
|
||||
|
||||
if (data_read.is_ok()) this->data_cursor += data_read.value();
|
||||
if (data_read.is_ok()) this->data_cursor += *data_read.ok();
|
||||
|
||||
return data_read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to seek to `offset` absolute position in the file, returning the new absolute
|
||||
* cursor or a [coral::io_error] value to indicate an error occured.
|
||||
* cursor or a [io_error] value to indicate an error occured.
|
||||
*/
|
||||
coral::expected<coral::u64, coral::io_error> seek(coral::u64 offset) override {
|
||||
if (this->data_offset < sizeof(header)) return coral::io_error::unavailable;
|
||||
expected<u64, io_error> seek(u64 offset) override {
|
||||
if (this->data_offset < sizeof(header)) return io_error::unavailable;
|
||||
|
||||
this->data_cursor = offset;
|
||||
|
||||
return coral::io_error::unavailable;
|
||||
return io_error::unavailable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read to read the absolute file cursor position, returning it or a
|
||||
* [coral::io_error] value to indicate an error occured.
|
||||
* Attempts to read to read the absolute file cursor position, returning it or a [io_error]
|
||||
* value to indicate an error occured.
|
||||
*/
|
||||
coral::expected<coral::u64, coral::io_error> tell() override {
|
||||
if (this->data_offset < sizeof(header)) return coral::io_error::unavailable;
|
||||
expected<u64, io_error> tell() override {
|
||||
if (this->data_offset < sizeof(header)) return io_error::unavailable;
|
||||
|
||||
return this->data_cursor;
|
||||
}
|
||||
|
||||
private:
|
||||
coral::file_reader * archive_reader {nullptr};
|
||||
file_reader * archive_reader {nullptr};
|
||||
|
||||
coral::u64 data_offset {0};
|
||||
u64 data_offset {0};
|
||||
|
||||
coral::u64 data_length {0};
|
||||
u64 data_length {0};
|
||||
|
||||
coral::u64 data_cursor {0};
|
||||
u64 data_cursor {0};
|
||||
};
|
||||
|
||||
export namespace oar {
|
||||
struct archive : public coral::fs {
|
||||
archive(coral::fs * backing_fs, coral::path const & archive_path) {
|
||||
struct archive : public fs {
|
||||
archive(fs * backing_fs, path const & archive_path) {
|
||||
this->backing_fs = backing_fs;
|
||||
this->archive_path = archive_path;
|
||||
}
|
||||
|
||||
walk_result walk_files(path const & target_path, closure<bool(path const &)> const & apply) override {
|
||||
bool not_found {false};
|
||||
bool has_io_error {false};
|
||||
|
||||
this->backing_fs->read_file(this->archive_path, [&](file_reader & archive_reader) {
|
||||
entry archive_entry{&archive_reader};
|
||||
|
||||
if (archive_entry.find(entry_kind::directory, target_path) != entry::find_result::ok) {
|
||||
not_found = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
constexpr usize path_size {sizeof(path)};
|
||||
u8 path_buffer[path_size] {0};
|
||||
expected const data_read {archive_entry.read(path_buffer)};
|
||||
|
||||
if (data_read.is_error()) {
|
||||
has_io_error = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (usize const data_read_value {*data_read.ok()}; data_read_value != path_size) {
|
||||
if (data_read_value != 0) has_io_error = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify existence of zero terminator in path.
|
||||
if (!coral::find_last(path_buffer, 0).has_value()) {
|
||||
has_io_error = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (archive_entry.read(path_buffer).map<bool>(coral::equality_predicate(path_size)).ok_or(false))
|
||||
|
||||
if (!apply(*reinterpret_cast<path const *>(path_buffer))) return;
|
||||
}
|
||||
});
|
||||
|
||||
if (not_found) return walk_result::not_found;
|
||||
|
||||
if (has_io_error) return walk_result::io_error;
|
||||
|
||||
return walk_result::ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the archive for the [coral::fs::access_rules] and returns them.
|
||||
* Queries the archive for the [fs::access_rules] and returns them.
|
||||
*/
|
||||
access_rules query_access() override {
|
||||
return {
|
||||
@ -217,37 +281,119 @@ export namespace oar {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open a readable context for reading from the archive file identified by
|
||||
* `file_path`, doing nothing if the requested file could not be found.
|
||||
* Attempts to open a readable context for reading from the archive file identified by `file_path`, doing
|
||||
* nothing if the requested file could not be found.
|
||||
*/
|
||||
void read_file(coral::path const & file_path,
|
||||
coral::closure<void(coral::file_reader &)> const & then) override {
|
||||
void read_file(path const & file_path, closure<void(file_reader &)> const & then) override {
|
||||
if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return;
|
||||
|
||||
if ((this->backing_fs == nullptr) || (this->archive_path.byte_size() == 0)) return;
|
||||
this->backing_fs->read_file(this->archive_path, [&](file_reader & archive_reader) {
|
||||
entry archive_entry {&archive_reader};
|
||||
|
||||
this->backing_fs->read_file(this->archive_path,
|
||||
[&](coral::file_reader & archive_reader) {
|
||||
archive_file file{&archive_reader};
|
||||
if (archive_entry.find(entry_kind::file, file_path) != entry::find_result::ok) return;
|
||||
|
||||
if (file.find(file_path) != archive_file::find_result::ok) return;
|
||||
|
||||
then(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to open a writable context for reading from the archive file identified by
|
||||
* `file_path`, however this will always do nothing as archive file-systems are read-only.
|
||||
*/
|
||||
void write_file(coral::path const & file_path,
|
||||
coral::closure<void(coral::file_writer &)> const & then) override {
|
||||
|
||||
// Read-only file system.
|
||||
}
|
||||
then(archive_entry);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
coral::fs * backing_fs;
|
||||
fs * backing_fs;
|
||||
|
||||
coral::path archive_path;
|
||||
path archive_path;
|
||||
};
|
||||
|
||||
enum class [[nodiscard]] bundle_result {
|
||||
ok,
|
||||
out_of_memory,
|
||||
too_many_files,
|
||||
io_error,
|
||||
};
|
||||
|
||||
bundle_result bundle(coral::allocator & allocator, fs & output_fs,
|
||||
path const & output_path, fs & input_fs, path const & input_path) {
|
||||
|
||||
coral::small_stack<block, 64> archive_blocks {allocator};
|
||||
u64 file_count {0};
|
||||
|
||||
// Walk input dir to create blocks for all files needed.
|
||||
{
|
||||
bool has_memory {true};
|
||||
|
||||
if (input_fs.walk_files(input_path, [&](path const & entry_path) -> bool {
|
||||
has_memory = archive_blocks.push({.layout = {.path = entry_path}}) == coral::push_result::ok;
|
||||
|
||||
return !has_memory;
|
||||
|
||||
file_count += 1;
|
||||
}) != fs::walk_result::ok) return bundle_result::io_error;
|
||||
|
||||
if (!has_memory) return bundle_result::out_of_memory;
|
||||
|
||||
if (file_count > coral::u32_max) return bundle_result::too_many_files;
|
||||
}
|
||||
|
||||
// Write header, file data, and blocks to archive.
|
||||
{
|
||||
bool has_io_error {false};
|
||||
|
||||
output_fs.write_file(output_path, [&](coral::file_writer & archive_writer) {
|
||||
header archive_header {};
|
||||
|
||||
coral::copy(archive_header.layout.signature, signature_magic);
|
||||
|
||||
archive_header.layout.entry_count = static_cast<coral::u32>(file_count);
|
||||
|
||||
if (!archive_writer.write(archive_header.bytes).map<bool>(header::is_sizeof).ok_or(false)) {
|
||||
has_io_error = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!archive_blocks.every([&](block & archive_block) -> bool {
|
||||
bool file_read {false};
|
||||
|
||||
input_fs.read_file(archive_block.layout.path, [&](coral::file_reader & entry_reader) {
|
||||
expected const data_position {entry_reader.tell()};
|
||||
|
||||
if (data_position.is_error()) {
|
||||
has_io_error = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
archive_block.layout.data_offset = *data_position.ok();
|
||||
|
||||
{
|
||||
u8 stream_buffer[4096] {0};
|
||||
expected const data_written {coral::stream(archive_writer, entry_reader, stream_buffer)};
|
||||
|
||||
if (data_written.is_error()) {
|
||||
has_io_error = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
archive_block.layout.data_length = *data_written.ok();
|
||||
}
|
||||
|
||||
file_read = true;
|
||||
});
|
||||
|
||||
return file_read && (!has_io_error);
|
||||
})) return;
|
||||
|
||||
if (!archive_blocks.every([&](block const & archive_block) -> bool {
|
||||
if (!archive_writer.write(archive_block.bytes).map<bool>(block::is_sizeof).ok_or(false)) {
|
||||
has_io_error = true;
|
||||
}
|
||||
|
||||
return !has_io_error;
|
||||
})) return;
|
||||
});
|
||||
|
||||
if (has_io_error) return bundle_result::io_error;
|
||||
}
|
||||
|
||||
return bundle_result::ok;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user