C++20 Port #5
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}} {}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue