Improve error handling with core::expected

This commit is contained in:
kayomn 2023-02-18 13:19:01 +00:00
parent a9e0588878
commit 43383058c6
4 changed files with 148 additions and 59 deletions

View File

@ -70,6 +70,22 @@ export namespace core {
__builtin_unreachable();
}
template<typename scalar> constexpr scalar max(scalar const & a, scalar const & b) {
return (a > b) ? a : b;
}
template<typename scalar> constexpr scalar min(scalar const & a, scalar const & b) {
return (a < b) ? a : b;
}
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));
}
f32 round32(f32 value) {
return __builtin_roundf(value);
}
template<typename type> struct slice {
usize length;
@ -167,6 +183,50 @@ export namespace core {
u8 buffer[sizeof(element) + 1];
};
template<typename value_element, typename error_element> struct [[nodiscard]] expected {
expected(value_element const & value) : buffer{0} {
(*reinterpret_cast<value_element *>(this->buffer)) = value;
this->buffer[buffer_size] = 1;
}
expected(error_element const & error) : buffer{0} {
(*reinterpret_cast<error_element *>(this->buffer)) = error;
}
bool is_ok() const {
return this->buffer[buffer_size];
}
value_element & value() {
if (!this->is_ok()) unreachable();
return *reinterpret_cast<value_element *>(this->buffer);
}
value_element const & value() const {
if (!this->is_ok()) unreachable();
return *reinterpret_cast<value_element const *>(this->buffer);
}
error_element & error() {
if (this->is_ok()) unreachable();
return *reinterpret_cast<error_element *>(this->buffer);
}
error_element const & error() const {
if (this->is_ok()) unreachable();
return *reinterpret_cast<error_element const *>(this->buffer);
}
private:
static constexpr usize buffer_size = max(sizeof(value_element), sizeof(error_element));
u8 buffer[buffer_size + 1];
};
template<typename> struct callable;
template<typename return_type, typename... argument_types> struct callable<return_type(argument_types...)> {
@ -218,9 +278,13 @@ export namespace core {
return a;
}
using readable = callable<optional<usize>(slice<u8> const &)>;
enum class io_error {
unavailable,
};
using writable = callable<optional<usize>(slice<u8 const> const &)>;
using readable = callable<expected<usize, io_error>(slice<u8> const &)>;
using writable = callable<expected<usize, io_error>(slice<u8 const> const &)>;
template<typename element_type> bool equals(slice<element_type const> const & a, slice<element_type const> const & b) {
if (a.length != b.length) return false;
@ -230,43 +294,27 @@ export namespace core {
return true;
}
optional<usize> stream(writable const & output, readable const & input, slice<u8> const & buffer) {
expected<usize, io_error> stream(writable const & output, readable const & input, slice<u8> const & buffer) {
usize written = 0;
optional maybe_read = input(buffer);
expected maybe_read = input(buffer);
if (!maybe_read.has_value()) return {};
if (!maybe_read.is_ok()) return maybe_read.error();
usize read = maybe_read.value();
while (read != 0) {
optional const maybe_written = output(buffer.until(read));
expected const maybe_written = output(buffer.until(read));
if (!maybe_written.has_value()) return {};
if (!maybe_written.is_ok()) return maybe_read.error();
written += maybe_written.value();
maybe_read = input(buffer);
if (!maybe_read.has_value()) return {};
if (!maybe_read.is_ok()) return maybe_read.error();
read = maybe_read.value();
}
return written;
}
template<typename scalar> scalar max(scalar const & a, scalar const & b) {
return (a > b) ? a : b;
}
template<typename scalar> scalar min(scalar const & a, scalar const & b) {
return (a < b) ? a : b;
}
template<typename scalar> scalar clamp(scalar const & value, scalar const & min_value, scalar const & max_value) {
return max(min_value, min(max_value, value));
}
f32 round32(f32 value) {
return __builtin_roundf(value);
}
}

View File

@ -107,10 +107,10 @@ export namespace core {
};
struct sequence_writer : public writable {
sequence_writer(sequence<u8> * target_sequence) : writable{[target_sequence](slice<u8 const> const & buffer) -> optional<usize> {
sequence_writer(sequence<u8> * target_sequence) : writable{[target_sequence](slice<u8 const> const & buffer) -> expected<usize, io_error> {
optional const maybe_error = target_sequence->append(buffer);
if (maybe_error.has_value()) return {};
if (maybe_error.has_value()) return io_error::unavailable;
return buffer.length;
}} {}

View File

@ -19,16 +19,32 @@ struct token {
token_kind kind;
};
using tokenizable = core::callable<token()>;
struct tokenizer {
core::slice<char const> source;
tokenizer(core::slice<char const> const & source) : source{source} {}
token next() {
core::usize cursor = 0;
while (cursor < source.length) {
}
return token{
.kind = token_kind::end,
};
}
};
struct bytecode {
bytecode(core::allocator * allocator) : error_message_buffer{allocator} {
}
bool compile(tokenizable const & bytecode_tokenizable) {
bool compile(tokenizer bytecode_tokenizer) {
for (;;) {
token const initial_token = bytecode_tokenizable();
token const initial_token = bytecode_tokenizer.next();
switch (initial_token.kind) {
case token_kind::end: return true;
@ -38,7 +54,7 @@ struct bytecode {
}
}
kym::value execute(kym::vm & vm, core::slice<kym::value> const & arguments) {
kym::value execute(kym::vm & vm, core::slice<kym::value const> const & arguments) {
return kym::nil;
}
@ -51,22 +67,49 @@ struct bytecode {
};
export namespace kym {
struct bound_object {
core::callable<void()> cleanup;
value default_call(vm & owning_vm, void * userdata, core::slice<value const> const & arguments) {
return nil;
}
core::callable<value(core::slice<value>)> call;
core::expected<core::usize, core::io_error> default_stringify(vm & owning_vm, void * userdata, core::writable const & writable) {
return writable(core::slice("[object]").as_bytes());
}
struct bound_object {
void * userdata;
struct {
void(*cleanup)(vm &, void *);
value(*call)(vm &, void *, core::slice<value const> const &);
core::expected<core::usize, core::io_error>(*stringify)(vm &, void *, core::writable const &);
} behavior;
bound_object(vm * owning_vm) : userdata{nullptr}, owning_vm{owning_vm}, behavior{
.cleanup = [](vm & owning_vm, void * userdata) {},
.call = default_call,
.stringify = default_stringify,
} {}
void cleanup() {
this->behavior.cleanup(*this->owning_vm, this->userdata);
}
value call(core::slice<value const> const & arguments) {
return this->behavior.call(*this->owning_vm, this->userdata, arguments);
}
core::expected<core::usize, core::io_error> stringify(core::writable const & writable) {
return this->behavior.stringify(*this->owning_vm, this->userdata, writable);
}
value get_field(core::slice<char const> const & field_name) {
return nil;
}
bool is_string() {
return false;
}
core::usize stringify(core::writable const & writable) {
return 0;
}
private:
vm * owning_vm;
};
struct vm {
@ -100,13 +143,7 @@ export namespace kym {
if (source_bytecode == nullptr) return nil;
core::usize cursor = 0;
if (source_bytecode->compile([]() {
return token{
};
})) {
if (source_bytecode->compile(tokenizer{source})) {
this->log(source_bytecode->error_message());
this->allocator->deallocate(source_bytecode);
@ -114,12 +151,14 @@ export namespace kym {
}
return this->new_object([this, source_bytecode](bound_object & object) {
object.cleanup = [this, source_bytecode]() {
this->allocator->deallocate(source_bytecode);
object.userdata = source_bytecode;
object.behavior.cleanup = [](vm & owning_vm, void * userdata) {
owning_vm.allocator->deallocate(userdata);
};
object.call = [this, source_bytecode](core::slice<value> const & arguments) {
return source_bytecode->execute(*this, arguments);
object.behavior.call = [](vm & owning_vm, void * userdata, core::slice<value const> const & arguments) -> value {
return reinterpret_cast<bytecode *>(userdata)->execute(owning_vm, arguments);
};
this->allocator->deallocate(source_bytecode);

View File

@ -33,29 +33,31 @@ extern "C" int main(int argc, char const * const * argv) {
core::u8 config_source_stream_buffer[1024] = {};
if (!core::stream(core::sequence_writer{&config_source},
config_readable, config_source_stream_buffer).has_value()) return;
config_readable, config_source_stream_buffer).is_ok()) return;
vm.with_object(vm.compile(config_source.as_slice().as_chars()), [&](kym::bound_object & config_script) {
vm.with_object(config_script.call({}), [&](kym::bound_object & config) {
core::u16 const width = config.get_field("width").as_u16().value_or(0);
if (width == 0) return system.log(
app::log_level::error, "failed to decode `width` property of config");
if (width == 0) return system.log(app::log_level::error,
"failed to decode `width` property of config");
core::u16 const height = config.get_field("height").as_u16().value_or(0);
if (height == 0) return system.log(
app::log_level::error, "failed to decode `height` property of config");
if (height == 0) return system.log(app::log_level::error,
"failed to decode `height` property of config");
graphics.show(width, height);
vm.with_object(config.get_field("title"), [&](kym::bound_object & title) {
core::stack<core::u8, 128> title_buffer{&system.thread_safe_allocator()};
if (!title.is_string()) return system.log(
app::log_level::error, "failed to decode `title` property of config");
if (!title.stringify(core::sequence_writer(&title_buffer)).is_ok()) {
system.log(app::log_level::error,
"failed to decode `title` property of config");
title.stringify(core::sequence_writer(&title_buffer));
return;
}
is_config_loaded = true;
});