Improve error handling with core::expected
This commit is contained in:
parent
a9e0588878
commit
43383058c6
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}} {}
|
}} {}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue