rbxx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +72 -0
  3. data/exe/rbxx +6 -0
  4. data/ext-cmake/FindRuby.cmake +42 -0
  5. data/ext-cmake/rbxx-config.cmake +25 -0
  6. data/include/rbxx/arg.hpp +347 -0
  7. data/include/rbxx/callback.hpp +54 -0
  8. data/include/rbxx/class.hpp +630 -0
  9. data/include/rbxx/data_object.hpp +221 -0
  10. data/include/rbxx/detail/ruby_include.hpp +36 -0
  11. data/include/rbxx/exception.hpp +116 -0
  12. data/include/rbxx/extension.hpp +23 -0
  13. data/include/rbxx/function.hpp +463 -0
  14. data/include/rbxx/module.hpp +69 -0
  15. data/include/rbxx/nogvl.hpp +146 -0
  16. data/include/rbxx/object.hpp +68 -0
  17. data/include/rbxx/operators.hpp +31 -0
  18. data/include/rbxx/policies.hpp +36 -0
  19. data/include/rbxx/protect.hpp +72 -0
  20. data/include/rbxx/rbxx.hpp +20 -0
  21. data/include/rbxx/registry.hpp +145 -0
  22. data/include/rbxx/stl/array.hpp +40 -0
  23. data/include/rbxx/stl/chrono.hpp +26 -0
  24. data/include/rbxx/stl/detail.hpp +60 -0
  25. data/include/rbxx/stl/filesystem.hpp +21 -0
  26. data/include/rbxx/stl/map.hpp +49 -0
  27. data/include/rbxx/stl/optional.hpp +23 -0
  28. data/include/rbxx/stl/set.hpp +30 -0
  29. data/include/rbxx/stl/tuple.hpp +74 -0
  30. data/include/rbxx/stl/variant.hpp +35 -0
  31. data/include/rbxx/stl/vector.hpp +34 -0
  32. data/include/rbxx/stl.hpp +11 -0
  33. data/include/rbxx/type_caster.hpp +247 -0
  34. data/include/rbxx/value.hpp +63 -0
  35. data/include/rbxx/version.hpp +14 -0
  36. data/lib/rbxx/cli.rb +127 -0
  37. data/lib/rbxx/mkmf.rb +144 -0
  38. data/lib/rbxx/rake_tasks.rb +44 -0
  39. data/lib/rbxx/version.rb +5 -0
  40. data/lib/rbxx.rb +8 -0
  41. data/single_include/rbxx/rbxx.hpp +3211 -0
  42. data/templates/CMakeLists.txt.erb +14 -0
  43. data/templates/Gemfile.erb +10 -0
  44. data/templates/README.md.erb +11 -0
  45. data/templates/Rakefile.erb +23 -0
  46. data/templates/ci.yml.erb +21 -0
  47. data/templates/extconf.rb.erb +5 -0
  48. data/templates/extconf_cmake.rb.erb +5 -0
  49. data/templates/extension.cpp.erb +23 -0
  50. data/templates/gemspec.erb +16 -0
  51. data/templates/lib.rb.erb +4 -0
  52. data/templates/release.yml.erb +21 -0
  53. data/templates/test.rb.erb +12 -0
  54. data/templates/version.rb.erb +5 -0
  55. metadata +99 -0
@@ -0,0 +1,221 @@
1
+ #pragma once
2
+
3
+ #include <rbxx/policies.hpp>
4
+ #include <rbxx/registry.hpp>
5
+
6
+ #include <memory>
7
+ #include <string>
8
+ #include <type_traits>
9
+ #include <utility>
10
+
11
+ namespace rbxx {
12
+
13
+ /// @brief A Ruby VALUE member that remains marked and compaction-safe inside a C++ object.
14
+ /// @code rbxx::member_value child{ruby_child}; @endcode
15
+ class member_value {
16
+ public:
17
+ member_value() noexcept { rb_gc_register_address(&stored_); }
18
+ explicit member_value(value initial) noexcept : stored_(initial.raw()) {
19
+ rb_gc_register_address(&stored_);
20
+ }
21
+ explicit member_value(VALUE initial) noexcept : stored_(initial) {
22
+ rb_gc_register_address(&stored_);
23
+ }
24
+ member_value(const member_value& other) noexcept : stored_(other.stored_) {
25
+ rb_gc_register_address(&stored_);
26
+ }
27
+ member_value(member_value&& other) noexcept : stored_(other.stored_) {
28
+ rb_gc_register_address(&stored_);
29
+ other.stored_ = Qnil;
30
+ }
31
+ member_value& operator=(const member_value& other) noexcept {
32
+ stored_ = other.stored_;
33
+ return *this;
34
+ }
35
+ member_value& operator=(member_value&& other) noexcept {
36
+ stored_ = other.stored_;
37
+ other.stored_ = Qnil;
38
+ return *this;
39
+ }
40
+ ~member_value() noexcept { rb_gc_unregister_address(&stored_); }
41
+
42
+ [[nodiscard]] value get() const noexcept { return value{stored_}; }
43
+ void set(value updated) noexcept { stored_ = updated.raw(); }
44
+
45
+ private:
46
+ VALUE stored_ = Qnil;
47
+ };
48
+
49
+ namespace detail {
50
+
51
+ template <typename T> struct is_non_bindable_class : std::false_type {};
52
+ template <typename T> struct is_non_bindable_class<std::unique_ptr<T>> : std::true_type {};
53
+ template <typename T> struct is_non_bindable_class<std::shared_ptr<T>> : std::true_type {};
54
+
55
+ enum class ownership { owned, borrowed, shared };
56
+
57
+ template <typename T> struct data_wrapper {
58
+ T* pointer = nullptr;
59
+ ownership mode = ownership::borrowed;
60
+ std::shared_ptr<T> shared_owner;
61
+
62
+ ~data_wrapper() noexcept {
63
+ if (mode == ownership::owned) {
64
+ delete pointer;
65
+ }
66
+ }
67
+ };
68
+
69
+ template <typename T> void data_mark(void*) noexcept {}
70
+ template <typename T> void data_compact(void*) noexcept {}
71
+
72
+ template <typename T> void data_free(void* raw) noexcept {
73
+ delete static_cast<data_wrapper<T>*>(raw);
74
+ }
75
+
76
+ template <typename T> std::size_t data_size(const void* raw) noexcept {
77
+ const auto* wrapper = static_cast<const data_wrapper<T>*>(raw);
78
+ return sizeof(data_wrapper<T>) +
79
+ (wrapper != nullptr && wrapper->pointer != nullptr ? sizeof(T) : 0U);
80
+ }
81
+
82
+ template <typename T> rb_data_type_t& typed_data_type() {
83
+ static auto* name = new std::string("rbxx::" + std::string(type_name<T>()));
84
+ static rb_data_type_t type = {
85
+ name->c_str(),
86
+ {data_mark<T>, data_free<T>, data_size<T>, data_compact<T>, {nullptr}},
87
+ nullptr,
88
+ nullptr,
89
+ RUBY_TYPED_FREE_IMMEDIATELY,
90
+ };
91
+ return type;
92
+ }
93
+
94
+ template <typename T> value wrap_data(std::unique_ptr<data_wrapper<T>> wrapper) {
95
+ class_info& information = registered_class<T>();
96
+ VALUE result = protect(rb_data_typed_object_wrap, information.ruby_class,
97
+ static_cast<void*>(wrapper.get()), information.data_type);
98
+ wrapper.release();
99
+ return value{result};
100
+ }
101
+
102
+ template <typename T> value wrap_copy(const T& input) {
103
+ auto wrapper = std::make_unique<data_wrapper<T>>();
104
+ wrapper->pointer = new T(input);
105
+ wrapper->mode = ownership::owned;
106
+ return wrap_data(std::move(wrapper));
107
+ }
108
+
109
+ template <typename T> value wrap_move(T&& input) {
110
+ auto wrapper = std::make_unique<data_wrapper<T>>();
111
+ wrapper->pointer = new T(std::move(input));
112
+ wrapper->mode = ownership::owned;
113
+ return wrap_data(std::move(wrapper));
114
+ }
115
+
116
+ template <typename T> value wrap_reference(T* input) {
117
+ if (input == nullptr) {
118
+ return value{Qnil};
119
+ }
120
+ auto wrapper = std::make_unique<data_wrapper<T>>();
121
+ wrapper->pointer = input;
122
+ wrapper->mode = ownership::borrowed;
123
+ return wrap_data(std::move(wrapper));
124
+ }
125
+
126
+ template <typename T> value wrap_take(std::unique_ptr<T> input) {
127
+ if (!input) {
128
+ return value{Qnil};
129
+ }
130
+ auto wrapper = std::make_unique<data_wrapper<T>>();
131
+ wrapper->pointer = input.release();
132
+ wrapper->mode = ownership::owned;
133
+ return wrap_data(std::move(wrapper));
134
+ }
135
+
136
+ template <typename T> value wrap_shared(std::shared_ptr<T> input) {
137
+ if (!input) {
138
+ return value{Qnil};
139
+ }
140
+ auto wrapper = std::make_unique<data_wrapper<T>>();
141
+ wrapper->pointer = input.get();
142
+ wrapper->mode = ownership::shared;
143
+ wrapper->shared_owner = std::move(input);
144
+ return wrap_data(std::move(wrapper));
145
+ }
146
+
147
+ template <typename T, typename Base = void>
148
+ void register_data_class(VALUE ruby_class,
149
+ std::source_location location = std::source_location::current()) {
150
+ rb_data_type_t& type = typed_data_type<T>();
151
+ if constexpr (!std::is_void_v<Base>) {
152
+ type.parent = &typed_data_type<Base>();
153
+ }
154
+ type_registry::instance().add<T, Base>(ruby_class, &type, location);
155
+ type_registry::instance().set_native_pointer<T>(
156
+ [](void* raw) { return static_cast<void*>(static_cast<data_wrapper<T>*>(raw)->pointer); });
157
+ }
158
+
159
+ template <typename T> VALUE allocate_data_object(VALUE klass) {
160
+ VALUE result = Qnil;
161
+ VALUE pending = Qnil;
162
+ try {
163
+ auto wrapper = std::make_unique<data_wrapper<T>>();
164
+ result = protect(rb_data_typed_object_wrap, klass, static_cast<void*>(wrapper.get()),
165
+ &typed_data_type<T>());
166
+ wrapper.release();
167
+ } catch (...) {
168
+ pending = translate_current_exception();
169
+ }
170
+ if (!NIL_P(pending)) {
171
+ rb_exc_raise(pending);
172
+ }
173
+ return result;
174
+ }
175
+
176
+ template <typename T> data_wrapper<T>& exact_wrapper(value self) {
177
+ if (!RB_TYPE_P(self.raw(), T_DATA) || !RTYPEDDATA_P(self.raw()) ||
178
+ RTYPEDDATA_TYPE(self.raw()) != &typed_data_type<T>()) {
179
+ throw ruby_error(make_exception(rb_eTypeError, "rbxx: unexpected TypedData class"));
180
+ }
181
+ return *static_cast<data_wrapper<T>*>(RTYPEDDATA_DATA(self.raw()));
182
+ }
183
+
184
+ } // namespace detail
185
+
186
+ template <typename T>
187
+ struct type_caster<T, std::enable_if_t<std::is_class_v<T> && !std::is_same_v<T, object> &&
188
+ !detail::is_non_bindable_class<T>::value>> {
189
+ static constexpr std::string_view name = "bound C++ object";
190
+ static T& load(value input) { return detail::load_registered<T>(input); }
191
+ static value dump(const T& input) { return detail::wrap_copy(input); }
192
+ static value dump(T&& input) { return detail::wrap_move(std::move(input)); }
193
+ static bool matches(value input) noexcept { return detail::registered_matches<T>(input); }
194
+ };
195
+
196
+ template <typename T> struct type_caster<T*> {
197
+ static constexpr std::string_view name = "bound C++ object or nil";
198
+ static T* load(value input) {
199
+ return input.is_nil() ? nullptr : std::addressof(detail::load_registered<T>(input));
200
+ }
201
+ static value dump(T* input) { return detail::wrap_reference(input); }
202
+ static bool matches(value input) noexcept {
203
+ return input.is_nil() || detail::registered_matches<T>(input);
204
+ }
205
+ };
206
+
207
+ template <typename T> struct type_caster<std::unique_ptr<T>> {
208
+ static constexpr std::string_view name = "owned C++ object";
209
+ static value dump(std::unique_ptr<T> input) { return detail::wrap_take(std::move(input)); }
210
+ static bool matches(value) noexcept { return false; }
211
+ };
212
+
213
+ template <typename T> struct type_caster<std::shared_ptr<T>> {
214
+ static constexpr std::string_view name = "shared C++ object";
215
+ static value dump(std::shared_ptr<T> input) { return detail::wrap_shared(std::move(input)); }
216
+ static bool matches(value input) noexcept {
217
+ return RB_TYPE_P(input.raw(), T_DATA) && RTYPEDDATA_P(input.raw());
218
+ }
219
+ };
220
+
221
+ } // namespace rbxx
@@ -0,0 +1,36 @@
1
+ #pragma once
2
+
3
+ #if defined(_MSC_VER)
4
+ #ifndef NOMINMAX
5
+ #define NOMINMAX
6
+ #endif
7
+ #pragma warning(push)
8
+ #pragma warning(disable : 4127 4244 4267 4996)
9
+ #else
10
+ #pragma GCC diagnostic push
11
+ #pragma GCC diagnostic ignored "-Wconversion"
12
+ #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
13
+ #pragma GCC diagnostic ignored "-Wsign-conversion"
14
+ #pragma GCC diagnostic ignored "-Wunused-parameter"
15
+ #endif
16
+
17
+ #include <ruby.h>
18
+ #include <ruby/thread.h>
19
+ #include <ruby/version.h>
20
+
21
+ #if defined(_MSC_VER)
22
+ #pragma warning(pop)
23
+ #else
24
+ #pragma GCC diagnostic pop
25
+ #endif
26
+
27
+ #define RBXX_RUBY_VERSION_MAJOR RUBY_API_VERSION_MAJOR
28
+ #define RBXX_RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
29
+ #define RBXX_RUBY_VERSION_TEENY RUBY_API_VERSION_TEENY
30
+
31
+ #if RUBY_API_VERSION_MAJOR < 4
32
+ // CRuby exports this debug helper before 4.0 but does not declare it in ruby/thread.h.
33
+ extern "C" int ruby_thread_has_gvl_p(void);
34
+ #endif
35
+
36
+ static_assert(RUBY_API_VERSION_MAJOR >= 3, "rbxx requires CRuby 3.1 or newer");
@@ -0,0 +1,116 @@
1
+ #pragma once
2
+
3
+ #include <rbxx/object.hpp>
4
+
5
+ #include <exception>
6
+ #include <new>
7
+ #include <stdexcept>
8
+ #include <string>
9
+
10
+ namespace rbxx {
11
+
12
+ /// @brief A C++ exception that preserves the original Ruby exception object.
13
+ /// @code catch (const rbxx::ruby_error& error) { error.reraise(); } @endcode
14
+ class ruby_error : public std::exception {
15
+ public:
16
+ /// @brief Pins a Ruby exception for propagation through C++ frames.
17
+ explicit ruby_error(value exception) : exception_(exception) {}
18
+ explicit ruby_error(VALUE exception) : exception_(exception) {}
19
+
20
+ /// @brief Returns a stable description for C++ diagnostics.
21
+ [[nodiscard]] const char* what() const noexcept override { return "Ruby exception"; }
22
+
23
+ /// @brief Returns the preserved Ruby exception.
24
+ [[nodiscard]] value exception() const noexcept { return exception_.get(); }
25
+
26
+ /// @brief Returns the Ruby exception class name.
27
+ [[nodiscard]] std::string ruby_class_name() const;
28
+
29
+ /// @brief Returns the Ruby exception message.
30
+ [[nodiscard]] std::string message() const;
31
+
32
+ /// @brief Throws a copy so the outer Ruby boundary can transparently re-raise it.
33
+ [[noreturn]] void reraise() const { throw *this; }
34
+
35
+ private:
36
+ object exception_;
37
+ };
38
+
39
+ namespace detail {
40
+
41
+ inline VALUE exception_class_name(VALUE exception) { return rb_class_name(CLASS_OF(exception)); }
42
+
43
+ inline VALUE exception_message(VALUE exception) {
44
+ VALUE message = rb_funcall(exception, rb_intern("message"), 0);
45
+ return rb_obj_as_string(message);
46
+ }
47
+
48
+ inline std::string protected_exception_string(VALUE exception, VALUE (*function)(VALUE)) {
49
+ int state = 0;
50
+ VALUE string = rb_protect(function, exception, &state);
51
+ if (state != 0) {
52
+ VALUE nested = rb_errinfo();
53
+ rb_set_errinfo(Qnil);
54
+ throw ruby_error(nested);
55
+ }
56
+ return std::string(RSTRING_PTR(string), static_cast<std::size_t>(RSTRING_LEN(string)));
57
+ }
58
+
59
+ struct exception_creation {
60
+ VALUE klass;
61
+ const char* message;
62
+ };
63
+
64
+ inline VALUE create_exception(VALUE opaque) {
65
+ auto* creation = reinterpret_cast<exception_creation*>(opaque);
66
+ return rb_exc_new_cstr(creation->klass, creation->message);
67
+ }
68
+
69
+ inline VALUE make_exception(VALUE klass, const char* message) noexcept {
70
+ exception_creation creation{klass, message};
71
+ int state = 0;
72
+ VALUE result = rb_protect(create_exception, reinterpret_cast<VALUE>(&creation), &state);
73
+ if (state == 0) {
74
+ return result;
75
+ }
76
+
77
+ result = rb_errinfo();
78
+ rb_set_errinfo(Qnil);
79
+ return result;
80
+ }
81
+
82
+ /// @brief Converts the active C++ exception into a Ruby exception VALUE without raising it.
83
+ /// @code catch (...) { pending = rbxx::detail::translate_current_exception(); } @endcode
84
+ inline VALUE translate_current_exception() noexcept {
85
+ try {
86
+ throw;
87
+ } catch (const ruby_error& error) {
88
+ return error.exception().raw();
89
+ } catch (const std::invalid_argument& error) {
90
+ return make_exception(rb_eArgError, error.what());
91
+ } catch (const std::out_of_range& error) {
92
+ return make_exception(rb_eRangeError, error.what());
93
+ } catch (const std::range_error& error) {
94
+ return make_exception(rb_eRangeError, error.what());
95
+ } catch (const std::bad_alloc& error) {
96
+ return make_exception(rb_eNoMemError, error.what());
97
+ } catch (const std::domain_error& error) {
98
+ return make_exception(rb_eMathDomainError, error.what());
99
+ } catch (const std::exception& error) {
100
+ return make_exception(rb_eRuntimeError, error.what());
101
+ } catch (...) {
102
+ return make_exception(rb_eRuntimeError, "unknown C++ exception");
103
+ }
104
+ }
105
+
106
+ } // namespace detail
107
+
108
+ inline std::string ruby_error::ruby_class_name() const {
109
+ return detail::protected_exception_string(exception_.raw(), detail::exception_class_name);
110
+ }
111
+
112
+ inline std::string ruby_error::message() const {
113
+ return detail::protected_exception_string(exception_.raw(), detail::exception_message);
114
+ }
115
+
116
+ } // namespace rbxx
@@ -0,0 +1,23 @@
1
+ #pragma once
2
+
3
+ #include <rbxx/exception.hpp>
4
+
5
+ #if defined(_WIN32)
6
+ #define RBXX_EXPORT __declspec(dllexport)
7
+ #else
8
+ #define RBXX_EXPORT __attribute__((visibility("default")))
9
+ #endif
10
+
11
+ #define RBXX_EXTENSION(name) \
12
+ static void rbxx_init_body_##name(); \
13
+ extern "C" RBXX_EXPORT void Init_##name() { \
14
+ VALUE rbxx_pending_exception = Qnil; \
15
+ try { \
16
+ rbxx_init_body_##name(); \
17
+ return; \
18
+ } catch (...) { \
19
+ rbxx_pending_exception = ::rbxx::detail::translate_current_exception(); \
20
+ } \
21
+ rb_exc_raise(rbxx_pending_exception); \
22
+ } \
23
+ static void rbxx_init_body_##name()