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(); __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 { template<typename type> struct slice {
usize length; usize length;
@ -167,6 +183,50 @@ export namespace core {
u8 buffer[sizeof(element) + 1]; 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> struct callable;
template<typename return_type, typename... argument_types> struct callable<return_type(argument_types...)> { template<typename return_type, typename... argument_types> struct callable<return_type(argument_types...)> {
@ -218,9 +278,13 @@ export namespace core {
return a; 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) { 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; if (a.length != b.length) return false;
@ -230,43 +294,27 @@ export namespace core {
return true; 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; 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(); usize read = maybe_read.value();
while (read != 0) { 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(); written += maybe_written.value();
maybe_read = input(buffer); maybe_read = input(buffer);
if (!maybe_read.has_value()) return {}; if (!maybe_read.is_ok()) return maybe_read.error();
read = maybe_read.value(); read = maybe_read.value();
} }
return written; 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 { 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); 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; return buffer.length;
}} {} }} {}

View File

@ -19,16 +19,32 @@ struct token {
token_kind kind; 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 { struct bytecode {
bytecode(core::allocator * allocator) : error_message_buffer{allocator} { bytecode(core::allocator * allocator) : error_message_buffer{allocator} {
} }
bool compile(tokenizable const & bytecode_tokenizable) { bool compile(tokenizer bytecode_tokenizer) {
for (;;) { for (;;) {
token const initial_token = bytecode_tokenizable(); token const initial_token = bytecode_tokenizer.next();
switch (initial_token.kind) { switch (initial_token.kind) {
case token_kind::end: return true; 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; return kym::nil;
} }
@ -51,22 +67,49 @@ struct bytecode {
}; };
export namespace kym { export namespace kym {
struct bound_object { value default_call(vm & owning_vm, void * userdata, core::slice<value const> const & arguments) {
core::callable<void()> cleanup; 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) { value get_field(core::slice<char const> const & field_name) {
return nil; return nil;
} }
bool is_string() { private:
return false; vm * owning_vm;
}
core::usize stringify(core::writable const & writable) {
return 0;
}
}; };
struct vm { struct vm {
@ -100,13 +143,7 @@ export namespace kym {
if (source_bytecode == nullptr) return nil; if (source_bytecode == nullptr) return nil;
core::usize cursor = 0; if (source_bytecode->compile(tokenizer{source})) {
if (source_bytecode->compile([]() {
return token{
};
})) {
this->log(source_bytecode->error_message()); this->log(source_bytecode->error_message());
this->allocator->deallocate(source_bytecode); this->allocator->deallocate(source_bytecode);
@ -114,12 +151,14 @@ export namespace kym {
} }
return this->new_object([this, source_bytecode](bound_object & object) { return this->new_object([this, source_bytecode](bound_object & object) {
object.cleanup = [this, source_bytecode]() { object.userdata = source_bytecode;
this->allocator->deallocate(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) { object.behavior.call = [](vm & owning_vm, void * userdata, core::slice<value const> const & arguments) -> value {
return source_bytecode->execute(*this, arguments); return reinterpret_cast<bytecode *>(userdata)->execute(owning_vm, arguments);
}; };
this->allocator->deallocate(source_bytecode); 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] = {}; core::u8 config_source_stream_buffer[1024] = {};
if (!core::stream(core::sequence_writer{&config_source}, 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(vm.compile(config_source.as_slice().as_chars()), [&](kym::bound_object & config_script) {
vm.with_object(config_script.call({}), [&](kym::bound_object & config) { vm.with_object(config_script.call({}), [&](kym::bound_object & config) {
core::u16 const width = config.get_field("width").as_u16().value_or(0); core::u16 const width = config.get_field("width").as_u16().value_or(0);
if (width == 0) return system.log( if (width == 0) return system.log(app::log_level::error,
app::log_level::error, "failed to decode `width` property of config"); "failed to decode `width` property of config");
core::u16 const height = config.get_field("height").as_u16().value_or(0); core::u16 const height = config.get_field("height").as_u16().value_or(0);
if (height == 0) return system.log( if (height == 0) return system.log(app::log_level::error,
app::log_level::error, "failed to decode `height` property of config"); "failed to decode `height` property of config");
graphics.show(width, height); graphics.show(width, height);
vm.with_object(config.get_field("title"), [&](kym::bound_object & title) { vm.with_object(config.get_field("title"), [&](kym::bound_object & title) {
core::stack<core::u8, 128> title_buffer{&system.thread_safe_allocator()}; core::stack<core::u8, 128> title_buffer{&system.thread_safe_allocator()};
if (!title.is_string()) return system.log( if (!title.stringify(core::sequence_writer(&title_buffer)).is_ok()) {
app::log_level::error, "failed to decode `title` property of config"); 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; is_config_loaded = true;
}); });