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,3211 @@
|
|
|
1
|
+
// Generated by scripts/amalgamate.rb. Do not edit directly.
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
// BEGIN rbxx/detail/ruby_include.hpp
|
|
5
|
+
|
|
6
|
+
#if defined(_MSC_VER)
|
|
7
|
+
#ifndef NOMINMAX
|
|
8
|
+
#define NOMINMAX
|
|
9
|
+
#endif
|
|
10
|
+
#pragma warning(push)
|
|
11
|
+
#pragma warning(disable : 4127 4244 4267 4996)
|
|
12
|
+
#else
|
|
13
|
+
#pragma GCC diagnostic push
|
|
14
|
+
#pragma GCC diagnostic ignored "-Wconversion"
|
|
15
|
+
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
16
|
+
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
|
17
|
+
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
#include <ruby.h>
|
|
21
|
+
#include <ruby/thread.h>
|
|
22
|
+
#include <ruby/version.h>
|
|
23
|
+
|
|
24
|
+
#if defined(_MSC_VER)
|
|
25
|
+
#pragma warning(pop)
|
|
26
|
+
#else
|
|
27
|
+
#pragma GCC diagnostic pop
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
#define RBXX_RUBY_VERSION_MAJOR RUBY_API_VERSION_MAJOR
|
|
31
|
+
#define RBXX_RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
|
|
32
|
+
#define RBXX_RUBY_VERSION_TEENY RUBY_API_VERSION_TEENY
|
|
33
|
+
|
|
34
|
+
#if RUBY_API_VERSION_MAJOR < 4
|
|
35
|
+
// CRuby exports this debug helper before 4.0 but does not declare it in ruby/thread.h.
|
|
36
|
+
extern "C" int ruby_thread_has_gvl_p(void);
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
static_assert(RUBY_API_VERSION_MAJOR >= 3, "rbxx requires CRuby 3.1 or newer");
|
|
40
|
+
// END rbxx/detail/ruby_include.hpp
|
|
41
|
+
|
|
42
|
+
// BEGIN rbxx/value.hpp
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
#include <type_traits>
|
|
46
|
+
|
|
47
|
+
namespace rbxx {
|
|
48
|
+
|
|
49
|
+
/// @brief A zero-overhead, non-owning view of a Ruby VALUE.
|
|
50
|
+
/// @code rbxx::value nil{Qnil}; @endcode
|
|
51
|
+
class value {
|
|
52
|
+
public:
|
|
53
|
+
/// @brief Constructs a nil value.
|
|
54
|
+
constexpr value() noexcept = default;
|
|
55
|
+
|
|
56
|
+
/// @brief Wraps a raw Ruby VALUE without pinning it.
|
|
57
|
+
/// @code auto wrapped = rbxx::value{Qtrue}; @endcode
|
|
58
|
+
explicit constexpr value(VALUE raw) noexcept : raw_(raw) {}
|
|
59
|
+
|
|
60
|
+
/// @brief Returns the wrapped Ruby VALUE.
|
|
61
|
+
/// @code VALUE raw = wrapped.raw(); @endcode
|
|
62
|
+
[[nodiscard]] constexpr VALUE raw() const noexcept { return raw_; }
|
|
63
|
+
|
|
64
|
+
/// @brief Returns whether this value is nil.
|
|
65
|
+
/// @code if (wrapped.is_nil()) { return; } @endcode
|
|
66
|
+
[[nodiscard]] constexpr bool is_nil() const noexcept { return NIL_P(raw_); }
|
|
67
|
+
|
|
68
|
+
/// @brief Returns whether this value is a Ruby boolean.
|
|
69
|
+
[[nodiscard]] constexpr bool is_bool() const noexcept { return raw_ == Qtrue || raw_ == Qfalse; }
|
|
70
|
+
|
|
71
|
+
/// @brief Returns whether this value is a Ruby Integer.
|
|
72
|
+
[[nodiscard]] bool is_integer() const noexcept { return RB_INTEGER_TYPE_P(raw_); }
|
|
73
|
+
|
|
74
|
+
/// @brief Returns whether this value is a Ruby Float.
|
|
75
|
+
[[nodiscard]] bool is_float() const noexcept { return RB_TYPE_P(raw_, T_FLOAT); }
|
|
76
|
+
|
|
77
|
+
/// @brief Returns whether this value is a Ruby String.
|
|
78
|
+
[[nodiscard]] bool is_string() const noexcept { return RB_TYPE_P(raw_, T_STRING); }
|
|
79
|
+
|
|
80
|
+
/// @brief Returns whether this value is a Ruby Symbol.
|
|
81
|
+
[[nodiscard]] bool is_symbol() const noexcept { return SYMBOL_P(raw_); }
|
|
82
|
+
|
|
83
|
+
/// @brief Returns whether this value is a Ruby Array.
|
|
84
|
+
[[nodiscard]] bool is_array() const noexcept { return RB_TYPE_P(raw_, T_ARRAY); }
|
|
85
|
+
|
|
86
|
+
/// @brief Returns whether this value is a Ruby Hash.
|
|
87
|
+
[[nodiscard]] bool is_hash() const noexcept { return RB_TYPE_P(raw_, T_HASH); }
|
|
88
|
+
|
|
89
|
+
private:
|
|
90
|
+
VALUE raw_ = Qnil;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
static_assert(std::is_trivially_copyable_v<value>);
|
|
94
|
+
static_assert(sizeof(value) == sizeof(VALUE));
|
|
95
|
+
|
|
96
|
+
/// @brief Keeps a temporary Ruby value visible to the conservative GC.
|
|
97
|
+
/// @code rbxx::gc_guard(value); @endcode
|
|
98
|
+
inline void gc_guard(value guarded) noexcept {
|
|
99
|
+
VALUE raw = guarded.raw();
|
|
100
|
+
RB_GC_GUARD(raw);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
} // namespace rbxx
|
|
104
|
+
// END rbxx/value.hpp
|
|
105
|
+
|
|
106
|
+
// BEGIN rbxx/object.hpp
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
#include <memory>
|
|
110
|
+
#include <utility>
|
|
111
|
+
|
|
112
|
+
namespace rbxx {
|
|
113
|
+
namespace detail {
|
|
114
|
+
|
|
115
|
+
class object_cell {
|
|
116
|
+
public:
|
|
117
|
+
explicit object_cell(VALUE initial) noexcept : stored_(initial) {
|
|
118
|
+
rb_gc_register_address(&stored_);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
object_cell(const object_cell&) = delete;
|
|
122
|
+
object_cell& operator=(const object_cell&) = delete;
|
|
123
|
+
|
|
124
|
+
~object_cell() noexcept { rb_gc_unregister_address(&stored_); }
|
|
125
|
+
|
|
126
|
+
[[nodiscard]] VALUE raw() const noexcept { return stored_; }
|
|
127
|
+
|
|
128
|
+
private:
|
|
129
|
+
VALUE stored_;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
} // namespace detail
|
|
133
|
+
|
|
134
|
+
/// @brief A copyable, movable RAII handle that pins a Ruby object across GC and compaction.
|
|
135
|
+
/// @code rbxx::object pinned{rbxx::value{ruby_value}}; @endcode
|
|
136
|
+
class object {
|
|
137
|
+
public:
|
|
138
|
+
/// @brief Constructs an empty handle representing nil.
|
|
139
|
+
object() noexcept = default;
|
|
140
|
+
|
|
141
|
+
/// @brief Pins the supplied Ruby value.
|
|
142
|
+
/// @code rbxx::object pinned{rbxx::value{Qtrue}}; @endcode
|
|
143
|
+
explicit object(value initial) : cell_(std::make_shared<detail::object_cell>(initial.raw())) {}
|
|
144
|
+
|
|
145
|
+
/// @brief Pins the supplied raw Ruby VALUE.
|
|
146
|
+
explicit object(VALUE initial) : object(value{initial}) {}
|
|
147
|
+
|
|
148
|
+
object(const object&) noexcept = default;
|
|
149
|
+
object(object&&) noexcept = default;
|
|
150
|
+
object& operator=(const object&) noexcept = default;
|
|
151
|
+
object& operator=(object&&) noexcept = default;
|
|
152
|
+
~object() = default;
|
|
153
|
+
|
|
154
|
+
/// @brief Returns the pinned object as a non-owning value view.
|
|
155
|
+
/// @code rbxx::value current = pinned.get(); @endcode
|
|
156
|
+
[[nodiscard]] value get() const noexcept { return value{raw()}; }
|
|
157
|
+
|
|
158
|
+
/// @brief Returns the current raw VALUE, updated after compaction.
|
|
159
|
+
[[nodiscard]] VALUE raw() const noexcept { return cell_ ? cell_->raw() : Qnil; }
|
|
160
|
+
|
|
161
|
+
/// @brief Returns whether the handle is empty or pins nil.
|
|
162
|
+
[[nodiscard]] bool is_nil() const noexcept { return NIL_P(raw()); }
|
|
163
|
+
|
|
164
|
+
/// @brief Releases this handle's ownership of the pin.
|
|
165
|
+
/// @code pinned.reset(); @endcode
|
|
166
|
+
void reset() noexcept { cell_.reset(); }
|
|
167
|
+
|
|
168
|
+
private:
|
|
169
|
+
std::shared_ptr<detail::object_cell> cell_;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
} // namespace rbxx
|
|
173
|
+
// END rbxx/object.hpp
|
|
174
|
+
|
|
175
|
+
// BEGIN rbxx/exception.hpp
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
#include <exception>
|
|
179
|
+
#include <new>
|
|
180
|
+
#include <stdexcept>
|
|
181
|
+
#include <string>
|
|
182
|
+
|
|
183
|
+
namespace rbxx {
|
|
184
|
+
|
|
185
|
+
/// @brief A C++ exception that preserves the original Ruby exception object.
|
|
186
|
+
/// @code catch (const rbxx::ruby_error& error) { error.reraise(); } @endcode
|
|
187
|
+
class ruby_error : public std::exception {
|
|
188
|
+
public:
|
|
189
|
+
/// @brief Pins a Ruby exception for propagation through C++ frames.
|
|
190
|
+
explicit ruby_error(value exception) : exception_(exception) {}
|
|
191
|
+
explicit ruby_error(VALUE exception) : exception_(exception) {}
|
|
192
|
+
|
|
193
|
+
/// @brief Returns a stable description for C++ diagnostics.
|
|
194
|
+
[[nodiscard]] const char* what() const noexcept override { return "Ruby exception"; }
|
|
195
|
+
|
|
196
|
+
/// @brief Returns the preserved Ruby exception.
|
|
197
|
+
[[nodiscard]] value exception() const noexcept { return exception_.get(); }
|
|
198
|
+
|
|
199
|
+
/// @brief Returns the Ruby exception class name.
|
|
200
|
+
[[nodiscard]] std::string ruby_class_name() const;
|
|
201
|
+
|
|
202
|
+
/// @brief Returns the Ruby exception message.
|
|
203
|
+
[[nodiscard]] std::string message() const;
|
|
204
|
+
|
|
205
|
+
/// @brief Throws a copy so the outer Ruby boundary can transparently re-raise it.
|
|
206
|
+
[[noreturn]] void reraise() const { throw *this; }
|
|
207
|
+
|
|
208
|
+
private:
|
|
209
|
+
object exception_;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
namespace detail {
|
|
213
|
+
|
|
214
|
+
inline VALUE exception_class_name(VALUE exception) { return rb_class_name(CLASS_OF(exception)); }
|
|
215
|
+
|
|
216
|
+
inline VALUE exception_message(VALUE exception) {
|
|
217
|
+
VALUE message = rb_funcall(exception, rb_intern("message"), 0);
|
|
218
|
+
return rb_obj_as_string(message);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
inline std::string protected_exception_string(VALUE exception, VALUE (*function)(VALUE)) {
|
|
222
|
+
int state = 0;
|
|
223
|
+
VALUE string = rb_protect(function, exception, &state);
|
|
224
|
+
if (state != 0) {
|
|
225
|
+
VALUE nested = rb_errinfo();
|
|
226
|
+
rb_set_errinfo(Qnil);
|
|
227
|
+
throw ruby_error(nested);
|
|
228
|
+
}
|
|
229
|
+
return std::string(RSTRING_PTR(string), static_cast<std::size_t>(RSTRING_LEN(string)));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
struct exception_creation {
|
|
233
|
+
VALUE klass;
|
|
234
|
+
const char* message;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
inline VALUE create_exception(VALUE opaque) {
|
|
238
|
+
auto* creation = reinterpret_cast<exception_creation*>(opaque);
|
|
239
|
+
return rb_exc_new_cstr(creation->klass, creation->message);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
inline VALUE make_exception(VALUE klass, const char* message) noexcept {
|
|
243
|
+
exception_creation creation{klass, message};
|
|
244
|
+
int state = 0;
|
|
245
|
+
VALUE result = rb_protect(create_exception, reinterpret_cast<VALUE>(&creation), &state);
|
|
246
|
+
if (state == 0) {
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
result = rb_errinfo();
|
|
251
|
+
rb_set_errinfo(Qnil);
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// @brief Converts the active C++ exception into a Ruby exception VALUE without raising it.
|
|
256
|
+
/// @code catch (...) { pending = rbxx::detail::translate_current_exception(); } @endcode
|
|
257
|
+
inline VALUE translate_current_exception() noexcept {
|
|
258
|
+
try {
|
|
259
|
+
throw;
|
|
260
|
+
} catch (const ruby_error& error) {
|
|
261
|
+
return error.exception().raw();
|
|
262
|
+
} catch (const std::invalid_argument& error) {
|
|
263
|
+
return make_exception(rb_eArgError, error.what());
|
|
264
|
+
} catch (const std::out_of_range& error) {
|
|
265
|
+
return make_exception(rb_eRangeError, error.what());
|
|
266
|
+
} catch (const std::range_error& error) {
|
|
267
|
+
return make_exception(rb_eRangeError, error.what());
|
|
268
|
+
} catch (const std::bad_alloc& error) {
|
|
269
|
+
return make_exception(rb_eNoMemError, error.what());
|
|
270
|
+
} catch (const std::domain_error& error) {
|
|
271
|
+
return make_exception(rb_eMathDomainError, error.what());
|
|
272
|
+
} catch (const std::exception& error) {
|
|
273
|
+
return make_exception(rb_eRuntimeError, error.what());
|
|
274
|
+
} catch (...) {
|
|
275
|
+
return make_exception(rb_eRuntimeError, "unknown C++ exception");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
} // namespace detail
|
|
280
|
+
|
|
281
|
+
inline std::string ruby_error::ruby_class_name() const {
|
|
282
|
+
return detail::protected_exception_string(exception_.raw(), detail::exception_class_name);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
inline std::string ruby_error::message() const {
|
|
286
|
+
return detail::protected_exception_string(exception_.raw(), detail::exception_message);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
} // namespace rbxx
|
|
290
|
+
// END rbxx/exception.hpp
|
|
291
|
+
|
|
292
|
+
// BEGIN rbxx/protect.hpp
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
#include <exception>
|
|
296
|
+
#include <functional>
|
|
297
|
+
#include <optional>
|
|
298
|
+
#include <tuple>
|
|
299
|
+
#include <type_traits>
|
|
300
|
+
#include <utility>
|
|
301
|
+
|
|
302
|
+
namespace rbxx {
|
|
303
|
+
namespace detail {
|
|
304
|
+
|
|
305
|
+
template <typename Function, typename... Args> struct protect_payload {
|
|
306
|
+
using function_type = std::decay_t<Function>;
|
|
307
|
+
using result_type = std::invoke_result_t<function_type&, std::decay_t<Args>&...>;
|
|
308
|
+
using stored_result = std::conditional_t<std::is_void_v<result_type>, bool, result_type>;
|
|
309
|
+
|
|
310
|
+
function_type function;
|
|
311
|
+
std::tuple<std::decay_t<Args>...> args;
|
|
312
|
+
std::optional<stored_result> result;
|
|
313
|
+
std::exception_ptr cpp_exception;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
template <typename Payload> VALUE protect_trampoline(VALUE opaque) noexcept {
|
|
317
|
+
auto* payload = reinterpret_cast<Payload*>(opaque);
|
|
318
|
+
try {
|
|
319
|
+
if constexpr (std::is_void_v<typename Payload::result_type>) {
|
|
320
|
+
std::apply(payload->function, payload->args);
|
|
321
|
+
payload->result.emplace(true);
|
|
322
|
+
} else {
|
|
323
|
+
payload->result.emplace(std::apply(payload->function, payload->args));
|
|
324
|
+
}
|
|
325
|
+
} catch (...) {
|
|
326
|
+
payload->cpp_exception = std::current_exception();
|
|
327
|
+
}
|
|
328
|
+
return Qnil;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
} // namespace detail
|
|
332
|
+
|
|
333
|
+
/// @brief Invokes a Ruby C API operation through rb_protect and converts longjmp to ruby_error.
|
|
334
|
+
/// @code VALUE string = rbxx::protect(rb_utf8_str_new_cstr, "safe"); @endcode
|
|
335
|
+
template <typename Function, typename... Args> auto protect(Function&& function, Args&&... args) {
|
|
336
|
+
using payload_type = detail::protect_payload<Function, Args...>;
|
|
337
|
+
static_assert(!std::is_reference_v<typename payload_type::result_type>,
|
|
338
|
+
"rbxx::protect does not support reference return types");
|
|
339
|
+
|
|
340
|
+
payload_type payload{std::forward<Function>(function),
|
|
341
|
+
std::tuple<std::decay_t<Args>...>{std::forward<Args>(args)...}, std::nullopt,
|
|
342
|
+
nullptr};
|
|
343
|
+
int state = 0;
|
|
344
|
+
rb_protect(detail::protect_trampoline<payload_type>, reinterpret_cast<VALUE>(&payload), &state);
|
|
345
|
+
|
|
346
|
+
if (state != 0) {
|
|
347
|
+
VALUE exception = rb_errinfo();
|
|
348
|
+
rb_set_errinfo(Qnil);
|
|
349
|
+
throw ruby_error(exception);
|
|
350
|
+
}
|
|
351
|
+
if (payload.cpp_exception) {
|
|
352
|
+
std::rethrow_exception(payload.cpp_exception);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if constexpr (std::is_void_v<typename payload_type::result_type>) {
|
|
356
|
+
return;
|
|
357
|
+
} else {
|
|
358
|
+
return std::move(*payload.result);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
} // namespace rbxx
|
|
363
|
+
// END rbxx/protect.hpp
|
|
364
|
+
|
|
365
|
+
// BEGIN rbxx/type_caster.hpp
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
#include <concepts>
|
|
369
|
+
#include <cstddef>
|
|
370
|
+
#include <limits>
|
|
371
|
+
#include <string>
|
|
372
|
+
#include <string_view>
|
|
373
|
+
#include <type_traits>
|
|
374
|
+
#include <utility>
|
|
375
|
+
|
|
376
|
+
namespace rbxx {
|
|
377
|
+
|
|
378
|
+
/// @brief Customization point for conversion between Ruby and C++ values.
|
|
379
|
+
/// @code template <> struct rbxx::type_caster<MyType> { /* load/dump/matches */ }; @endcode
|
|
380
|
+
template <typename T, typename Enable = void> struct type_caster;
|
|
381
|
+
|
|
382
|
+
/// @brief True when T can be converted to a Ruby value.
|
|
383
|
+
template <typename T>
|
|
384
|
+
concept to_ruby_convertible = requires(T&& input) {
|
|
385
|
+
{
|
|
386
|
+
type_caster<std::remove_cvref_t<T>>::dump(std::forward<T>(input))
|
|
387
|
+
} -> std::convertible_to<value>;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/// @brief True when a Ruby value can be converted to T.
|
|
391
|
+
template <typename T>
|
|
392
|
+
concept from_ruby_convertible =
|
|
393
|
+
requires(value input) { type_caster<std::remove_cvref_t<T>>::load(input); };
|
|
394
|
+
|
|
395
|
+
namespace detail {
|
|
396
|
+
|
|
397
|
+
template <typename T> constexpr std::string_view type_name() noexcept {
|
|
398
|
+
#if defined(_MSC_VER)
|
|
399
|
+
constexpr std::string_view signature = __FUNCSIG__;
|
|
400
|
+
constexpr std::string_view prefix = "type_name<";
|
|
401
|
+
const auto start = signature.find(prefix) + prefix.size();
|
|
402
|
+
return signature.substr(start, signature.find(">(void)", start) - start);
|
|
403
|
+
#else
|
|
404
|
+
constexpr std::string_view signature = __PRETTY_FUNCTION__;
|
|
405
|
+
constexpr std::string_view prefix = "T = ";
|
|
406
|
+
const auto start = signature.find(prefix) + prefix.size();
|
|
407
|
+
const auto end = signature.find_first_of("];", start);
|
|
408
|
+
return signature.substr(start, end - start);
|
|
409
|
+
#endif
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
[[noreturn]] inline void throw_type_error(const char* expected, value actual) {
|
|
413
|
+
const char* actual_name = rb_obj_classname(actual.raw());
|
|
414
|
+
std::string message = "rbxx: expected ";
|
|
415
|
+
message += expected;
|
|
416
|
+
message += ", got ";
|
|
417
|
+
message += actual_name;
|
|
418
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
template <typename Integer> Integer load_integer(value input) {
|
|
422
|
+
if constexpr (std::is_signed_v<Integer>) {
|
|
423
|
+
const auto converted = protect(rb_num2ll, input.raw());
|
|
424
|
+
constexpr auto minimum = static_cast<LONG_LONG>(std::numeric_limits<Integer>::min());
|
|
425
|
+
constexpr auto maximum = static_cast<LONG_LONG>(std::numeric_limits<Integer>::max());
|
|
426
|
+
if (converted < minimum || converted > maximum) {
|
|
427
|
+
throw std::range_error("rbxx: Integer is outside the target C++ signed integer range");
|
|
428
|
+
}
|
|
429
|
+
return static_cast<Integer>(converted);
|
|
430
|
+
} else {
|
|
431
|
+
const auto converted = protect(rb_num2ull, input.raw());
|
|
432
|
+
constexpr auto maximum = static_cast<unsigned LONG_LONG>(std::numeric_limits<Integer>::max());
|
|
433
|
+
if (converted > maximum) {
|
|
434
|
+
throw std::range_error("rbxx: Integer is outside the target C++ unsigned integer range");
|
|
435
|
+
}
|
|
436
|
+
return static_cast<Integer>(converted);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
template <typename Integer> value dump_integer(Integer input) {
|
|
441
|
+
if constexpr (std::is_signed_v<Integer>) {
|
|
442
|
+
if (RB_FIXABLE(static_cast<LONG_LONG>(input))) {
|
|
443
|
+
return value{LONG2FIX(static_cast<long>(input))};
|
|
444
|
+
}
|
|
445
|
+
return value{protect(rb_ll2inum, static_cast<LONG_LONG>(input))};
|
|
446
|
+
} else {
|
|
447
|
+
if (RB_POSFIXABLE(static_cast<unsigned LONG_LONG>(input))) {
|
|
448
|
+
return value{LONG2FIX(static_cast<long>(input))};
|
|
449
|
+
}
|
|
450
|
+
return value{protect(rb_ull2inum, static_cast<unsigned LONG_LONG>(input))};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
inline const char* checked_string_pointer(value input) {
|
|
455
|
+
if (!input.is_string()) {
|
|
456
|
+
throw_type_error("String", input);
|
|
457
|
+
}
|
|
458
|
+
VALUE raw = input.raw();
|
|
459
|
+
return protect([raw]() mutable {
|
|
460
|
+
VALUE string = raw;
|
|
461
|
+
return StringValueCStr(string);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
inline VALUE checked_string_value(value input) {
|
|
466
|
+
if (input.is_string()) {
|
|
467
|
+
return input.raw();
|
|
468
|
+
}
|
|
469
|
+
VALUE converted = protect(rb_check_string_type, input.raw());
|
|
470
|
+
if (NIL_P(converted)) {
|
|
471
|
+
throw_type_error("String or object responding to #to_str", input);
|
|
472
|
+
}
|
|
473
|
+
return converted;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
inline value dump_utf8(const char* bytes, std::size_t size) {
|
|
477
|
+
if (size > static_cast<std::size_t>(std::numeric_limits<long>::max())) {
|
|
478
|
+
throw std::length_error("rbxx: String is too large for CRuby");
|
|
479
|
+
}
|
|
480
|
+
return value{protect(rb_utf8_str_new, bytes, static_cast<long>(size))};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
} // namespace detail
|
|
484
|
+
|
|
485
|
+
template <typename T>
|
|
486
|
+
struct type_caster<T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>>> {
|
|
487
|
+
static constexpr std::string_view name = "Integer";
|
|
488
|
+
|
|
489
|
+
/// @brief Loads a range-checked C++ integer.
|
|
490
|
+
static T load(value input) { return detail::load_integer<T>(input); }
|
|
491
|
+
|
|
492
|
+
/// @brief Dumps a C++ integer as a Ruby Integer.
|
|
493
|
+
static value dump(T input) { return detail::dump_integer(input); }
|
|
494
|
+
|
|
495
|
+
/// @brief Returns whether the Ruby value is an Integer.
|
|
496
|
+
static bool matches(value input) noexcept { return input.is_integer(); }
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
template <> struct type_caster<bool> {
|
|
500
|
+
static constexpr std::string_view name = "Boolean";
|
|
501
|
+
|
|
502
|
+
static bool load(value input) {
|
|
503
|
+
if (!input.is_bool()) {
|
|
504
|
+
detail::throw_type_error("true or false", input);
|
|
505
|
+
}
|
|
506
|
+
return input.raw() == Qtrue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
static value dump(bool input) noexcept { return value{input ? Qtrue : Qfalse}; }
|
|
510
|
+
static bool matches(value input) noexcept { return input.is_bool(); }
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
template <> struct type_caster<float> {
|
|
514
|
+
static constexpr std::string_view name = "Float";
|
|
515
|
+
static float load(value input) {
|
|
516
|
+
if (input.is_float()) {
|
|
517
|
+
return static_cast<float>(RFLOAT_VALUE(input.raw()));
|
|
518
|
+
}
|
|
519
|
+
return static_cast<float>(protect(rb_num2dbl, input.raw()));
|
|
520
|
+
}
|
|
521
|
+
static value dump(float input) {
|
|
522
|
+
return value{protect(rb_float_new, static_cast<double>(input))};
|
|
523
|
+
}
|
|
524
|
+
static bool matches(value input) noexcept { return input.is_float() || input.is_integer(); }
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
template <> struct type_caster<double> {
|
|
528
|
+
static constexpr std::string_view name = "Float";
|
|
529
|
+
static double load(value input) {
|
|
530
|
+
return input.is_float() ? RFLOAT_VALUE(input.raw()) : protect(rb_num2dbl, input.raw());
|
|
531
|
+
}
|
|
532
|
+
static value dump(double input) { return value{protect(rb_float_new, input)}; }
|
|
533
|
+
static bool matches(value input) noexcept { return input.is_float() || input.is_integer(); }
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
template <> struct type_caster<const char*> {
|
|
537
|
+
static constexpr std::string_view name = "String";
|
|
538
|
+
static const char* load(value input) { return detail::checked_string_pointer(input); }
|
|
539
|
+
static value dump(const char* input) {
|
|
540
|
+
if (input == nullptr) {
|
|
541
|
+
detail::throw_type_error("non-null C string", value{Qnil});
|
|
542
|
+
}
|
|
543
|
+
return value{protect(rb_utf8_str_new_cstr, input)};
|
|
544
|
+
}
|
|
545
|
+
static bool matches(value input) noexcept { return input.is_string(); }
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
template <> struct type_caster<std::string> {
|
|
549
|
+
static constexpr std::string_view name = "String";
|
|
550
|
+
static std::string load(value input) {
|
|
551
|
+
VALUE converted = detail::checked_string_value(input);
|
|
552
|
+
const char* bytes = protect([converted]() mutable {
|
|
553
|
+
VALUE string = converted;
|
|
554
|
+
return StringValueCStr(string);
|
|
555
|
+
});
|
|
556
|
+
return std::string(bytes, static_cast<std::size_t>(RSTRING_LEN(converted)));
|
|
557
|
+
}
|
|
558
|
+
static value dump(const std::string& input) {
|
|
559
|
+
return detail::dump_utf8(input.data(), input.size());
|
|
560
|
+
}
|
|
561
|
+
static bool matches(value input) noexcept { return input.is_string(); }
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
template <> struct type_caster<std::string_view> {
|
|
565
|
+
static constexpr std::string_view name = "String";
|
|
566
|
+
static std::string_view load(value input) {
|
|
567
|
+
const char* bytes = detail::checked_string_pointer(input);
|
|
568
|
+
return std::string_view(bytes, static_cast<std::size_t>(RSTRING_LEN(input.raw())));
|
|
569
|
+
}
|
|
570
|
+
static value dump(std::string_view input) {
|
|
571
|
+
return detail::dump_utf8(input.data(), input.size());
|
|
572
|
+
}
|
|
573
|
+
static bool matches(value input) noexcept { return input.is_string(); }
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
template <> struct type_caster<value> {
|
|
577
|
+
static constexpr std::string_view name = "Object";
|
|
578
|
+
static value load(value input) noexcept { return input; }
|
|
579
|
+
static value dump(value input) noexcept { return input; }
|
|
580
|
+
static bool matches(value) noexcept { return true; }
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
template <> struct type_caster<object> {
|
|
584
|
+
static constexpr std::string_view name = "Object";
|
|
585
|
+
static object load(value input) { return object{input}; }
|
|
586
|
+
static value dump(const object& input) noexcept { return input.get(); }
|
|
587
|
+
static bool matches(value) noexcept { return true; }
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
/// @brief Converts a C++ value to Ruby with an actionable missing-caster diagnostic.
|
|
591
|
+
/// @code rbxx::value result = rbxx::to_ruby(42); @endcode
|
|
592
|
+
template <typename T> [[nodiscard]] value to_ruby(T&& input) {
|
|
593
|
+
using converted_type = std::remove_cvref_t<T>;
|
|
594
|
+
static_assert(to_ruby_convertible<converted_type>,
|
|
595
|
+
"rbxx: type has no type_caster; bind it with def_class<T>() or specialize "
|
|
596
|
+
"rbxx::type_caster<T>");
|
|
597
|
+
return type_caster<converted_type>::dump(std::forward<T>(input));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/// @brief Converts a Ruby value to C++ with an actionable missing-caster diagnostic.
|
|
601
|
+
/// @code int result = rbxx::from_ruby<int>(ruby_value); @endcode
|
|
602
|
+
template <typename T> [[nodiscard]] decltype(auto) from_ruby(value input) {
|
|
603
|
+
using converted_type = std::remove_cvref_t<T>;
|
|
604
|
+
static_assert(from_ruby_convertible<converted_type>,
|
|
605
|
+
"rbxx: type has no type_caster; bind it with def_class<T>() or specialize "
|
|
606
|
+
"rbxx::type_caster<T>");
|
|
607
|
+
return type_caster<converted_type>::load(input);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
} // namespace rbxx
|
|
611
|
+
// END rbxx/type_caster.hpp
|
|
612
|
+
|
|
613
|
+
// BEGIN rbxx/arg.hpp
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
#include <algorithm>
|
|
617
|
+
#include <array>
|
|
618
|
+
#include <cstddef>
|
|
619
|
+
#include <string>
|
|
620
|
+
#include <tuple>
|
|
621
|
+
#include <type_traits>
|
|
622
|
+
#include <utility>
|
|
623
|
+
#include <vector>
|
|
624
|
+
|
|
625
|
+
namespace rbxx {
|
|
626
|
+
|
|
627
|
+
struct argument_spec {
|
|
628
|
+
std::string name;
|
|
629
|
+
bool keyword = false;
|
|
630
|
+
bool has_default = false;
|
|
631
|
+
object default_value;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
/// @brief Declares a named positional argument and an optional default.
|
|
635
|
+
/// @code function.def("add", fn, rbxx::arg("delta") = 1); @endcode
|
|
636
|
+
class arg {
|
|
637
|
+
public:
|
|
638
|
+
explicit arg(const char* name) : name_(name) {}
|
|
639
|
+
operator argument_spec() const { return argument_spec{name_, false, false, {}}; }
|
|
640
|
+
|
|
641
|
+
template <typename T> argument_spec operator=(T&& default_value) const {
|
|
642
|
+
return argument_spec{name_, false, true, object{to_ruby(std::forward<T>(default_value))}};
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private:
|
|
646
|
+
std::string name_;
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
/// @brief Declares a keyword argument and an optional default.
|
|
650
|
+
/// @code function.def("open", fn, rbxx::kwarg("mode") = "fast"); @endcode
|
|
651
|
+
class kwarg {
|
|
652
|
+
public:
|
|
653
|
+
explicit kwarg(const char* name) : name_(name) {}
|
|
654
|
+
operator argument_spec() const { return argument_spec{name_, true, false, {}}; }
|
|
655
|
+
|
|
656
|
+
template <typename T> argument_spec operator=(T&& default_value) const {
|
|
657
|
+
return argument_spec{name_, true, true, object{to_ruby(std::forward<T>(default_value))}};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
private:
|
|
661
|
+
std::string name_;
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
/// @brief Required Ruby block passed to a bound C++ callable.
|
|
665
|
+
class block {
|
|
666
|
+
public:
|
|
667
|
+
explicit block(value callable) : callable_(callable) {}
|
|
668
|
+
|
|
669
|
+
[[nodiscard]] value get() const noexcept { return callable_.get(); }
|
|
670
|
+
|
|
671
|
+
template <typename Return = value, typename... Args> Return call(Args&&... args) const {
|
|
672
|
+
std::array<VALUE, sizeof...(Args)> arguments{to_ruby(std::forward<Args>(args)).raw()...};
|
|
673
|
+
VALUE result = protect(rb_funcallv, callable_.raw(), rb_intern("call"),
|
|
674
|
+
static_cast<int>(sizeof...(Args)), arguments.data());
|
|
675
|
+
if constexpr (std::is_same_v<Return, value>) {
|
|
676
|
+
return value{result};
|
|
677
|
+
} else {
|
|
678
|
+
return from_ruby<Return>(value{result});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
private:
|
|
683
|
+
object callable_;
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
/// @brief Optional Ruby block passed to a bound C++ callable.
|
|
687
|
+
class optional_block {
|
|
688
|
+
public:
|
|
689
|
+
optional_block() = default;
|
|
690
|
+
explicit optional_block(value callable) : callable_(callable) {}
|
|
691
|
+
|
|
692
|
+
[[nodiscard]] explicit operator bool() const noexcept { return !callable_.is_nil(); }
|
|
693
|
+
[[nodiscard]] value get() const noexcept { return callable_.get(); }
|
|
694
|
+
|
|
695
|
+
template <typename Return = value, typename... Args> Return call(Args&&... args) const {
|
|
696
|
+
if (!*this) {
|
|
697
|
+
throw std::invalid_argument("rbxx: optional block is not present");
|
|
698
|
+
}
|
|
699
|
+
return block{callable_.get()}.template call<Return>(std::forward<Args>(args)...);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private:
|
|
703
|
+
object callable_;
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
/// @brief Remaining positional Ruby arguments.
|
|
707
|
+
class args {
|
|
708
|
+
public:
|
|
709
|
+
explicit args(std::vector<value> values) : values_(std::move(values)) {}
|
|
710
|
+
[[nodiscard]] const std::vector<value>& values() const noexcept { return values_; }
|
|
711
|
+
[[nodiscard]] std::size_t size() const noexcept { return values_.size(); }
|
|
712
|
+
[[nodiscard]] value operator[](std::size_t index) const { return values_.at(index); }
|
|
713
|
+
|
|
714
|
+
private:
|
|
715
|
+
std::vector<value> values_;
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
template <> struct type_caster<block> {
|
|
719
|
+
static block load(value input) { return block{input}; }
|
|
720
|
+
static value dump(const block& input) noexcept { return input.get(); }
|
|
721
|
+
static bool matches(value input) noexcept { return RTEST(rb_obj_is_proc(input.raw())); }
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
template <> struct type_caster<optional_block> {
|
|
725
|
+
static optional_block load(value input) {
|
|
726
|
+
return input.is_nil() ? optional_block{} : optional_block{input};
|
|
727
|
+
}
|
|
728
|
+
static value dump(const optional_block& input) noexcept {
|
|
729
|
+
return input ? input.get() : value{Qnil};
|
|
730
|
+
}
|
|
731
|
+
static bool matches(value input) noexcept {
|
|
732
|
+
return input.is_nil() || RTEST(rb_obj_is_proc(input.raw()));
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
template <> struct type_caster<args> {
|
|
737
|
+
static args load(value input) { return args{{input}}; }
|
|
738
|
+
static value dump(const args& input) {
|
|
739
|
+
return value{protect([&input] {
|
|
740
|
+
VALUE result = rb_ary_new_capa(static_cast<long>(input.size()));
|
|
741
|
+
for (value element : input.values()) {
|
|
742
|
+
rb_ary_push(result, element.raw());
|
|
743
|
+
}
|
|
744
|
+
return result;
|
|
745
|
+
})};
|
|
746
|
+
}
|
|
747
|
+
static bool matches(value) noexcept { return true; }
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
namespace detail {
|
|
751
|
+
|
|
752
|
+
template <typename Argument> auto load_argument(value input) {
|
|
753
|
+
using loaded_type = decltype(from_ruby<Argument>(input));
|
|
754
|
+
if constexpr (std::is_lvalue_reference_v<loaded_type>) {
|
|
755
|
+
return std::ref(from_ruby<Argument>(input));
|
|
756
|
+
} else {
|
|
757
|
+
return from_ruby<Argument>(input);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
template <typename T>
|
|
762
|
+
inline constexpr bool is_block_argument_v = std::is_same_v<std::remove_cvref_t<T>, block>;
|
|
763
|
+
template <typename T>
|
|
764
|
+
inline constexpr bool is_optional_block_argument_v =
|
|
765
|
+
std::is_same_v<std::remove_cvref_t<T>, optional_block>;
|
|
766
|
+
template <typename T>
|
|
767
|
+
inline constexpr bool is_rest_argument_v = std::is_same_v<std::remove_cvref_t<T>, args>;
|
|
768
|
+
template <typename T>
|
|
769
|
+
inline constexpr bool is_special_argument_v =
|
|
770
|
+
is_block_argument_v<T> || is_optional_block_argument_v<T> || is_rest_argument_v<T>;
|
|
771
|
+
|
|
772
|
+
template <typename Tuple, std::size_t... Index>
|
|
773
|
+
consteval std::size_t normal_argument_count(std::index_sequence<Index...>) {
|
|
774
|
+
return (std::size_t{0} + ... +
|
|
775
|
+
(is_special_argument_v<std::tuple_element_t<Index, Tuple>> ? 0U : 1U));
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
template <typename Tuple> consteval std::size_t normal_argument_count() {
|
|
779
|
+
return normal_argument_count<Tuple>(std::make_index_sequence<std::tuple_size_v<Tuple>>{});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
inline std::vector<argument_spec> make_argument_specs() { return {}; }
|
|
783
|
+
|
|
784
|
+
template <typename... Specs> std::vector<argument_spec> make_argument_specs(Specs&&... specs) {
|
|
785
|
+
static_assert((std::is_convertible_v<Specs, argument_spec> && ...),
|
|
786
|
+
"rbxx: binding options must be arg(...) or kwarg(...)");
|
|
787
|
+
std::vector<argument_spec> result;
|
|
788
|
+
result.reserve(sizeof...(Specs));
|
|
789
|
+
(result.emplace_back(static_cast<argument_spec>(std::forward<Specs>(specs))), ...);
|
|
790
|
+
return result;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
struct parsed_arguments {
|
|
794
|
+
std::vector<value> slots;
|
|
795
|
+
std::vector<value> rest;
|
|
796
|
+
value block_value{Qnil};
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
template <typename Tuple> class argument_parser {
|
|
800
|
+
public:
|
|
801
|
+
static constexpr std::size_t arity = std::tuple_size_v<Tuple>;
|
|
802
|
+
|
|
803
|
+
explicit argument_parser(std::vector<argument_spec> specs) : specs_(std::move(specs)) {
|
|
804
|
+
constexpr std::size_t normal = normal_argument_count<Tuple>();
|
|
805
|
+
if (specs_.empty()) {
|
|
806
|
+
specs_.reserve(normal);
|
|
807
|
+
for (std::size_t index = 0; index < normal; ++index) {
|
|
808
|
+
specs_.push_back(argument_spec{"arg" + std::to_string(index + 1U), false, false, {}});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (specs_.size() != normal) {
|
|
812
|
+
throw std::invalid_argument("rbxx: argument annotations must match non-block parameters");
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
[[nodiscard]] parsed_arguments parse(int argc, const VALUE* argv) const {
|
|
817
|
+
parsed_arguments parsed;
|
|
818
|
+
parsed.slots.resize(arity, value{Qundef});
|
|
819
|
+
|
|
820
|
+
int positional_count = argc;
|
|
821
|
+
VALUE keywords = Qnil;
|
|
822
|
+
if (argc > 0 && rb_keyword_given_p()) {
|
|
823
|
+
keywords = argv[argc - 1];
|
|
824
|
+
--positional_count;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
std::vector<VALUE> keyword_values(specs_.size(), Qundef);
|
|
828
|
+
read_keywords(keywords, keyword_values);
|
|
829
|
+
fill_slots(parsed, positional_count, argv, keyword_values, std::make_index_sequence<arity>{});
|
|
830
|
+
read_block(parsed);
|
|
831
|
+
return parsed;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
[[nodiscard]] bool configured() const noexcept {
|
|
835
|
+
return std::any_of(specs_.begin(), specs_.end(),
|
|
836
|
+
[](const argument_spec& spec) { return spec.keyword || spec.has_default; });
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
private:
|
|
840
|
+
void read_keywords(VALUE keywords, std::vector<VALUE>& values) const {
|
|
841
|
+
std::vector<ID> required;
|
|
842
|
+
std::vector<ID> optional;
|
|
843
|
+
std::vector<std::size_t> required_order;
|
|
844
|
+
std::vector<std::size_t> optional_order;
|
|
845
|
+
for (std::size_t index = 0; index < specs_.size(); ++index) {
|
|
846
|
+
const auto& spec = specs_[index];
|
|
847
|
+
if (!spec.keyword) {
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
ID id = protect(rb_intern, spec.name.c_str());
|
|
851
|
+
(spec.has_default ? optional : required).push_back(id);
|
|
852
|
+
(spec.has_default ? optional_order : required_order).push_back(index);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (required.empty() && optional.empty()) {
|
|
856
|
+
if (!NIL_P(keywords) && RHASH_SIZE(keywords) != 0) {
|
|
857
|
+
throw ruby_error(make_exception(rb_eArgError, "rbxx: unknown keyword arguments"));
|
|
858
|
+
}
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (NIL_P(keywords)) {
|
|
863
|
+
keywords = protect(rb_hash_new);
|
|
864
|
+
}
|
|
865
|
+
std::vector<ID> table = required;
|
|
866
|
+
table.insert(table.end(), optional.begin(), optional.end());
|
|
867
|
+
std::vector<std::size_t> order = required_order;
|
|
868
|
+
order.insert(order.end(), optional_order.begin(), optional_order.end());
|
|
869
|
+
std::vector<VALUE> extracted(table.size(), Qundef);
|
|
870
|
+
protect([&] {
|
|
871
|
+
return rb_get_kwargs(keywords, table.data(), static_cast<int>(required.size()),
|
|
872
|
+
static_cast<int>(optional.size()), extracted.data());
|
|
873
|
+
});
|
|
874
|
+
for (std::size_t index = 0; index < order.size(); ++index) {
|
|
875
|
+
values[order[index]] = extracted[index];
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
template <std::size_t... Index>
|
|
880
|
+
void fill_slots(parsed_arguments& parsed, int positional_count, const VALUE* argv,
|
|
881
|
+
const std::vector<VALUE>& keyword_values, std::index_sequence<Index...>) const {
|
|
882
|
+
std::size_t spec_index = 0;
|
|
883
|
+
int positional_index = 0;
|
|
884
|
+
(fill_slot<Index>(parsed, positional_count, positional_index, argv, spec_index, keyword_values),
|
|
885
|
+
...);
|
|
886
|
+
if (positional_index < positional_count && !has_rest(std::make_index_sequence<arity>{})) {
|
|
887
|
+
throw ruby_error(make_exception(rb_eArgError, "rbxx: too many positional arguments"));
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
template <std::size_t Index>
|
|
892
|
+
void fill_slot(parsed_arguments& parsed, int positional_count, int& positional_index,
|
|
893
|
+
const VALUE* argv, std::size_t& spec_index,
|
|
894
|
+
const std::vector<VALUE>& keyword_values) const {
|
|
895
|
+
using argument = std::tuple_element_t<Index, Tuple>;
|
|
896
|
+
if constexpr (is_rest_argument_v<argument>) {
|
|
897
|
+
while (positional_index < positional_count) {
|
|
898
|
+
parsed.rest.emplace_back(argv[positional_index++]);
|
|
899
|
+
}
|
|
900
|
+
} else if constexpr (!is_block_argument_v<argument> &&
|
|
901
|
+
!is_optional_block_argument_v<argument>) {
|
|
902
|
+
const argument_spec& spec = specs_[spec_index];
|
|
903
|
+
VALUE selected = Qundef;
|
|
904
|
+
if (spec.keyword) {
|
|
905
|
+
selected = keyword_values[spec_index];
|
|
906
|
+
} else if (positional_index < positional_count) {
|
|
907
|
+
selected = argv[positional_index++];
|
|
908
|
+
}
|
|
909
|
+
if (selected == Qundef && spec.has_default) {
|
|
910
|
+
selected = spec.default_value.raw();
|
|
911
|
+
}
|
|
912
|
+
if (selected == Qundef) {
|
|
913
|
+
std::string message = "rbxx: missing argument ";
|
|
914
|
+
message += spec.name;
|
|
915
|
+
throw ruby_error(make_exception(rb_eArgError, message.c_str()));
|
|
916
|
+
}
|
|
917
|
+
parsed.slots[Index] = value{selected};
|
|
918
|
+
++spec_index;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
void read_block(parsed_arguments& parsed) const {
|
|
923
|
+
constexpr bool required = contains_required_block(std::make_index_sequence<arity>{});
|
|
924
|
+
if (rb_block_given_p()) {
|
|
925
|
+
parsed.block_value = value{protect(rb_block_proc)};
|
|
926
|
+
} else if (required) {
|
|
927
|
+
throw ruby_error(make_exception(rb_eArgError, "rbxx: required block was not given"));
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
template <std::size_t... Index>
|
|
932
|
+
static consteval bool contains_required_block(std::index_sequence<Index...>) {
|
|
933
|
+
return (is_block_argument_v<std::tuple_element_t<Index, Tuple>> || ...);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
template <std::size_t... Index> static consteval bool has_rest(std::index_sequence<Index...>) {
|
|
937
|
+
return (is_rest_argument_v<std::tuple_element_t<Index, Tuple>> || ...);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
std::vector<argument_spec> specs_;
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
template <typename Argument>
|
|
944
|
+
auto load_parsed_argument(const parsed_arguments& parsed, std::size_t index) {
|
|
945
|
+
if constexpr (is_block_argument_v<Argument>) {
|
|
946
|
+
return block{parsed.block_value};
|
|
947
|
+
} else if constexpr (is_optional_block_argument_v<Argument>) {
|
|
948
|
+
return parsed.block_value.is_nil() ? optional_block{} : optional_block{parsed.block_value};
|
|
949
|
+
} else if constexpr (is_rest_argument_v<Argument>) {
|
|
950
|
+
return args{parsed.rest};
|
|
951
|
+
} else {
|
|
952
|
+
return load_argument<Argument>(parsed.slots[index]);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
} // namespace detail
|
|
957
|
+
|
|
958
|
+
} // namespace rbxx
|
|
959
|
+
// END rbxx/arg.hpp
|
|
960
|
+
|
|
961
|
+
// BEGIN rbxx/policies.hpp
|
|
962
|
+
|
|
963
|
+
#include <cstddef>
|
|
964
|
+
|
|
965
|
+
namespace rbxx {
|
|
966
|
+
|
|
967
|
+
namespace policy {
|
|
968
|
+
|
|
969
|
+
/// @brief Internal value carried by a public return-value policy constant.
|
|
970
|
+
enum class kind { automatic, copy, take, reference, shared };
|
|
971
|
+
|
|
972
|
+
/// @brief Selects ownership behavior when converting a C++ return value to Ruby.
|
|
973
|
+
struct return_value_policy {
|
|
974
|
+
kind value;
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
/// @name Return-value policies
|
|
978
|
+
/// @{
|
|
979
|
+
inline constexpr return_value_policy automatic{kind::automatic};
|
|
980
|
+
inline constexpr return_value_policy copy{kind::copy};
|
|
981
|
+
inline constexpr return_value_policy take{kind::take};
|
|
982
|
+
inline constexpr return_value_policy reference{kind::reference};
|
|
983
|
+
inline constexpr return_value_policy shared{kind::shared};
|
|
984
|
+
/// @}
|
|
985
|
+
|
|
986
|
+
} // namespace policy
|
|
987
|
+
|
|
988
|
+
/// @brief Keeps one Ruby object alive for as long as another remains reachable.
|
|
989
|
+
/// @details Index 0 is the return value, 1 is self, and 2 onward are arguments.
|
|
990
|
+
/// @code .def("child", &Owner::child, policy::reference, keep_alive<0, 1>()) @endcode
|
|
991
|
+
template <std::size_t Nurse, std::size_t Patient> struct keep_alive {
|
|
992
|
+
static constexpr std::size_t nurse = Nurse;
|
|
993
|
+
static constexpr std::size_t patient = Patient;
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
} // namespace rbxx
|
|
997
|
+
// END rbxx/policies.hpp
|
|
998
|
+
|
|
999
|
+
// BEGIN rbxx/registry.hpp
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
#include <functional>
|
|
1003
|
+
#include <memory>
|
|
1004
|
+
#include <mutex>
|
|
1005
|
+
#include <source_location>
|
|
1006
|
+
#include <string>
|
|
1007
|
+
#include <typeindex>
|
|
1008
|
+
#include <unordered_map>
|
|
1009
|
+
|
|
1010
|
+
namespace rbxx::detail {
|
|
1011
|
+
|
|
1012
|
+
struct class_info {
|
|
1013
|
+
std::type_index cpp_type{typeid(void)};
|
|
1014
|
+
VALUE ruby_class = Qnil;
|
|
1015
|
+
const rb_data_type_t* data_type = nullptr;
|
|
1016
|
+
std::function<void*(void*)> native_pointer;
|
|
1017
|
+
std::unordered_map<std::type_index, std::function<void*(void*)>> conversions;
|
|
1018
|
+
std::source_location registered_at = std::source_location::current();
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
class type_registry {
|
|
1022
|
+
public:
|
|
1023
|
+
static type_registry& instance() {
|
|
1024
|
+
static auto* registry = new type_registry();
|
|
1025
|
+
return *registry;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
template <typename T, typename Base = void>
|
|
1029
|
+
void add(VALUE ruby_class, const rb_data_type_t* data_type,
|
|
1030
|
+
std::source_location location = std::source_location::current()) {
|
|
1031
|
+
auto information = std::make_unique<class_info>();
|
|
1032
|
+
information->cpp_type = std::type_index(typeid(T));
|
|
1033
|
+
information->ruby_class = ruby_class;
|
|
1034
|
+
information->data_type = data_type;
|
|
1035
|
+
information->conversions.emplace(std::type_index(typeid(T)),
|
|
1036
|
+
[](void* pointer) { return pointer; });
|
|
1037
|
+
information->registered_at = location;
|
|
1038
|
+
|
|
1039
|
+
if constexpr (!std::is_void_v<Base>) {
|
|
1040
|
+
class_info* base = find(std::type_index(typeid(Base)));
|
|
1041
|
+
if (base == nullptr) {
|
|
1042
|
+
std::string message = "rbxx: base C++ type ";
|
|
1043
|
+
message += type_name<Base>();
|
|
1044
|
+
message += " is not registered; call def_class<Base>() first";
|
|
1045
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
1046
|
+
}
|
|
1047
|
+
for (const auto& [target, conversion] : base->conversions) {
|
|
1048
|
+
information->conversions.emplace(target, [conversion](void* pointer) {
|
|
1049
|
+
auto* derived = static_cast<T*>(pointer);
|
|
1050
|
+
return conversion(static_cast<Base*>(derived));
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
std::lock_guard lock(write_mutex_);
|
|
1056
|
+
class_info* stored = information.get();
|
|
1057
|
+
by_data_type_.insert_or_assign(data_type, stored);
|
|
1058
|
+
by_type_.insert_or_assign(std::type_index(typeid(T)), std::move(information));
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
template <typename T> void set_native_pointer(std::function<void*(void*)> extractor) {
|
|
1062
|
+
class_info* information = find(std::type_index(typeid(T)));
|
|
1063
|
+
if (information != nullptr) {
|
|
1064
|
+
information->native_pointer = std::move(extractor);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
[[nodiscard]] class_info* find(std::type_index type) const noexcept {
|
|
1069
|
+
auto found = by_type_.find(type);
|
|
1070
|
+
return found == by_type_.end() ? nullptr : found->second.get();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
[[nodiscard]] class_info* find(const rb_data_type_t* type) const noexcept {
|
|
1074
|
+
auto found = by_data_type_.find(type);
|
|
1075
|
+
return found == by_data_type_.end() ? nullptr : found->second;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
private:
|
|
1079
|
+
std::mutex write_mutex_;
|
|
1080
|
+
std::unordered_map<std::type_index, std::unique_ptr<class_info>> by_type_;
|
|
1081
|
+
std::unordered_map<const rb_data_type_t*, class_info*> by_data_type_;
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
template <typename T> [[noreturn]] void throw_unregistered_type() {
|
|
1085
|
+
std::string message = "rbxx: C++ type ";
|
|
1086
|
+
message += type_name<T>();
|
|
1087
|
+
message += " is not registered; call def_class<T>() or specialize rbxx::type_caster<T>";
|
|
1088
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
template <typename T> class_info& registered_class() {
|
|
1092
|
+
class_info* information = type_registry::instance().find(std::type_index(typeid(T)));
|
|
1093
|
+
if (information == nullptr) {
|
|
1094
|
+
throw_unregistered_type<T>();
|
|
1095
|
+
}
|
|
1096
|
+
return *information;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
template <typename T> T& load_registered(value input) {
|
|
1100
|
+
VALUE raw = input.raw();
|
|
1101
|
+
if (!RB_TYPE_P(raw, T_DATA) || !RTYPEDDATA_P(raw)) {
|
|
1102
|
+
std::string message = "rbxx: expected registered C++ type ";
|
|
1103
|
+
message += type_name<T>();
|
|
1104
|
+
message += ", got ";
|
|
1105
|
+
message += rb_obj_classname(raw);
|
|
1106
|
+
message += "; call def_class<T>() for the expected type";
|
|
1107
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
class_info* actual = type_registry::instance().find(RTYPEDDATA_TYPE(raw));
|
|
1111
|
+
if (actual == nullptr || !actual->native_pointer) {
|
|
1112
|
+
throw_unregistered_type<T>();
|
|
1113
|
+
}
|
|
1114
|
+
auto conversion = actual->conversions.find(std::type_index(typeid(T)));
|
|
1115
|
+
if (conversion == actual->conversions.end()) {
|
|
1116
|
+
std::string message = "rbxx: wrapped C++ type cannot be converted to ";
|
|
1117
|
+
message += type_name<T>();
|
|
1118
|
+
message += "; register the inheritance with def_class<Derived, Base>()";
|
|
1119
|
+
message += "; actual type was registered at ";
|
|
1120
|
+
message += actual->registered_at.file_name();
|
|
1121
|
+
message += ":";
|
|
1122
|
+
message += std::to_string(actual->registered_at.line());
|
|
1123
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
void* native = actual->native_pointer(RTYPEDDATA_DATA(raw));
|
|
1127
|
+
if (native == nullptr) {
|
|
1128
|
+
throw ruby_error(make_exception(rb_eRuntimeError, "rbxx: C++ object is not initialized"));
|
|
1129
|
+
}
|
|
1130
|
+
return *static_cast<T*>(conversion->second(native));
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
template <typename T> bool registered_matches(value input) noexcept {
|
|
1134
|
+
if (!RB_TYPE_P(input.raw(), T_DATA) || !RTYPEDDATA_P(input.raw())) {
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
class_info* actual = type_registry::instance().find(RTYPEDDATA_TYPE(input.raw()));
|
|
1138
|
+
return actual != nullptr &&
|
|
1139
|
+
actual->conversions.contains(std::type_index(typeid(std::remove_cv_t<T>)));
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
} // namespace rbxx::detail
|
|
1143
|
+
// END rbxx/registry.hpp
|
|
1144
|
+
|
|
1145
|
+
// BEGIN rbxx/data_object.hpp
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
#include <memory>
|
|
1149
|
+
#include <string>
|
|
1150
|
+
#include <type_traits>
|
|
1151
|
+
#include <utility>
|
|
1152
|
+
|
|
1153
|
+
namespace rbxx {
|
|
1154
|
+
|
|
1155
|
+
/// @brief A Ruby VALUE member that remains marked and compaction-safe inside a C++ object.
|
|
1156
|
+
/// @code rbxx::member_value child{ruby_child}; @endcode
|
|
1157
|
+
class member_value {
|
|
1158
|
+
public:
|
|
1159
|
+
member_value() noexcept { rb_gc_register_address(&stored_); }
|
|
1160
|
+
explicit member_value(value initial) noexcept : stored_(initial.raw()) {
|
|
1161
|
+
rb_gc_register_address(&stored_);
|
|
1162
|
+
}
|
|
1163
|
+
explicit member_value(VALUE initial) noexcept : stored_(initial) {
|
|
1164
|
+
rb_gc_register_address(&stored_);
|
|
1165
|
+
}
|
|
1166
|
+
member_value(const member_value& other) noexcept : stored_(other.stored_) {
|
|
1167
|
+
rb_gc_register_address(&stored_);
|
|
1168
|
+
}
|
|
1169
|
+
member_value(member_value&& other) noexcept : stored_(other.stored_) {
|
|
1170
|
+
rb_gc_register_address(&stored_);
|
|
1171
|
+
other.stored_ = Qnil;
|
|
1172
|
+
}
|
|
1173
|
+
member_value& operator=(const member_value& other) noexcept {
|
|
1174
|
+
stored_ = other.stored_;
|
|
1175
|
+
return *this;
|
|
1176
|
+
}
|
|
1177
|
+
member_value& operator=(member_value&& other) noexcept {
|
|
1178
|
+
stored_ = other.stored_;
|
|
1179
|
+
other.stored_ = Qnil;
|
|
1180
|
+
return *this;
|
|
1181
|
+
}
|
|
1182
|
+
~member_value() noexcept { rb_gc_unregister_address(&stored_); }
|
|
1183
|
+
|
|
1184
|
+
[[nodiscard]] value get() const noexcept { return value{stored_}; }
|
|
1185
|
+
void set(value updated) noexcept { stored_ = updated.raw(); }
|
|
1186
|
+
|
|
1187
|
+
private:
|
|
1188
|
+
VALUE stored_ = Qnil;
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
namespace detail {
|
|
1192
|
+
|
|
1193
|
+
template <typename T> struct is_non_bindable_class : std::false_type {};
|
|
1194
|
+
template <typename T> struct is_non_bindable_class<std::unique_ptr<T>> : std::true_type {};
|
|
1195
|
+
template <typename T> struct is_non_bindable_class<std::shared_ptr<T>> : std::true_type {};
|
|
1196
|
+
|
|
1197
|
+
enum class ownership { owned, borrowed, shared };
|
|
1198
|
+
|
|
1199
|
+
template <typename T> struct data_wrapper {
|
|
1200
|
+
T* pointer = nullptr;
|
|
1201
|
+
ownership mode = ownership::borrowed;
|
|
1202
|
+
std::shared_ptr<T> shared_owner;
|
|
1203
|
+
|
|
1204
|
+
~data_wrapper() noexcept {
|
|
1205
|
+
if (mode == ownership::owned) {
|
|
1206
|
+
delete pointer;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
template <typename T> void data_mark(void*) noexcept {}
|
|
1212
|
+
template <typename T> void data_compact(void*) noexcept {}
|
|
1213
|
+
|
|
1214
|
+
template <typename T> void data_free(void* raw) noexcept {
|
|
1215
|
+
delete static_cast<data_wrapper<T>*>(raw);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
template <typename T> std::size_t data_size(const void* raw) noexcept {
|
|
1219
|
+
const auto* wrapper = static_cast<const data_wrapper<T>*>(raw);
|
|
1220
|
+
return sizeof(data_wrapper<T>) +
|
|
1221
|
+
(wrapper != nullptr && wrapper->pointer != nullptr ? sizeof(T) : 0U);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
template <typename T> rb_data_type_t& typed_data_type() {
|
|
1225
|
+
static auto* name = new std::string("rbxx::" + std::string(type_name<T>()));
|
|
1226
|
+
static rb_data_type_t type = {
|
|
1227
|
+
name->c_str(),
|
|
1228
|
+
{data_mark<T>, data_free<T>, data_size<T>, data_compact<T>, {nullptr}},
|
|
1229
|
+
nullptr,
|
|
1230
|
+
nullptr,
|
|
1231
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
|
1232
|
+
};
|
|
1233
|
+
return type;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
template <typename T> value wrap_data(std::unique_ptr<data_wrapper<T>> wrapper) {
|
|
1237
|
+
class_info& information = registered_class<T>();
|
|
1238
|
+
VALUE result = protect(rb_data_typed_object_wrap, information.ruby_class,
|
|
1239
|
+
static_cast<void*>(wrapper.get()), information.data_type);
|
|
1240
|
+
wrapper.release();
|
|
1241
|
+
return value{result};
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
template <typename T> value wrap_copy(const T& input) {
|
|
1245
|
+
auto wrapper = std::make_unique<data_wrapper<T>>();
|
|
1246
|
+
wrapper->pointer = new T(input);
|
|
1247
|
+
wrapper->mode = ownership::owned;
|
|
1248
|
+
return wrap_data(std::move(wrapper));
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
template <typename T> value wrap_move(T&& input) {
|
|
1252
|
+
auto wrapper = std::make_unique<data_wrapper<T>>();
|
|
1253
|
+
wrapper->pointer = new T(std::move(input));
|
|
1254
|
+
wrapper->mode = ownership::owned;
|
|
1255
|
+
return wrap_data(std::move(wrapper));
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
template <typename T> value wrap_reference(T* input) {
|
|
1259
|
+
if (input == nullptr) {
|
|
1260
|
+
return value{Qnil};
|
|
1261
|
+
}
|
|
1262
|
+
auto wrapper = std::make_unique<data_wrapper<T>>();
|
|
1263
|
+
wrapper->pointer = input;
|
|
1264
|
+
wrapper->mode = ownership::borrowed;
|
|
1265
|
+
return wrap_data(std::move(wrapper));
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
template <typename T> value wrap_take(std::unique_ptr<T> input) {
|
|
1269
|
+
if (!input) {
|
|
1270
|
+
return value{Qnil};
|
|
1271
|
+
}
|
|
1272
|
+
auto wrapper = std::make_unique<data_wrapper<T>>();
|
|
1273
|
+
wrapper->pointer = input.release();
|
|
1274
|
+
wrapper->mode = ownership::owned;
|
|
1275
|
+
return wrap_data(std::move(wrapper));
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
template <typename T> value wrap_shared(std::shared_ptr<T> input) {
|
|
1279
|
+
if (!input) {
|
|
1280
|
+
return value{Qnil};
|
|
1281
|
+
}
|
|
1282
|
+
auto wrapper = std::make_unique<data_wrapper<T>>();
|
|
1283
|
+
wrapper->pointer = input.get();
|
|
1284
|
+
wrapper->mode = ownership::shared;
|
|
1285
|
+
wrapper->shared_owner = std::move(input);
|
|
1286
|
+
return wrap_data(std::move(wrapper));
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
template <typename T, typename Base = void>
|
|
1290
|
+
void register_data_class(VALUE ruby_class,
|
|
1291
|
+
std::source_location location = std::source_location::current()) {
|
|
1292
|
+
rb_data_type_t& type = typed_data_type<T>();
|
|
1293
|
+
if constexpr (!std::is_void_v<Base>) {
|
|
1294
|
+
type.parent = &typed_data_type<Base>();
|
|
1295
|
+
}
|
|
1296
|
+
type_registry::instance().add<T, Base>(ruby_class, &type, location);
|
|
1297
|
+
type_registry::instance().set_native_pointer<T>(
|
|
1298
|
+
[](void* raw) { return static_cast<void*>(static_cast<data_wrapper<T>*>(raw)->pointer); });
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
template <typename T> VALUE allocate_data_object(VALUE klass) {
|
|
1302
|
+
VALUE result = Qnil;
|
|
1303
|
+
VALUE pending = Qnil;
|
|
1304
|
+
try {
|
|
1305
|
+
auto wrapper = std::make_unique<data_wrapper<T>>();
|
|
1306
|
+
result = protect(rb_data_typed_object_wrap, klass, static_cast<void*>(wrapper.get()),
|
|
1307
|
+
&typed_data_type<T>());
|
|
1308
|
+
wrapper.release();
|
|
1309
|
+
} catch (...) {
|
|
1310
|
+
pending = translate_current_exception();
|
|
1311
|
+
}
|
|
1312
|
+
if (!NIL_P(pending)) {
|
|
1313
|
+
rb_exc_raise(pending);
|
|
1314
|
+
}
|
|
1315
|
+
return result;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
template <typename T> data_wrapper<T>& exact_wrapper(value self) {
|
|
1319
|
+
if (!RB_TYPE_P(self.raw(), T_DATA) || !RTYPEDDATA_P(self.raw()) ||
|
|
1320
|
+
RTYPEDDATA_TYPE(self.raw()) != &typed_data_type<T>()) {
|
|
1321
|
+
throw ruby_error(make_exception(rb_eTypeError, "rbxx: unexpected TypedData class"));
|
|
1322
|
+
}
|
|
1323
|
+
return *static_cast<data_wrapper<T>*>(RTYPEDDATA_DATA(self.raw()));
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
} // namespace detail
|
|
1327
|
+
|
|
1328
|
+
template <typename T>
|
|
1329
|
+
struct type_caster<T, std::enable_if_t<std::is_class_v<T> && !std::is_same_v<T, object> &&
|
|
1330
|
+
!detail::is_non_bindable_class<T>::value>> {
|
|
1331
|
+
static constexpr std::string_view name = "bound C++ object";
|
|
1332
|
+
static T& load(value input) { return detail::load_registered<T>(input); }
|
|
1333
|
+
static value dump(const T& input) { return detail::wrap_copy(input); }
|
|
1334
|
+
static value dump(T&& input) { return detail::wrap_move(std::move(input)); }
|
|
1335
|
+
static bool matches(value input) noexcept { return detail::registered_matches<T>(input); }
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
template <typename T> struct type_caster<T*> {
|
|
1339
|
+
static constexpr std::string_view name = "bound C++ object or nil";
|
|
1340
|
+
static T* load(value input) {
|
|
1341
|
+
return input.is_nil() ? nullptr : std::addressof(detail::load_registered<T>(input));
|
|
1342
|
+
}
|
|
1343
|
+
static value dump(T* input) { return detail::wrap_reference(input); }
|
|
1344
|
+
static bool matches(value input) noexcept {
|
|
1345
|
+
return input.is_nil() || detail::registered_matches<T>(input);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
template <typename T> struct type_caster<std::unique_ptr<T>> {
|
|
1350
|
+
static constexpr std::string_view name = "owned C++ object";
|
|
1351
|
+
static value dump(std::unique_ptr<T> input) { return detail::wrap_take(std::move(input)); }
|
|
1352
|
+
static bool matches(value) noexcept { return false; }
|
|
1353
|
+
};
|
|
1354
|
+
|
|
1355
|
+
template <typename T> struct type_caster<std::shared_ptr<T>> {
|
|
1356
|
+
static constexpr std::string_view name = "shared C++ object";
|
|
1357
|
+
static value dump(std::shared_ptr<T> input) { return detail::wrap_shared(std::move(input)); }
|
|
1358
|
+
static bool matches(value input) noexcept {
|
|
1359
|
+
return RB_TYPE_P(input.raw(), T_DATA) && RTYPEDDATA_P(input.raw());
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
} // namespace rbxx
|
|
1364
|
+
// END rbxx/data_object.hpp
|
|
1365
|
+
|
|
1366
|
+
// BEGIN rbxx/callback.hpp
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
#include <array>
|
|
1370
|
+
#include <functional>
|
|
1371
|
+
#include <stdexcept>
|
|
1372
|
+
#include <type_traits>
|
|
1373
|
+
#include <utility>
|
|
1374
|
+
|
|
1375
|
+
namespace rbxx {
|
|
1376
|
+
|
|
1377
|
+
namespace detail {
|
|
1378
|
+
|
|
1379
|
+
template <typename Return, typename... Args>
|
|
1380
|
+
struct is_non_bindable_class<std::function<Return(Args...)>> : std::true_type {};
|
|
1381
|
+
|
|
1382
|
+
inline void require_callback_gvl() {
|
|
1383
|
+
#if defined(RBXX_DEBUG)
|
|
1384
|
+
if (ruby_thread_has_gvl_p() == 0) {
|
|
1385
|
+
throw std::runtime_error(
|
|
1386
|
+
"rbxx: Ruby callback invoked without the GVL; reacquire it before calling the Proc");
|
|
1387
|
+
}
|
|
1388
|
+
#endif
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
} // namespace detail
|
|
1392
|
+
|
|
1393
|
+
/// @brief Converts a Ruby Proc into a GC-pinned C++ std::function.
|
|
1394
|
+
template <typename Return, typename... Args> struct type_caster<std::function<Return(Args...)>> {
|
|
1395
|
+
static constexpr std::string_view name = "Proc";
|
|
1396
|
+
|
|
1397
|
+
static std::function<Return(Args...)> load(value input) {
|
|
1398
|
+
if (!matches(input)) {
|
|
1399
|
+
detail::throw_type_error("Proc", input);
|
|
1400
|
+
}
|
|
1401
|
+
object callable{input};
|
|
1402
|
+
return [callable = std::move(callable)](Args... args) -> Return {
|
|
1403
|
+
detail::require_callback_gvl();
|
|
1404
|
+
std::array<VALUE, sizeof...(Args)> converted{to_ruby(std::forward<Args>(args)).raw()...};
|
|
1405
|
+
VALUE result = protect(rb_funcallv, callable.raw(), rb_intern("call"),
|
|
1406
|
+
static_cast<int>(converted.size()), converted.data());
|
|
1407
|
+
if constexpr (std::is_void_v<Return>) {
|
|
1408
|
+
return;
|
|
1409
|
+
} else {
|
|
1410
|
+
return from_ruby<Return>(value{result});
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
static bool matches(value input) noexcept { return RTEST(rb_obj_is_proc(input.raw())); }
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
} // namespace rbxx
|
|
1419
|
+
// END rbxx/callback.hpp
|
|
1420
|
+
|
|
1421
|
+
// BEGIN rbxx/function.hpp
|
|
1422
|
+
|
|
1423
|
+
|
|
1424
|
+
#include <functional>
|
|
1425
|
+
#include <limits>
|
|
1426
|
+
#include <memory>
|
|
1427
|
+
#include <mutex>
|
|
1428
|
+
#include <sstream>
|
|
1429
|
+
#include <stdexcept>
|
|
1430
|
+
#include <string>
|
|
1431
|
+
#include <tuple>
|
|
1432
|
+
#include <type_traits>
|
|
1433
|
+
#include <unordered_map>
|
|
1434
|
+
#include <utility>
|
|
1435
|
+
#include <vector>
|
|
1436
|
+
|
|
1437
|
+
namespace rbxx::detail {
|
|
1438
|
+
|
|
1439
|
+
struct keep_alive_spec {
|
|
1440
|
+
std::size_t nurse;
|
|
1441
|
+
std::size_t patient;
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
template <std::size_t Nurse, std::size_t Patient>
|
|
1445
|
+
constexpr keep_alive_spec make_keep_alive_spec(keep_alive<Nurse, Patient>) noexcept {
|
|
1446
|
+
return {Nurse, Patient};
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
inline value keep_alive_value(std::size_t index, value result, value self, int argc,
|
|
1450
|
+
const VALUE* argv) {
|
|
1451
|
+
if (index == 0U) {
|
|
1452
|
+
return result;
|
|
1453
|
+
}
|
|
1454
|
+
if (index == 1U) {
|
|
1455
|
+
return self;
|
|
1456
|
+
}
|
|
1457
|
+
const std::size_t argument = index - 2U;
|
|
1458
|
+
if (argument >= static_cast<std::size_t>(argc)) {
|
|
1459
|
+
throw std::out_of_range("rbxx: keep_alive index exceeds the Ruby argument count");
|
|
1460
|
+
}
|
|
1461
|
+
return value{argv[argument]};
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
inline void apply_keep_alive(const std::vector<keep_alive_spec>& policies, value result, value self,
|
|
1465
|
+
int argc, const VALUE* argv) {
|
|
1466
|
+
for (const keep_alive_spec& selected : policies) {
|
|
1467
|
+
value nurse = keep_alive_value(selected.nurse, result, self, argc, argv);
|
|
1468
|
+
value patient = keep_alive_value(selected.patient, result, self, argc, argv);
|
|
1469
|
+
if (nurse.is_nil() || patient.is_nil()) {
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
if (selected.nurse > static_cast<std::size_t>(std::numeric_limits<unsigned int>::max())) {
|
|
1473
|
+
throw std::out_of_range("rbxx: keep_alive nurse index is too large");
|
|
1474
|
+
}
|
|
1475
|
+
std::string name = "@__rbxx_keep_" + std::to_string(selected.nurse);
|
|
1476
|
+
protect([nurse, patient, &name] {
|
|
1477
|
+
ID id = rb_intern(name.c_str());
|
|
1478
|
+
VALUE storage = rb_ivar_get(nurse.raw(), id);
|
|
1479
|
+
if (NIL_P(storage)) {
|
|
1480
|
+
storage = rb_ary_new();
|
|
1481
|
+
rb_ivar_set(nurse.raw(), id, storage);
|
|
1482
|
+
} else if (!RB_TYPE_P(storage, T_ARRAY)) {
|
|
1483
|
+
rb_raise(rb_eTypeError, "rbxx: keep_alive storage was replaced with a non-Array");
|
|
1484
|
+
}
|
|
1485
|
+
rb_ary_push(storage, patient.raw());
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
template <typename T> struct function_traits;
|
|
1491
|
+
|
|
1492
|
+
template <typename Return, typename... Args> struct function_traits<Return(Args...)> {
|
|
1493
|
+
using return_type = Return;
|
|
1494
|
+
using args_tuple = std::tuple<Args...>;
|
|
1495
|
+
static constexpr std::size_t arity = sizeof...(Args);
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
template <typename Return, typename... Args>
|
|
1499
|
+
struct function_traits<Return (*)(Args...)> : function_traits<Return(Args...)> {};
|
|
1500
|
+
|
|
1501
|
+
template <typename Return, typename... Args>
|
|
1502
|
+
struct function_traits<Return (*)(Args...) noexcept> : function_traits<Return(Args...)> {};
|
|
1503
|
+
|
|
1504
|
+
template <typename Return, typename... Args>
|
|
1505
|
+
struct function_traits<std::function<Return(Args...)>> : function_traits<Return(Args...)> {};
|
|
1506
|
+
|
|
1507
|
+
template <typename Class, typename Return, typename... Args>
|
|
1508
|
+
struct function_traits<Return (Class::*)(Args...)> : function_traits<Return(Args...)> {};
|
|
1509
|
+
|
|
1510
|
+
template <typename Class, typename Return, typename... Args>
|
|
1511
|
+
struct function_traits<Return (Class::*)(Args...) const> : function_traits<Return(Args...)> {};
|
|
1512
|
+
|
|
1513
|
+
template <typename Class, typename Return, typename... Args>
|
|
1514
|
+
struct function_traits<Return (Class::*)(Args...) noexcept> : function_traits<Return(Args...)> {};
|
|
1515
|
+
|
|
1516
|
+
template <typename Class, typename Return, typename... Args>
|
|
1517
|
+
struct function_traits<Return (Class::*)(Args...) const noexcept>
|
|
1518
|
+
: function_traits<Return(Args...)> {};
|
|
1519
|
+
|
|
1520
|
+
template <typename T, typename = void> struct resolved_function_traits {};
|
|
1521
|
+
|
|
1522
|
+
template <typename T>
|
|
1523
|
+
struct resolved_function_traits<T, std::void_t<decltype(&std::remove_reference_t<T>::operator())>>
|
|
1524
|
+
: function_traits<decltype(&std::remove_reference_t<T>::operator())> {};
|
|
1525
|
+
|
|
1526
|
+
template <typename T>
|
|
1527
|
+
requires std::is_function_v<std::remove_pointer_t<std::decay_t<T>>>
|
|
1528
|
+
struct resolved_function_traits<T, void> : function_traits<std::decay_t<T>> {};
|
|
1529
|
+
|
|
1530
|
+
template <typename Return, typename... Args>
|
|
1531
|
+
struct resolved_function_traits<std::function<Return(Args...)>, void>
|
|
1532
|
+
: function_traits<std::function<Return(Args...)>> {};
|
|
1533
|
+
|
|
1534
|
+
template <typename T>
|
|
1535
|
+
concept function_signature = requires {
|
|
1536
|
+
typename resolved_function_traits<std::decay_t<T>>::return_type;
|
|
1537
|
+
typename resolved_function_traits<std::decay_t<T>>::args_tuple;
|
|
1538
|
+
};
|
|
1539
|
+
|
|
1540
|
+
template <typename Argument> int argument_match_score(value input) noexcept {
|
|
1541
|
+
using type = std::remove_cvref_t<Argument>;
|
|
1542
|
+
if constexpr (is_special_argument_v<type>) {
|
|
1543
|
+
return 0;
|
|
1544
|
+
} else if constexpr (std::is_integral_v<type> && !std::is_same_v<type, bool>) {
|
|
1545
|
+
return input.is_integer() ? 2 : 0;
|
|
1546
|
+
} else if constexpr (std::is_floating_point_v<type>) {
|
|
1547
|
+
return input.is_float() ? 2 : (input.is_integer() ? 1 : 0);
|
|
1548
|
+
} else if constexpr (std::is_same_v<type, std::string> ||
|
|
1549
|
+
std::is_same_v<type, std::string_view> ||
|
|
1550
|
+
std::is_same_v<type, const char*>) {
|
|
1551
|
+
return input.is_string() ? 2 : 0;
|
|
1552
|
+
} else if constexpr (std::is_same_v<type, value> || std::is_same_v<type, object>) {
|
|
1553
|
+
return 1;
|
|
1554
|
+
} else {
|
|
1555
|
+
return type_caster<type>::matches(input) ? 2 : 0;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
template <typename Tuple, std::size_t... Index>
|
|
1560
|
+
int tuple_match_score(int argc, const VALUE* argv, std::index_sequence<Index...>) noexcept {
|
|
1561
|
+
if (argc != static_cast<int>(sizeof...(Index))) {
|
|
1562
|
+
return -1;
|
|
1563
|
+
}
|
|
1564
|
+
std::array<int, sizeof...(Index)> scores{
|
|
1565
|
+
argument_match_score<std::tuple_element_t<Index, Tuple>>(value{argv[Index]})...};
|
|
1566
|
+
int total = 0;
|
|
1567
|
+
for (int score : scores) {
|
|
1568
|
+
if (score == 0) {
|
|
1569
|
+
return -1;
|
|
1570
|
+
}
|
|
1571
|
+
total += score;
|
|
1572
|
+
}
|
|
1573
|
+
return total;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
template <typename Tuple, std::size_t... Index>
|
|
1577
|
+
consteval bool arguments_convertible(std::index_sequence<Index...>) {
|
|
1578
|
+
return (from_ruby_convertible<std::tuple_element_t<Index, Tuple>> && ...);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
template <typename Function> consteval bool function_arguments_convertible() {
|
|
1582
|
+
using traits = resolved_function_traits<std::decay_t<Function>>;
|
|
1583
|
+
using args = typename traits::args_tuple;
|
|
1584
|
+
return arguments_convertible<args>(std::make_index_sequence<std::tuple_size_v<args>>{});
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
template <typename Function> consteval bool function_return_convertible() {
|
|
1588
|
+
using result = typename resolved_function_traits<std::decay_t<Function>>::return_type;
|
|
1589
|
+
return std::is_void_v<result> || to_ruby_convertible<result>;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
template <typename Result> value dump_result(Result&& result, policy::kind selected);
|
|
1593
|
+
|
|
1594
|
+
class native_function {
|
|
1595
|
+
public:
|
|
1596
|
+
native_function() = default;
|
|
1597
|
+
native_function(const native_function&) = delete;
|
|
1598
|
+
native_function& operator=(const native_function&) = delete;
|
|
1599
|
+
virtual ~native_function() = default;
|
|
1600
|
+
|
|
1601
|
+
[[nodiscard]] virtual value invoke(int argc, const VALUE* argv, value self) = 0;
|
|
1602
|
+
[[nodiscard]] virtual std::string signature() const = 0;
|
|
1603
|
+
[[nodiscard]] virtual bool accepts_arity(int argc) const noexcept = 0;
|
|
1604
|
+
[[nodiscard]] virtual int match_score(int argc, const VALUE* argv) const noexcept = 0;
|
|
1605
|
+
[[nodiscard]] virtual std::size_t declared_arity() const noexcept = 0;
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
template <typename Function> class native_function_impl final : public native_function {
|
|
1609
|
+
public:
|
|
1610
|
+
explicit native_function_impl(Function function, std::vector<argument_spec> specs = {})
|
|
1611
|
+
: function_(std::move(function)), parser_(std::move(specs)) {}
|
|
1612
|
+
|
|
1613
|
+
[[nodiscard]] value invoke(int argc, const VALUE* argv, value) override {
|
|
1614
|
+
using traits = resolved_function_traits<Function>;
|
|
1615
|
+
parsed_arguments parsed = parser_.parse(argc, argv);
|
|
1616
|
+
return invoke_with_args(parsed, std::make_index_sequence<traits::arity>{});
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
[[nodiscard]] std::string signature() const override {
|
|
1620
|
+
return std::string(type_name<Function>());
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override {
|
|
1624
|
+
using traits = resolved_function_traits<Function>;
|
|
1625
|
+
if (has_rest_args(std::make_index_sequence<traits::arity>{})) {
|
|
1626
|
+
return true;
|
|
1627
|
+
}
|
|
1628
|
+
if (parser_.configured() || has_special_args(std::make_index_sequence<traits::arity>{})) {
|
|
1629
|
+
return argc <= static_cast<int>(traits::arity + 1U);
|
|
1630
|
+
}
|
|
1631
|
+
return argc == static_cast<int>(traits::arity);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
[[nodiscard]] int match_score(int argc, const VALUE* argv) const noexcept override {
|
|
1635
|
+
using traits = resolved_function_traits<Function>;
|
|
1636
|
+
using args = typename traits::args_tuple;
|
|
1637
|
+
if (parser_.configured() || has_special_args(std::make_index_sequence<traits::arity>{})) {
|
|
1638
|
+
return accepts_arity(argc) ? 0 : -1;
|
|
1639
|
+
}
|
|
1640
|
+
return tuple_match_score<args>(argc, argv, std::make_index_sequence<traits::arity>{});
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override {
|
|
1644
|
+
return resolved_function_traits<Function>::arity;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
private:
|
|
1648
|
+
template <std::size_t... Index>
|
|
1649
|
+
[[nodiscard]] value invoke_with_args(const parsed_arguments& parsed,
|
|
1650
|
+
std::index_sequence<Index...>) {
|
|
1651
|
+
using traits = resolved_function_traits<Function>;
|
|
1652
|
+
using args = typename traits::args_tuple;
|
|
1653
|
+
auto converted = std::tuple<decltype(load_parsed_argument<std::tuple_element_t<Index, args>>(
|
|
1654
|
+
parsed, Index))...>{
|
|
1655
|
+
load_parsed_argument<std::tuple_element_t<Index, args>>(parsed, Index)...};
|
|
1656
|
+
|
|
1657
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
1658
|
+
std::apply(function_, converted);
|
|
1659
|
+
return value{Qnil};
|
|
1660
|
+
} else {
|
|
1661
|
+
decltype(auto) result = std::apply(function_, converted);
|
|
1662
|
+
return dump_result(std::forward<decltype(result)>(result), policy::kind::automatic);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
template <std::size_t... Index>
|
|
1667
|
+
static consteval bool has_special_args(std::index_sequence<Index...>) {
|
|
1668
|
+
using args = typename resolved_function_traits<Function>::args_tuple;
|
|
1669
|
+
return (is_special_argument_v<std::tuple_element_t<Index, args>> || ...);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
template <std::size_t... Index>
|
|
1673
|
+
static consteval bool has_rest_args(std::index_sequence<Index...>) {
|
|
1674
|
+
using args = typename resolved_function_traits<Function>::args_tuple;
|
|
1675
|
+
return (is_rest_argument_v<std::tuple_element_t<Index, args>> || ...);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
Function function_;
|
|
1679
|
+
argument_parser<typename resolved_function_traits<Function>::args_tuple> parser_;
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
template <typename Result> value dump_result(Result&& result, policy::kind selected) {
|
|
1683
|
+
using result_type = Result;
|
|
1684
|
+
using bare_type = std::remove_cvref_t<Result>;
|
|
1685
|
+
if constexpr (std::is_pointer_v<bare_type> && std::is_class_v<std::remove_pointer_t<bare_type>>) {
|
|
1686
|
+
using pointee = std::remove_pointer_t<bare_type>;
|
|
1687
|
+
if (result == nullptr) {
|
|
1688
|
+
return value{Qnil};
|
|
1689
|
+
}
|
|
1690
|
+
if (selected == policy::kind::copy) {
|
|
1691
|
+
return wrap_copy(*result);
|
|
1692
|
+
}
|
|
1693
|
+
if (selected == policy::kind::take) {
|
|
1694
|
+
return wrap_take(std::unique_ptr<pointee>{result});
|
|
1695
|
+
}
|
|
1696
|
+
if (selected == policy::kind::shared) {
|
|
1697
|
+
throw std::invalid_argument("rbxx: shared policy requires std::shared_ptr<T>");
|
|
1698
|
+
}
|
|
1699
|
+
return wrap_reference(result);
|
|
1700
|
+
} else if constexpr (std::is_lvalue_reference_v<result_type> && std::is_class_v<bare_type>) {
|
|
1701
|
+
if (selected == policy::kind::copy) {
|
|
1702
|
+
return wrap_copy(result);
|
|
1703
|
+
}
|
|
1704
|
+
if (selected == policy::kind::take || selected == policy::kind::shared) {
|
|
1705
|
+
throw std::invalid_argument("rbxx: take/shared policy cannot be used with a reference");
|
|
1706
|
+
}
|
|
1707
|
+
return wrap_reference(std::addressof(result));
|
|
1708
|
+
} else {
|
|
1709
|
+
return to_ruby(std::forward<Result>(result));
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
struct method_key {
|
|
1714
|
+
VALUE owner;
|
|
1715
|
+
ID method;
|
|
1716
|
+
|
|
1717
|
+
friend bool operator==(const method_key&, const method_key&) = default;
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
struct method_key_hash {
|
|
1721
|
+
[[nodiscard]] std::size_t operator()(const method_key& key) const noexcept {
|
|
1722
|
+
const auto owner = static_cast<std::size_t>(key.owner);
|
|
1723
|
+
const auto method = static_cast<std::size_t>(key.method);
|
|
1724
|
+
return owner ^ (method + 0x9e3779b9U + (owner << 6U) + (owner >> 2U));
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
|
|
1728
|
+
class method_registry {
|
|
1729
|
+
public:
|
|
1730
|
+
static method_registry& instance() {
|
|
1731
|
+
static auto* registry = new method_registry();
|
|
1732
|
+
return *registry;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
bool add(VALUE owner, ID method, std::unique_ptr<native_function> function) {
|
|
1736
|
+
std::lock_guard lock(write_mutex_);
|
|
1737
|
+
auto& overloads = functions_[method_key{owner, method}];
|
|
1738
|
+
const bool first = overloads.empty();
|
|
1739
|
+
overloads.push_back(std::move(function));
|
|
1740
|
+
return first;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
using overloads = std::vector<std::unique_ptr<native_function>>;
|
|
1744
|
+
|
|
1745
|
+
[[nodiscard]] const overloads* find(VALUE self, ID method) const noexcept {
|
|
1746
|
+
if (const auto* direct = find_exact(self, method)) {
|
|
1747
|
+
return direct;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
VALUE klass = CLASS_OF(self);
|
|
1751
|
+
while (!NIL_P(klass)) {
|
|
1752
|
+
if (const auto* inherited = find_exact(klass, method)) {
|
|
1753
|
+
return inherited;
|
|
1754
|
+
}
|
|
1755
|
+
klass = rb_class_superclass(klass);
|
|
1756
|
+
}
|
|
1757
|
+
return find_exact(Qnil, method);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
private:
|
|
1761
|
+
[[nodiscard]] const overloads* find_exact(VALUE owner, ID method) const noexcept {
|
|
1762
|
+
auto found = functions_.find(method_key{owner, method});
|
|
1763
|
+
return found == functions_.end() ? nullptr : std::addressof(found->second);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
std::mutex write_mutex_;
|
|
1767
|
+
std::unordered_map<method_key, overloads, method_key_hash> functions_;
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
inline VALUE function_trampoline(int argc, VALUE* argv, VALUE self) {
|
|
1771
|
+
VALUE pending = Qnil;
|
|
1772
|
+
VALUE result = Qnil;
|
|
1773
|
+
try {
|
|
1774
|
+
ID method = rb_frame_this_func();
|
|
1775
|
+
const auto* functions = method_registry::instance().find(self, method);
|
|
1776
|
+
if (functions == nullptr || functions->empty()) {
|
|
1777
|
+
throw std::runtime_error("rbxx: native function registry entry was not found");
|
|
1778
|
+
}
|
|
1779
|
+
native_function* selected = nullptr;
|
|
1780
|
+
int best_score = -1;
|
|
1781
|
+
for (const auto& candidate : *functions) {
|
|
1782
|
+
int score = candidate->match_score(argc, argv);
|
|
1783
|
+
if (score > best_score) {
|
|
1784
|
+
best_score = score;
|
|
1785
|
+
selected = candidate.get();
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
if (selected == nullptr && functions->size() == 1U && functions->front()->accepts_arity(argc)) {
|
|
1789
|
+
selected = functions->front().get();
|
|
1790
|
+
}
|
|
1791
|
+
if (selected == nullptr) {
|
|
1792
|
+
std::string message = "rbxx: no matching overload";
|
|
1793
|
+
if (functions->size() == 1U) {
|
|
1794
|
+
message += "; expected ";
|
|
1795
|
+
message += std::to_string(functions->front()->declared_arity());
|
|
1796
|
+
message += ", actual ";
|
|
1797
|
+
message += std::to_string(argc);
|
|
1798
|
+
}
|
|
1799
|
+
message += "; candidates:";
|
|
1800
|
+
for (const auto& candidate : *functions) {
|
|
1801
|
+
message += "\n ";
|
|
1802
|
+
message += candidate->signature();
|
|
1803
|
+
}
|
|
1804
|
+
throw ruby_error(make_exception(rb_eArgError, message.c_str()));
|
|
1805
|
+
}
|
|
1806
|
+
result = selected->invoke(argc, argv, value{self}).raw();
|
|
1807
|
+
} catch (...) {
|
|
1808
|
+
pending = translate_current_exception();
|
|
1809
|
+
}
|
|
1810
|
+
if (!NIL_P(pending)) {
|
|
1811
|
+
rb_exc_raise(pending);
|
|
1812
|
+
}
|
|
1813
|
+
return result;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
template <typename Function>
|
|
1817
|
+
void register_function(VALUE owner, const char* name, Function&& function,
|
|
1818
|
+
std::vector<argument_spec> specs = {}, bool global = false) {
|
|
1819
|
+
using stored_function = std::decay_t<Function>;
|
|
1820
|
+
if constexpr (!function_signature<stored_function>) {
|
|
1821
|
+
static_assert(
|
|
1822
|
+
function_signature<stored_function>,
|
|
1823
|
+
"rbxx: callable signature cannot be determined; use a non-generic lambda, function "
|
|
1824
|
+
"pointer, or std::function");
|
|
1825
|
+
} else if constexpr (!function_arguments_convertible<stored_function>()) {
|
|
1826
|
+
static_assert(
|
|
1827
|
+
function_arguments_convertible<stored_function>(),
|
|
1828
|
+
"rbxx: function argument type has no type_caster; bind the type with def_class<T>() "
|
|
1829
|
+
"or specialize rbxx::type_caster<T>");
|
|
1830
|
+
} else if constexpr (!function_return_convertible<stored_function>()) {
|
|
1831
|
+
static_assert(
|
|
1832
|
+
function_return_convertible<stored_function>(),
|
|
1833
|
+
"rbxx: function return type has no type_caster; bind the type with def_class<T>() "
|
|
1834
|
+
"or specialize rbxx::type_caster<T>");
|
|
1835
|
+
} else {
|
|
1836
|
+
ID method = protect(rb_intern, name);
|
|
1837
|
+
const bool first =
|
|
1838
|
+
method_registry::instance().add(global ? Qnil : owner, method,
|
|
1839
|
+
std::make_unique<native_function_impl<stored_function>>(
|
|
1840
|
+
std::forward<Function>(function), std::move(specs)));
|
|
1841
|
+
if (first) {
|
|
1842
|
+
protect([owner, name, global] {
|
|
1843
|
+
if (global) {
|
|
1844
|
+
rb_define_global_function(name, function_trampoline, -1);
|
|
1845
|
+
} else {
|
|
1846
|
+
rb_define_module_function(owner, name, function_trampoline, -1);
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
template <typename Function>
|
|
1854
|
+
void register_static_function(VALUE owner, const char* name, Function&& function,
|
|
1855
|
+
std::vector<argument_spec> specs = {}) {
|
|
1856
|
+
using stored_function = std::decay_t<Function>;
|
|
1857
|
+
if constexpr (!function_signature<stored_function>) {
|
|
1858
|
+
static_assert(
|
|
1859
|
+
function_signature<stored_function>,
|
|
1860
|
+
"rbxx: callable signature cannot be determined; use a non-generic lambda, function "
|
|
1861
|
+
"pointer, or std::function");
|
|
1862
|
+
} else if constexpr (!function_arguments_convertible<stored_function>()) {
|
|
1863
|
+
static_assert(function_arguments_convertible<stored_function>(),
|
|
1864
|
+
"rbxx: function argument type has no type_caster");
|
|
1865
|
+
} else if constexpr (!function_return_convertible<stored_function>()) {
|
|
1866
|
+
static_assert(function_return_convertible<stored_function>(),
|
|
1867
|
+
"rbxx: function return type has no type_caster");
|
|
1868
|
+
} else {
|
|
1869
|
+
ID method = protect(rb_intern, name);
|
|
1870
|
+
const bool first =
|
|
1871
|
+
method_registry::instance().add(owner, method,
|
|
1872
|
+
std::make_unique<native_function_impl<stored_function>>(
|
|
1873
|
+
std::forward<Function>(function), std::move(specs)));
|
|
1874
|
+
if (first) {
|
|
1875
|
+
protect([owner, name] { rb_define_singleton_method(owner, name, function_trampoline, -1); });
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
} // namespace rbxx::detail
|
|
1881
|
+
// END rbxx/function.hpp
|
|
1882
|
+
|
|
1883
|
+
// BEGIN rbxx/operators.hpp
|
|
1884
|
+
|
|
1885
|
+
namespace rbxx::op {
|
|
1886
|
+
|
|
1887
|
+
/// @brief Metadata for mapping a C++ callable to a Ruby operator method.
|
|
1888
|
+
struct name {
|
|
1889
|
+
const char* ruby_name;
|
|
1890
|
+
bool include_comparable = false;
|
|
1891
|
+
};
|
|
1892
|
+
|
|
1893
|
+
/// @name Ruby operator names
|
|
1894
|
+
/// @{
|
|
1895
|
+
inline constexpr name add{"+"};
|
|
1896
|
+
inline constexpr name subtract{"-"};
|
|
1897
|
+
inline constexpr name multiply{"*"};
|
|
1898
|
+
inline constexpr name divide{"/"};
|
|
1899
|
+
inline constexpr name modulo{"%"};
|
|
1900
|
+
inline constexpr name equal{"=="};
|
|
1901
|
+
inline constexpr name compare{"<=>", true};
|
|
1902
|
+
inline constexpr name less{"<"};
|
|
1903
|
+
inline constexpr name greater{">"};
|
|
1904
|
+
inline constexpr name index{"[]"};
|
|
1905
|
+
inline constexpr name index_set{"[]="};
|
|
1906
|
+
inline constexpr name left_shift{"<<"};
|
|
1907
|
+
inline constexpr name to_s{"to_s"};
|
|
1908
|
+
inline constexpr name inspect{"inspect"};
|
|
1909
|
+
inline constexpr name hash{"hash"};
|
|
1910
|
+
inline constexpr name call{"call"};
|
|
1911
|
+
/// @}
|
|
1912
|
+
|
|
1913
|
+
} // namespace rbxx::op
|
|
1914
|
+
// END rbxx/operators.hpp
|
|
1915
|
+
|
|
1916
|
+
// BEGIN rbxx/module.hpp
|
|
1917
|
+
|
|
1918
|
+
|
|
1919
|
+
#include <source_location>
|
|
1920
|
+
#include <utility>
|
|
1921
|
+
|
|
1922
|
+
namespace rbxx {
|
|
1923
|
+
|
|
1924
|
+
template <typename T> class class_;
|
|
1925
|
+
|
|
1926
|
+
/// @brief A non-owning DSL handle for a Ruby Module.
|
|
1927
|
+
/// @code rbxx::define_module("Demo").def("answer", [] { return 42; }); @endcode
|
|
1928
|
+
class module {
|
|
1929
|
+
public:
|
|
1930
|
+
/// @brief Wraps an existing Ruby module VALUE.
|
|
1931
|
+
explicit module(value wrapped) noexcept : wrapped_(wrapped) {}
|
|
1932
|
+
|
|
1933
|
+
/// @brief Returns the wrapped Ruby module.
|
|
1934
|
+
[[nodiscard]] value get() const noexcept { return wrapped_; }
|
|
1935
|
+
|
|
1936
|
+
/// @brief Defines a Ruby module function backed by a C++ callable.
|
|
1937
|
+
/// @code mod.def("sum", [](int a, int b) { return a + b; }); @endcode
|
|
1938
|
+
template <typename Function, typename... Specs>
|
|
1939
|
+
module& def(const char* name, Function&& function, Specs&&... specs) {
|
|
1940
|
+
using traits = detail::resolved_function_traits<std::decay_t<Function>>;
|
|
1941
|
+
if constexpr (detail::function_signature<std::decay_t<Function>>) {
|
|
1942
|
+
static_assert(sizeof...(Specs) == 0U ||
|
|
1943
|
+
sizeof...(Specs) ==
|
|
1944
|
+
detail::normal_argument_count<typename traits::args_tuple>(),
|
|
1945
|
+
"rbxx: argument annotation count must match callable parameters");
|
|
1946
|
+
}
|
|
1947
|
+
detail::register_function(wrapped_.raw(), name, std::forward<Function>(function),
|
|
1948
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...));
|
|
1949
|
+
return *this;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
template <typename Function, typename... Specs>
|
|
1953
|
+
module& def(op::name operation, Function&& function, Specs&&... specs) {
|
|
1954
|
+
return def(operation.ruby_name, std::forward<Function>(function),
|
|
1955
|
+
std::forward<Specs>(specs)...);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/// @brief Defines a Ruby class backed by CRuby TypedData.
|
|
1959
|
+
/// @code auto counter = mod.def_class<Counter>("Counter"); @endcode
|
|
1960
|
+
template <typename T, typename Base = void>
|
|
1961
|
+
class_<T> def_class(const char* name,
|
|
1962
|
+
std::source_location location = std::source_location::current());
|
|
1963
|
+
|
|
1964
|
+
private:
|
|
1965
|
+
value wrapped_;
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
/// @brief Defines or reopens a top-level Ruby module.
|
|
1969
|
+
/// @code rbxx::module demo = rbxx::define_module("Demo"); @endcode
|
|
1970
|
+
[[nodiscard]] inline module define_module(const char* name) {
|
|
1971
|
+
return module{value{protect(rb_define_module, name)}};
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/// @brief Defines a private Kernel method callable as a Ruby global function.
|
|
1975
|
+
/// @code rbxx::define_global_function("native_sum", [](int a, int b) { return a + b; }); @endcode
|
|
1976
|
+
template <typename Function, typename... Specs>
|
|
1977
|
+
void define_global_function(const char* name, Function&& function, Specs&&... specs) {
|
|
1978
|
+
detail::register_function(Qnil, name, std::forward<Function>(function),
|
|
1979
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...), true);
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
} // namespace rbxx
|
|
1983
|
+
// END rbxx/module.hpp
|
|
1984
|
+
|
|
1985
|
+
// BEGIN rbxx/class.hpp
|
|
1986
|
+
|
|
1987
|
+
|
|
1988
|
+
#include <array>
|
|
1989
|
+
#include <cstddef>
|
|
1990
|
+
#include <iterator>
|
|
1991
|
+
#include <memory>
|
|
1992
|
+
#include <optional>
|
|
1993
|
+
#include <sstream>
|
|
1994
|
+
#include <string>
|
|
1995
|
+
#include <tuple>
|
|
1996
|
+
#include <type_traits>
|
|
1997
|
+
#include <utility>
|
|
1998
|
+
|
|
1999
|
+
namespace rbxx {
|
|
2000
|
+
|
|
2001
|
+
/// @brief Constructor signature marker used by class_::def.
|
|
2002
|
+
/// @code binding.def(rbxx::init<int>()); @endcode
|
|
2003
|
+
template <typename... Args> struct init_tag {};
|
|
2004
|
+
|
|
2005
|
+
/// @brief Creates a constructor signature marker.
|
|
2006
|
+
template <typename... Args> [[nodiscard]] constexpr init_tag<Args...> init() noexcept { return {}; }
|
|
2007
|
+
|
|
2008
|
+
namespace detail {
|
|
2009
|
+
|
|
2010
|
+
template <typename Method> struct member_method_traits;
|
|
2011
|
+
|
|
2012
|
+
template <typename Class, typename Return, typename... Args>
|
|
2013
|
+
struct member_method_traits<Return (Class::*)(Args...)> : function_traits<Return(Args...)> {
|
|
2014
|
+
using class_type = Class;
|
|
2015
|
+
};
|
|
2016
|
+
|
|
2017
|
+
template <typename Class, typename Return, typename... Args>
|
|
2018
|
+
struct member_method_traits<Return (Class::*)(Args...) const> : function_traits<Return(Args...)> {
|
|
2019
|
+
using class_type = Class;
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
template <typename Class, typename Return, typename... Args>
|
|
2023
|
+
struct member_method_traits<Return (Class::*)(Args...) noexcept>
|
|
2024
|
+
: function_traits<Return(Args...)> {
|
|
2025
|
+
using class_type = Class;
|
|
2026
|
+
};
|
|
2027
|
+
|
|
2028
|
+
template <typename Class, typename Return, typename... Args>
|
|
2029
|
+
struct member_method_traits<Return (Class::*)(Args...) const noexcept>
|
|
2030
|
+
: function_traits<Return(Args...)> {
|
|
2031
|
+
using class_type = Class;
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
inline void define_instance_trampoline(VALUE klass, const char* name) {
|
|
2035
|
+
protect([klass, name] { rb_define_method(klass, name, function_trampoline, -1); });
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
struct fast_zero_slot {
|
|
2039
|
+
VALUE (*invoke)(void*, VALUE);
|
|
2040
|
+
void* context;
|
|
2041
|
+
};
|
|
2042
|
+
|
|
2043
|
+
inline constexpr std::size_t fast_zero_slot_count = 64U;
|
|
2044
|
+
|
|
2045
|
+
inline std::array<fast_zero_slot, fast_zero_slot_count>& fast_zero_slots() {
|
|
2046
|
+
static auto* slots = new std::array<fast_zero_slot, fast_zero_slot_count>{};
|
|
2047
|
+
return *slots;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
template <std::size_t Index> VALUE fast_zero_trampoline(VALUE self) {
|
|
2051
|
+
VALUE result = Qnil;
|
|
2052
|
+
VALUE pending = Qnil;
|
|
2053
|
+
try {
|
|
2054
|
+
const fast_zero_slot& slot = fast_zero_slots()[Index];
|
|
2055
|
+
result = slot.invoke(slot.context, self);
|
|
2056
|
+
} catch (...) {
|
|
2057
|
+
pending = translate_current_exception();
|
|
2058
|
+
}
|
|
2059
|
+
if (!NIL_P(pending)) {
|
|
2060
|
+
rb_exc_raise(pending);
|
|
2061
|
+
}
|
|
2062
|
+
return result;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
using fast_zero_method = VALUE (*)(VALUE);
|
|
2066
|
+
|
|
2067
|
+
template <std::size_t Index = 0U> fast_zero_method select_fast_zero_method(std::size_t selected) {
|
|
2068
|
+
if constexpr (Index < fast_zero_slot_count) {
|
|
2069
|
+
if (selected == Index) {
|
|
2070
|
+
return &fast_zero_trampoline<Index>;
|
|
2071
|
+
}
|
|
2072
|
+
return select_fast_zero_method<Index + 1U>(selected);
|
|
2073
|
+
}
|
|
2074
|
+
return nullptr;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
inline std::optional<std::size_t> add_fast_zero_slot(fast_zero_slot slot) {
|
|
2078
|
+
static std::size_t next = 0U;
|
|
2079
|
+
if (next == fast_zero_slot_count) {
|
|
2080
|
+
return std::nullopt;
|
|
2081
|
+
}
|
|
2082
|
+
const std::size_t selected = next++;
|
|
2083
|
+
fast_zero_slots()[selected] = slot;
|
|
2084
|
+
return selected;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
template <typename Bound> Bound& load_bound_fast(VALUE self) {
|
|
2088
|
+
if (RB_TYPE_P(self, T_DATA) && RTYPEDDATA_P(self) &&
|
|
2089
|
+
RTYPEDDATA_TYPE(self) == &typed_data_type<Bound>()) {
|
|
2090
|
+
auto& wrapper = *static_cast<data_wrapper<Bound>*>(RTYPEDDATA_DATA(self));
|
|
2091
|
+
if (wrapper.pointer == nullptr) {
|
|
2092
|
+
throw ruby_error(make_exception(rb_eRuntimeError, "rbxx: C++ object is not initialized"));
|
|
2093
|
+
}
|
|
2094
|
+
return *wrapper.pointer;
|
|
2095
|
+
}
|
|
2096
|
+
return load_registered<Bound>(value{self});
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
template <typename Bound, typename Method> struct fast_member_context {
|
|
2100
|
+
Method method;
|
|
2101
|
+
policy::kind selected;
|
|
2102
|
+
};
|
|
2103
|
+
|
|
2104
|
+
template <typename Bound, typename Method> VALUE invoke_fast_member(void* opaque, VALUE self) {
|
|
2105
|
+
auto& context = *static_cast<fast_member_context<Bound, Method>*>(opaque);
|
|
2106
|
+
Bound& native = load_bound_fast<Bound>(self);
|
|
2107
|
+
using traits = member_method_traits<Method>;
|
|
2108
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
2109
|
+
std::invoke(context.method, native);
|
|
2110
|
+
return Qnil;
|
|
2111
|
+
} else {
|
|
2112
|
+
decltype(auto) result = std::invoke(context.method, native);
|
|
2113
|
+
return dump_result(std::forward<decltype(result)>(result), context.selected).raw();
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
template <typename Bound, typename Method>
|
|
2118
|
+
bool define_fast_member(VALUE klass, const char* name, Method method, policy::kind selected) {
|
|
2119
|
+
auto context = std::make_unique<fast_member_context<Bound, Method>>(method, selected);
|
|
2120
|
+
const auto slot =
|
|
2121
|
+
add_fast_zero_slot(fast_zero_slot{&invoke_fast_member<Bound, Method>, context.get()});
|
|
2122
|
+
if (!slot) {
|
|
2123
|
+
return false;
|
|
2124
|
+
}
|
|
2125
|
+
fast_zero_method trampoline = select_fast_zero_method(*slot);
|
|
2126
|
+
protect([klass, name, trampoline] { rb_define_method(klass, name, trampoline, 0); });
|
|
2127
|
+
context.release();
|
|
2128
|
+
return true;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
template <typename Bound, auto Method> VALUE direct_member_trampoline(VALUE self) {
|
|
2132
|
+
VALUE result = Qnil;
|
|
2133
|
+
VALUE pending = Qnil;
|
|
2134
|
+
try {
|
|
2135
|
+
Bound& native = load_bound_fast<Bound>(self);
|
|
2136
|
+
using traits = member_method_traits<decltype(Method)>;
|
|
2137
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
2138
|
+
std::invoke(Method, native);
|
|
2139
|
+
} else {
|
|
2140
|
+
decltype(auto) native_result = std::invoke(Method, native);
|
|
2141
|
+
result =
|
|
2142
|
+
dump_result(std::forward<decltype(native_result)>(native_result), policy::kind::automatic)
|
|
2143
|
+
.raw();
|
|
2144
|
+
}
|
|
2145
|
+
} catch (...) {
|
|
2146
|
+
pending = translate_current_exception();
|
|
2147
|
+
}
|
|
2148
|
+
if (!NIL_P(pending)) {
|
|
2149
|
+
rb_exc_raise(pending);
|
|
2150
|
+
}
|
|
2151
|
+
return result;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
template <typename Bound, auto Method> void define_direct_member(VALUE klass, const char* name) {
|
|
2155
|
+
constexpr fast_zero_method trampoline = &direct_member_trampoline<Bound, Method>;
|
|
2156
|
+
protect([klass, name] { rb_define_method(klass, name, trampoline, 0); });
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
template <typename T, typename... Args> class constructor_function final : public native_function {
|
|
2160
|
+
public:
|
|
2161
|
+
explicit constructor_function(std::vector<argument_spec> specs = {})
|
|
2162
|
+
: parser_(std::move(specs)) {}
|
|
2163
|
+
|
|
2164
|
+
[[nodiscard]] value invoke(int argc, const VALUE* argv, value self) override {
|
|
2165
|
+
parsed_arguments parsed = parser_.parse(argc, argv);
|
|
2166
|
+
auto converted = load(parsed, std::index_sequence_for<Args...>{});
|
|
2167
|
+
auto& wrapper = exact_wrapper<T>(self);
|
|
2168
|
+
if (wrapper.pointer != nullptr) {
|
|
2169
|
+
throw ruby_error(make_exception(rb_eRuntimeError, "rbxx: C++ object is already initialized"));
|
|
2170
|
+
}
|
|
2171
|
+
wrapper.pointer = std::apply(
|
|
2172
|
+
[](auto&&... arguments) { return new T(std::forward<decltype(arguments)>(arguments)...); },
|
|
2173
|
+
converted);
|
|
2174
|
+
wrapper.mode = ownership::owned;
|
|
2175
|
+
return value{Qnil};
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
[[nodiscard]] std::string signature() const override {
|
|
2179
|
+
return "init<" + std::string(type_name<T>()) + ">";
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override {
|
|
2183
|
+
return parser_.configured() ? argc <= static_cast<int>(sizeof...(Args) + 1U)
|
|
2184
|
+
: argc == static_cast<int>(sizeof...(Args));
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
[[nodiscard]] int match_score(int argc, const VALUE* argv) const noexcept override {
|
|
2188
|
+
if (parser_.configured()) {
|
|
2189
|
+
return accepts_arity(argc) ? 0 : -1;
|
|
2190
|
+
}
|
|
2191
|
+
using tuple = std::tuple<Args...>;
|
|
2192
|
+
return tuple_match_score<tuple>(argc, argv, std::index_sequence_for<Args...>{});
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override { return sizeof...(Args); }
|
|
2196
|
+
|
|
2197
|
+
private:
|
|
2198
|
+
template <std::size_t... Index>
|
|
2199
|
+
static auto load(const parsed_arguments& parsed, std::index_sequence<Index...>) {
|
|
2200
|
+
return std::tuple<decltype(load_parsed_argument<Args>(parsed, Index))...>{
|
|
2201
|
+
load_parsed_argument<Args>(parsed, Index)...};
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
argument_parser<std::tuple<Args...>> parser_;
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
template <typename Bound, typename Method> class member_function final : public native_function {
|
|
2208
|
+
public:
|
|
2209
|
+
member_function(Method method, policy::kind selected, std::vector<argument_spec> specs = {},
|
|
2210
|
+
std::vector<keep_alive_spec> keep_alive = {})
|
|
2211
|
+
: method_(method), policy_(selected), parser_(std::move(specs)),
|
|
2212
|
+
keep_alive_(std::move(keep_alive)) {}
|
|
2213
|
+
|
|
2214
|
+
[[nodiscard]] value invoke(int argc, const VALUE* argv, value self) override {
|
|
2215
|
+
using traits = member_method_traits<Method>;
|
|
2216
|
+
parsed_arguments parsed = parser_.parse(argc, argv);
|
|
2217
|
+
Bound& native = load_registered<Bound>(self);
|
|
2218
|
+
value result = invoke_native(native, parsed, std::make_index_sequence<traits::arity>{});
|
|
2219
|
+
apply_keep_alive(keep_alive_, result, self, argc, argv);
|
|
2220
|
+
return result;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
[[nodiscard]] std::string signature() const override { return std::string(type_name<Method>()); }
|
|
2224
|
+
|
|
2225
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override {
|
|
2226
|
+
constexpr auto arity = member_method_traits<Method>::arity;
|
|
2227
|
+
return parser_.configured() ? argc <= static_cast<int>(arity + 1U)
|
|
2228
|
+
: argc == static_cast<int>(arity);
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
[[nodiscard]] int match_score(int argc, const VALUE* argv) const noexcept override {
|
|
2232
|
+
using traits = member_method_traits<Method>;
|
|
2233
|
+
if (parser_.configured()) {
|
|
2234
|
+
return accepts_arity(argc) ? 0 : -1;
|
|
2235
|
+
}
|
|
2236
|
+
return tuple_match_score<typename traits::args_tuple>(
|
|
2237
|
+
argc, argv, std::make_index_sequence<traits::arity>{});
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override {
|
|
2241
|
+
return member_method_traits<Method>::arity;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
private:
|
|
2245
|
+
template <std::size_t... Index>
|
|
2246
|
+
[[nodiscard]] value invoke_native(Bound& self, const parsed_arguments& parsed,
|
|
2247
|
+
std::index_sequence<Index...>) {
|
|
2248
|
+
using traits = member_method_traits<Method>;
|
|
2249
|
+
using args = typename traits::args_tuple;
|
|
2250
|
+
auto converted = std::tuple<decltype(load_parsed_argument<std::tuple_element_t<Index, args>>(
|
|
2251
|
+
parsed, Index))...>{
|
|
2252
|
+
load_parsed_argument<std::tuple_element_t<Index, args>>(parsed, Index)...};
|
|
2253
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
2254
|
+
std::apply([&](auto&&... values) { std::invoke(method_, self, values...); }, converted);
|
|
2255
|
+
return value{Qnil};
|
|
2256
|
+
} else {
|
|
2257
|
+
decltype(auto) result = std::apply(
|
|
2258
|
+
[&](auto&&... values) -> decltype(auto) { return std::invoke(method_, self, values...); },
|
|
2259
|
+
converted);
|
|
2260
|
+
return dump_result(std::forward<decltype(result)>(result), policy_);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
Method method_;
|
|
2265
|
+
policy::kind policy_;
|
|
2266
|
+
argument_parser<typename member_method_traits<Method>::args_tuple> parser_;
|
|
2267
|
+
std::vector<keep_alive_spec> keep_alive_;
|
|
2268
|
+
};
|
|
2269
|
+
|
|
2270
|
+
template <typename Tuple, std::size_t... Index>
|
|
2271
|
+
consteval bool self_arguments_convertible(std::index_sequence<Index...>) {
|
|
2272
|
+
return (from_ruby_convertible<std::tuple_element_t<Index + 1U, Tuple>> && ...);
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
template <typename Tuple, std::size_t... Index>
|
|
2276
|
+
auto tuple_tail_type(std::index_sequence<Index...>)
|
|
2277
|
+
-> std::tuple<std::tuple_element_t<Index + 1U, Tuple>...>;
|
|
2278
|
+
|
|
2279
|
+
template <typename Tuple>
|
|
2280
|
+
using tuple_tail_t =
|
|
2281
|
+
decltype(tuple_tail_type<Tuple>(std::make_index_sequence<std::tuple_size_v<Tuple> - 1U>{}));
|
|
2282
|
+
|
|
2283
|
+
template <typename Bound, typename Function> class self_function final : public native_function {
|
|
2284
|
+
public:
|
|
2285
|
+
using traits = resolved_function_traits<Function>;
|
|
2286
|
+
using args = typename traits::args_tuple;
|
|
2287
|
+
static constexpr std::size_t ruby_arity = traits::arity - 1U;
|
|
2288
|
+
|
|
2289
|
+
self_function(Function function, policy::kind selected, std::vector<argument_spec> specs = {},
|
|
2290
|
+
std::vector<keep_alive_spec> keep_alive = {})
|
|
2291
|
+
: function_(std::move(function)), policy_(selected), parser_(std::move(specs)),
|
|
2292
|
+
keep_alive_(std::move(keep_alive)) {
|
|
2293
|
+
static_assert(traits::arity > 0,
|
|
2294
|
+
"rbxx: instance lambda must accept self as its first argument");
|
|
2295
|
+
using self_argument = std::tuple_element_t<0, args>;
|
|
2296
|
+
static_assert(std::is_lvalue_reference_v<self_argument> &&
|
|
2297
|
+
std::is_same_v<std::remove_cvref_t<self_argument>, Bound>,
|
|
2298
|
+
"rbxx: instance lambda first argument must be T& or const T&");
|
|
2299
|
+
static_assert(self_arguments_convertible<args>(std::make_index_sequence<ruby_arity>{}),
|
|
2300
|
+
"rbxx: instance lambda argument type has no type_caster");
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
[[nodiscard]] value invoke(int argc, const VALUE* argv, value self) override {
|
|
2304
|
+
parsed_arguments parsed = parser_.parse(argc, argv);
|
|
2305
|
+
Bound& native = load_registered<Bound>(self);
|
|
2306
|
+
value result = invoke_native(native, parsed, std::make_index_sequence<ruby_arity>{});
|
|
2307
|
+
apply_keep_alive(keep_alive_, result, self, argc, argv);
|
|
2308
|
+
return result;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
[[nodiscard]] std::string signature() const override {
|
|
2312
|
+
return std::string(type_name<Function>());
|
|
2313
|
+
}
|
|
2314
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override {
|
|
2315
|
+
return parser_.configured() ? argc <= static_cast<int>(ruby_arity + 1U)
|
|
2316
|
+
: argc == static_cast<int>(ruby_arity);
|
|
2317
|
+
}
|
|
2318
|
+
[[nodiscard]] int match_score(int argc, const VALUE* argv) const noexcept override {
|
|
2319
|
+
if (parser_.configured()) {
|
|
2320
|
+
return accepts_arity(argc) ? 0 : -1;
|
|
2321
|
+
}
|
|
2322
|
+
using ruby_args = tuple_tail_t<args>;
|
|
2323
|
+
return tuple_match_score<ruby_args>(argc, argv, std::make_index_sequence<ruby_arity>{});
|
|
2324
|
+
}
|
|
2325
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override { return ruby_arity; }
|
|
2326
|
+
|
|
2327
|
+
private:
|
|
2328
|
+
template <std::size_t... Index>
|
|
2329
|
+
[[nodiscard]] value invoke_native(Bound& self, const parsed_arguments& parsed,
|
|
2330
|
+
std::index_sequence<Index...>) {
|
|
2331
|
+
auto converted =
|
|
2332
|
+
std::tuple<decltype(load_parsed_argument<std::tuple_element_t<Index + 1U, args>>(
|
|
2333
|
+
parsed, Index))...>{
|
|
2334
|
+
load_parsed_argument<std::tuple_element_t<Index + 1U, args>>(parsed, Index)...};
|
|
2335
|
+
if constexpr (std::is_void_v<typename traits::return_type>) {
|
|
2336
|
+
std::apply([&](auto&&... values) { std::invoke(function_, self, values...); }, converted);
|
|
2337
|
+
return value{Qnil};
|
|
2338
|
+
} else {
|
|
2339
|
+
decltype(auto) result = std::apply(
|
|
2340
|
+
[&](auto&&... values) -> decltype(auto) {
|
|
2341
|
+
return std::invoke(function_, self, values...);
|
|
2342
|
+
},
|
|
2343
|
+
converted);
|
|
2344
|
+
return dump_result(std::forward<decltype(result)>(result), policy_);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
Function function_;
|
|
2349
|
+
policy::kind policy_;
|
|
2350
|
+
argument_parser<tuple_tail_t<args>> parser_;
|
|
2351
|
+
std::vector<keep_alive_spec> keep_alive_;
|
|
2352
|
+
};
|
|
2353
|
+
|
|
2354
|
+
template <typename Bound, auto Begin, auto End>
|
|
2355
|
+
class iterable_function final : public native_function {
|
|
2356
|
+
public:
|
|
2357
|
+
[[nodiscard]] value invoke(int, const VALUE*, value self) override {
|
|
2358
|
+
if (rb_block_given_p() == 0) {
|
|
2359
|
+
VALUE enumerator = protect([self] {
|
|
2360
|
+
return rb_enumeratorize_with_size(self.raw(), ID2SYM(rb_intern("each")), 0, nullptr,
|
|
2361
|
+
size_callback);
|
|
2362
|
+
});
|
|
2363
|
+
return value{enumerator};
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
Bound& native = load_registered<Bound>(self);
|
|
2367
|
+
auto iterator = std::invoke(Begin, native);
|
|
2368
|
+
const auto finish = std::invoke(End, native);
|
|
2369
|
+
for (; iterator != finish; ++iterator) {
|
|
2370
|
+
value item = to_ruby(*iterator);
|
|
2371
|
+
protect(rb_yield, item.raw());
|
|
2372
|
+
}
|
|
2373
|
+
return self;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
[[nodiscard]] std::string signature() const override { return "each()"; }
|
|
2377
|
+
[[nodiscard]] bool accepts_arity(int argc) const noexcept override { return argc == 0; }
|
|
2378
|
+
[[nodiscard]] int match_score(int argc, const VALUE*) const noexcept override {
|
|
2379
|
+
return argc == 0 ? 0 : -1;
|
|
2380
|
+
}
|
|
2381
|
+
[[nodiscard]] std::size_t declared_arity() const noexcept override { return 0U; }
|
|
2382
|
+
|
|
2383
|
+
private:
|
|
2384
|
+
static VALUE size_callback(VALUE self, VALUE, VALUE) {
|
|
2385
|
+
VALUE result = Qnil;
|
|
2386
|
+
VALUE pending = Qnil;
|
|
2387
|
+
try {
|
|
2388
|
+
Bound& native = load_registered<Bound>(value{self});
|
|
2389
|
+
auto first = std::invoke(Begin, native);
|
|
2390
|
+
auto last = std::invoke(End, native);
|
|
2391
|
+
result = to_ruby(std::distance(first, last)).raw();
|
|
2392
|
+
} catch (...) {
|
|
2393
|
+
pending = translate_current_exception();
|
|
2394
|
+
}
|
|
2395
|
+
if (!NIL_P(pending)) {
|
|
2396
|
+
rb_exc_raise(pending);
|
|
2397
|
+
}
|
|
2398
|
+
return result;
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
|
|
2402
|
+
} // namespace detail
|
|
2403
|
+
|
|
2404
|
+
/// @brief Fluent binding handle for a C++ class exposed as Ruby TypedData.
|
|
2405
|
+
/// @code binding.def(rbxx::init<int>()).def("value", &Counter::value); @endcode
|
|
2406
|
+
template <typename T> class class_ {
|
|
2407
|
+
public:
|
|
2408
|
+
explicit class_(value ruby_class) noexcept : ruby_class_(ruby_class) {}
|
|
2409
|
+
|
|
2410
|
+
[[nodiscard]] value get() const noexcept { return ruby_class_; }
|
|
2411
|
+
|
|
2412
|
+
/// @brief Defines a compile-time-bound zero-argument member on the minimal dispatch path.
|
|
2413
|
+
/// @code binding.def<&Counter::value>("value"); @endcode
|
|
2414
|
+
template <auto Method> class_& def(const char* name) {
|
|
2415
|
+
using method_type = decltype(Method);
|
|
2416
|
+
static_assert(std::is_member_function_pointer_v<method_type>,
|
|
2417
|
+
"rbxx: compile-time def requires a member function pointer");
|
|
2418
|
+
using traits = detail::member_method_traits<method_type>;
|
|
2419
|
+
static_assert(traits::arity == 0U,
|
|
2420
|
+
"rbxx: compile-time def currently supports zero-argument members");
|
|
2421
|
+
static_assert(std::is_void_v<typename traits::return_type> ||
|
|
2422
|
+
to_ruby_convertible<typename traits::return_type>,
|
|
2423
|
+
"rbxx: member function return type has no type_caster");
|
|
2424
|
+
|
|
2425
|
+
ID method = protect(rb_intern, name);
|
|
2426
|
+
const bool first = detail::method_registry::instance().add(
|
|
2427
|
+
ruby_class_.raw(), method,
|
|
2428
|
+
std::make_unique<detail::member_function<T, method_type>>(Method, policy::kind::automatic));
|
|
2429
|
+
if (first) {
|
|
2430
|
+
detail::define_direct_member<T, Method>(ruby_class_.raw(), name);
|
|
2431
|
+
} else {
|
|
2432
|
+
detail::define_instance_trampoline(ruby_class_.raw(), name);
|
|
2433
|
+
}
|
|
2434
|
+
return *this;
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
/// @brief Defines a C++ constructor and optional Ruby argument annotations.
|
|
2438
|
+
template <typename... Args, typename... Specs>
|
|
2439
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
2440
|
+
class_& def(init_tag<Args...>, Specs&&... specs) {
|
|
2441
|
+
static_assert(sizeof...(Specs) == 0U ||
|
|
2442
|
+
sizeof...(Specs) == detail::normal_argument_count<std::tuple<Args...>>(),
|
|
2443
|
+
"rbxx: argument annotation count must match constructor parameters");
|
|
2444
|
+
static_assert((from_ruby_convertible<Args> && ...),
|
|
2445
|
+
"rbxx: constructor argument type has no type_caster");
|
|
2446
|
+
ID method = protect(rb_intern, "initialize");
|
|
2447
|
+
const bool first = detail::method_registry::instance().add(
|
|
2448
|
+
ruby_class_.raw(), method,
|
|
2449
|
+
std::make_unique<detail::constructor_function<T, Args...>>(
|
|
2450
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...)));
|
|
2451
|
+
if (first) {
|
|
2452
|
+
detail::define_instance_trampoline(ruby_class_.raw(), "initialize");
|
|
2453
|
+
}
|
|
2454
|
+
return *this;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
/// @brief Defines an instance method backed by a member pointer or self-first callable.
|
|
2458
|
+
template <typename Function, typename... Specs>
|
|
2459
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
2460
|
+
class_& def(const char* name, Function&& function, Specs&&... specs) {
|
|
2461
|
+
return bind_function(name, std::forward<Function>(function), policy::automatic,
|
|
2462
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...));
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
template <typename Function, typename... Specs>
|
|
2466
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
2467
|
+
class_& def(const char* name, Function&& function, policy::return_value_policy selected,
|
|
2468
|
+
Specs&&... specs) {
|
|
2469
|
+
return bind_function(name, std::forward<Function>(function), selected,
|
|
2470
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...));
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
template <typename Function, std::size_t Nurse, std::size_t Patient, typename... Specs>
|
|
2474
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
2475
|
+
class_& def(const char* name, Function&& function, policy::return_value_policy selected,
|
|
2476
|
+
keep_alive<Nurse, Patient> lifetime, Specs&&... specs) {
|
|
2477
|
+
return bind_function(name, std::forward<Function>(function), selected,
|
|
2478
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...),
|
|
2479
|
+
{detail::make_keep_alive_spec(lifetime)});
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
template <typename Function, std::size_t Nurse, std::size_t Patient, typename... Specs>
|
|
2483
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
2484
|
+
class_& def(const char* name, Function&& function, keep_alive<Nurse, Patient> lifetime,
|
|
2485
|
+
Specs&&... specs) {
|
|
2486
|
+
return bind_function(name, std::forward<Function>(function), policy::automatic,
|
|
2487
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...),
|
|
2488
|
+
{detail::make_keep_alive_spec(lifetime)});
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
template <typename Function, typename... Specs>
|
|
2492
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
2493
|
+
class_& def(op::name operation, Function&& function, Specs&&... specs) {
|
|
2494
|
+
class_& result =
|
|
2495
|
+
def(operation.ruby_name, std::forward<Function>(function), std::forward<Specs>(specs)...);
|
|
2496
|
+
include_comparable(operation);
|
|
2497
|
+
return result;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
template <typename Function, typename... Specs>
|
|
2501
|
+
requires((std::is_convertible_v<Specs, argument_spec>) && ...)
|
|
2502
|
+
class_& def(op::name operation, Function&& function, policy::return_value_policy selected,
|
|
2503
|
+
Specs&&... specs) {
|
|
2504
|
+
class_& result = def(operation.ruby_name, std::forward<Function>(function), selected,
|
|
2505
|
+
std::forward<Specs>(specs)...);
|
|
2506
|
+
include_comparable(operation);
|
|
2507
|
+
return result;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
/// @brief Defines a singleton method on the bound Ruby class.
|
|
2511
|
+
template <typename Function, typename... Specs>
|
|
2512
|
+
class_& def_static(const char* name, Function&& function, Specs&&... specs) {
|
|
2513
|
+
detail::register_static_function(ruby_class_.raw(), name, std::forward<Function>(function),
|
|
2514
|
+
detail::make_argument_specs(std::forward<Specs>(specs)...));
|
|
2515
|
+
return *this;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
/// @brief Defines a Ruby reader for a public C++ data member.
|
|
2519
|
+
template <typename Member> class_& def_attr_reader(const char* name, Member T::*member) {
|
|
2520
|
+
return def(name, [member](const T& self) -> const Member& { return self.*member; });
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/// @brief Defines a Ruby writer for a public C++ data member.
|
|
2524
|
+
template <typename Member> class_& def_attr_writer(const char* name, Member T::*member) {
|
|
2525
|
+
std::string writer = std::string(name) + "=";
|
|
2526
|
+
return def(writer.c_str(), [member](T& self, Member updated) {
|
|
2527
|
+
self.*member = std::move(updated);
|
|
2528
|
+
return self.*member;
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
/// @brief Defines Ruby reader and writer methods for a public C++ data member.
|
|
2533
|
+
template <typename Member> class_& def_attr_accessor(const char* name, Member T::*member) {
|
|
2534
|
+
def_attr_reader(name, member);
|
|
2535
|
+
return def_attr_writer(name, member);
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
/// @brief Defines Ruby each, includes Enumerable, and returns sized enumerators without a block.
|
|
2539
|
+
template <auto Begin, auto End> class_& def_iterable() {
|
|
2540
|
+
using item_reference = decltype(*std::invoke(Begin, std::declval<T&>()));
|
|
2541
|
+
static_assert(to_ruby_convertible<item_reference>,
|
|
2542
|
+
"rbxx: iterable item type has no type_caster");
|
|
2543
|
+
ID method = protect(rb_intern, "each");
|
|
2544
|
+
const bool first = detail::method_registry::instance().add(
|
|
2545
|
+
ruby_class_.raw(), method, std::make_unique<detail::iterable_function<T, Begin, End>>());
|
|
2546
|
+
if (first) {
|
|
2547
|
+
detail::define_instance_trampoline(ruby_class_.raw(), "each");
|
|
2548
|
+
}
|
|
2549
|
+
protect(rb_include_module, ruby_class_.raw(), rb_mEnumerable);
|
|
2550
|
+
return *this;
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
private:
|
|
2554
|
+
void include_comparable(op::name operation) {
|
|
2555
|
+
if (operation.include_comparable) {
|
|
2556
|
+
protect(rb_include_module, ruby_class_.raw(), rb_mComparable);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
template <typename Function>
|
|
2561
|
+
class_& bind_function(const char* name, Function&& function, policy::return_value_policy selected,
|
|
2562
|
+
std::vector<argument_spec> specs,
|
|
2563
|
+
std::vector<detail::keep_alive_spec> keep_alive = {}) {
|
|
2564
|
+
using stored_function = std::decay_t<Function>;
|
|
2565
|
+
ID method = protect(rb_intern, name);
|
|
2566
|
+
if constexpr (std::is_member_function_pointer_v<stored_function>) {
|
|
2567
|
+
using traits = detail::member_method_traits<stored_function>;
|
|
2568
|
+
static_assert(detail::arguments_convertible<typename traits::args_tuple>(
|
|
2569
|
+
std::make_index_sequence<traits::arity>{}),
|
|
2570
|
+
"rbxx: member function argument type has no type_caster");
|
|
2571
|
+
const bool fast = traits::arity == 0U && specs.empty() && keep_alive.empty();
|
|
2572
|
+
const bool first = detail::method_registry::instance().add(
|
|
2573
|
+
ruby_class_.raw(), method,
|
|
2574
|
+
std::make_unique<detail::member_function<T, stored_function>>(
|
|
2575
|
+
function, selected.value, std::move(specs), std::move(keep_alive)));
|
|
2576
|
+
bool defined_fast = false;
|
|
2577
|
+
if constexpr (traits::arity == 0U) {
|
|
2578
|
+
defined_fast =
|
|
2579
|
+
first && fast &&
|
|
2580
|
+
detail::define_fast_member<T>(ruby_class_.raw(), name, function, selected.value);
|
|
2581
|
+
}
|
|
2582
|
+
if (!defined_fast) {
|
|
2583
|
+
detail::define_instance_trampoline(ruby_class_.raw(), name);
|
|
2584
|
+
}
|
|
2585
|
+
} else {
|
|
2586
|
+
static_assert(detail::function_signature<stored_function>,
|
|
2587
|
+
"rbxx: instance callable signature cannot be determined");
|
|
2588
|
+
detail::method_registry::instance().add(
|
|
2589
|
+
ruby_class_.raw(), method,
|
|
2590
|
+
std::make_unique<detail::self_function<T, stored_function>>(
|
|
2591
|
+
std::forward<Function>(function), selected.value, std::move(specs),
|
|
2592
|
+
std::move(keep_alive)));
|
|
2593
|
+
detail::define_instance_trampoline(ruby_class_.raw(), name);
|
|
2594
|
+
}
|
|
2595
|
+
return *this;
|
|
2596
|
+
}
|
|
2597
|
+
value ruby_class_;
|
|
2598
|
+
};
|
|
2599
|
+
|
|
2600
|
+
template <typename T, typename Base>
|
|
2601
|
+
class_<T> module::def_class(const char* name, std::source_location location) {
|
|
2602
|
+
VALUE superclass = rb_cObject;
|
|
2603
|
+
if constexpr (!std::is_void_v<Base>) {
|
|
2604
|
+
static_assert(std::is_base_of_v<Base, T>, "rbxx: Base must be a C++ base class of T");
|
|
2605
|
+
superclass = detail::registered_class<Base>().ruby_class;
|
|
2606
|
+
}
|
|
2607
|
+
VALUE klass = protect(rb_define_class_under, wrapped_.raw(), name, superclass);
|
|
2608
|
+
detail::register_data_class<T, Base>(klass, location);
|
|
2609
|
+
protect([klass] { rb_define_alloc_func(klass, detail::allocate_data_object<T>); });
|
|
2610
|
+
return class_<T>{value{klass}};
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
} // namespace rbxx
|
|
2614
|
+
// END rbxx/class.hpp
|
|
2615
|
+
|
|
2616
|
+
// BEGIN rbxx/extension.hpp
|
|
2617
|
+
|
|
2618
|
+
|
|
2619
|
+
#if defined(_WIN32)
|
|
2620
|
+
#define RBXX_EXPORT __declspec(dllexport)
|
|
2621
|
+
#else
|
|
2622
|
+
#define RBXX_EXPORT __attribute__((visibility("default")))
|
|
2623
|
+
#endif
|
|
2624
|
+
|
|
2625
|
+
#define RBXX_EXTENSION(name) \
|
|
2626
|
+
static void rbxx_init_body_##name(); \
|
|
2627
|
+
extern "C" RBXX_EXPORT void Init_##name() { \
|
|
2628
|
+
VALUE rbxx_pending_exception = Qnil; \
|
|
2629
|
+
try { \
|
|
2630
|
+
rbxx_init_body_##name(); \
|
|
2631
|
+
return; \
|
|
2632
|
+
} catch (...) { \
|
|
2633
|
+
rbxx_pending_exception = ::rbxx::detail::translate_current_exception(); \
|
|
2634
|
+
} \
|
|
2635
|
+
rb_exc_raise(rbxx_pending_exception); \
|
|
2636
|
+
} \
|
|
2637
|
+
static void rbxx_init_body_##name()
|
|
2638
|
+
// END rbxx/extension.hpp
|
|
2639
|
+
|
|
2640
|
+
// BEGIN rbxx/nogvl.hpp
|
|
2641
|
+
|
|
2642
|
+
|
|
2643
|
+
#include <exception>
|
|
2644
|
+
#include <functional>
|
|
2645
|
+
#include <optional>
|
|
2646
|
+
#include <stdexcept>
|
|
2647
|
+
#include <tuple>
|
|
2648
|
+
#include <type_traits>
|
|
2649
|
+
#include <utility>
|
|
2650
|
+
|
|
2651
|
+
namespace rbxx {
|
|
2652
|
+
namespace detail {
|
|
2653
|
+
|
|
2654
|
+
struct default_unblock_function {};
|
|
2655
|
+
|
|
2656
|
+
template <typename T> struct is_std_function : std::false_type {};
|
|
2657
|
+
template <typename Return, typename... Args>
|
|
2658
|
+
struct is_std_function<std::function<Return(Args...)>> : std::true_type {};
|
|
2659
|
+
|
|
2660
|
+
template <typename T>
|
|
2661
|
+
inline constexpr bool gvl_independent_v = !std::is_same_v<std::remove_cvref_t<T>, value> &&
|
|
2662
|
+
!std::is_same_v<std::remove_cvref_t<T>, object> &&
|
|
2663
|
+
!std::is_same_v<std::remove_cvref_t<T>, block> &&
|
|
2664
|
+
!std::is_same_v<std::remove_cvref_t<T>, optional_block> &&
|
|
2665
|
+
!std::is_same_v<std::remove_cvref_t<T>, args> &&
|
|
2666
|
+
!is_std_function<std::remove_cvref_t<T>>::value;
|
|
2667
|
+
|
|
2668
|
+
template <typename Function, typename Unblock, typename Return, typename Tuple>
|
|
2669
|
+
class nogvl_adapter_impl;
|
|
2670
|
+
|
|
2671
|
+
template <typename Function, typename Unblock, typename Return, typename... Args>
|
|
2672
|
+
class nogvl_adapter_impl<Function, Unblock, Return, std::tuple<Args...>> {
|
|
2673
|
+
public:
|
|
2674
|
+
explicit nogvl_adapter_impl(Function function) : function_(std::move(function)) {
|
|
2675
|
+
validate_signature();
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
nogvl_adapter_impl(Function function, Unblock unblock)
|
|
2679
|
+
: function_(std::move(function)), unblock_(std::move(unblock)) {
|
|
2680
|
+
validate_signature();
|
|
2681
|
+
static_assert(std::is_nothrow_invocable_v<Unblock&>,
|
|
2682
|
+
"rbxx: nogvl interrupt function must be noexcept");
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
Return operator()(Args... args) {
|
|
2686
|
+
payload work{this, std::tuple<Args...>{std::forward<Args>(args)...}, std::nullopt, nullptr};
|
|
2687
|
+
if constexpr (std::is_same_v<Unblock, default_unblock_function>) {
|
|
2688
|
+
protect([&work] { rb_thread_call_without_gvl(run, &work, RUBY_UBF_IO, nullptr); });
|
|
2689
|
+
} else {
|
|
2690
|
+
protect([&work, this] {
|
|
2691
|
+
rb_thread_call_without_gvl(run, &work, interrupt, std::addressof(unblock_));
|
|
2692
|
+
});
|
|
2693
|
+
}
|
|
2694
|
+
if (work.exception) {
|
|
2695
|
+
std::rethrow_exception(work.exception);
|
|
2696
|
+
}
|
|
2697
|
+
if constexpr (!std::is_void_v<Return>) {
|
|
2698
|
+
if (!work.result) {
|
|
2699
|
+
throw std::runtime_error("rbxx: nogvl function was interrupted before producing a result");
|
|
2700
|
+
}
|
|
2701
|
+
return std::move(*work.result);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
private:
|
|
2706
|
+
using stored_result = std::conditional_t<std::is_void_v<Return>, bool, Return>;
|
|
2707
|
+
|
|
2708
|
+
struct payload {
|
|
2709
|
+
nogvl_adapter_impl* adapter;
|
|
2710
|
+
std::tuple<Args...> arguments;
|
|
2711
|
+
std::optional<stored_result> result;
|
|
2712
|
+
std::exception_ptr exception;
|
|
2713
|
+
};
|
|
2714
|
+
|
|
2715
|
+
static consteval void validate_signature() {
|
|
2716
|
+
static_assert(!std::is_reference_v<Return>,
|
|
2717
|
+
"rbxx: nogvl return values must be owned C++ values");
|
|
2718
|
+
static_assert(gvl_independent_v<Return>,
|
|
2719
|
+
"rbxx: nogvl return type must not access Ruby without the GVL");
|
|
2720
|
+
static_assert((gvl_independent_v<Args> && ...),
|
|
2721
|
+
"rbxx: nogvl arguments must not access Ruby without the GVL");
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
static void* run(void* opaque) noexcept {
|
|
2725
|
+
auto* work = static_cast<payload*>(opaque);
|
|
2726
|
+
try {
|
|
2727
|
+
if constexpr (std::is_void_v<Return>) {
|
|
2728
|
+
std::apply(
|
|
2729
|
+
[&](auto&&... values) {
|
|
2730
|
+
std::invoke(work->adapter->function_, std::forward<Args>(values)...);
|
|
2731
|
+
},
|
|
2732
|
+
work->arguments);
|
|
2733
|
+
work->result.emplace(true);
|
|
2734
|
+
} else {
|
|
2735
|
+
work->result.emplace(std::apply(
|
|
2736
|
+
[&](auto&&... values) -> Return {
|
|
2737
|
+
return std::invoke(work->adapter->function_, std::forward<Args>(values)...);
|
|
2738
|
+
},
|
|
2739
|
+
work->arguments));
|
|
2740
|
+
}
|
|
2741
|
+
} catch (...) {
|
|
2742
|
+
work->exception = std::current_exception();
|
|
2743
|
+
}
|
|
2744
|
+
return nullptr;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
static void interrupt(void* opaque) noexcept { std::invoke(*static_cast<Unblock*>(opaque)); }
|
|
2748
|
+
|
|
2749
|
+
Function function_;
|
|
2750
|
+
[[no_unique_address]] Unblock unblock_{};
|
|
2751
|
+
};
|
|
2752
|
+
|
|
2753
|
+
template <typename Function, typename Unblock = default_unblock_function>
|
|
2754
|
+
using nogvl_adapter =
|
|
2755
|
+
nogvl_adapter_impl<std::decay_t<Function>, std::decay_t<Unblock>,
|
|
2756
|
+
typename resolved_function_traits<std::decay_t<Function>>::return_type,
|
|
2757
|
+
typename resolved_function_traits<std::decay_t<Function>>::args_tuple>;
|
|
2758
|
+
|
|
2759
|
+
} // namespace detail
|
|
2760
|
+
|
|
2761
|
+
/// @brief Wraps a pure C++ callable so its execution occurs without the Ruby GVL.
|
|
2762
|
+
template <typename Function> [[nodiscard]] auto nogvl(Function&& function) {
|
|
2763
|
+
using stored_function = std::decay_t<Function>;
|
|
2764
|
+
static_assert(detail::function_signature<stored_function>,
|
|
2765
|
+
"rbxx: nogvl requires a callable with a concrete signature");
|
|
2766
|
+
static_assert(!std::is_member_function_pointer_v<stored_function>,
|
|
2767
|
+
"rbxx: nogvl expects a free function or callable object");
|
|
2768
|
+
return detail::nogvl_adapter<stored_function>{std::forward<Function>(function)};
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
/// @brief Wraps a pure C++ callable with a noexcept interruption hook.
|
|
2772
|
+
template <typename Function, typename Unblock>
|
|
2773
|
+
[[nodiscard]] auto nogvl_interruptible(Function&& function, Unblock&& unblock) {
|
|
2774
|
+
using stored_function = std::decay_t<Function>;
|
|
2775
|
+
using stored_unblock = std::decay_t<Unblock>;
|
|
2776
|
+
static_assert(detail::function_signature<stored_function>,
|
|
2777
|
+
"rbxx: nogvl_interruptible requires a callable with a concrete signature");
|
|
2778
|
+
static_assert(!std::is_member_function_pointer_v<stored_function>,
|
|
2779
|
+
"rbxx: nogvl_interruptible expects a free function or callable object");
|
|
2780
|
+
return detail::nogvl_adapter<stored_function, stored_unblock>{std::forward<Function>(function),
|
|
2781
|
+
std::forward<Unblock>(unblock)};
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
} // namespace rbxx
|
|
2785
|
+
// END rbxx/nogvl.hpp
|
|
2786
|
+
|
|
2787
|
+
// BEGIN rbxx/stl/detail.hpp
|
|
2788
|
+
|
|
2789
|
+
|
|
2790
|
+
#include <limits>
|
|
2791
|
+
#include <string>
|
|
2792
|
+
#include <type_traits>
|
|
2793
|
+
|
|
2794
|
+
namespace rbxx::detail {
|
|
2795
|
+
|
|
2796
|
+
inline VALUE coerce_array(value input) {
|
|
2797
|
+
VALUE converted = protect(rb_check_array_type, input.raw());
|
|
2798
|
+
if (NIL_P(converted)) {
|
|
2799
|
+
throw_type_error("Array or object responding to #to_ary", input);
|
|
2800
|
+
}
|
|
2801
|
+
return converted;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
inline VALUE coerce_hash(value input) {
|
|
2805
|
+
VALUE converted = protect(rb_check_hash_type, input.raw());
|
|
2806
|
+
if (NIL_P(converted)) {
|
|
2807
|
+
throw_type_error("Hash or object responding to #to_hash", input);
|
|
2808
|
+
}
|
|
2809
|
+
return converted;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
template <typename Range> value dump_array_range(const Range& input) {
|
|
2813
|
+
const auto size = input.size();
|
|
2814
|
+
if (size > static_cast<std::size_t>(std::numeric_limits<long>::max())) {
|
|
2815
|
+
throw std::length_error("rbxx: container is too large for a Ruby Array");
|
|
2816
|
+
}
|
|
2817
|
+
return value{protect([&input, size] {
|
|
2818
|
+
VALUE result = rb_ary_new_capa(static_cast<long>(size));
|
|
2819
|
+
for (const auto& element : input) {
|
|
2820
|
+
using element_type = std::remove_cvref_t<decltype(element)>;
|
|
2821
|
+
if constexpr (std::is_floating_point_v<element_type>) {
|
|
2822
|
+
rb_ary_push(result, rb_float_new(static_cast<double>(element)));
|
|
2823
|
+
} else {
|
|
2824
|
+
rb_ary_push(result, to_ruby(element).raw());
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
return result;
|
|
2828
|
+
})};
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
template <typename Map> value dump_hash_range(const Map& input) {
|
|
2832
|
+
return value{protect([&input] {
|
|
2833
|
+
VALUE result = rb_hash_new();
|
|
2834
|
+
for (const auto& [key, mapped] : input) {
|
|
2835
|
+
rb_hash_aset(result, to_ruby(key).raw(), to_ruby(mapped).raw());
|
|
2836
|
+
}
|
|
2837
|
+
return result;
|
|
2838
|
+
})};
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
inline VALUE hash_pairs(VALUE hash) {
|
|
2842
|
+
return protect([hash] { return rb_funcall(hash, rb_intern("to_a"), 0); });
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
} // namespace rbxx::detail
|
|
2846
|
+
// END rbxx/stl/detail.hpp
|
|
2847
|
+
|
|
2848
|
+
// BEGIN rbxx/stl/array.hpp
|
|
2849
|
+
|
|
2850
|
+
|
|
2851
|
+
#include <array>
|
|
2852
|
+
#include <sstream>
|
|
2853
|
+
|
|
2854
|
+
namespace rbxx {
|
|
2855
|
+
namespace detail {
|
|
2856
|
+
template <typename T, std::size_t Size>
|
|
2857
|
+
struct is_non_bindable_class<std::array<T, Size>> : std::true_type {};
|
|
2858
|
+
} // namespace detail
|
|
2859
|
+
|
|
2860
|
+
template <typename T, std::size_t Size> struct type_caster<std::array<T, Size>> {
|
|
2861
|
+
static constexpr std::string_view name = "Array";
|
|
2862
|
+
|
|
2863
|
+
static std::array<T, Size> load(value input) {
|
|
2864
|
+
VALUE array = detail::coerce_array(input);
|
|
2865
|
+
if (RARRAY_LEN(array) != static_cast<long>(Size)) {
|
|
2866
|
+
std::ostringstream message;
|
|
2867
|
+
message << "rbxx: expected Array of length " << Size << ", got " << RARRAY_LEN(array);
|
|
2868
|
+
throw ruby_error(detail::make_exception(rb_eArgError, message.str().c_str()));
|
|
2869
|
+
}
|
|
2870
|
+
return load_elements(array, std::make_index_sequence<Size>{});
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
static value dump(const std::array<T, Size>& input) { return detail::dump_array_range(input); }
|
|
2874
|
+
|
|
2875
|
+
static bool matches(value input) noexcept {
|
|
2876
|
+
return input.is_array() && RARRAY_LEN(input.raw()) == static_cast<long>(Size);
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
private:
|
|
2880
|
+
template <std::size_t... Index>
|
|
2881
|
+
static std::array<T, Size> load_elements(VALUE array, std::index_sequence<Index...>) {
|
|
2882
|
+
return {from_ruby<T>(value{RARRAY_AREF(array, static_cast<long>(Index))})...};
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
|
|
2886
|
+
} // namespace rbxx
|
|
2887
|
+
// END rbxx/stl/array.hpp
|
|
2888
|
+
|
|
2889
|
+
// BEGIN rbxx/stl/chrono.hpp
|
|
2890
|
+
|
|
2891
|
+
|
|
2892
|
+
#include <chrono>
|
|
2893
|
+
|
|
2894
|
+
namespace rbxx {
|
|
2895
|
+
namespace detail {
|
|
2896
|
+
template <typename Rep, typename Period>
|
|
2897
|
+
struct is_non_bindable_class<std::chrono::duration<Rep, Period>> : std::true_type {};
|
|
2898
|
+
} // namespace detail
|
|
2899
|
+
|
|
2900
|
+
template <typename Rep, typename Period> struct type_caster<std::chrono::duration<Rep, Period>> {
|
|
2901
|
+
using duration_type = std::chrono::duration<Rep, Period>;
|
|
2902
|
+
static constexpr std::string_view name = "seconds";
|
|
2903
|
+
static duration_type load(value input) {
|
|
2904
|
+
const auto seconds = std::chrono::duration<double>{from_ruby<double>(input)};
|
|
2905
|
+
return std::chrono::duration_cast<duration_type>(seconds);
|
|
2906
|
+
}
|
|
2907
|
+
static value dump(const duration_type& input) {
|
|
2908
|
+
return to_ruby(std::chrono::duration<double>(input).count());
|
|
2909
|
+
}
|
|
2910
|
+
static bool matches(value input) noexcept { return type_caster<double>::matches(input); }
|
|
2911
|
+
};
|
|
2912
|
+
|
|
2913
|
+
} // namespace rbxx
|
|
2914
|
+
// END rbxx/stl/chrono.hpp
|
|
2915
|
+
|
|
2916
|
+
// BEGIN rbxx/stl/filesystem.hpp
|
|
2917
|
+
|
|
2918
|
+
|
|
2919
|
+
#include <filesystem>
|
|
2920
|
+
|
|
2921
|
+
namespace rbxx {
|
|
2922
|
+
namespace detail {
|
|
2923
|
+
template <> struct is_non_bindable_class<std::filesystem::path> : std::true_type {};
|
|
2924
|
+
} // namespace detail
|
|
2925
|
+
|
|
2926
|
+
template <> struct type_caster<std::filesystem::path> {
|
|
2927
|
+
static constexpr std::string_view name = "String path";
|
|
2928
|
+
static std::filesystem::path load(value input) {
|
|
2929
|
+
return std::filesystem::path{from_ruby<std::string>(input)};
|
|
2930
|
+
}
|
|
2931
|
+
static value dump(const std::filesystem::path& input) { return to_ruby(input.string()); }
|
|
2932
|
+
static bool matches(value input) noexcept { return type_caster<std::string>::matches(input); }
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2935
|
+
} // namespace rbxx
|
|
2936
|
+
// END rbxx/stl/filesystem.hpp
|
|
2937
|
+
|
|
2938
|
+
// BEGIN rbxx/stl/map.hpp
|
|
2939
|
+
|
|
2940
|
+
|
|
2941
|
+
#include <map>
|
|
2942
|
+
#include <unordered_map>
|
|
2943
|
+
|
|
2944
|
+
namespace rbxx {
|
|
2945
|
+
namespace detail {
|
|
2946
|
+
template <typename Key, typename Mapped, typename Compare, typename Allocator>
|
|
2947
|
+
struct is_non_bindable_class<std::map<Key, Mapped, Compare, Allocator>> : std::true_type {};
|
|
2948
|
+
template <typename Key, typename Mapped, typename Hash, typename Equal, typename Allocator>
|
|
2949
|
+
struct is_non_bindable_class<std::unordered_map<Key, Mapped, Hash, Equal, Allocator>>
|
|
2950
|
+
: std::true_type {};
|
|
2951
|
+
|
|
2952
|
+
template <typename Map> Map load_map(value input) {
|
|
2953
|
+
VALUE pairs = hash_pairs(coerce_hash(input));
|
|
2954
|
+
Map result;
|
|
2955
|
+
const long length = RARRAY_LEN(pairs);
|
|
2956
|
+
for (long index = 0; index < length; ++index) {
|
|
2957
|
+
VALUE pair = RARRAY_AREF(pairs, index);
|
|
2958
|
+
using key_type = typename Map::key_type;
|
|
2959
|
+
using mapped_type = typename Map::mapped_type;
|
|
2960
|
+
result.emplace(from_ruby<key_type>(value{RARRAY_AREF(pair, 0)}),
|
|
2961
|
+
from_ruby<mapped_type>(value{RARRAY_AREF(pair, 1)}));
|
|
2962
|
+
}
|
|
2963
|
+
return result;
|
|
2964
|
+
}
|
|
2965
|
+
} // namespace detail
|
|
2966
|
+
|
|
2967
|
+
template <typename Key, typename Mapped, typename Compare, typename Allocator>
|
|
2968
|
+
struct type_caster<std::map<Key, Mapped, Compare, Allocator>> {
|
|
2969
|
+
using map_type = std::map<Key, Mapped, Compare, Allocator>;
|
|
2970
|
+
static constexpr std::string_view name = "Hash";
|
|
2971
|
+
static map_type load(value input) { return detail::load_map<map_type>(input); }
|
|
2972
|
+
static value dump(const map_type& input) { return detail::dump_hash_range(input); }
|
|
2973
|
+
static bool matches(value input) noexcept { return input.is_hash(); }
|
|
2974
|
+
};
|
|
2975
|
+
|
|
2976
|
+
template <typename Key, typename Mapped, typename Hash, typename Equal, typename Allocator>
|
|
2977
|
+
struct type_caster<std::unordered_map<Key, Mapped, Hash, Equal, Allocator>> {
|
|
2978
|
+
using map_type = std::unordered_map<Key, Mapped, Hash, Equal, Allocator>;
|
|
2979
|
+
static constexpr std::string_view name = "Hash";
|
|
2980
|
+
static map_type load(value input) { return detail::load_map<map_type>(input); }
|
|
2981
|
+
static value dump(const map_type& input) { return detail::dump_hash_range(input); }
|
|
2982
|
+
static bool matches(value input) noexcept { return input.is_hash(); }
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
} // namespace rbxx
|
|
2986
|
+
// END rbxx/stl/map.hpp
|
|
2987
|
+
|
|
2988
|
+
// BEGIN rbxx/stl/optional.hpp
|
|
2989
|
+
|
|
2990
|
+
|
|
2991
|
+
#include <optional>
|
|
2992
|
+
|
|
2993
|
+
namespace rbxx {
|
|
2994
|
+
namespace detail {
|
|
2995
|
+
template <typename T> struct is_non_bindable_class<std::optional<T>> : std::true_type {};
|
|
2996
|
+
} // namespace detail
|
|
2997
|
+
|
|
2998
|
+
template <typename T> struct type_caster<std::optional<T>> {
|
|
2999
|
+
static constexpr std::string_view name = "Object or nil";
|
|
3000
|
+
static std::optional<T> load(value input) {
|
|
3001
|
+
return input.is_nil() ? std::nullopt : std::optional<T>{from_ruby<T>(input)};
|
|
3002
|
+
}
|
|
3003
|
+
static value dump(const std::optional<T>& input) { return input ? to_ruby(*input) : value{Qnil}; }
|
|
3004
|
+
static bool matches(value input) noexcept {
|
|
3005
|
+
return input.is_nil() || type_caster<T>::matches(input);
|
|
3006
|
+
}
|
|
3007
|
+
};
|
|
3008
|
+
|
|
3009
|
+
} // namespace rbxx
|
|
3010
|
+
// END rbxx/stl/optional.hpp
|
|
3011
|
+
|
|
3012
|
+
// BEGIN rbxx/stl/set.hpp
|
|
3013
|
+
|
|
3014
|
+
|
|
3015
|
+
#include <set>
|
|
3016
|
+
|
|
3017
|
+
namespace rbxx {
|
|
3018
|
+
namespace detail {
|
|
3019
|
+
template <typename T, typename Compare, typename Allocator>
|
|
3020
|
+
struct is_non_bindable_class<std::set<T, Compare, Allocator>> : std::true_type {};
|
|
3021
|
+
} // namespace detail
|
|
3022
|
+
|
|
3023
|
+
template <typename T, typename Compare, typename Allocator>
|
|
3024
|
+
struct type_caster<std::set<T, Compare, Allocator>> {
|
|
3025
|
+
using set_type = std::set<T, Compare, Allocator>;
|
|
3026
|
+
static constexpr std::string_view name = "Array";
|
|
3027
|
+
static set_type load(value input) {
|
|
3028
|
+
VALUE array = detail::coerce_array(input);
|
|
3029
|
+
set_type result;
|
|
3030
|
+
const long length = RARRAY_LEN(array);
|
|
3031
|
+
for (long index = 0; index < length; ++index) {
|
|
3032
|
+
result.insert(from_ruby<T>(value{RARRAY_AREF(array, index)}));
|
|
3033
|
+
}
|
|
3034
|
+
return result;
|
|
3035
|
+
}
|
|
3036
|
+
static value dump(const set_type& input) { return detail::dump_array_range(input); }
|
|
3037
|
+
static bool matches(value input) noexcept { return input.is_array(); }
|
|
3038
|
+
};
|
|
3039
|
+
|
|
3040
|
+
} // namespace rbxx
|
|
3041
|
+
// END rbxx/stl/set.hpp
|
|
3042
|
+
|
|
3043
|
+
// BEGIN rbxx/stl/tuple.hpp
|
|
3044
|
+
|
|
3045
|
+
|
|
3046
|
+
#include <tuple>
|
|
3047
|
+
#include <utility>
|
|
3048
|
+
|
|
3049
|
+
namespace rbxx {
|
|
3050
|
+
namespace detail {
|
|
3051
|
+
template <typename First, typename Second>
|
|
3052
|
+
struct is_non_bindable_class<std::pair<First, Second>> : std::true_type {};
|
|
3053
|
+
template <typename... Items> struct is_non_bindable_class<std::tuple<Items...>> : std::true_type {};
|
|
3054
|
+
|
|
3055
|
+
inline void require_tuple_length(VALUE array, long expected) {
|
|
3056
|
+
if (RARRAY_LEN(array) == expected) {
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
std::string message = "rbxx: expected Array tuple of length ";
|
|
3060
|
+
message += std::to_string(expected);
|
|
3061
|
+
message += ", got ";
|
|
3062
|
+
message += std::to_string(RARRAY_LEN(array));
|
|
3063
|
+
throw ruby_error(make_exception(rb_eArgError, message.c_str()));
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
template <typename Tuple, std::size_t... Index>
|
|
3067
|
+
Tuple load_tuple(VALUE array, std::index_sequence<Index...>) {
|
|
3068
|
+
return Tuple{from_ruby<std::tuple_element_t<Index, Tuple>>(
|
|
3069
|
+
value{RARRAY_AREF(array, static_cast<long>(Index))})...};
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
template <typename Tuple, std::size_t... Index>
|
|
3073
|
+
value dump_tuple(const Tuple& input, std::index_sequence<Index...>) {
|
|
3074
|
+
return value{protect([&input] {
|
|
3075
|
+
VALUE result = rb_ary_new_capa(static_cast<long>(sizeof...(Index)));
|
|
3076
|
+
(rb_ary_push(result, to_ruby(std::get<Index>(input)).raw()), ...);
|
|
3077
|
+
return result;
|
|
3078
|
+
})};
|
|
3079
|
+
}
|
|
3080
|
+
} // namespace detail
|
|
3081
|
+
|
|
3082
|
+
template <typename First, typename Second> struct type_caster<std::pair<First, Second>> {
|
|
3083
|
+
using pair_type = std::pair<First, Second>;
|
|
3084
|
+
static constexpr std::string_view name = "Array(2)";
|
|
3085
|
+
static pair_type load(value input) {
|
|
3086
|
+
VALUE array = detail::coerce_array(input);
|
|
3087
|
+
detail::require_tuple_length(array, 2);
|
|
3088
|
+
return {from_ruby<First>(value{RARRAY_AREF(array, 0)}),
|
|
3089
|
+
from_ruby<Second>(value{RARRAY_AREF(array, 1)})};
|
|
3090
|
+
}
|
|
3091
|
+
static value dump(const pair_type& input) {
|
|
3092
|
+
return detail::dump_tuple(input, std::index_sequence<0, 1>{});
|
|
3093
|
+
}
|
|
3094
|
+
static bool matches(value input) noexcept {
|
|
3095
|
+
return input.is_array() && RARRAY_LEN(input.raw()) == 2;
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
|
|
3099
|
+
template <typename... Items> struct type_caster<std::tuple<Items...>> {
|
|
3100
|
+
using tuple_type = std::tuple<Items...>;
|
|
3101
|
+
static constexpr std::string_view name = "Array tuple";
|
|
3102
|
+
static tuple_type load(value input) {
|
|
3103
|
+
VALUE array = detail::coerce_array(input);
|
|
3104
|
+
detail::require_tuple_length(array, static_cast<long>(sizeof...(Items)));
|
|
3105
|
+
return detail::load_tuple<tuple_type>(array, std::index_sequence_for<Items...>{});
|
|
3106
|
+
}
|
|
3107
|
+
static value dump(const tuple_type& input) {
|
|
3108
|
+
return detail::dump_tuple(input, std::index_sequence_for<Items...>{});
|
|
3109
|
+
}
|
|
3110
|
+
static bool matches(value input) noexcept {
|
|
3111
|
+
return input.is_array() && RARRAY_LEN(input.raw()) == static_cast<long>(sizeof...(Items));
|
|
3112
|
+
}
|
|
3113
|
+
};
|
|
3114
|
+
|
|
3115
|
+
} // namespace rbxx
|
|
3116
|
+
// END rbxx/stl/tuple.hpp
|
|
3117
|
+
|
|
3118
|
+
// BEGIN rbxx/stl/variant.hpp
|
|
3119
|
+
|
|
3120
|
+
|
|
3121
|
+
#include <variant>
|
|
3122
|
+
|
|
3123
|
+
namespace rbxx {
|
|
3124
|
+
namespace detail {
|
|
3125
|
+
template <typename... Items>
|
|
3126
|
+
struct is_non_bindable_class<std::variant<Items...>> : std::true_type {};
|
|
3127
|
+
|
|
3128
|
+
template <typename Variant, std::size_t Index = 0> Variant load_variant(value input) {
|
|
3129
|
+
if constexpr (Index == std::variant_size_v<Variant>) {
|
|
3130
|
+
throw_type_error("one of the std::variant alternatives", input);
|
|
3131
|
+
} else {
|
|
3132
|
+
using alternative = std::variant_alternative_t<Index, Variant>;
|
|
3133
|
+
if (type_caster<alternative>::matches(input)) {
|
|
3134
|
+
return Variant{std::in_place_index<Index>, from_ruby<alternative>(input)};
|
|
3135
|
+
}
|
|
3136
|
+
return load_variant<Variant, Index + 1U>(input);
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
} // namespace detail
|
|
3140
|
+
|
|
3141
|
+
template <typename... Items> struct type_caster<std::variant<Items...>> {
|
|
3142
|
+
using variant_type = std::variant<Items...>;
|
|
3143
|
+
static constexpr std::string_view name = "variant";
|
|
3144
|
+
static variant_type load(value input) { return detail::load_variant<variant_type>(input); }
|
|
3145
|
+
static value dump(const variant_type& input) {
|
|
3146
|
+
return std::visit([](const auto& selected) { return to_ruby(selected); }, input);
|
|
3147
|
+
}
|
|
3148
|
+
static bool matches(value input) noexcept { return (type_caster<Items>::matches(input) || ...); }
|
|
3149
|
+
};
|
|
3150
|
+
|
|
3151
|
+
} // namespace rbxx
|
|
3152
|
+
// END rbxx/stl/variant.hpp
|
|
3153
|
+
|
|
3154
|
+
// BEGIN rbxx/stl/vector.hpp
|
|
3155
|
+
|
|
3156
|
+
|
|
3157
|
+
#include <vector>
|
|
3158
|
+
|
|
3159
|
+
namespace rbxx {
|
|
3160
|
+
namespace detail {
|
|
3161
|
+
template <typename T, typename Allocator>
|
|
3162
|
+
struct is_non_bindable_class<std::vector<T, Allocator>> : std::true_type {};
|
|
3163
|
+
} // namespace detail
|
|
3164
|
+
|
|
3165
|
+
template <typename T, typename Allocator> struct type_caster<std::vector<T, Allocator>> {
|
|
3166
|
+
static constexpr std::string_view name = "Array";
|
|
3167
|
+
|
|
3168
|
+
static std::vector<T, Allocator> load(value input) {
|
|
3169
|
+
VALUE array = detail::coerce_array(input);
|
|
3170
|
+
const auto length = RARRAY_LEN(array);
|
|
3171
|
+
std::vector<T, Allocator> result;
|
|
3172
|
+
result.reserve(static_cast<std::size_t>(length));
|
|
3173
|
+
for (long index = 0; index < length; ++index) {
|
|
3174
|
+
result.push_back(from_ruby<T>(value{RARRAY_AREF(array, index)}));
|
|
3175
|
+
}
|
|
3176
|
+
return result;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
static value dump(const std::vector<T, Allocator>& input) {
|
|
3180
|
+
return detail::dump_array_range(input);
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
static bool matches(value input) noexcept { return input.is_array(); }
|
|
3184
|
+
};
|
|
3185
|
+
|
|
3186
|
+
} // namespace rbxx
|
|
3187
|
+
// END rbxx/stl/vector.hpp
|
|
3188
|
+
|
|
3189
|
+
// BEGIN rbxx/stl.hpp
|
|
3190
|
+
|
|
3191
|
+
// END rbxx/stl.hpp
|
|
3192
|
+
|
|
3193
|
+
// BEGIN rbxx/version.hpp
|
|
3194
|
+
|
|
3195
|
+
#define RBXX_VERSION_MAJOR 0
|
|
3196
|
+
#define RBXX_VERSION_MINOR 1
|
|
3197
|
+
#define RBXX_VERSION_PATCH 0
|
|
3198
|
+
#define RBXX_VERSION "0.1.0"
|
|
3199
|
+
|
|
3200
|
+
namespace rbxx {
|
|
3201
|
+
|
|
3202
|
+
/// @brief The rbxx semantic version string.
|
|
3203
|
+
/// @code auto version = rbxx::version; @endcode
|
|
3204
|
+
inline constexpr const char* version = RBXX_VERSION;
|
|
3205
|
+
|
|
3206
|
+
} // namespace rbxx
|
|
3207
|
+
// END rbxx/version.hpp
|
|
3208
|
+
|
|
3209
|
+
// BEGIN rbxx/rbxx.hpp
|
|
3210
|
+
|
|
3211
|
+
// END rbxx/rbxx.hpp
|