From 3142e4c4ce7cde133e5a6d0ebfc11bd88ba6cbb1 Mon Sep 17 00:00:00 2001 From: King_DuckZ Date: Sat, 25 Apr 2020 20:45:59 +0200 Subject: [PATCH] Start working on a c++ interface. --- .gitignore | 2 + meson.build | 3 + src/main.cpp | 24 ++----- src/wren/configuration.cpp | 13 ++++ src/wren/configuration.hpp | 25 +++++++ src/wren/def_configuration.cpp | 12 ++++ src/wren/def_configuration.hpp | 14 ++++ src/wren/error_type.hpp | 14 ++++ src/wren/has_method.hpp | 53 ++++++++++++++ src/wren/vm.cpp | 116 ++++++++++++++++++++++++++++++ src/wren/vm.hpp | 125 +++++++++++++++++++++++++++++++++ 11 files changed, 382 insertions(+), 19 deletions(-) create mode 100644 .gitignore create mode 100644 src/wren/configuration.cpp create mode 100644 src/wren/configuration.hpp create mode 100644 src/wren/def_configuration.cpp create mode 100644 src/wren/def_configuration.hpp create mode 100644 src/wren/error_type.hpp create mode 100644 src/wren/has_method.hpp create mode 100644 src/wren/vm.cpp create mode 100644 src/wren/vm.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8f1c27 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +tags +compile_commands.json diff --git a/meson.build b/meson.build index 8397c1b..5f7d38a 100644 --- a/meson.build +++ b/meson.build @@ -8,6 +8,9 @@ wren_dep = dependency('wren', version: '>=0.2.0', fallback: ['wren', 'wren_dep'] executable(meson.project_name(), 'src/main.cpp', + 'src/wren/vm.cpp', + 'src/wren/configuration.cpp', + 'src/wren/def_configuration.cpp', dependencies: [wren_dep], install: true, ) diff --git a/src/main.cpp b/src/main.cpp index a41eda5..400a1de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,32 +1,18 @@ +#include "wren/vm.hpp" +#include "wren/def_configuration.hpp" #include -#include #include #include namespace { - void wren_print (WrenVM*, const char* text) { - std::cout << text; - } - - void wren_error (WrenVM*, WrenErrorType type, const char* module, int line, const char* message) { - std::cerr << "Wren error: " << message << " in " << module << ':' << line << '\n'; - } } //unnamed namespace int main() { std::cout << "hello world\n"; - WrenConfiguration config; - wrenInitConfiguration(&config); - config.writeFn = &wren_print; - config.errorFn = &wren_error; - WrenVM* vm = wrenNewVM(&config); - WrenInterpretResult result = wrenInterpret( - vm, - "my_module", - "System.print(\"I am running in a VM!\")" - ); + wren::DefConfiguration config; + wren::VM vm(&config); + vm.interpret("my_module", "System.print(\"I am running in a VM!\")"); std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - wrenFreeVM(vm); return 0; } diff --git a/src/wren/configuration.cpp b/src/wren/configuration.cpp new file mode 100644 index 0000000..50eb074 --- /dev/null +++ b/src/wren/configuration.cpp @@ -0,0 +1,13 @@ +#include "configuration.hpp" +#include + +namespace wren { + Configuration::Configuration() { + WrenConfiguration conf; + wrenInitConfiguration(&conf); + + m_initial_heap_size = conf.initialHeapSize; + m_min_heap_size = conf.minHeapSize; + m_heap_growth_percent = conf.heapGrowthPercent; + } +} //namespace wren diff --git a/src/wren/configuration.hpp b/src/wren/configuration.hpp new file mode 100644 index 0000000..aaa306d --- /dev/null +++ b/src/wren/configuration.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace wren { + class Configuration { + public: + Configuration(); + ~Configuration() noexcept = default; + + std::size_t initial_heap_size() const { return m_initial_heap_size; } + void set_initial_heap_size(std::size_t v) { m_initial_heap_size = v; } + + std::size_t min_heap_size() const { return m_min_heap_size; } + void set_min_heap_size(std::size_t v) { m_min_heap_size = v; } + + std::size_t heap_growth_percent() const { return m_heap_growth_percent; } + void set_heap_growth_percent(std::size_t v) { m_heap_growth_percent = v; } + + private: + std::size_t m_initial_heap_size; + std::size_t m_min_heap_size; + std::size_t m_heap_growth_percent; + }; +} //namespace wren diff --git a/src/wren/def_configuration.cpp b/src/wren/def_configuration.cpp new file mode 100644 index 0000000..d5af0f8 --- /dev/null +++ b/src/wren/def_configuration.cpp @@ -0,0 +1,12 @@ +#include "def_configuration.hpp" +#include + +namespace wren { + void DefConfiguration::write_fn (VM*, const char* text) { + std::cout << text; + } + + void DefConfiguration::error_fn (VM*, ErrorType type, const char* module, int line, const char* message) { + std::cerr << "Wren error: " << message << " in " << module << ':' << line << '\n'; + } +} //namespace wren diff --git a/src/wren/def_configuration.hpp b/src/wren/def_configuration.hpp new file mode 100644 index 0000000..763b2ba --- /dev/null +++ b/src/wren/def_configuration.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "configuration.hpp" +#include "error_type.hpp" + +namespace wren { + class VM; + + class DefConfiguration : public Configuration { + public: + static void write_fn (VM*, const char* text); + static void error_fn (VM*, ErrorType, const char* module, int line, const char* msg); + }; +} //namespace wren diff --git a/src/wren/error_type.hpp b/src/wren/error_type.hpp new file mode 100644 index 0000000..07fe119 --- /dev/null +++ b/src/wren/error_type.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace wren { + enum class ErrorType { + // A syntax or resolution error detected at compile time. + Compile, + + // The error message for a runtime error. + Runtime, + + // One entry of a runtime error's stack trace. + StackTrace + }; +} //namespace wren diff --git a/src/wren/has_method.hpp b/src/wren/has_method.hpp new file mode 100644 index 0000000..1ae4a4c --- /dev/null +++ b/src/wren/has_method.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include + +//see https://stackoverflow.com/a/10707822 +#define define_method_info(method_name, pretty_name, ret_type, ...) \ + class method_ ## pretty_name { \ + typedef std::integral_constant no_method_t; \ + typedef std::integral_constant has_method_t; \ + typedef std::integral_constant has_static_method_t; \ + template \ + static has_method_t test(ret_type (A::*)(Args...)) { \ + return {}; \ + } \ + template \ + static has_static_method_t test(ret_type (*)(Args...)) { \ + return {}; \ + } \ + template \ + static auto test(decltype(std::declval().method_name(std::declval()...))*,void *) { \ + typedef decltype(test(&A::method_name)) return_type; \ + return return_type{}; \ + } \ + template \ + static no_method_t test(...) { \ + return {}; \ + } \ + public: \ + template< typename T> \ + class exists { \ + typedef decltype(test(0,0)) found_t; \ + public: \ + static const constexpr bool value = (found_t::value != no_method_t::value); \ + }; \ + template \ + class is_static { \ + typedef decltype(test(0,0)) found_t; \ + public: \ + static const constexpr bool value = (found_t::value == has_static_method_t::value); \ + }; \ + template struct static_ptr { \ + typedef ret_type (*type)(__VA_ARGS__); \ + }; \ + template struct nonstatic_ptr { \ + typedef ret_type (T::*type)(__VA_ARGS__); \ + }; \ + template struct ptr { \ + typedef typename std::conditional::value, typename static_ptr::type, typename std::conditional::value, typename nonstatic_ptr::type, void>::type>::type type; \ + }; \ + } + +namespace wren { +} //namespace wren diff --git a/src/wren/vm.cpp b/src/wren/vm.cpp new file mode 100644 index 0000000..faaf54f --- /dev/null +++ b/src/wren/vm.cpp @@ -0,0 +1,116 @@ +#include "vm.hpp" +#include "configuration.hpp" +#include +#include +#include + +namespace wren { + namespace { + [[gnu::const]] + ErrorType to_error_type (WrenErrorType wet) { + switch (wet) { + case WREN_ERROR_COMPILE: return ErrorType::Compile; + case WREN_ERROR_RUNTIME: return ErrorType::Runtime; + case WREN_ERROR_STACK_TRACE: return ErrorType::StackTrace; + default: return ErrorType::Runtime; + }; + } + + void write_fn (WrenVM* wvm, const char* text) { + auto cb = static_cast(wrenGetUserData(wvm)); + assert(cb); + assert(cb->write_fn and cb->config_obj and cb->vm); + cb->write_fn(*cb->config_obj, cb->vm, text); + } + + void error_fn (WrenVM* wvm, WrenErrorType type, const char* module, int line, const char* message) { + auto cb = static_cast(wrenGetUserData(wvm)); + assert(cb); + assert(cb->error_fn and cb->config_obj and cb->vm); + cb->error_fn(*cb->config_obj, cb->vm, to_error_type(type), module, line, message); + } + + const char* resolve_module_fn (WrenVM* wvm, const char* importer, const char* name) { + auto cb = static_cast(wrenGetUserData(wvm)); + assert(cb); + assert(cb->resolve_module_fn and cb->config_obj and cb->vm); + return cb->resolve_module_fn(*cb->config_obj, cb->vm, importer, name); + } + + char* load_module_fn (WrenVM* wvm, const char* name) { + auto cb = static_cast(wrenGetUserData(wvm)); + assert(cb); + assert(cb->load_module_fn and cb->config_obj and cb->vm); + return cb->load_module_fn(*cb->config_obj, cb->vm, name); + } + } //unnamed namespace + + VM::VM (Configuration* conf, const detail::Callbacks& cb) : + m_callbacks(cb), + m_vm(nullptr) + { + WrenConfiguration wconf; + wrenInitConfiguration(&wconf); + wconf.userData = static_cast(&m_callbacks); + + wconf.initialHeapSize = conf->initial_heap_size(); + wconf.minHeapSize = conf->min_heap_size(); + wconf.heapGrowthPercent = conf->heap_growth_percent(); + + if (cb.write_fn) + wconf.writeFn = &write_fn; + + if (cb.error_fn) + wconf.errorFn = &error_fn; + + if (cb.reallocate_fn) + wconf.reallocateFn = cb.reallocate_fn; + + if (cb.resolve_module_fn) + wconf.resolveModuleFn = &resolve_module_fn; + + if (cb.load_module_fn) + wconf.loadModuleFn = &load_module_fn; + + m_vm = wrenNewVM(&wconf); + + if (not m_vm) + throw std::runtime_error("Failed to initialize Wren VM"); + } + + VM::VM (VM&& other) : + m_vm(nullptr) + { + std::swap(m_vm, other.m_vm); + m_callbacks = other.m_callbacks; + wrenSetUserData(m_vm, &m_callbacks); + } + + VM::~VM() noexcept { + if (m_vm) + wrenFreeVM(m_vm); +#if !defined(NDEBUG) + m_vm = nullptr; +#endif + } + + void VM::interpret (const char* module_name, const char* script) { + using std::string; + using std::runtime_error; + + const WrenInterpretResult res = wrenInterpret(m_vm, module_name, script); + switch (res) { + case WREN_RESULT_COMPILE_ERROR: + throw runtime_error(string("Compilation of ") + module_name + " has failed"); + case WREN_RESULT_RUNTIME_ERROR: + throw runtime_error(string("A runtime error occurred in ") + module_name); + + case WREN_RESULT_SUCCESS: + break; + } + } + + void VM::interpret (const std::string& module_name, const std::string& script) { + return interpret(module_name.c_str(), script.c_str()); + } +} //namespace wren diff --git a/src/wren/vm.hpp b/src/wren/vm.hpp new file mode 100644 index 0000000..a9f6542 --- /dev/null +++ b/src/wren/vm.hpp @@ -0,0 +1,125 @@ +#pragma once + +#include "has_method.hpp" +#include "error_type.hpp" +#include + +extern "C" { + typedef struct WrenVM WrenVM; +} //extern C + +namespace wren { + class Configuration; + class VM; + + namespace detail { + struct Callbacks { + void (*write_fn)(Configuration&, VM*, const char*); + void (*error_fn)(Configuration&, VM*, ErrorType, const char*, int, const char*); + void* (*reallocate_fn)(void*, std::size_t); + const char* (*resolve_module_fn)(Configuration&, VM*, const char*, const char*); + char* (*load_module_fn)(Configuration&, VM*, const char*); + + Configuration* config_obj; + VM* vm; + }; + } //namespace detail + + class VM { + public: + template + VM (T* conf); + VM (const VM&) = delete; + VM (VM&& other); + ~VM() noexcept; + + void interpret (const char* module_name, const char* script); + void interpret (const std::string& module_name, const std::string& script); + + private: + VM (Configuration* conf, const detail::Callbacks&); + + template + detail::Callbacks to_callbacks (T& conf); + + detail::Callbacks m_callbacks; + WrenVM* m_vm; + }; + + namespace detail { + define_method_info(write_fn, write, void, VM*, const char*); + define_method_info(error_fn, error, void, VM*, ErrorType, const char*, int, const char*); + define_method_info(reallocate_fn, reallocate, void*, void*, std::size_t); + define_method_info(resolve_module_fn, resolve_module, const char*, const char*, const char*); + define_method_info(load_module_fn, load_module, char*, VM*, const char*); + + namespace fwd { + template + inline void write_fn (Configuration& obj, VM* vm, const char* text) { + reinterpret_cast(obj).write_fn(vm, text); + } + + template + inline void error_fn (Configuration& obj, VM* vm, ErrorType et, const char* module, int line, const char* message) { + reinterpret_cast(obj).error_fn(vm, et, module, line, message); + } + + template + inline const char* resolve_module_fn (Configuration& obj, VM* vm, void* memory, std::size_t new_size) { + return reinterpret_cast(obj).resolve_module_fn(vm, memory, new_size); + } + + template + inline char* load_module_fn (Configuration& obj, VM* vm, const char* name) { + return reinterpret_cast(obj).load_module_fn(vm, name); + } + } //namespace fwd + } //namespace detail + + template + inline detail::Callbacks VM::to_callbacks (T& conf) { + using detail::method_write; + using detail::method_error; + using detail::method_reallocate; + using detail::method_resolve_module; + using detail::method_load_module; + + detail::Callbacks ret; + ret.config_obj = &conf; + ret.vm = this; + + if constexpr (method_write::exists::value) + ret.write_fn = &detail::fwd::write_fn; + else + ret.write_fn = nullptr; + + if constexpr (method_error::exists::value) + ret.error_fn = &detail::fwd::error_fn; + else + ret.error_fn = nullptr; + + if constexpr (method_reallocate::exists::value and method_reallocate::is_static::value) + ret.reallocate_fn = &T::reallocate_fn; + else + ret.reallocate_fn = nullptr; + static_assert(not method_reallocate::exists::value or method_reallocate::is_static::value, "Realloc function must be a static function"); + + if constexpr (method_resolve_module::exists::value) + ret.resolve_module_fn = &detail::fwd::resolve_module_fn; + else + ret.resolve_module_fn = nullptr; + + if constexpr (method_load_module::exists::value) + ret.load_module_fn = &detail::fwd::load_module_fn; + else + ret.load_module_fn = nullptr; + + return ret; + } + + template + inline VM::VM (T* conf) : + VM(static_cast(conf), to_callbacks(*conf)) + { + } +} //namespace wren