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.
- checksums.yaml +7 -0
- data/README.md +72 -0
- data/exe/rbxx +6 -0
- data/ext-cmake/FindRuby.cmake +42 -0
- data/ext-cmake/rbxx-config.cmake +25 -0
- data/include/rbxx/arg.hpp +347 -0
- data/include/rbxx/callback.hpp +54 -0
- data/include/rbxx/class.hpp +630 -0
- data/include/rbxx/data_object.hpp +221 -0
- data/include/rbxx/detail/ruby_include.hpp +36 -0
- data/include/rbxx/exception.hpp +116 -0
- data/include/rbxx/extension.hpp +23 -0
- data/include/rbxx/function.hpp +463 -0
- data/include/rbxx/module.hpp +69 -0
- data/include/rbxx/nogvl.hpp +146 -0
- data/include/rbxx/object.hpp +68 -0
- data/include/rbxx/operators.hpp +31 -0
- data/include/rbxx/policies.hpp +36 -0
- data/include/rbxx/protect.hpp +72 -0
- data/include/rbxx/rbxx.hpp +20 -0
- data/include/rbxx/registry.hpp +145 -0
- data/include/rbxx/stl/array.hpp +40 -0
- data/include/rbxx/stl/chrono.hpp +26 -0
- data/include/rbxx/stl/detail.hpp +60 -0
- data/include/rbxx/stl/filesystem.hpp +21 -0
- data/include/rbxx/stl/map.hpp +49 -0
- data/include/rbxx/stl/optional.hpp +23 -0
- data/include/rbxx/stl/set.hpp +30 -0
- data/include/rbxx/stl/tuple.hpp +74 -0
- data/include/rbxx/stl/variant.hpp +35 -0
- data/include/rbxx/stl/vector.hpp +34 -0
- data/include/rbxx/stl.hpp +11 -0
- data/include/rbxx/type_caster.hpp +247 -0
- data/include/rbxx/value.hpp +63 -0
- data/include/rbxx/version.hpp +14 -0
- data/lib/rbxx/cli.rb +127 -0
- data/lib/rbxx/mkmf.rb +144 -0
- data/lib/rbxx/rake_tasks.rb +44 -0
- data/lib/rbxx/version.rb +5 -0
- data/lib/rbxx.rb +8 -0
- data/single_include/rbxx/rbxx.hpp +3211 -0
- data/templates/CMakeLists.txt.erb +14 -0
- data/templates/Gemfile.erb +10 -0
- data/templates/README.md.erb +11 -0
- data/templates/Rakefile.erb +23 -0
- data/templates/ci.yml.erb +21 -0
- data/templates/extconf.rb.erb +5 -0
- data/templates/extconf_cmake.rb.erb +5 -0
- data/templates/extension.cpp.erb +23 -0
- data/templates/gemspec.erb +16 -0
- data/templates/lib.rb.erb +4 -0
- data/templates/release.yml.erb +21 -0
- data/templates/test.rb.erb +12 -0
- data/templates/version.rb.erb +5 -0
- metadata +99 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/module.hpp>
|
|
4
|
+
|
|
5
|
+
#include <array>
|
|
6
|
+
#include <cstddef>
|
|
7
|
+
#include <iterator>
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <optional>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
#include <string>
|
|
12
|
+
#include <tuple>
|
|
13
|
+
#include <type_traits>
|
|
14
|
+
#include <utility>
|
|
15
|
+
|
|
16
|
+
namespace rbxx {
|
|
17
|
+
|
|
18
|
+
/// @brief Constructor signature marker used by class_::def.
|
|
19
|
+
/// @code binding.def(rbxx::init<int>()); @endcode
|
|
20
|
+
template <typename... Args> struct init_tag {};
|
|
21
|
+
|
|
22
|
+
/// @brief Creates a constructor signature marker.
|
|
23
|
+
template <typename... Args> [[nodiscard]] constexpr init_tag<Args...> init() noexcept { return {}; }
|
|
24
|
+
|
|
25
|
+
namespace detail {
|
|
26
|
+
|
|
27
|
+
template <typename Method> struct member_method_traits;
|
|
28
|
+
|
|
29
|
+
template <typename Class, typename Return, typename... Args>
|
|
30
|
+
struct member_method_traits<Return (Class::*)(Args...)> : function_traits<Return(Args...)> {
|
|
31
|
+
using class_type = Class;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
template <typename Class, typename Return, typename... Args>
|
|
35
|
+
struct member_method_traits<Return (Class::*)(Args...) const> : function_traits<Return(Args...)> {
|
|
36
|
+
using class_type = Class;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
template <typename Class, typename Return, typename... Args>
|
|
40
|
+
struct member_method_traits<Return (Class::*)(Args...) noexcept>
|
|
41
|
+
: function_traits<Return(Args...)> {
|
|
42
|
+
using class_type = Class;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
template <typename Class, typename Return, typename... Args>
|
|
46
|
+
struct member_method_traits<Return (Class::*)(Args...) const noexcept>
|
|
47
|
+
: function_traits<Return(Args...)> {
|
|
48
|
+
using class_type = Class;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
inline void define_instance_trampoline(VALUE klass, const char* name) {
|
|
52
|
+
protect([klass, name] { rb_define_method(klass, name, function_trampoline, -1); });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
struct fast_zero_slot {
|
|
56
|
+
VALUE (*invoke)(void*, VALUE);
|
|
57
|
+
void* context;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
inline constexpr std::size_t fast_zero_slot_count = 64U;
|
|
61
|
+
|
|
62
|
+
inline std::array<fast_zero_slot, fast_zero_slot_count>& fast_zero_slots() {
|
|
63
|
+
static auto* slots = new std::array<fast_zero_slot, fast_zero_slot_count>{};
|
|
64
|
+
return *slots;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
template <std::size_t Index> VALUE fast_zero_trampoline(VALUE self) {
|
|
68
|
+
VALUE result = Qnil;
|
|
69
|
+
VALUE pending = Qnil;
|
|
70
|
+
try {
|
|
71
|
+
const fast_zero_slot& slot = fast_zero_slots()[Index];
|
|
72
|
+
result = slot.invoke(slot.context, self);
|
|
73
|
+
} catch (...) {
|
|
74
|
+
pending = translate_current_exception();
|
|
75
|
+
}
|
|
76
|
+
if (!NIL_P(pending)) {
|
|
77
|
+
rb_exc_raise(pending);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
using fast_zero_method = VALUE (*)(VALUE);
|
|
83
|
+
|
|
84
|
+
template <std::size_t Index = 0U> fast_zero_method select_fast_zero_method(std::size_t selected) {
|
|
85
|
+
if constexpr (Index < fast_zero_slot_count) {
|
|
86
|
+
if (selected == Index) {
|
|
87
|
+
return &fast_zero_trampoline<Index>;
|
|
88
|
+
}
|
|
89
|
+
return select_fast_zero_method<Index + 1U>(selected);
|
|
90
|
+
}
|
|
91
|
+
return nullptr;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
inline std::optional<std::size_t> add_fast_zero_slot(fast_zero_slot slot) {
|
|
95
|
+
static std::size_t next = 0U;
|
|
96
|
+
if (next == fast_zero_slot_count) {
|
|
97
|
+
return std::nullopt;
|
|
98
|
+
}
|
|
99
|
+
const std::size_t selected = next++;
|
|
100
|
+
fast_zero_slots()[selected] = slot;
|
|
101
|
+
return selected;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
template <typename Bound> Bound& load_bound_fast(VALUE self) {
|
|
105
|
+
if (RB_TYPE_P(self, T_DATA) && RTYPEDDATA_P(self) &&
|
|
106
|
+
RTYPEDDATA_TYPE(self) == &typed_data_type<Bound>()) {
|
|
107
|
+
auto& wrapper = *static_cast<data_wrapper<Bound>*>(RTYPEDDATA_DATA(self));
|
|
108
|
+
if (wrapper.pointer == nullptr) {
|
|
109
|
+
throw ruby_error(make_exception(rb_eRuntimeError, "rbxx: C++ object is not initialized"));
|
|
110
|
+
}
|
|
111
|
+
return *wrapper.pointer;
|
|
112
|
+
}
|
|
113
|
+
return load_registered<Bound>(value{self});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
template <typename Bound, typename Method> struct fast_member_context {
|
|
117
|
+
Method method;
|
|
118
|
+
policy::kind selected;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
template <typename Bound, typename Method> VALUE invoke_fast_member(void* opaque, VALUE self) {
|
|
122
|
+
auto& context = *static_cast<fast_member_context<Bound, Method>*>(opaque);
|
|
123
|
+
Bound& native = load_bound_fast<Bound>(self);
|
|
124
|
+
using traits = member_method_traits<Method>;
|
|
125
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
126
|
+
std::invoke(context.method, native);
|
|
127
|
+
return Qnil;
|
|
128
|
+
} else {
|
|
129
|
+
decltype(auto) result = std::invoke(context.method, native);
|
|
130
|
+
return dump_result(std::forward<decltype(result)>(result), context.selected).raw();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
template <typename Bound, typename Method>
|
|
135
|
+
bool define_fast_member(VALUE klass, const char* name, Method method, policy::kind selected) {
|
|
136
|
+
auto context = std::make_unique<fast_member_context<Bound, Method>>(method, selected);
|
|
137
|
+
const auto slot =
|
|
138
|
+
add_fast_zero_slot(fast_zero_slot{&invoke_fast_member<Bound, Method>, context.get()});
|
|
139
|
+
if (!slot) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
fast_zero_method trampoline = select_fast_zero_method(*slot);
|
|
143
|
+
protect([klass, name, trampoline] { rb_define_method(klass, name, trampoline, 0); });
|
|
144
|
+
context.release();
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
template <typename Bound, auto Method> VALUE direct_member_trampoline(VALUE self) {
|
|
149
|
+
VALUE result = Qnil;
|
|
150
|
+
VALUE pending = Qnil;
|
|
151
|
+
try {
|
|
152
|
+
Bound& native = load_bound_fast<Bound>(self);
|
|
153
|
+
using traits = member_method_traits<decltype(Method)>;
|
|
154
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
155
|
+
std::invoke(Method, native);
|
|
156
|
+
} else {
|
|
157
|
+
decltype(auto) native_result = std::invoke(Method, native);
|
|
158
|
+
result =
|
|
159
|
+
dump_result(std::forward<decltype(native_result)>(native_result), policy::kind::automatic)
|
|
160
|
+
.raw();
|
|
161
|
+
}
|
|
162
|
+
} catch (...) {
|
|
163
|
+
pending = translate_current_exception();
|
|
164
|
+
}
|
|
165
|
+
if (!NIL_P(pending)) {
|
|
166
|
+
rb_exc_raise(pending);
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
template <typename Bound, auto Method> void define_direct_member(VALUE klass, const char* name) {
|
|
172
|
+
constexpr fast_zero_method trampoline = &direct_member_trampoline<Bound, Method>;
|
|
173
|
+
protect([klass, name] { rb_define_method(klass, name, trampoline, 0); });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
template <typename T, typename... Args> class constructor_function final : public native_function {
|
|
177
|
+
public:
|
|
178
|
+
explicit constructor_function(std::vector<argument_spec> specs = {})
|
|
179
|
+
: parser_(std::move(specs)) {}
|
|
180
|
+
|
|
181
|
+
[[nodiscard]] value invoke(int argc, const VALUE* argv, value self) override {
|
|
182
|
+
parsed_arguments parsed = parser_.parse(argc, argv);
|
|
183
|
+
auto converted = load(parsed, std::index_sequence_for<Args...>{});
|
|
184
|
+
auto& wrapper = exact_wrapper<T>(self);
|
|
185
|
+
if (wrapper.pointer != nullptr) {
|
|
186
|
+
throw ruby_error(make_exception(rb_eRuntimeError, "rbxx: C++ object is already initialized"));
|
|
187
|
+
}
|
|
188
|
+
wrapper.pointer = std::apply(
|
|
189
|
+
[](auto&&... arguments) { return new T(std::forward<decltype(arguments)>(arguments)...); },
|
|
190
|
+
converted);
|
|
191
|
+
wrapper.mode = ownership::owned;
|
|
192
|
+
return value{Qnil};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
[[nodiscard]] std::string signature() const override {
|
|
196
|
+
return "init<" + std::string(type_name<T>()) + ">";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override {
|
|
200
|
+
return parser_.configured() ? argc <= static_cast<int>(sizeof...(Args) + 1U)
|
|
201
|
+
: argc == static_cast<int>(sizeof...(Args));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[[nodiscard]] int match_score(int argc, const VALUE* argv) const noexcept override {
|
|
205
|
+
if (parser_.configured()) {
|
|
206
|
+
return accepts_arity(argc) ? 0 : -1;
|
|
207
|
+
}
|
|
208
|
+
using tuple = std::tuple<Args...>;
|
|
209
|
+
return tuple_match_score<tuple>(argc, argv, std::index_sequence_for<Args...>{});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override { return sizeof...(Args); }
|
|
213
|
+
|
|
214
|
+
private:
|
|
215
|
+
template <std::size_t... Index>
|
|
216
|
+
static auto load(const parsed_arguments& parsed, std::index_sequence<Index...>) {
|
|
217
|
+
return std::tuple<decltype(load_parsed_argument<Args>(parsed, Index))...>{
|
|
218
|
+
load_parsed_argument<Args>(parsed, Index)...};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
argument_parser<std::tuple<Args...>> parser_;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
template <typename Bound, typename Method> class member_function final : public native_function {
|
|
225
|
+
public:
|
|
226
|
+
member_function(Method method, policy::kind selected, std::vector<argument_spec> specs = {},
|
|
227
|
+
std::vector<keep_alive_spec> keep_alive = {})
|
|
228
|
+
: method_(method), policy_(selected), parser_(std::move(specs)),
|
|
229
|
+
keep_alive_(std::move(keep_alive)) {}
|
|
230
|
+
|
|
231
|
+
[[nodiscard]] value invoke(int argc, const VALUE* argv, value self) override {
|
|
232
|
+
using traits = member_method_traits<Method>;
|
|
233
|
+
parsed_arguments parsed = parser_.parse(argc, argv);
|
|
234
|
+
Bound& native = load_registered<Bound>(self);
|
|
235
|
+
value result = invoke_native(native, parsed, std::make_index_sequence<traits::arity>{});
|
|
236
|
+
apply_keep_alive(keep_alive_, result, self, argc, argv);
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
[[nodiscard]] std::string signature() const override { return std::string(type_name<Method>()); }
|
|
241
|
+
|
|
242
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override {
|
|
243
|
+
constexpr auto arity = member_method_traits<Method>::arity;
|
|
244
|
+
return parser_.configured() ? argc <= static_cast<int>(arity + 1U)
|
|
245
|
+
: argc == static_cast<int>(arity);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
[[nodiscard]] int match_score(int argc, const VALUE* argv) const noexcept override {
|
|
249
|
+
using traits = member_method_traits<Method>;
|
|
250
|
+
if (parser_.configured()) {
|
|
251
|
+
return accepts_arity(argc) ? 0 : -1;
|
|
252
|
+
}
|
|
253
|
+
return tuple_match_score<typename traits::args_tuple>(
|
|
254
|
+
argc, argv, std::make_index_sequence<traits::arity>{});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override {
|
|
258
|
+
return member_method_traits<Method>::arity;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private:
|
|
262
|
+
template <std::size_t... Index>
|
|
263
|
+
[[nodiscard]] value invoke_native(Bound& self, const parsed_arguments& parsed,
|
|
264
|
+
std::index_sequence<Index...>) {
|
|
265
|
+
using traits = member_method_traits<Method>;
|
|
266
|
+
using args = typename traits::args_tuple;
|
|
267
|
+
auto converted = std::tuple<decltype(load_parsed_argument<std::tuple_element_t<Index, args>>(
|
|
268
|
+
parsed, Index))...>{
|
|
269
|
+
load_parsed_argument<std::tuple_element_t<Index, args>>(parsed, Index)...};
|
|
270
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
271
|
+
std::apply([&](auto&&... values) { std::invoke(method_, self, values...); }, converted);
|
|
272
|
+
return value{Qnil};
|
|
273
|
+
} else {
|
|
274
|
+
decltype(auto) result = std::apply(
|
|
275
|
+
[&](auto&&... values) -> decltype(auto) { return std::invoke(method_, self, values...); },
|
|
276
|
+
converted);
|
|
277
|
+
return dump_result(std::forward<decltype(result)>(result), policy_);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Method method_;
|
|
282
|
+
policy::kind policy_;
|
|
283
|
+
argument_parser<typename member_method_traits<Method>::args_tuple> parser_;
|
|
284
|
+
std::vector<keep_alive_spec> keep_alive_;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
template <typename Tuple, std::size_t... Index>
|
|
288
|
+
consteval bool self_arguments_convertible(std::index_sequence<Index...>) {
|
|
289
|
+
return (from_ruby_convertible<std::tuple_element_t<Index + 1U, Tuple>> && ...);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
template <typename Tuple, std::size_t... Index>
|
|
293
|
+
auto tuple_tail_type(std::index_sequence<Index...>)
|
|
294
|
+
-> std::tuple<std::tuple_element_t<Index + 1U, Tuple>...>;
|
|
295
|
+
|
|
296
|
+
template <typename Tuple>
|
|
297
|
+
using tuple_tail_t =
|
|
298
|
+
decltype(tuple_tail_type<Tuple>(std::make_index_sequence<std::tuple_size_v<Tuple> - 1U>{}));
|
|
299
|
+
|
|
300
|
+
template <typename Bound, typename Function> class self_function final : public native_function {
|
|
301
|
+
public:
|
|
302
|
+
using traits = resolved_function_traits<Function>;
|
|
303
|
+
using args = typename traits::args_tuple;
|
|
304
|
+
static constexpr std::size_t ruby_arity = traits::arity - 1U;
|
|
305
|
+
|
|
306
|
+
self_function(Function function, policy::kind selected, std::vector<argument_spec> specs = {},
|
|
307
|
+
std::vector<keep_alive_spec> keep_alive = {})
|
|
308
|
+
: function_(std::move(function)), policy_(selected), parser_(std::move(specs)),
|
|
309
|
+
keep_alive_(std::move(keep_alive)) {
|
|
310
|
+
static_assert(traits::arity > 0,
|
|
311
|
+
"rbxx: instance lambda must accept self as its first argument");
|
|
312
|
+
using self_argument = std::tuple_element_t<0, args>;
|
|
313
|
+
static_assert(std::is_lvalue_reference_v<self_argument> &&
|
|
314
|
+
std::is_same_v<std::remove_cvref_t<self_argument>, Bound>,
|
|
315
|
+
"rbxx: instance lambda first argument must be T& or const T&");
|
|
316
|
+
static_assert(self_arguments_convertible<args>(std::make_index_sequence<ruby_arity>{}),
|
|
317
|
+
"rbxx: instance lambda argument type has no type_caster");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
[[nodiscard]] value invoke(int argc, const VALUE* argv, value self) override {
|
|
321
|
+
parsed_arguments parsed = parser_.parse(argc, argv);
|
|
322
|
+
Bound& native = load_registered<Bound>(self);
|
|
323
|
+
value result = invoke_native(native, parsed, std::make_index_sequence<ruby_arity>{});
|
|
324
|
+
apply_keep_alive(keep_alive_, result, self, argc, argv);
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
[[nodiscard]] std::string signature() const override {
|
|
329
|
+
return std::string(type_name<Function>());
|
|
330
|
+
}
|
|
331
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override {
|
|
332
|
+
return parser_.configured() ? argc <= static_cast<int>(ruby_arity + 1U)
|
|
333
|
+
: argc == static_cast<int>(ruby_arity);
|
|
334
|
+
}
|
|
335
|
+
[[nodiscard]] int match_score(int argc, const VALUE* argv) const noexcept override {
|
|
336
|
+
if (parser_.configured()) {
|
|
337
|
+
return accepts_arity(argc) ? 0 : -1;
|
|
338
|
+
}
|
|
339
|
+
using ruby_args = tuple_tail_t<args>;
|
|
340
|
+
return tuple_match_score<ruby_args>(argc, argv, std::make_index_sequence<ruby_arity>{});
|
|
341
|
+
}
|
|
342
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override { return ruby_arity; }
|
|
343
|
+
|
|
344
|
+
private:
|
|
345
|
+
template <std::size_t... Index>
|
|
346
|
+
[[nodiscard]] value invoke_native(Bound& self, const parsed_arguments& parsed,
|
|
347
|
+
std::index_sequence<Index...>) {
|
|
348
|
+
auto converted =
|
|
349
|
+
std::tuple<decltype(load_parsed_argument<std::tuple_element_t<Index + 1U, args>>(
|
|
350
|
+
parsed, Index))...>{
|
|
351
|
+
load_parsed_argument<std::tuple_element_t<Index + 1U, args>>(parsed, Index)...};
|
|
352
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
353
|
+
std::apply([&](auto&&... values) { std::invoke(function_, self, values...); }, converted);
|
|
354
|
+
return value{Qnil};
|
|
355
|
+
} else {
|
|
356
|
+
decltype(auto) result = std::apply(
|
|
357
|
+
[&](auto&&... values) -> decltype(auto) {
|
|
358
|
+
return std::invoke(function_, self, values...);
|
|
359
|
+
},
|
|
360
|
+
converted);
|
|
361
|
+
return dump_result(std::forward<decltype(result)>(result), policy_);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
Function function_;
|
|
366
|
+
policy::kind policy_;
|
|
367
|
+
argument_parser<tuple_tail_t<args>> parser_;
|
|
368
|
+
std::vector<keep_alive_spec> keep_alive_;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
template <typename Bound, auto Begin, auto End>
|
|
372
|
+
class iterable_function final : public native_function {
|
|
373
|
+
public:
|
|
374
|
+
[[nodiscard]] value invoke(int, const VALUE*, value self) override {
|
|
375
|
+
if (rb_block_given_p() == 0) {
|
|
376
|
+
VALUE enumerator = protect([self] {
|
|
377
|
+
return rb_enumeratorize_with_size(self.raw(), ID2SYM(rb_intern("each")), 0, nullptr,
|
|
378
|
+
size_callback);
|
|
379
|
+
});
|
|
380
|
+
return value{enumerator};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
Bound& native = load_registered<Bound>(self);
|
|
384
|
+
auto iterator = std::invoke(Begin, native);
|
|
385
|
+
const auto finish = std::invoke(End, native);
|
|
386
|
+
for (; iterator != finish; ++iterator) {
|
|
387
|
+
value item = to_ruby(*iterator);
|
|
388
|
+
protect(rb_yield, item.raw());
|
|
389
|
+
}
|
|
390
|
+
return self;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
[[nodiscard]] std::string signature() const override { return "each()"; }
|
|
394
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override { return argc == 0; }
|
|
395
|
+
[[nodiscard]] int match_score(int argc, const VALUE*) const noexcept override {
|
|
396
|
+
return argc == 0 ? 0 : -1;
|
|
397
|
+
}
|
|
398
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override { return 0U; }
|
|
399
|
+
|
|
400
|
+
private:
|
|
401
|
+
static VALUE size_callback(VALUE self, VALUE, VALUE) {
|
|
402
|
+
VALUE result = Qnil;
|
|
403
|
+
VALUE pending = Qnil;
|
|
404
|
+
try {
|
|
405
|
+
Bound& native = load_registered<Bound>(value{self});
|
|
406
|
+
auto first = std::invoke(Begin, native);
|
|
407
|
+
auto last = std::invoke(End, native);
|
|
408
|
+
result = to_ruby(std::distance(first, last)).raw();
|
|
409
|
+
} catch (...) {
|
|
410
|
+
pending = translate_current_exception();
|
|
411
|
+
}
|
|
412
|
+
if (!NIL_P(pending)) {
|
|
413
|
+
rb_exc_raise(pending);
|
|
414
|
+
}
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
} // namespace detail
|
|
420
|
+
|
|
421
|
+
/// @brief Fluent binding handle for a C++ class exposed as Ruby TypedData.
|
|
422
|
+
/// @code binding.def(rbxx::init<int>()).def("value", &Counter::value); @endcode
|
|
423
|
+
template <typename T> class class_ {
|
|
424
|
+
public:
|
|
425
|
+
explicit class_(value ruby_class) noexcept : ruby_class_(ruby_class) {}
|
|
426
|
+
|
|
427
|
+
[[nodiscard]] value get() const noexcept { return ruby_class_; }
|
|
428
|
+
|
|
429
|
+
/// @brief Defines a compile-time-bound zero-argument member on the minimal dispatch path.
|
|
430
|
+
/// @code binding.def<&Counter::value>("value"); @endcode
|
|
431
|
+
template <auto Method> class_& def(const char* name) {
|
|
432
|
+
using method_type = decltype(Method);
|
|
433
|
+
static_assert(std::is_member_function_pointer_v<method_type>,
|
|
434
|
+
"rbxx: compile-time def requires a member function pointer");
|
|
435
|
+
using traits = detail::member_method_traits<method_type>;
|
|
436
|
+
static_assert(traits::arity == 0U,
|
|
437
|
+
"rbxx: compile-time def currently supports zero-argument members");
|
|
438
|
+
static_assert(std::is_void_v<typename traits::return_type> ||
|
|
439
|
+
to_ruby_convertible<typename traits::return_type>,
|
|
440
|
+
"rbxx: member function return type has no type_caster");
|
|
441
|
+
|
|
442
|
+
ID method = protect(rb_intern, name);
|
|
443
|
+
const bool first = detail::method_registry::instance().add(
|
|
444
|
+
ruby_class_.raw(), method,
|
|
445
|
+
std::make_unique<detail::member_function<T, method_type>>(Method, policy::kind::automatic));
|
|
446
|
+
if (first) {
|
|
447
|
+
detail::define_direct_member<T, Method>(ruby_class_.raw(), name);
|
|
448
|
+
} else {
|
|
449
|
+
detail::define_instance_trampoline(ruby_class_.raw(), name);
|
|
450
|
+
}
|
|
451
|
+
return *this;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/// @brief Defines a C++ constructor and optional Ruby argument annotations.
|
|
455
|
+
template <typename... Args, typename... Specs>
|
|
456
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
457
|
+
class_& def(init_tag<Args...>, Specs&&... specs) {
|
|
458
|
+
static_assert(sizeof...(Specs) == 0U ||
|
|
459
|
+
sizeof...(Specs) == detail::normal_argument_count<std::tuple<Args...>>(),
|
|
460
|
+
"rbxx: argument annotation count must match constructor parameters");
|
|
461
|
+
static_assert((from_ruby_convertible<Args> && ...),
|
|
462
|
+
"rbxx: constructor argument type has no type_caster");
|
|
463
|
+
ID method = protect(rb_intern, "initialize");
|
|
464
|
+
const bool first = detail::method_registry::instance().add(
|
|
465
|
+
ruby_class_.raw(), method,
|
|
466
|
+
std::make_unique<detail::constructor_function<T, Args...>>(
|
|
467
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...)));
|
|
468
|
+
if (first) {
|
|
469
|
+
detail::define_instance_trampoline(ruby_class_.raw(), "initialize");
|
|
470
|
+
}
|
|
471
|
+
return *this;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/// @brief Defines an instance method backed by a member pointer or self-first callable.
|
|
475
|
+
template <typename Function, typename... Specs>
|
|
476
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
477
|
+
class_& def(const char* name, Function&& function, Specs&&... specs) {
|
|
478
|
+
return bind_function(name, std::forward<Function>(function), policy::automatic,
|
|
479
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
template <typename Function, typename... Specs>
|
|
483
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
484
|
+
class_& def(const char* name, Function&& function, policy::return_value_policy selected,
|
|
485
|
+
Specs&&... specs) {
|
|
486
|
+
return bind_function(name, std::forward<Function>(function), selected,
|
|
487
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
template <typename Function, std::size_t Nurse, std::size_t Patient, typename... Specs>
|
|
491
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
492
|
+
class_& def(const char* name, Function&& function, policy::return_value_policy selected,
|
|
493
|
+
keep_alive<Nurse, Patient> lifetime, Specs&&... specs) {
|
|
494
|
+
return bind_function(name, std::forward<Function>(function), selected,
|
|
495
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...),
|
|
496
|
+
{detail::make_keep_alive_spec(lifetime)});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
template <typename Function, std::size_t Nurse, std::size_t Patient, typename... Specs>
|
|
500
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
501
|
+
class_& def(const char* name, Function&& function, keep_alive<Nurse, Patient> lifetime,
|
|
502
|
+
Specs&&... specs) {
|
|
503
|
+
return bind_function(name, std::forward<Function>(function), policy::automatic,
|
|
504
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...),
|
|
505
|
+
{detail::make_keep_alive_spec(lifetime)});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
template <typename Function, typename... Specs>
|
|
509
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
510
|
+
class_& def(op::name operation, Function&& function, Specs&&... specs) {
|
|
511
|
+
class_& result =
|
|
512
|
+
def(operation.ruby_name, std::forward<Function>(function), std::forward<Specs>(specs)...);
|
|
513
|
+
include_comparable(operation);
|
|
514
|
+
return result;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
template <typename Function, typename... Specs>
|
|
518
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
519
|
+
class_& def(op::name operation, Function&& function, policy::return_value_policy selected,
|
|
520
|
+
Specs&&... specs) {
|
|
521
|
+
class_& result = def(operation.ruby_name, std::forward<Function>(function), selected,
|
|
522
|
+
std::forward<Specs>(specs)...);
|
|
523
|
+
include_comparable(operation);
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/// @brief Defines a singleton method on the bound Ruby class.
|
|
528
|
+
template <typename Function, typename... Specs>
|
|
529
|
+
class_& def_static(const char* name, Function&& function, Specs&&... specs) {
|
|
530
|
+
detail::register_static_function(ruby_class_.raw(), name, std::forward<Function>(function),
|
|
531
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...));
|
|
532
|
+
return *this;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/// @brief Defines a Ruby reader for a public C++ data member.
|
|
536
|
+
template <typename Member> class_& def_attr_reader(const char* name, Member T::*member) {
|
|
537
|
+
return def(name, [member](const T& self) -> const Member& { return self.*member; });
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/// @brief Defines a Ruby writer for a public C++ data member.
|
|
541
|
+
template <typename Member> class_& def_attr_writer(const char* name, Member T::*member) {
|
|
542
|
+
std::string writer = std::string(name) + "=";
|
|
543
|
+
return def(writer.c_str(), [member](T& self, Member updated) {
|
|
544
|
+
self.*member = std::move(updated);
|
|
545
|
+
return self.*member;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/// @brief Defines Ruby reader and writer methods for a public C++ data member.
|
|
550
|
+
template <typename Member> class_& def_attr_accessor(const char* name, Member T::*member) {
|
|
551
|
+
def_attr_reader(name, member);
|
|
552
|
+
return def_attr_writer(name, member);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/// @brief Defines Ruby each, includes Enumerable, and returns sized enumerators without a block.
|
|
556
|
+
template <auto Begin, auto End> class_& def_iterable() {
|
|
557
|
+
using item_reference = decltype(*std::invoke(Begin, std::declval<T&>()));
|
|
558
|
+
static_assert(to_ruby_convertible<item_reference>,
|
|
559
|
+
"rbxx: iterable item type has no type_caster");
|
|
560
|
+
ID method = protect(rb_intern, "each");
|
|
561
|
+
const bool first = detail::method_registry::instance().add(
|
|
562
|
+
ruby_class_.raw(), method, std::make_unique<detail::iterable_function<T, Begin, End>>());
|
|
563
|
+
if (first) {
|
|
564
|
+
detail::define_instance_trampoline(ruby_class_.raw(), "each");
|
|
565
|
+
}
|
|
566
|
+
protect(rb_include_module, ruby_class_.raw(), rb_mEnumerable);
|
|
567
|
+
return *this;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private:
|
|
571
|
+
void include_comparable(op::name operation) {
|
|
572
|
+
if (operation.include_comparable) {
|
|
573
|
+
protect(rb_include_module, ruby_class_.raw(), rb_mComparable);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
template <typename Function>
|
|
578
|
+
class_& bind_function(const char* name, Function&& function, policy::return_value_policy selected,
|
|
579
|
+
std::vector<argument_spec> specs,
|
|
580
|
+
std::vector<detail::keep_alive_spec> keep_alive = {}) {
|
|
581
|
+
using stored_function = std::decay_t<Function>;
|
|
582
|
+
ID method = protect(rb_intern, name);
|
|
583
|
+
if constexpr (std::is_member_function_pointer_v<stored_function>) {
|
|
584
|
+
using traits = detail::member_method_traits<stored_function>;
|
|
585
|
+
static_assert(detail::arguments_convertible<typename traits::args_tuple>(
|
|
586
|
+
std::make_index_sequence<traits::arity>{}),
|
|
587
|
+
"rbxx: member function argument type has no type_caster");
|
|
588
|
+
const bool fast = traits::arity == 0U && specs.empty() && keep_alive.empty();
|
|
589
|
+
const bool first = detail::method_registry::instance().add(
|
|
590
|
+
ruby_class_.raw(), method,
|
|
591
|
+
std::make_unique<detail::member_function<T, stored_function>>(
|
|
592
|
+
function, selected.value, std::move(specs), std::move(keep_alive)));
|
|
593
|
+
bool defined_fast = false;
|
|
594
|
+
if constexpr (traits::arity == 0U) {
|
|
595
|
+
defined_fast =
|
|
596
|
+
first && fast &&
|
|
597
|
+
detail::define_fast_member<T>(ruby_class_.raw(), name, function, selected.value);
|
|
598
|
+
}
|
|
599
|
+
if (!defined_fast) {
|
|
600
|
+
detail::define_instance_trampoline(ruby_class_.raw(), name);
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
static_assert(detail::function_signature<stored_function>,
|
|
604
|
+
"rbxx: instance callable signature cannot be determined");
|
|
605
|
+
detail::method_registry::instance().add(
|
|
606
|
+
ruby_class_.raw(), method,
|
|
607
|
+
std::make_unique<detail::self_function<T, stored_function>>(
|
|
608
|
+
std::forward<Function>(function), selected.value, std::move(specs),
|
|
609
|
+
std::move(keep_alive)));
|
|
610
|
+
detail::define_instance_trampoline(ruby_class_.raw(), name);
|
|
611
|
+
}
|
|
612
|
+
return *this;
|
|
613
|
+
}
|
|
614
|
+
value ruby_class_;
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
template <typename T, typename Base>
|
|
618
|
+
class_<T> module::def_class(const char* name, std::source_location location) {
|
|
619
|
+
VALUE superclass = rb_cObject;
|
|
620
|
+
if constexpr (!std::is_void_v<Base>) {
|
|
621
|
+
static_assert(std::is_base_of_v<Base, T>, "rbxx: Base must be a C++ base class of T");
|
|
622
|
+
superclass = detail::registered_class<Base>().ruby_class;
|
|
623
|
+
}
|
|
624
|
+
VALUE klass = protect(rb_define_class_under, wrapped_.raw(), name, superclass);
|
|
625
|
+
detail::register_data_class<T, Base>(klass, location);
|
|
626
|
+
protect([klass] { rb_define_alloc_func(klass, detail::allocate_data_object<T>); });
|
|
627
|
+
return class_<T>{value{klass}};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
} // namespace rbxx
|