Compare commits

..

6 Commits

Author SHA1 Message Date
45bf416230 add more test
Some checks failed
ubuntu / Explore-Gitea-Actions (push) Failing after 4m51s
2025-12-09 18:15:14 +08:00
e67f4c3a4d use mrb string to malloc method memory & add native init 2025-12-09 18:09:30 +08:00
ef4ada6035 update ignore 2025-12-09 18:08:41 +08:00
e38f5850d3 better call template & move to include/murbypp dir & fix test namepsace
Some checks failed
ubuntu / Explore-Gitea-Actions (push) Failing after 1m47s
2025-10-22 23:30:54 +08:00
75caef3bd2 use namespace & not fix return 2025-10-22 22:42:42 +08:00
2e5df6f1d6 remove debug used option 2025-10-22 22:33:37 +08:00
10 changed files with 494 additions and 390 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
cmake-*/ cmake-*/
build/ build/
.idea/ .idea/
CMakeSettings.json
.vs/

View File

@@ -4,7 +4,6 @@ project(mrubypp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(mruby_ROOT "/mnt/d/Sources/mruby/build/host")
option(MRUBYPP_BUILD_TEST "Build Catch2 Test" OFF) option(MRUBYPP_BUILD_TEST "Build Catch2 Test" OFF)
@@ -15,21 +14,19 @@ if (MSVC)
endif () endif ()
add_library(mrubypp INTERFACE add_library(mrubypp INTERFACE
mrubypp.h include/mrubypp/engine.h
mrubypp_converters.h include/mrubypp/converters.h
mrubypp_arena_guard.h include/mrubypp/arena_guard.h
mrubypp_bind_class.h) include/mrubypp/bind_class.h)
target_include_directories(mrubypp INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
include_directories(${CMAKE_CURRENT_LIST_DIR})
if (${MRUBYPP_BUILD_TEST}) if (${MRUBYPP_BUILD_TEST})
if (NOT mruby_ROOT) if (NOT DEFINED mruby_ROOT)
message(NOTICE "[mrubypp] Using default mruby build") message(NOTICE "[mrubypp] Using default mruby build")
set(mruby_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/deps/mruby") set(mruby_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/deps/mruby")
endif () endif ()
add_subdirectory(deps/Catch2) add_subdirectory(deps/Catch2)
enable_testing() enable_testing()

View File

@@ -7,15 +7,16 @@
#include <mruby.h> #include <mruby.h>
namespace mrubypp {
// gc arena // gc arena
class mrubypp_arena_guard { class arena_guard {
public: public:
explicit mrubypp_arena_guard(mrb_state *mrb) : mrb(mrb) { explicit arena_guard(mrb_state *mrb) : mrb(mrb) {
ai = mrb_gc_arena_save(mrb); ai = mrb_gc_arena_save(mrb);
} }
mrubypp_arena_guard(mrubypp_arena_guard &&other) = delete; arena_guard(arena_guard &&other) = delete;
mrubypp_arena_guard(const mrubypp_arena_guard &other) = delete; arena_guard(const arena_guard &other) = delete;
~mrubypp_arena_guard() { mrb_gc_arena_restore(mrb, ai); } ~arena_guard() { mrb_gc_arena_restore(mrb, ai); }
// arena_idx // arena_idx
[[nodiscard]] int get_ai() const { return ai; } [[nodiscard]] int get_ai() const { return ai; }
@@ -24,4 +25,5 @@ private:
mrb_state *mrb; mrb_state *mrb;
int ai; int ai;
}; };
} // namespace mrubypp
#endif // MRUBYPP_MRUBYPP_UTILS_H #endif // MRUBYPP_MRUBYPP_UTILS_H

View File

@@ -0,0 +1,269 @@
#ifndef MRUBYPP_BIND_CLASS
#define MRUBYPP_BIND_CLASS
#include "arena_guard.h"
#include "converters.h"
#include <string>
#include <tuple>
#include <mruby/class.h>
#include <mruby/data.h>
#include <mruby/proc.h>
namespace mrubypp {
template <typename... Args, size_t... Is>
std::tuple<Args...> get_args_helper_impl(mrb_state *mrb, const mrb_value *argv,
std::index_sequence<Is...>) {
return std::make_tuple(
converter<typename std::decay<Args>::type>::from_mrb(mrb, argv[Is])...);
}
template <typename... Args>
std::tuple<typename std::decay<Args>::type...> get_args_helper(mrb_state *mrb) {
arena_guard guard(mrb);
auto argc = mrb_get_argc(mrb);
auto argv = mrb_get_argv(mrb);
if (argc != sizeof...(Args)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "argument count mismatch");
}
return get_args_helper_impl<typename std::decay<Args>::type...>(
mrb, argv, std::index_sequence_for<Args...>{});
}
template <typename T, typename Ret, typename... MethodArgs>
struct method_wrapper {
using MethodType = Ret (T::*)(MethodArgs...);
static mrb_value method_to_ptr(mrb_state* mrb, MethodType method) {
mrb_value bytes = mrb_str_new(mrb, NULL, sizeof(MethodType));
void* ptr = (void*)RSTRING_PTR(bytes);
memcpy(ptr, &method, sizeof(MethodType));
return bytes;
}
static MethodType ptr_to_method(void *ptr) {
MethodType method;
memcpy(&method, ptr, sizeof(MethodType));
return method;
}
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto args = get_args_helper<MethodArgs...>(mrb);
T *instance = static_cast<T *>(DATA_PTR(self));
if (!instance) {
mrb_raise(mrb, E_RUNTIME_ERROR, "invalid instance");
}
void *method_ptr = (void*)RSTRING_PTR(mrb_cfunc_env_get(mrb, 0));
auto method = ptr_to_method(method_ptr);
if constexpr (std::is_same_v<Ret, void>) {
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
return mrb_nil_value();
} else {
Ret result =
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
return converter<Ret>::to_mrb(mrb, result);
}
}
};
template <typename T, typename Ret, typename... MethodArgs>
struct const_method_wrapper {
using MethodType = Ret (T::*)(MethodArgs...) const;
static mrb_value method_to_ptr(mrb_state* mrb, MethodType method) {
mrb_value bytes = mrb_str_new(mrb, NULL, sizeof(MethodType));
void* ptr = (void*)RSTRING_PTR(bytes);
memcpy(ptr, &method, sizeof(MethodType));
return bytes;
}
static MethodType ptr_to_method(void *ptr) {
MethodType method;
memcpy(&method, ptr, sizeof(MethodType));
return method;
}
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto args = get_args_helper<MethodArgs...>(mrb);
T *instance = static_cast<T *>(DATA_PTR(self));
if (!instance) {
mrb_raise(mrb, E_RUNTIME_ERROR, "invalid instance");
}
void *method_ptr = (void*)RSTRING_PTR(mrb_cfunc_env_get(mrb, 0));
auto method = ptr_to_method(method_ptr);
if constexpr (std::is_same_v<Ret, void>) {
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
return mrb_nil_value();
} else {
Ret result =
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
return converter<Ret>::to_mrb(mrb, result);
}
}
};
template <typename T, typename Ret, typename... FuncArgs>
struct function_wrapper {
using FunctionType = Ret (*)(FuncArgs...);
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto func = (FunctionType)mrb_cptr(mrb_cfunc_env_get(mrb, 0));
auto args = get_args_helper<FuncArgs...>(mrb);
if constexpr (std::is_same_v<Ret, void>) {
std::apply(func, args);
return mrb_nil_value();
} else {
Ret result = std::apply(func, args);
return converter<Ret>::to_mrb(mrb, result);
}
}
};
template <typename T> class bind_class;
template <typename T, typename... Args> struct constructor_wrapper {
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto args = get_args_helper<Args...>(mrb);
T *instance = std::apply([](Args... args) { return new T(args...); }, args);
DATA_PTR(self) = instance;
DATA_TYPE(self) = &bind_class<T>::data_type;
return self;
}
};
template <typename T> class bind_class {
public:
static const struct mrb_data_type data_type;
bind_class(mrb_state *mrb, std::string name)
: mrb_(mrb), name_(std::move(name)) {
rclass_ = mrb_define_class(mrb_, name_.c_str(), mrb_->object_class);
MRB_SET_INSTANCE_TT(rclass_, MRB_TT_DATA);
mrb_define_method(
mrb_, rclass_, "initialize",
[](mrb_state *mrb, mrb_value self) -> mrb_value {
mrb_raise(mrb, E_NOTIMP_ERROR,
"initialize must be defined explicitly");
return self;
},
MRB_ARGS_ANY());
}
template <typename... Args> bind_class &def_constructor() {
mrb_define_method(mrb_, rclass_, "initialize",
&constructor_wrapper<T, Args...>::wrapper,
MRB_ARGS_REQ(sizeof...(Args)));
return *this;
}
bind_class& def_constructor(mrb_func_t func,
mrb_aspec aspec = MRB_ARGS_ANY()) {
return def_native("initialize", func, aspec);
}
template <typename Ret, typename... MethodArgs>
bind_class &def_method(const std::string &name,
Ret (T::*method)(MethodArgs...)) {
mrb_sym method_sym = mrb_intern_cstr(mrb_, name.c_str());
using wrap = method_wrapper<T, Ret, MethodArgs...>;
auto obj = wrap::method_to_ptr(mrb_, method);
mrb_gc_protect(mrb_, obj);
mrb_value env[] = { obj };
struct RProc *p = mrb_proc_new_cfunc_with_env(mrb_, &wrap::wrapper, 1, env);
mrb_method_t m;
MRB_METHOD_FROM_PROC(m, p);
mrb_define_method_raw(mrb_, rclass_, method_sym, m);
// MRB_ARGS_REQ(sizeof...(MethodArgs))
return *this;
}
template <typename Ret, typename... MethodArgs>
bind_class &def_method(const std::string &name,
Ret (T::*method)(MethodArgs...) const) {
mrb_sym method_sym = mrb_intern_cstr(mrb_, name.c_str());
using wrap = const_method_wrapper<T, Ret, MethodArgs...>;
auto obj = wrap::method_to_ptr(mrb_, method);
mrb_gc_protect(mrb_, obj);
mrb_value env[] = { obj };
struct RProc *p = mrb_proc_new_cfunc_with_env(mrb_, &wrap::wrapper, 1, env);
mrb_method_t m;
MRB_METHOD_FROM_PROC(m, p);
mrb_define_method_raw(mrb_, rclass_, method_sym, m);
return *this;
}
template <typename Ret, typename... FuncArgs>
bind_class &def_class_method(const std::string &name,
Ret (*func)(FuncArgs...)) {
using wrap = function_wrapper<T, Ret, FuncArgs...>;
mrb_value env[] = {
mrb_cptr_value(mrb_, (void *)func),
};
mrb_sym func_sym = mrb_intern_cstr(mrb_, name.c_str());
struct RProc *p = mrb_proc_new_cfunc_with_env(mrb_, &wrap::wrapper, 1, env);
mrb_method_t m;
MRB_METHOD_FROM_PROC(m, p);
// register and replace
mrb_define_class_method_id(mrb_, rclass_, func_sym, NULL, MRB_ARGS_ANY());
mrb_define_method_raw(mrb_, rclass_->c, func_sym, m);
return *this;
}
// get set
template <typename Ret>
bind_class &def_property(const std::string &name, Ret (T::*getter)() const,
void (T::*setter)(Ret)) {
def_method(name, getter);
def_method(name + "=", setter);
return *this;
}
bind_class &def_native(const std::string &name, mrb_func_t func,
mrb_aspec aspec = MRB_ARGS_ANY()) {
mrb_define_method(mrb_, rclass_, name.c_str(), func, aspec);
return *this;
}
[[nodiscard]] struct RClass *get_rclass() const { return rclass_; }
[[nodiscard]] static T *get_this(mrb_state *mrb, mrb_value value) {
T *instance = static_cast<T *>(DATA_PTR(value));
if (!instance) {
mrb_raise(mrb, E_RUNTIME_ERROR, "invalid instance");
}
return instance;
}
private:
static void free_instance(mrb_state *mrb, void *ptr) {
if (ptr) {
T *instance = static_cast<T *>(ptr);
delete instance;
}
}
mrb_state *mrb_;
struct RClass *rclass_;
std::string name_;
};
template <typename T> const struct mrb_data_type bind_class<T>::data_type = {
typeid(T).name(), bind_class<T>::free_instance
};
} // namespace mrubypp
#endif

View File

@@ -1,7 +1,7 @@
#ifndef MRUBYPP_STD_CONVERTERS_H #ifndef MRUBYPP_STD_CONVERTERS_H
#define MRUBYPP_STD_CONVERTERS_H #define MRUBYPP_STD_CONVERTERS_H
#include "mrubypp_arena_guard.h" #include "arena_guard.h"
#include <mruby.h> #include <mruby.h>
#include <mruby/array.h> #include <mruby/array.h>
@@ -9,7 +9,9 @@
#include <string> #include <string>
#include <vector> #include <vector>
template <typename T> struct mrubypp_converter { namespace mrubypp {
template <typename T> struct converter {
static mrb_value to_mrb(mrb_state *mrb, const T &var) { static mrb_value to_mrb(mrb_state *mrb, const T &var) {
static_assert(sizeof(T) == 0, "Specialization required"); static_assert(sizeof(T) == 0, "Specialization required");
return mrb_nil_value(); return mrb_nil_value();
@@ -20,7 +22,7 @@ template <typename T> struct mrubypp_converter {
} }
}; };
template <> struct mrubypp_converter<int> { template <> struct converter<int> {
static mrb_value to_mrb(mrb_state *mrb, int var) { static mrb_value to_mrb(mrb_state *mrb, int var) {
return mrb_fixnum_value(var); return mrb_fixnum_value(var);
} }
@@ -29,7 +31,7 @@ template <> struct mrubypp_converter<int> {
} }
}; };
template <> struct mrubypp_converter<unsigned int> { template <> struct converter<unsigned int> {
static mrb_value to_mrb(mrb_state *mrb, unsigned int var) { static mrb_value to_mrb(mrb_state *mrb, unsigned int var) {
return mrb_fixnum_value(var); return mrb_fixnum_value(var);
} }
@@ -38,7 +40,7 @@ template <> struct mrubypp_converter<unsigned int> {
} }
}; };
template <> struct mrubypp_converter<float> { template <> struct converter<float> {
static mrb_value to_mrb(mrb_state *mrb, float var) { static mrb_value to_mrb(mrb_state *mrb, float var) {
return mrb_float_value(mrb, var); return mrb_float_value(mrb, var);
} }
@@ -47,7 +49,7 @@ template <> struct mrubypp_converter<float> {
} }
}; };
template <> struct mrubypp_converter<double> { template <> struct converter<double> {
static mrb_value to_mrb(mrb_state *mrb, double var) { static mrb_value to_mrb(mrb_state *mrb, double var) {
return mrb_float_value(mrb, var); return mrb_float_value(mrb, var);
} }
@@ -56,7 +58,7 @@ template <> struct mrubypp_converter<double> {
} }
}; };
template <> struct mrubypp_converter<std::string> { template <> struct converter<std::string> {
static mrb_value to_mrb(mrb_state *mrb, const std::string &var) { static mrb_value to_mrb(mrb_state *mrb, const std::string &var) {
return mrb_str_new(mrb, var.data(), (mrb_int)var.size()); return mrb_str_new(mrb, var.data(), (mrb_int)var.size());
} }
@@ -69,34 +71,35 @@ template <> struct mrubypp_converter<std::string> {
} }
}; };
template <> struct mrubypp_converter<const char *> { template <> struct converter<const char *> {
static mrb_value to_mrb(mrb_state *mrb, const char *var) { static mrb_value to_mrb(mrb_state *mrb, const char *var) {
return mrb_str_new(mrb, var, (mrb_int)strlen(var)); return mrb_str_new(mrb, var, (mrb_int)strlen(var));
} }
}; };
template <typename T> struct mrubypp_converter<std::vector<T>> { template <typename T> struct converter<std::vector<T>> {
static mrb_value to_mrb(mrb_state *mrb, const std::vector<T> &var) { static mrb_value to_mrb(mrb_state *mrb, const std::vector<T> &var) {
mrb_value ary = mrb_ary_new_capa(mrb, static_cast<mrb_int>(var.size())); mrb_value ary = mrb_ary_new_capa(mrb, static_cast<mrb_int>(var.size()));
for (const T &el : var) { for (const T &el : var) {
mrubypp_arena_guard guard(mrb); arena_guard guard(mrb);
mrb_ary_push(mrb, ary, mrubypp_converter<T>::to_mrb(mrb, el)); mrb_ary_push(mrb, ary, converter<T>::to_mrb(mrb, el));
} }
return ary; return ary;
} }
static std::vector<T> from_mrb(mrb_state *mrb, mrb_value value) { static std::vector<T> from_mrb(mrb_state *mrb, mrb_value value) {
mrubypp_arena_guard guard(mrb); arena_guard guard(mrb);
std::vector<T> result; std::vector<T> result;
if (mrb_array_p(value)) { if (mrb_array_p(value)) {
int len = RARRAY_LEN(value); int len = RARRAY_LEN(value);
for (int i = 0; i < len; ++i) { for (int i = 0; i < len; ++i) {
result.append( result.append(converter<T>::from_mrb(mrb, mrb_ary_ref(mrb, value, i)));
mrubypp_converter<T>::from_mrb(mrb, mrb_ary_ref(mrb, value, i)));
} }
} }
return result; return result;
} }
}; };
} // namespace mrubypp
#endif // MRUBYPP_STD_CONVERTERS_H #endif // MRUBYPP_STD_CONVERTERS_H

60
include/mrubypp/engine.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef MRUBYPP_H
#define MRUBYPP_H
#include "arena_guard.h"
#include "converters.h"
#include <functional>
#include <mruby.h>
#include <string>
#include <mruby/compile.h>
namespace mrubypp {
class engine {
public:
engine() { mrb_ = mrb_open(); }
~engine() { mrb_close(mrb_); }
void load(const std::string &str) {
if (!mrb_)
return;
mrb_load_string(mrb_, str.c_str());
}
template <typename T, typename... Args>
typename std::enable_if<!std::is_same<T, void>::value, T>::type
call(const std::string &funcName, Args... args) {
arena_guard guard(mrb_);
if constexpr (sizeof...(Args) > 0) {
mrb_value argv[] = {converter<Args>::to_mrb(mrb_, args)...};
mrb_sym sym = mrb_intern_cstr(mrb_, funcName.data());
mrb_value result = mrb_funcall_argv(mrb_, mrb_top_self(mrb_), sym,
sizeof...(Args), argv);
return converter<T>::from_mrb(mrb_, result);
}
mrb_value result =
mrb_funcall(mrb_, mrb_top_self(mrb_), funcName.data(), 0);
return converter<T>::from_mrb(mrb_, result);
}
template <typename T = void, typename... Args>
typename std::enable_if<std::is_same<T, void>::value, T>::type
call(const std::string &funcName, Args... args) {
if constexpr (sizeof...(Args) > 0) {
arena_guard guard(mrb_);
mrb_value argv[] = {converter<Args>::to_mrb(mrb_, args)...};
mrb_sym sym = mrb_intern_cstr(mrb_, funcName.data());
mrb_funcall_argv(mrb_, mrb_top_self(mrb_), sym, sizeof...(Args), argv);
} else {
mrb_funcall(mrb_, mrb_top_self(mrb_), funcName.data(), 0);
}
}
[[nodiscard]] mrb_state *get_mrb() const { return mrb_; }
private:
mrb_state *mrb_;
};
} // namespace mrubypp
#endif // MRUBYPP_H

View File

@@ -1,52 +0,0 @@
#ifndef MRUBYPP_H
#define MRUBYPP_H
#include "mrubypp_arena_guard.h"
#include "mrubypp_bind_class.h"
#include "mrubypp_converters.h"
#include <functional>
#include <mruby.h>
#include <string>
#include <mruby/compile.h>
class mrubypp {
public:
mrubypp() { mrb_ = mrb_open(); }
~mrubypp() { mrb_close(mrb_); }
void load(const std::string &str) {
if (!mrb_)
return;
mrb_load_string(mrb_, str.c_str());
}
template <typename T> T call(const std::string &funcName) {
mrubypp_arena_guard guard(mrb_);
mrb_value result =
mrb_funcall(mrb_, mrb_top_self(mrb_), funcName.data(), 0);
return mrubypp_converter<T>::from_mrb(mrb_, result);
}
template <typename T, typename... Args>
T call(const std::string &funcName, Args... args) {
mrubypp_arena_guard guard(mrb_);
mrb_value argv[] = {mrubypp_converter<Args>::to_mrb(mrb_, args)...};
mrb_sym sym = mrb_intern_cstr(mrb_, funcName.data());
mrb_value result =
mrb_funcall_argv(mrb_, mrb_top_self(mrb_), sym, sizeof...(Args), argv);
return mrubypp_converter<T>::from_mrb(mrb_, result);
}
template <typename T>
mrubypp_class_builder<T> class_builder(const std::string &class_name) {
return mrubypp_class_builder<T>(mrb_, class_name);
}
[[nodiscard]] mrb_state *get_mrb() const { return mrb_; }
private:
mrb_state *mrb_;
};
#endif // MRUBYPP_H

View File

@@ -1,286 +0,0 @@
#ifndef MRUBYPP_BIND_CLASS
#define MRUBYPP_BIND_CLASS
#include "mrubypp_arena_guard.h"
#include "mrubypp_converters.h"
#include <functional>
#include <map>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <mruby/class.h>
#include <mruby/data.h>
#include <mruby/proc.h>
template<typename... Args, size_t... Is>
std::tuple<Args...> mrubypp_get_args_helper_impl(mrb_state *mrb,
const mrb_value *argv,
std::index_sequence<Is...>) {
return std::make_tuple(
mrubypp_converter<typename std::decay<Args>::type>::from_mrb(
mrb, argv[Is])...);
}
template<typename... Args>
std::tuple<typename std::decay<Args>::type...>
mrubypp_get_args_helper(mrb_state *mrb) {
mrubypp_arena_guard guard(mrb);
auto argc = mrb_get_argc(mrb);
auto argv = mrb_get_argv(mrb);
if (argc != sizeof...(Args)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "argument count mismatch");
}
return mrubypp_get_args_helper_impl<typename std::decay<Args>::type...>(
mrb, argv, std::index_sequence_for<Args...>{});
}
template<typename T, typename Ret, typename... MethodArgs>
struct mrubypp_method_wrapper {
using MethodType = Ret (T::*)(MethodArgs...);
// TODO, fix memory leak
static void *method_to_ptr(MethodType method) {
auto ptr = malloc(sizeof(MethodType));
memcpy(ptr, &method, sizeof(MethodType));
return ptr;
}
static MethodType ptr_to_method(void *ptr) {
MethodType method;
memcpy(&method, ptr, sizeof(MethodType));
return method;
}
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto args = mrubypp_get_args_helper<MethodArgs...>(mrb);
T *instance = static_cast<T *>(DATA_PTR(self));
if (!instance) {
mrb_raise(mrb, E_RUNTIME_ERROR, "invalid instance");
}
void *method_ptr = mrb_cptr(mrb_cfunc_env_get(mrb, 0));
auto method = ptr_to_method(method_ptr);
if constexpr (std::is_same_v<Ret, void>) {
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
return mrb_nil_value();
} else {
Ret result =
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
return mrubypp_converter<Ret>::to_mrb(mrb, result);
}
}
};
template<typename T, typename Ret, typename... MethodArgs>
struct mrubypp_const_method_wrapper {
using MethodType = Ret (T::*)(MethodArgs...) const;
// TODO, fix memory leak
static void *method_to_ptr(MethodType method) {
auto ptr = malloc(sizeof(MethodType));
memcpy(ptr, &method, sizeof(MethodType));
return ptr;
}
static MethodType ptr_to_method(void *ptr) {
MethodType method;
memcpy(&method, ptr, sizeof(MethodType));
return method;
}
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto args = mrubypp_get_args_helper<MethodArgs...>(mrb);
T *instance = static_cast<T *>(DATA_PTR(self));
if (!instance) {
mrb_raise(mrb, E_RUNTIME_ERROR, "invalid instance");
}
void *method_ptr = mrb_cptr(mrb_cfunc_env_get(mrb, 0));
auto method = ptr_to_method(method_ptr);
if constexpr (std::is_same_v<Ret, void>) {
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
} else {
Ret result =
std::apply(method, std::tuple_cat(std::make_tuple(instance), args));
return mrubypp_converter<Ret>::to_mrb(mrb, result);
}
}
};
template<typename T, typename Ret, typename... FuncArgs>
struct mrubypp_function_wrapper {
using FunctionType = Ret (*)(FuncArgs...);
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto func = (FunctionType) mrb_cptr(mrb_cfunc_env_get(mrb, 0));
auto args = mrubypp_get_args_helper<FuncArgs...>(mrb);
if constexpr (std::is_same_v<Ret, void>) {
std::apply(func, args);
return mrb_nil_value();
} else {
Ret result = std::apply(func, args);
return mrubypp_converter<Ret>::to_mrb(mrb, result);
}
}
};
template<typename T>
class mrubypp_class_builder;
template<typename T, typename... Args>
struct mrubypp_constructor_wrapper {
static mrb_value wrapper(mrb_state *mrb, mrb_value self) {
auto args = mrubypp_get_args_helper<Args...>(mrb);
T *instance = std::apply([](Args... args) { return new T(args...); }, args);
DATA_PTR(self) = instance;
DATA_TYPE(self) = &mrubypp_class_builder<T>::data_type;
return self;
}
};
template<typename T>
class mrubypp_class_builder {
public:
static const struct mrb_data_type data_type;
mrubypp_class_builder(mrb_state *mrb, std::string name)
: mrb_(mrb), name_(std::move(name)) {
rclass_ = mrb_define_class(mrb_, name_.c_str(), mrb_->object_class);
MRB_SET_INSTANCE_TT(rclass_, MRB_TT_DATA);
mrb_define_method(
mrb_, rclass_, "initialize",
[](mrb_state *mrb, mrb_value self) -> mrb_value {
mrb_raise(mrb, E_NOTIMP_ERROR,
"initialize must be defined explicitly");
return self;
},
MRB_ARGS_ANY());
}
template<typename... Args>
mrubypp_class_builder &def_constructor() {
mrb_define_method(mrb_, rclass_, "initialize",
&mrubypp_constructor_wrapper<T, Args...>::wrapper,
MRB_ARGS_REQ(sizeof...(Args)));
return *this;
}
template<typename Ret, typename... MethodArgs>
mrubypp_class_builder &def_method(const std::string &name,
Ret (T::*method)(MethodArgs...)) {
mrb_sym method_sym = mrb_intern_cstr(mrb_, name.c_str());
using wrap = mrubypp_method_wrapper<T, Ret, MethodArgs...>;
auto ptr = wrap::method_to_ptr(method);
mrb_value env[] = {
mrb_cptr_value(mrb_, ptr),
};
struct RProc *p = mrb_proc_new_cfunc_with_env(
mrb_, &wrap::wrapper, 1, env);
mrb_method_t m;
MRB_METHOD_FROM_PROC(m, p);
mrb_define_method_raw(mrb_, rclass_, method_sym, m);
// MRB_ARGS_REQ(sizeof...(MethodArgs))
return *this;
}
template<typename Ret, typename... MethodArgs>
mrubypp_class_builder &def_method(const std::string &name,
Ret (T::*method)(MethodArgs...) const) {
mrb_sym method_sym = mrb_intern_cstr(mrb_, name.c_str());
using wrap = mrubypp_const_method_wrapper<T, Ret, MethodArgs...>;
auto ptr = wrap::method_to_ptr(method);
auto obj = mrb_cptr_value(mrb_, ptr);
mrb_gc_protect(mrb_, obj);
mrb_value env[] = {
obj
};
struct RProc *p = mrb_proc_new_cfunc_with_env(
mrb_, &wrap::wrapper, 1,
env);
mrb_method_t m;
MRB_METHOD_FROM_PROC(m, p);
mrb_define_method_raw(mrb_, rclass_, method_sym, m);
return *this;
}
template<typename Ret, typename... FuncArgs>
mrubypp_class_builder &def_class_method(const std::string &name,
Ret (*func)(FuncArgs...)) {
using wrap = mrubypp_function_wrapper<T, Ret, FuncArgs...>;
mrb_value env[] = {
mrb_cptr_value(mrb_, (void *) func),
};
mrb_sym func_sym = mrb_intern_cstr(mrb_, name.c_str());
struct RProc *p = mrb_proc_new_cfunc_with_env(
mrb_, &wrap::wrapper, 1, env);
mrb_method_t m;
MRB_METHOD_FROM_PROC(m, p);
// register and replace
mrb_define_class_method_id(mrb_, rclass_, func_sym, NULL, MRB_ARGS_ANY());
mrb_define_method_raw(mrb_, rclass_->c, func_sym, m);
return *this;
}
// get set
template<typename Ret>
mrubypp_class_builder &def_property(const std::string &name,
Ret (T::*getter)() const,
void (T::*setter)(Ret)) {
def_method(name, getter);
def_method(name + "=", setter);
return *this;
}
mrubypp_class_builder &def_native(const std::string &name, mrb_func_t func,
mrb_aspec aspec = MRB_ARGS_ANY()) {
mrb_define_method(mrb_, rclass_, name.c_str(), func, aspec);
return *this;
}
[[nodiscard]] struct RClass *get_rclass() const { return rclass_; }
[[nodiscard]] static T *get_this(mrb_state *mrb, mrb_value value) {
T *instance = static_cast<T *>(DATA_PTR(value));
if (!instance) {
mrb_raise(mrb, E_RUNTIME_ERROR, "invalid instance");
}
return instance;
}
private:
static void free_instance(mrb_state *mrb, void *ptr) {
if (ptr) {
T *instance = static_cast<T *>(ptr);
delete instance;
}
}
mrb_state *mrb_;
struct RClass *rclass_;
std::string name_;
};
template<typename T>
const struct mrb_data_type mrubypp_class_builder<T>::data_type = {
typeid(T).name(), mrubypp_class_builder<T>::free_instance
};
#endif

View File

@@ -4,12 +4,12 @@
#include <catch2/catch_all.hpp> #include <catch2/catch_all.hpp>
#include "mrubypp.h" #include <mrubypp/bind_class.h>
#include <mrubypp/engine.h>
class Point { class Point {
public: public:
Point(int x, int y) : x_(x), y_(y) { Point(int x, int y) : x_(x), y_(y) {}
}
void set_values(int x, int y) { void set_values(int x, int y) {
x_ = x; x_ = x;
@@ -18,15 +18,11 @@ public:
int get_x() const { return x_; } int get_x() const { return x_; }
void set_x(int x) { void set_x(int x) { x_ = x; }
x_ = x;
}
int get_y() const { return y_; } int get_y() const { return y_; }
void set_y(int y) { void set_y(int y) { y_ = y; }
y_ = y;
}
void add(const Point &other) { void add(const Point &other) {
this->x_ += other.x_; this->x_ += other.x_;
@@ -40,20 +36,40 @@ private:
int y_; int y_;
}; };
static mrb_value point_native_init(mrb_state* mrb, mrb_value self) {
auto count = mrb_get_argc(mrb);
Point* p = nullptr;
if (count == 0)
{
p = new Point(0, 0);
} else if (count == 1) {
auto argv = mrb_get_argv(mrb);
p = new Point(mrb_as_float(mrb, argv[0]), 0);
} else if (count == 2) {
auto argv = mrb_get_argv(mrb);
p = new Point(mrb_as_float(mrb, argv[0]), mrb_as_float(mrb, argv[1]));
} else {
mrb_raise(mrb, E_ARGUMENT_ERROR, "Point wrong number of arguments");
}
DATA_PTR(self) = p;
DATA_TYPE(self) = &mrubypp::bind_class<Point>::data_type;
return self;
}
static mrb_value point_native_div(mrb_state *mrb, mrb_value self) { static mrb_value point_native_div(mrb_state *mrb, mrb_value self) {
auto point = mrubypp_class_builder<Point>::get_this(mrb, self); auto point = mrubypp::bind_class<Point>::get_this(mrb, self);
auto divisor = mrubypp_converter<int>::from_mrb(mrb, mrb_get_arg1(mrb)); auto divisor = mrubypp::converter<int>::from_mrb(mrb, mrb_get_arg1(mrb));
point->set_x(point->get_x() / divisor); point->set_x(point->get_x() / divisor);
point->set_y(point->get_y() / divisor); point->set_y(point->get_y() / divisor);
return self; return self;
} }
template<> template <> struct mrubypp::converter<Point> {
struct mrubypp_converter<Point> {
static mrb_value to_mrb(mrb_state *mrb, const Point &var) { static mrb_value to_mrb(mrb_state *mrb, const Point &var) {
mrb_value obj = mrb_obj_value( mrb_value obj = mrb_obj_value(
mrb_data_object_alloc(mrb, mrb->object_class, new Point(var), mrb_data_object_alloc(mrb, mrb->object_class, new Point(var),
&mrubypp_class_builder<Point>::data_type)); &mrubypp::bind_class<Point>::data_type));
return obj; return obj;
} }
@@ -66,16 +82,77 @@ struct mrubypp_converter<Point> {
} }
}; };
TEST_CASE("Point", "[class]") {
mrubypp engine; class Point3D {
engine.class_builder<Point>("Point") public:
.def_constructor<int, int>() Point3D(int x, int y, int z) : x_(x), y_(y), z_(z) {}
void set_values(int x, int y, int z) {
x_ = x;
y_ = y;
z_ = z;
}
int get_x() const { return x_; }
void set_x(int x) { x_ = x; }
int get_y() const { return y_; }
void set_y(int y) { y_ = y; }
int get_z() const { return z_; }
void set_z(int y) { z_ = y; }
void add(const Point3D& other) {
this->x_ += other.x_;
this->y_ += other.y_;
this->z_ += other.z_;
}
static int none() { return 1; }
private:
int x_;
int y_;
int z_;
};
template <> struct mrubypp::converter<Point3D> {
static mrb_value to_mrb(mrb_state* mrb, const Point3D& var) {
mrb_value obj = mrb_obj_value(
mrb_data_object_alloc(mrb, mrb->object_class, new Point3D(var),
&mrubypp::bind_class<Point3D>::data_type));
return obj;
}
static Point3D from_mrb(mrb_state* mrb, mrb_value value) {
if (mrb_type(value) == MRB_TT_DATA) {
Point3D* point = static_cast<Point3D*>(DATA_PTR(value));
return *point;
}
return Point3D(0, 0,0 );
}
};
TEST_CASE("bind_class", "[class]") {
mrubypp::engine engine;
mrubypp::bind_class<Point>(engine.get_mrb(), "Point")
.def_constructor(point_native_init, MRB_ARGS_OPT(2))
.def_method("add", &Point::add) .def_method("add", &Point::add)
.def_class_method("none", &Point::none) .def_class_method("none", &Point::none)
.def_property("x", &Point::get_x, &Point::set_x) .def_property("x", &Point::get_x, &Point::set_x)
.def_property("y", &Point::get_y, &Point::set_y) .def_property("y", &Point::get_y, &Point::set_y)
.def_native("div", point_native_div, MRB_ARGS_REQ(1)); .def_native("div", point_native_div, MRB_ARGS_REQ(1));
mrubypp::bind_class<Point3D>(engine.get_mrb(), "Point3D")
.def_constructor<int, int, int>()
.def_property("x", &Point3D::get_x, &Point3D::set_x)
.def_property("y", &Point3D::get_y, &Point3D::set_y)
.def_property("z", &Point3D::get_z, &Point3D::set_z);
engine.load(R"( engine.load(R"(
def test_method() def test_method()
p = Point.new(3, 4) p = Point.new(3, 4)
@@ -96,6 +173,14 @@ TEST_CASE("Point", "[class]") {
def test_class_method() def test_class_method()
return Point::none() return Point::none()
end end
def test_class_init()
return Point.new(10)
end
def test_point3d_init()
p = Point.new(10, 11)
p3d = Point3D.new(p.x, p.y, 5)
return p3d
end
)"); )");
SECTION("test_method") { SECTION("test_method") {
@@ -120,4 +205,17 @@ TEST_CASE("Point", "[class]") {
auto result = engine.call<int>("test_class_method"); auto result = engine.call<int>("test_class_method");
REQUIRE(result == 1); REQUIRE(result == 1);
} }
SECTION("test_class_init") {
auto result = engine.call<Point>("test_class_init");
REQUIRE(result.get_x() == 10);
REQUIRE(result.get_y() == 0);
}
SECTION("test_point3d_init") {
auto result = engine.call<Point3D>("test_point3d_init");
REQUIRE(result.get_x() == 10);
REQUIRE(result.get_y() == 11);
REQUIRE(result.get_z() == 5);
}
} }

View File

@@ -7,10 +7,10 @@
#include <catch2/catch_all.hpp> #include <catch2/catch_all.hpp>
#include "mrubypp.h" #include <mrubypp/engine.h>
TEST_CASE("none args call", "[engine]") { TEST_CASE("none args call", "[engine]") {
mrubypp engine; mrubypp::engine engine;
engine.load(R"( engine.load(R"(
def get_1() def get_1()
1 1
@@ -22,7 +22,7 @@ TEST_CASE("none args call", "[engine]") {
} }
TEST_CASE("args call", "[engine]") { TEST_CASE("args call", "[engine]") {
mrubypp engine; mrubypp::engine engine;
engine.load(R"( engine.load(R"(
def sort_and_get_first(a) def sort_and_get_first(a)
a.sort! a.sort!
@@ -35,8 +35,20 @@ TEST_CASE("args call", "[engine]") {
REQUIRE(b == 1); REQUIRE(b == 1);
} }
TEST_CASE("void call", "[engine]") {
mrubypp::engine engine;
engine.load(R"(
def no_return_puts(a)
puts a
end
)");
std::vector<int> a{3, 1, 2};
engine.call("no_return_puts", a);
}
TEST_CASE("call benchmark", "[!benchmark]") { TEST_CASE("call benchmark", "[!benchmark]") {
mrubypp engine; mrubypp::engine engine;
engine.load(R"( engine.load(R"(
def get_same(a) def get_same(a)
return a return a