rcx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1365 @@
1
+ // SPDX-License-Identifier: BSL-1.0
2
+ // SPDX-FileCopyrightText: Copyright 2024-2025 Kasumi Hanazuki <kasumi@rollingapple.net>
3
+
4
+ #include <concepts>
5
+ #include <memory>
6
+ #include <ranges>
7
+ #include <stdexcept>
8
+ #include <string_view>
9
+ #include <tuple>
10
+ #include <type_traits>
11
+ #include <typeinfo>
12
+ #include <utility>
13
+
14
+ #include <ffi.h>
15
+ #include <rcx/internal/rcx.hpp>
16
+
17
+ #if HAVE_CXXABI_H
18
+ #include <cxxabi.h>
19
+ #endif
20
+
21
+ namespace std {
22
+ template <std::derived_from<rcx::Value> T>
23
+ template <typename ParseContext>
24
+ constexpr ParseContext::iterator formatter<T, char>::parse(ParseContext &ctx) {
25
+ auto it = ctx.begin();
26
+ if(it == ctx.end()) {
27
+ return it;
28
+ }
29
+ if(*it == '#') {
30
+ inspect = true;
31
+ ++it;
32
+ }
33
+ if(it == ctx.end() || *it != '}') {
34
+ throw std::format_error("Invalid format args for std::formatter<ValueBase>.");
35
+ }
36
+ return it;
37
+ }
38
+
39
+ template <std::derived_from<rcx::Value> T>
40
+ template <typename FormatContext>
41
+ FormatContext::iterator formatter<T, char>::format(T value, FormatContext &ctx) const {
42
+ return std::format_to(ctx.out(), "{}",
43
+ static_cast<std::string_view>(inspect ? value.inspect() : value.to_string()));
44
+ }
45
+ }
46
+
47
+ namespace rcx {
48
+ namespace detail {
49
+ #if HAVE_ABI___CXA_DEMANGLE
50
+ static bool constexpr have_abi_cxa_demangle = true;
51
+ #else
52
+ static bool constexpr have_abi_cxa_demangle = false;
53
+ #endif
54
+ #if HAVE_ABI___CXA_CURRENT_EXCEPTION_TYPE
55
+ static bool constexpr have_abi_cxa_current_exception_type = true;
56
+ #else
57
+ static bool constexpr have_abi_cxa_current_exception_type = false;
58
+ #endif
59
+
60
+ auto cxx_protect(std::invocable<> auto const &functor) noexcept
61
+ -> std::invoke_result_t<decltype(functor)>;
62
+
63
+ inline NativeRbFunc *RCX_Nonnull alloc_callback(std::function<RbFunc> f) {
64
+ static std::array argtypes = {
65
+ &ffi_type_sint, // int argc
66
+ &ffi_type_pointer, // VALUE *argv
67
+ &ffi_type_pointer, // VALUE self (has the same size as void *)
68
+ };
69
+ static ffi_cif cif = [] {
70
+ ffi_cif cif;
71
+ if(ffi_prep_cif(&cif, FFI_DEFAULT_ABI, argtypes.size(), &ffi_type_pointer,
72
+ argtypes.data()) != FFI_OK) {
73
+ throw std::runtime_error("ffi_prep_cif failed");
74
+ }
75
+ return cif;
76
+ }();
77
+ static auto const trampoline = [](ffi_cif *RCX_Nonnull, void *RCX_Nonnull ret,
78
+ void *RCX_Nonnull args[], void *RCX_Nonnull function) {
79
+ // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
80
+ auto argc = *reinterpret_cast<int *>(args[0]);
81
+ auto argv = *reinterpret_cast<Value **>(args[1]);
82
+ auto self = *reinterpret_cast<Value *>(args[2]);
83
+ // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
84
+ *reinterpret_cast<Value *>(ret) = cxx_protect([&] {
85
+ return (*reinterpret_cast<decltype(f) *>(function))(std::span<Value>(argv, argc), self);
86
+ });
87
+ };
88
+
89
+ void *callback = nullptr;
90
+ auto closure = static_cast<ffi_closure *>(ffi_closure_alloc(sizeof(ffi_closure), &callback));
91
+
92
+ if(!closure) {
93
+ throw std::runtime_error{"ffi_closure_alloc failed"};
94
+ }
95
+
96
+ if(ffi_prep_closure_loc(closure, &cif, trampoline,
97
+ new decltype(f)(std::move(f)), // let it leak
98
+ callback) != FFI_OK) {
99
+ throw std::runtime_error{"ffi_prep_closure_loc failed"};
100
+ }
101
+
102
+ return reinterpret_cast<NativeRbFunc *>(callback);
103
+ }
104
+
105
+ template <concepts::ArgSpec... ArgSpec> struct Parser {
106
+ std::span<Value> args;
107
+ Value self;
108
+
109
+ auto parse(Ruby &ruby, std::invocable<typename ArgSpec::ResultType...> auto &&func)
110
+ -> std::invoke_result_t<decltype(func), typename ArgSpec::ResultType...> {
111
+ // Experssions in an initializer list is evaluated from left to right, in contrast to
112
+ // function arguments.
113
+ return std::apply(std::forward<decltype(func)>(func),
114
+ std::tuple<typename ArgSpec::ResultType...>{ArgSpec::parse(ruby, self, args)...});
115
+ }
116
+ };
117
+
118
+ template <typename... ArgSpec> struct method_callback {
119
+ template <typename F> static NativeRbFunc *RCX_Nonnull alloc(F &&function) {
120
+ return alloc_callback([function](std::span<Value> args, Value self) -> Value {
121
+ Parser<ArgSpec...> parser{args, self};
122
+ using Result = decltype(parser.parse(detail::unsafe_ruby(), std::move(function)));
123
+ if constexpr(std::is_void_v<Result>) {
124
+ parser.parse(detail::unsafe_ruby(), std::move(function));
125
+ return {};
126
+ } else {
127
+ return into_Value<Result>(parser.parse(detail::unsafe_ruby(), function));
128
+ }
129
+ });
130
+ }
131
+ };
132
+
133
+ inline void check_jump_tag(int state) {
134
+ enum {
135
+ RUBY_TAG_NONE = 0,
136
+ RUBY_TAG_RAISE = 6,
137
+ };
138
+
139
+ switch(state) {
140
+ case RUBY_TAG_NONE:
141
+ return;
142
+ case RUBY_TAG_RAISE: {
143
+ Exception const err = detail::unsafe_coerce<Exception>(rb_errinfo());
144
+ rb_set_errinfo(RUBY_Qnil);
145
+ throw err;
146
+ }
147
+ default:
148
+ throw Jump(state);
149
+ }
150
+ }
151
+
152
+ template <std::invocable<> F>
153
+ inline auto protect(F functor) -> auto
154
+ requires(noexcept(functor()))
155
+ {
156
+ int state = 0;
157
+
158
+ using Result = std::invoke_result_t<F>;
159
+ if constexpr(std::is_void_v<Result>) {
160
+ ::rb_protect(
161
+ [](VALUE callback) {
162
+ (*reinterpret_cast<F *>(callback))();
163
+ return RUBY_Qnil;
164
+ },
165
+ reinterpret_cast<VALUE>(&functor), &state);
166
+ check_jump_tag(state);
167
+ return;
168
+ } else {
169
+ std::optional<Result> result;
170
+ auto callback = [&functor, &result]() { *result = functor(); };
171
+ using Callback = decltype(callback);
172
+
173
+ ::rb_protect(
174
+ [](VALUE callback) {
175
+ (*reinterpret_cast<Callback *>(callback))();
176
+ return RUBY_Qnil;
177
+ },
178
+ reinterpret_cast<VALUE>(&callback), &state);
179
+ check_jump_tag(state);
180
+ return std::move(*result);
181
+ }
182
+ }
183
+
184
+ template <typename A, typename R>
185
+ requires(std::is_integral_v<A> && sizeof(A) == sizeof(VALUE) && std::is_integral_v<R> &&
186
+ sizeof(R) == sizeof(VALUE))
187
+ inline R protect(R (*RCX_Nonnull func)(A) noexcept, A arg) {
188
+ int state = 0;
189
+
190
+ auto result =
191
+ reinterpret_cast<R>(::rb_protect(reinterpret_cast<VALUE (*RCX_Nonnull)(VALUE)>(func),
192
+ reinterpret_cast<VALUE>(arg), &state));
193
+ check_jump_tag(state);
194
+ return result;
195
+ }
196
+
197
+ /**
198
+ * Assumes the (C) function doesn't throw C++ exceptions.
199
+ */
200
+ template <typename R, typename... A>
201
+ constexpr auto assume_noexcept(R (*RCX_Nonnull f)(A...)) noexcept
202
+ -> R (*RCX_Nonnull)(A...) noexcept {
203
+ return reinterpret_cast<R (*RCX_Nonnull)(A...) noexcept>(f);
204
+ }
205
+ template <typename R, typename... A>
206
+ constexpr auto assume_noexcept(R (*RCX_Nonnull f)(A..., ...)) noexcept
207
+ -> R (*RCX_Nonnull)(A..., ...) noexcept {
208
+ return reinterpret_cast<R (*RCX_Nonnull)(A..., ...) noexcept>(f);
209
+ }
210
+
211
+ /**
212
+ * Converts anything into ID.
213
+ *
214
+ * Do not store the return value. Dynamic IDs may be garbage-collected.
215
+ */
216
+ template <concepts::Identifier I> inline decltype(auto) into_ID(I &&id) noexcept {
217
+ if constexpr(requires {
218
+ { id.as_ID() } noexcept -> std::same_as<ID>;
219
+ }) {
220
+ return id.as_ID();
221
+ } else {
222
+ return Symbol(std::forward<decltype(id)>(id)).as_ID();
223
+ }
224
+ }
225
+
226
+ // Calculate the default type of the self parameter for the methods of ClassT<T>.
227
+ template <concepts::ConvertibleFromValue T>
228
+ using self_type =
229
+ std::conditional_t<std::derived_from<T, typed_data::WrappedStructBase>, T &, T>;
230
+ template <concepts::ConvertibleFromValue T>
231
+ using self_type_const =
232
+ std::conditional_t<std::derived_from<T, typed_data::WrappedStructBase>, T const &, T const>;
233
+ };
234
+
235
+ namespace arg {
236
+ template <concepts::ConvertibleFromValue T>
237
+ inline typename Self<T>::ResultType Self<T>::parse(Ruby &, Value self, std::span<Value> &) {
238
+ return from_Value<T>(self);
239
+ }
240
+
241
+ template <concepts::ConvertibleFromValue T, detail::cxstring name>
242
+ inline typename Arg<T, name>::ResultType Arg<T, name>::parse(
243
+ Ruby &, Value, std::span<Value> &args) {
244
+ if(args.empty()) {
245
+ throw Exception::format(builtin::ArgumentError, "Missing required argument: {}",
246
+ static_cast<std::string_view>(name));
247
+ }
248
+ auto arg = from_Value<T>(args.front());
249
+ args = std::ranges::drop_view(args, 1);
250
+ return arg;
251
+ }
252
+
253
+ template <concepts::ConvertibleFromValue T, detail::cxstring name>
254
+ inline typename ArgOpt<T, name>::ResultType ArgOpt<T, name>::parse(
255
+ Ruby &, Value, std::span<Value> &args) {
256
+ if(args.empty()) {
257
+ return std::nullopt;
258
+ }
259
+ auto arg = from_Value<T>(args.front());
260
+ args = std::ranges::drop_view(args, 1);
261
+ return arg;
262
+ }
263
+
264
+ inline ArgSplat::ResultType ArgSplat::parse(Ruby &, Value self, std::span<Value> &args) {
265
+ auto result = Array::new_from(args);
266
+ args = {};
267
+ return result;
268
+ }
269
+
270
+ inline Block::ResultType Block::parse(Ruby &, Value, std::span<Value> &) {
271
+ return detail::unsafe_coerce<Proc>(
272
+ detail::protect([]() noexcept { return ::rb_block_proc(); }));
273
+ }
274
+
275
+ inline BlockOpt::ResultType BlockOpt::parse(Ruby &ruby, Value self, std::span<Value> &args) {
276
+ if(!::rb_block_given_p()) {
277
+ return std::nullopt;
278
+ }
279
+ return block.parse(ruby, self, args);
280
+ }
281
+ }
282
+
283
+ namespace convert {
284
+ template <typename T> inline Value into_Value(T value) {
285
+ if constexpr(std::convertible_to<T, Value>) {
286
+ return value;
287
+ } else {
288
+ return IntoValue<std::remove_reference_t<T>>().convert(value);
289
+ }
290
+ }
291
+ template <typename T> inline auto from_Value(Value value) -> auto {
292
+ if constexpr(std::convertible_to<Value, T>) {
293
+ return value;
294
+ } else {
295
+ return FromValue<std::remove_reference_t<T>>().convert(value);
296
+ }
297
+ }
298
+
299
+ inline bool FromValue<bool>::convert(Value value) {
300
+ return RB_TEST(value.as_VALUE());
301
+ }
302
+ inline Value IntoValue<bool>::convert(bool value) {
303
+ return detail::unsafe_coerce<Value>(value ? RUBY_Qtrue : RUBY_Qfalse);
304
+ };
305
+
306
+ inline signed char FromValue<signed char>::convert(Value value) {
307
+ int const v = NUM2INT(value.as_VALUE());
308
+ signed char const r = static_cast<signed char>(v);
309
+ if(v != static_cast<int>(r)) {
310
+ throw Exception::format(builtin::RangeError,
311
+ "integer {} too {} to convert to 'signed char'", v, v < 0 ? "small" : "big");
312
+ }
313
+ return r;
314
+ }
315
+ inline Value IntoValue<signed char>::convert(signed char value) {
316
+ return detail::unsafe_coerce<Value>(INT2FIX(value));
317
+ };
318
+
319
+ inline unsigned char FromValue<unsigned char>::convert(Value value) {
320
+ int const v = NUM2INT(value.as_VALUE());
321
+ unsigned char const r = static_cast<unsigned char>(v);
322
+ if(v != static_cast<int>(r)) {
323
+ throw Exception::format(builtin::RangeError,
324
+ "integer {} too {} to convert to 'unsigned char'", v, v < 0 ? "small" : "big");
325
+ }
326
+ return r;
327
+ }
328
+ inline Value IntoValue<unsigned char>::convert(unsigned char value) {
329
+ return detail::unsafe_coerce<Value>(INT2FIX(value));
330
+ };
331
+
332
+ #define RCX_DEFINE_CONV(TYPE, FROM_VALUE, INTO_VALUE) \
333
+ inline TYPE FromValue<TYPE>::convert(Value value) { \
334
+ return detail::protect([v = value.as_VALUE()]() noexcept { return FROM_VALUE(v); }); \
335
+ } \
336
+ inline Value IntoValue<TYPE>::convert(TYPE value) { \
337
+ return detail::unsafe_coerce<Value>(INTO_VALUE(value)); \
338
+ }
339
+
340
+ RCX_DEFINE_CONV(short, RB_NUM2SHORT, RB_INT2FIX);
341
+ RCX_DEFINE_CONV(unsigned short, RB_NUM2USHORT, RB_INT2FIX);
342
+ RCX_DEFINE_CONV(int, RB_NUM2INT, RB_INT2NUM);
343
+ RCX_DEFINE_CONV(unsigned int, RB_NUM2UINT, RB_UINT2NUM);
344
+ RCX_DEFINE_CONV(long, RB_NUM2LONG, RB_LONG2NUM);
345
+ RCX_DEFINE_CONV(unsigned long, RB_NUM2ULONG, RB_ULONG2NUM);
346
+ RCX_DEFINE_CONV(long long, RB_NUM2LL, RB_LL2NUM);
347
+ RCX_DEFINE_CONV(unsigned long long, RB_NUM2ULL, RB_ULL2NUM);
348
+ RCX_DEFINE_CONV(double, rb_num2dbl, rb_float_new);
349
+ #undef RCX_DEFINE_CONV
350
+
351
+ #define RCX_DEFINE_CLASS_CONV(CLASS) \
352
+ inline ClassT<CLASS> FromValue<ClassT<CLASS>>::convert(Value value) { \
353
+ auto cls = from_Value<Class>(value); \
354
+ if(cls.is_subclass_of(builtin::CLASS)) { \
355
+ return detail::unsafe_coerce<ClassT<CLASS>>(cls.as_VALUE()); \
356
+ } \
357
+ throw Exception::format( \
358
+ builtin::ArgumentError, "Expected a subclass of {} but got {}", builtin::CLASS, cls); \
359
+ }
360
+
361
+ RCX_DEFINE_CLASS_CONV(Module);
362
+ RCX_DEFINE_CLASS_CONV(Class);
363
+ RCX_DEFINE_CLASS_CONV(Symbol);
364
+ RCX_DEFINE_CLASS_CONV(Proc);
365
+ RCX_DEFINE_CLASS_CONV(String);
366
+ RCX_DEFINE_CLASS_CONV(Array);
367
+ RCX_DEFINE_CLASS_CONV(Exception);
368
+ #ifdef RCX_IO_BUFFER
369
+ RCX_DEFINE_CLASS_CONV(IOBuffer);
370
+ #endif
371
+ #undef RCX_DEFINE_CLASS_CONV
372
+
373
+ template <std::derived_from<typed_data::WrappedStructBase> T>
374
+ inline std::reference_wrapper<T> FromValue<T>::convert(Value value) {
375
+ if constexpr(!std::is_const_v<T>) {
376
+ detail::protect([&]() noexcept { ::rb_check_frozen(value.as_VALUE()); });
377
+ }
378
+ auto const data = detail::protect([&]() noexcept {
379
+ return ::rb_check_typeddata(value.as_VALUE(), typed_data::DataType<T>::get());
380
+ });
381
+ if(!data) {
382
+ throw std::runtime_error{"Object is not yet initialized"};
383
+ }
384
+ return std::ref(*static_cast<T *>(data));
385
+ }
386
+
387
+ template <std::derived_from<typed_data::TwoWayAssociation> T>
388
+ inline Value IntoValue<T>::convert(T &value) {
389
+ if(auto const v = value.get_associated_value()) {
390
+ return *v;
391
+ }
392
+ throw std::runtime_error{"This object is not managed by Ruby"};
393
+ }
394
+
395
+ template <std::derived_from<typed_data::WrappedStructBase> T>
396
+ inline ClassT<T> FromValue<ClassT<T>>::convert(Value value) {
397
+ auto cls = from_Value<Class>(value);
398
+ if(cls.is_subclass_of(typed_data::DataType<T>::bound_class())) {
399
+ return detail::unsafe_coerce<ClassT<T>>(cls.as_VALUE());
400
+ }
401
+ throw Exception::format(builtin::ArgumentError, "Expected a subclass of {} but got {}",
402
+ typed_data::DataType<T>::bound_class(), cls);
403
+ }
404
+ }
405
+
406
+ namespace typed_data {
407
+ template <typename T> void dmark(gc::Gc, T *RCX_Nonnull) noexcept {
408
+ // noop
409
+ }
410
+ template <typename T> void dfree(T *RCX_Nonnull p) noexcept {
411
+ delete p;
412
+ }
413
+ template <typename T> size_t dsize(T const *RCX_Nonnull) noexcept {
414
+ return sizeof(T);
415
+ }
416
+
417
+ template <typename T, typename S>
418
+ inline ClassT<T> bind_data_type(ClassT<T> klass, ClassT<S> superclass) {
419
+ if constexpr(std::derived_from<T, typed_data::WrappedStructBase>) {
420
+ rb_data_type_t const *RCX_Nullable parent = nullptr;
421
+ if constexpr(!std::derived_from<S, Value>) {
422
+ parent = DataType<S>::get();
423
+
424
+ if(reinterpret_cast<VALUE>(parent->data) != superclass.as_VALUE()) {
425
+ throw std::runtime_error("superclass has mismatching static type");
426
+ }
427
+ }
428
+
429
+ DataType<T>::bind(klass, parent);
430
+ }
431
+ return klass;
432
+ }
433
+
434
+ template <typename T> inline rb_data_type_t const *RCX_Nonnull DataTypeStorage<T>::get() {
435
+ if(!data_type_)
436
+ throw std::runtime_error(
437
+ std::format("Type '{}' is not yet bound to a Ruby Class", typeid(T).name()));
438
+ return &*data_type_;
439
+ }
440
+
441
+ template <typename T> inline ClassT<T> DataTypeStorage<T>::bound_class() {
442
+ return detail::unsafe_coerce<ClassT<T>>(reinterpret_cast<VALUE>(get()->data));
443
+ }
444
+
445
+ template <typename T>
446
+ inline void DataTypeStorage<T>::bind(
447
+ ClassT<T> klass, rb_data_type_t const *RCX_Nullable parent) {
448
+ if(data_type_) {
449
+ throw std::runtime_error{"This type is already bound to a Ruby Class"};
450
+ }
451
+
452
+ data_type_ = rb_data_type_t{
453
+ .wrap_struct_name = strdup(klass.name().data()), // let it leek
454
+ .function = {
455
+ .dmark =
456
+ [](void *RCX_Nonnull p) noexcept {
457
+ using typed_data::dmark;
458
+ dmark(gc::Gc(gc::Phase::Marking), static_cast<T *>(p));
459
+ },
460
+ .dfree =
461
+ [](void *RCX_Nonnull p) noexcept {
462
+ using typed_data::dfree;
463
+ dfree(static_cast<T *>(p));
464
+ },
465
+ .dsize =
466
+ [](void const *RCX_Nonnull p) noexcept {
467
+ using typed_data::dsize;
468
+ return dsize(static_cast<T const *>(p));
469
+ },
470
+ .dcompact =
471
+ [](void *RCX_Nonnull p) noexcept {
472
+ using typed_data::dmark;
473
+ dmark(gc::Gc(gc::Phase::Compaction), static_cast<T *>(p));
474
+ },
475
+ // .reserved is zero-initialized
476
+ },
477
+ .parent = parent,
478
+ .data = reinterpret_cast<void *>(klass.as_VALUE()),
479
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
480
+ };
481
+ ::rb_gc_register_address(reinterpret_cast<VALUE *>(&data_type_->data));
482
+
483
+ ::rb_define_alloc_func(klass.as_VALUE(),
484
+ [](VALUE klass) { return ::rb_data_typed_object_wrap(klass, nullptr, get()); });
485
+ }
486
+
487
+ template <typename T>
488
+ template <typename... A>
489
+ requires std::constructible_from<T, A...>
490
+ inline Value DataTypeStorage<T>::initialize(Value value, A &&...args) {
491
+ auto data = new T(std::forward<A>(args)...);
492
+ RTYPEDDATA_DATA(value.as_VALUE()) = data; // Tracked by Ruby GC
493
+ if constexpr(std::derived_from<T, typed_data::TwoWayAssociation>) {
494
+ data->associate_value(value);
495
+ }
496
+ return value;
497
+ }
498
+
499
+ template <typename T>
500
+ inline Value DataTypeStorage<T>::initialize_copy(Value value, T const &obj)
501
+ requires std::copy_constructible<T>
502
+ {
503
+ auto data = new T(obj);
504
+ RTYPEDDATA_DATA(value.as_VALUE()) = data; // Tracked by Ruby GC
505
+ if constexpr(std::derived_from<T, typed_data::TwoWayAssociation>) {
506
+ data->associate_value(value);
507
+ }
508
+ return value;
509
+ }
510
+
511
+ template <std::derived_from<TwoWayAssociation> T>
512
+ inline void dmark(gc::Gc gc, T *RCX_Nonnull p) noexcept {
513
+ p->mark_associated_value(gc);
514
+ }
515
+ }
516
+
517
+ namespace gc {
518
+ inline Gc::Gc(Phase phase): phase_(phase) {
519
+ }
520
+
521
+ template <std::derived_from<ValueBase> T>
522
+ inline void Gc::mark_movable(T &value) const noexcept {
523
+ switch(phase_) {
524
+ case Phase::Marking:
525
+ ::rb_gc_mark_movable(value.as_VALUE());
526
+ break;
527
+ case Phase::Compaction:
528
+ value = detail::unsafe_coerce<T>(::rb_gc_location(value.as_VALUE()));
529
+ break;
530
+ default:; // unreachable
531
+ }
532
+ }
533
+ inline void Gc::mark_pinned(ValueBase value) const noexcept {
534
+ switch(phase_) {
535
+ case Phase::Marking:
536
+ ::rb_gc_mark(value.as_VALUE());
537
+ break;
538
+ case Phase::Compaction:
539
+ // no-op
540
+ break;
541
+ default:; // unreachable
542
+ }
543
+ }
544
+ }
545
+
546
+ /// Id
547
+
548
+ inline Id::Id(ID id): id_(id) {
549
+ }
550
+
551
+ inline ID Id::as_ID() const noexcept {
552
+ return id_;
553
+ }
554
+
555
+ namespace value {
556
+ /// ValueBase
557
+
558
+ inline constexpr ValueBase::ValueBase(): value_(RUBY_Qnil) {
559
+ }
560
+
561
+ inline constexpr ValueBase::ValueBase(VALUE value): value_(value) {
562
+ }
563
+
564
+ inline constexpr VALUE ValueBase::as_VALUE() const {
565
+ return value_;
566
+ }
567
+
568
+ inline bool ValueBase::is_nil() const {
569
+ return RB_NIL_P(value_);
570
+ }
571
+
572
+ inline bool ValueBase::is_frozen() const {
573
+ return ::rb_obj_frozen_p(as_VALUE());
574
+ }
575
+
576
+ template <typename T> inline bool ValueBase::is_instance_of(ClassT<T> klass) const {
577
+ return detail::protect([&]() noexcept {
578
+ Value const result =
579
+ detail::unsafe_coerce<Value>(::rb_obj_is_instance_of(as_VALUE(), klass.as_VALUE()));
580
+ return result.test();
581
+ });
582
+ }
583
+
584
+ template <typename T> inline bool ValueBase::is_kind_of(ClassT<T> klass) const {
585
+ return detail::protect([&]() noexcept {
586
+ Value const result =
587
+ detail::unsafe_coerce<Value>(::rb_obj_is_kind_of(as_VALUE(), klass.as_VALUE()));
588
+ return result.test();
589
+ });
590
+ }
591
+
592
+ /// ValueT
593
+
594
+ template <typename Derived, std::derived_from<ValueBase> Super, Nilability nilable>
595
+ ClassT<Derived> ValueT<Derived, Super, nilable>::get_class() const {
596
+ return detail::unsafe_coerce<ClassT<Derived>>(
597
+ detail::protect(detail::assume_noexcept(::rb_obj_class), this->as_VALUE()));
598
+ }
599
+
600
+ template <typename Derived, std::derived_from<ValueBase> Super, Nilability nilable>
601
+ Derived ValueT<Derived, Super, nilable>::freeze() const {
602
+ return detail::unsafe_coerce<Derived>(
603
+ detail::protect(detail::assume_noexcept(::rb_obj_freeze), this->as_VALUE()));
604
+ }
605
+
606
+ template <typename Derived, std::derived_from<ValueBase> Super, Nilability nilable>
607
+ template <concepts::ConvertibleFromValue Self, concepts::ArgSpec... ArgSpec>
608
+ inline Derived ValueT<Derived, Super, nilable>::define_singleton_method(
609
+ concepts::Identifier auto &&mid,
610
+ std::invocable<Self, typename ArgSpec::ResultType...> auto &&function, ArgSpec...) const {
611
+ auto const callback = detail::method_callback<arg::Self<Self>, ArgSpec...>::alloc(
612
+ std::forward<decltype(function)>(function));
613
+ detail::protect([&]() noexcept {
614
+ auto const singleton = ::rb_singleton_class(this->as_VALUE());
615
+ rb_define_method_id(
616
+ singleton, detail::into_ID(std::forward<decltype(mid)>(mid)), callback, -1);
617
+ });
618
+ return *static_cast<Derived const *>(this);
619
+ }
620
+
621
+ /// Value
622
+
623
+ template <concepts::ConvertibleFromValue R>
624
+ inline R Value::send(
625
+ concepts::Identifier auto &&mid, concepts::ConvertibleIntoValue auto &&...args) const {
626
+ return from_Value<R>(detail::unsafe_coerce<Value>(detail::protect([&]() noexcept {
627
+ return ::rb_funcall(as_VALUE(), detail::into_ID(std::forward<decltype(mid)>(mid)),
628
+ sizeof...(args), into_Value(std::forward<decltype(args)>(args)).as_VALUE()...);
629
+ })));
630
+ }
631
+
632
+ inline bool Value::test() const noexcept {
633
+ return RB_TEST(as_VALUE());
634
+ }
635
+
636
+ inline String Value::inspect() const {
637
+ return detail::unsafe_coerce<String>(
638
+ detail::protect([this]() noexcept { return ::rb_inspect(as_VALUE()); }));
639
+ }
640
+
641
+ inline String Value::to_string() const {
642
+ return detail::unsafe_coerce<String>(
643
+ detail::protect([this]() noexcept { return ::rb_obj_as_string(as_VALUE()); }));
644
+ }
645
+
646
+ inline bool Value::instance_variable_defined(concepts::Identifier auto &&name) const {
647
+ return detail::protect([&]() noexcept {
648
+ return ::rb_ivar_defined(as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)));
649
+ });
650
+ }
651
+
652
+ template <concepts::ConvertibleFromValue T>
653
+ inline auto Value::instance_variable_get(concepts::Identifier auto &&name) const -> auto {
654
+ return from_Value<T>(detail::unsafe_coerce<Value>(detail::protect([&]() noexcept {
655
+ return ::rb_ivar_get(as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)));
656
+ })));
657
+ }
658
+
659
+ inline void Value::instance_variable_set(
660
+ concepts::Identifier auto &&name, concepts::ConvertibleIntoValue auto &&value) const {
661
+ auto const v = into_Value(std::forward<decltype(value)>(value));
662
+ return detail::protect([&]() noexcept {
663
+ ::rb_ivar_set(
664
+ as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)), v.as_VALUE());
665
+ });
666
+ }
667
+
668
+ inline Value const Value::qnil = {RUBY_Qnil};
669
+ inline Value const Value::qtrue = {RUBY_Qtrue};
670
+ inline Value const Value::qfalse = {RUBY_Qfalse};
671
+ inline Value const Value::qundef = {RUBY_Qundef};
672
+ }
673
+
674
+ /// Module
675
+
676
+ namespace value {
677
+ inline Module Module::define_module(concepts::Identifier auto &&name) const {
678
+ return detail::unsafe_coerce<Module>(detail::protect([&]() noexcept {
679
+ return ::rb_define_module_id_under(
680
+ as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)));
681
+ }));
682
+ }
683
+
684
+ inline String Module::name() const {
685
+ return detail::unsafe_coerce<String>(::rb_class_path(as_VALUE()));
686
+ }
687
+
688
+ template <typename T, typename S>
689
+ inline ClassT<T> Module::define_class(
690
+ concepts::Identifier auto &&name, ClassT<S> superclass) const {
691
+ ClassT<T> klass = detail::unsafe_coerce<ClassT<T>>(detail::protect([&]() noexcept {
692
+ return ::rb_define_class_id_under(
693
+ as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)), superclass.as_VALUE());
694
+ }));
695
+ return typed_data::bind_data_type(klass, superclass);
696
+ }
697
+
698
+ template <typename T>
699
+ inline ClassT<T> Module::define_class(concepts::Identifier auto &&name) const {
700
+ return define_class<T>(std::forward<decltype(name)>(name), builtin::Object);
701
+ }
702
+
703
+ template <concepts::ConvertibleFromValue Self, concepts::ArgSpec... ArgSpec>
704
+ inline Module Module::define_method(concepts::Identifier auto &&mid,
705
+ std::invocable<Self, typename ArgSpec::ResultType...> auto &&function, ArgSpec...) const {
706
+ auto const callback = detail::method_callback<arg::Self<Self>, ArgSpec...>::alloc(function);
707
+ detail::protect([&]() noexcept {
708
+ rb_define_method_id(
709
+ as_VALUE(), detail::into_ID(std::forward<decltype(mid)>(mid)), callback, -1);
710
+ });
711
+ return *this;
712
+ }
713
+
714
+ inline Module Module::new_module() {
715
+ return detail::unsafe_coerce<Module>(
716
+ detail::protect([]() noexcept { return ::rb_module_new(); }));
717
+ }
718
+
719
+ inline bool Module::const_defined(concepts::Identifier auto &&name) const {
720
+ return detail::protect([&]() noexcept {
721
+ return ::rb_const_defined(as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)));
722
+ });
723
+ }
724
+
725
+ template <concepts::ConvertibleFromValue T>
726
+ inline T Module::const_get(concepts::Identifier auto &&name) const {
727
+ return from_Value<T>(detail::unsafe_coerce<Value>(detail::protect([&]() noexcept {
728
+ return ::rb_const_get(as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)));
729
+ })));
730
+ }
731
+
732
+ inline void Module::const_set(
733
+ concepts::Identifier auto &&name, concepts::ConvertibleIntoValue auto &&value) const {
734
+ auto const v = into_Value(std::forward<decltype(value)>(value));
735
+ return detail::protect([&]() noexcept {
736
+ ::rb_const_set(
737
+ as_VALUE(), detail::into_ID(std::forward<decltype(name)>(name)), v.as_VALUE());
738
+ });
739
+ }
740
+ }
741
+
742
+ inline Module convert::FromValue<Module>::convert(Value value) {
743
+ auto const type = ::rb_type(value.as_VALUE());
744
+ if(type != RUBY_T_MODULE && type != RUBY_T_CLASS) {
745
+ throw Exception::format(
746
+ builtin::TypeError, "Expected a Module but got a {}", value.get_class());
747
+ }
748
+ return detail::unsafe_coerce<Module>{value.as_VALUE()};
749
+ };
750
+
751
+ /// Class
752
+
753
+ namespace value {
754
+ template <typename T>
755
+ inline T ClassT<T>::new_instance(concepts::ConvertibleIntoValue auto &&...args) const
756
+ requires std::derived_from<T, ValueBase>
757
+ {
758
+ std::array<VALUE, sizeof...(args)> vargs{
759
+ into_Value(std::forward<decltype(args)>(args)).as_VALUE()...};
760
+ return detail::unsafe_coerce<T>(detail::protect([&]() noexcept {
761
+ return ::rb_class_new_instance(vargs.size(), vargs.data(), this->as_VALUE());
762
+ }));
763
+ }
764
+
765
+ template <typename T> inline Value ClassT<T>::allocate() const {
766
+ return detail::unsafe_coerce<Value>(
767
+ detail::protect(detail::assume_noexcept(::rb_obj_alloc), this->as_VALUE()));
768
+ }
769
+
770
+ template <typename T>
771
+ template <typename S>
772
+ inline bool ClassT<T>::is_subclass_of(ClassT<S> klass) const {
773
+ Value const result =
774
+ detail::unsafe_coerce<Value>(::rb_class_inherited_p(this->as_VALUE(), klass.as_VALUE()));
775
+ return result.test();
776
+ }
777
+
778
+ template <typename T>
779
+ template <typename S>
780
+ inline bool ClassT<T>::is_superclass_of(ClassT<S> klass) const {
781
+ Value const result =
782
+ detail::unsafe_coerce<Value>(::rb_class_inherited_p(klass.as_VALUE(), this->as_VALUE()));
783
+ return result.test();
784
+ }
785
+
786
+ template <typename T>
787
+ template <concepts::ArgSpec... ArgSpec>
788
+ inline ClassT<T> ClassT<T>::define_method(concepts::Identifier auto &&mid,
789
+ std::invocable<T &, typename ArgSpec::ResultType...> auto &&function, ArgSpec...) const {
790
+ auto const callback =
791
+ detail::method_callback<arg::Self<detail::self_type<T>>, ArgSpec...>::alloc(
792
+ std::forward<decltype(function)>(function));
793
+ detail::protect([&]() noexcept {
794
+ rb_define_method_id(
795
+ this->as_VALUE(), detail::into_ID(std::forward<decltype(mid)>(mid)), callback, -1);
796
+ });
797
+ return *this;
798
+ }
799
+
800
+ template <typename T>
801
+ template <concepts::ArgSpec... ArgSpec>
802
+ inline ClassT<T> ClassT<T>::define_method_const(concepts::Identifier auto &&mid,
803
+ std::invocable<T const &, typename ArgSpec::ResultType...> auto &&function,
804
+ ArgSpec...) const {
805
+ auto const callback =
806
+ detail::method_callback<arg::Self<detail::self_type_const<T>>, ArgSpec...>::alloc(
807
+ function);
808
+ detail::protect([&]() noexcept {
809
+ rb_define_method_id(
810
+ this->as_VALUE(), detail::into_ID(std::forward<decltype(mid)>(mid)), callback, -1);
811
+ });
812
+ return *this;
813
+ }
814
+
815
+ template <typename T>
816
+ template <concepts::ArgSpec... ArgSpec>
817
+ requires std::constructible_from<T, typename ArgSpec::ResultType...>
818
+ inline ClassT<T> ClassT<T>::define_constructor(ArgSpec...) const {
819
+ auto const callback = detail::method_callback<arg::Self<Value>, ArgSpec...>::alloc(
820
+ typed_data::DataType<T>::template initialize<typename ArgSpec::ResultType...>);
821
+ detail::protect([&]() noexcept {
822
+ using namespace literals;
823
+ rb_define_method_id(this->as_VALUE(), detail::into_ID("initialize"_id), callback, -1);
824
+ });
825
+ return *this;
826
+ }
827
+
828
+ template <typename T>
829
+ inline ClassT<T> ClassT<T>::define_copy_constructor() const
830
+ requires std::copy_constructible<T>
831
+ {
832
+ auto const callback = detail::method_callback<arg::Self<Value>, arg::Arg<T const &>>::alloc(
833
+ typed_data::DataType<T>::initialize_copy);
834
+ detail::protect([&]() noexcept {
835
+ using namespace literals;
836
+ rb_define_method_id(this->as_VALUE(), detail::into_ID("initialize_copy"_id), callback, -1);
837
+ });
838
+ return *this;
839
+ }
840
+
841
+ template <typename T> inline Class ClassT<T>::new_class() {
842
+ return new_class(builtin::Object);
843
+ }
844
+ template <typename T>
845
+ template <typename S>
846
+ inline ClassT<S> ClassT<T>::new_class(ClassT<S> superclass) {
847
+ return detail::unsafe_coerce<ClassT<S>>(
848
+ detail::protect(detail::assume_noexcept(::rb_class_new), superclass.as_VALUE()));
849
+ }
850
+ }
851
+
852
+ inline Class convert::FromValue<Class>::convert(Value value) {
853
+ if(::rb_type(value.as_VALUE()) != RUBY_T_CLASS) {
854
+ throw Exception::format(
855
+ builtin::TypeError, "Expected a Class but got a {}", value.get_class());
856
+ }
857
+ return detail::unsafe_coerce<Class>{value.as_VALUE()};
858
+ };
859
+
860
+ /// Symbol
861
+
862
+ namespace value {
863
+ template <size_t N> inline Symbol::Symbol(char const (&s)[N]) noexcept: Symbol({&s[0], N - 1}) {
864
+ }
865
+
866
+ inline Symbol::Symbol(std::string_view sv) noexcept
867
+ : Symbol(detail::protect([&]() noexcept {
868
+ return detail::assume_noexcept(::rb_to_symbol)(
869
+ detail::assume_noexcept(::rb_interned_str)(sv.data(), sv.size()));
870
+ })) {
871
+ }
872
+
873
+ inline ID Symbol::as_ID() const noexcept {
874
+ return detail::protect(detail::assume_noexcept(::rb_sym2id), as_VALUE());
875
+ }
876
+ }
877
+
878
+ inline Symbol convert::FromValue<Symbol>::convert(Value value) {
879
+ if(::rb_type(value.as_VALUE()) != RUBY_T_SYMBOL) {
880
+ throw Exception::format(
881
+ builtin::TypeError, "Expected a Symbol but got a {}", value.get_class());
882
+ }
883
+ return detail::unsafe_coerce<Symbol>(value.as_VALUE());
884
+ }
885
+
886
+ /// String
887
+
888
+ namespace value {
889
+ template <concepts::StringLike S> inline String String::intern_from(S &&s) {
890
+ using CharT = typename std::remove_cvref_t<S>::value_type;
891
+ using Traits = typename std::remove_cvref_t<S>::traits_type;
892
+ std::basic_string_view<CharT, Traits> sv(std::forward<S>(s));
893
+ return detail::unsafe_coerce<String>(detail::protect([&]() noexcept {
894
+ return (::rb_enc_interned_str)(
895
+ reinterpret_cast<char const *>(sv.data()), sv.size(), CharTraits<CharT>::encoding());
896
+ }));
897
+ }
898
+
899
+ template <concepts::CharLike CharT>
900
+ inline String String::intern_from(CharT const *RCX_Nonnull s) {
901
+ return intern_from(std::basic_string_view<CharT>(s));
902
+ }
903
+
904
+ template <concepts::StringLike S> inline String String::copy_from(S &&s) {
905
+ using CharT = typename std::remove_cvref_t<S>::value_type;
906
+ using Traits = typename std::remove_cvref_t<S>::traits_type;
907
+ std::basic_string_view<CharT, Traits> sv(std::forward<S>(s));
908
+ return detail::unsafe_coerce<String>(detail::protect([&]() noexcept {
909
+ return (::rb_enc_str_new)(
910
+ reinterpret_cast<char const *>(sv.data()), sv.size(), CharTraits<CharT>::encoding());
911
+ }));
912
+ }
913
+
914
+ template <concepts::CharLike CharT>
915
+ inline String String::copy_from(CharT const *RCX_Nonnull s) {
916
+ return copy_from(std::basic_string_view<CharT>(s));
917
+ }
918
+
919
+ inline size_t String::size() const noexcept {
920
+ return RSTRING_LEN(as_VALUE());
921
+ }
922
+
923
+ inline char *RCX_Nonnull String::data() const {
924
+ detail::protect([&]() noexcept { ::rb_check_frozen(as_VALUE()); });
925
+ return RSTRING_PTR(as_VALUE());
926
+ }
927
+
928
+ inline char const *RCX_Nonnull String::cdata() const noexcept {
929
+ return RSTRING_PTR(as_VALUE());
930
+ }
931
+
932
+ inline String::operator std::string_view() const noexcept {
933
+ return {data(), size()};
934
+ }
935
+
936
+ inline String String::lock() const {
937
+ return detail::unsafe_coerce<String>(
938
+ detail::protect(detail::assume_noexcept(::rb_str_locktmp), as_VALUE()));
939
+ }
940
+
941
+ inline String String::unlock() const {
942
+ return detail::unsafe_coerce<String>(
943
+ detail::protect(detail::assume_noexcept(::rb_str_unlocktmp), as_VALUE()));
944
+ }
945
+ }
946
+
947
+ inline String convert::FromValue<String>::convert(Value value) {
948
+ if(::rb_type(value.as_VALUE()) != RUBY_T_STRING) {
949
+ throw Exception::format(
950
+ builtin::TypeError, "Expected a String but got a {}", value.get_class());
951
+ }
952
+ return detail::unsafe_coerce<String>(value.as_VALUE());
953
+ }
954
+ inline std::string_view convert::FromValue<std::string_view>::convert(Value value) {
955
+ return std::string_view(from_Value<String>(value));
956
+ }
957
+
958
+ /// Proc
959
+
960
+ namespace value {
961
+ inline bool Proc::is_lambda() const {
962
+ Value const v = detail::unsafe_coerce<Value>(
963
+ detail::protect([&]() noexcept { return ::rb_proc_lambda_p(as_VALUE()); }));
964
+ return v.test();
965
+ }
966
+
967
+ inline Value Proc::call(Array args) const {
968
+ return detail::unsafe_coerce<Value>(
969
+ detail::protect([&]() noexcept { return ::rb_proc_call(as_VALUE(), args.as_VALUE()); }));
970
+ }
971
+ }
972
+
973
+ namespace convert {
974
+ inline Proc convert::FromValue<Proc>::convert(Value value) {
975
+ if(!rb_obj_is_proc(value.as_VALUE())) {
976
+ throw Exception::format(
977
+ builtin::TypeError, "Expected a Proc but got a {}", value.get_class());
978
+ }
979
+ return detail::unsafe_coerce<Proc>{value.as_VALUE()};
980
+ };
981
+ }
982
+
983
+ /// Exception
984
+
985
+ namespace value {
986
+ template <std::derived_from<Exception> E, typename... Args>
987
+ inline E Exception::format(ClassT<E> cls, std::format_string<Args...> fmt, Args &&...args) {
988
+ auto const msg = std::vformat(fmt.get(), std::make_format_args(args...));
989
+ return cls.new_instance(String::intern_from(msg));
990
+ }
991
+ }
992
+
993
+ namespace convert {
994
+ inline Exception convert::FromValue<Exception>::convert(Value value) {
995
+ if(!value.is_kind_of(builtin::Exception)) {
996
+ throw Exception::format(
997
+ builtin::TypeError, "Expected an Exception but got a {}", value.get_class());
998
+ }
999
+ return detail::unsafe_coerce<Exception>(value.as_VALUE());
1000
+ }
1001
+ }
1002
+
1003
+ #ifdef RCX_IO_BUFFER
1004
+ /// IOBuffer
1005
+ namespace value {
1006
+
1007
+ inline IOBuffer IOBuffer::new_internal(size_t size) {
1008
+ return detail::unsafe_coerce<IOBuffer>(detail::protect([size]() noexcept {
1009
+ // Let Ruby allocate a buffer
1010
+ return ::rb_io_buffer_new(nullptr, size, RB_IO_BUFFER_INTERNAL);
1011
+ }));
1012
+ }
1013
+
1014
+ inline IOBuffer IOBuffer::new_mapped(size_t size) {
1015
+ return detail::unsafe_coerce<IOBuffer>(detail::protect([size]() noexcept {
1016
+ // Let Ruby allocate a buffer
1017
+ return ::rb_io_buffer_new(nullptr, size, RB_IO_BUFFER_MAPPED);
1018
+ }));
1019
+ }
1020
+
1021
+ template <size_t N> inline IOBuffer IOBuffer::new_external(std::span<std::byte, N> bytes) {
1022
+ return detail::unsafe_coerce<IOBuffer>(detail::protect([bytes]() noexcept {
1023
+ return ::rb_io_buffer_new(bytes.data(), bytes.size(), RB_IO_BUFFER_EXTERNAL);
1024
+ }));
1025
+ }
1026
+
1027
+ template <size_t N>
1028
+ inline IOBuffer IOBuffer::new_external(std::span<std::byte const, N> bytes) {
1029
+ return detail::unsafe_coerce<IOBuffer>(detail::protect([bytes]() noexcept {
1030
+ return ::rb_io_buffer_new(const_cast<std::byte *>(bytes.data()), bytes.size(),
1031
+ static_cast<rb_io_buffer_flags>(RB_IO_BUFFER_EXTERNAL | RB_IO_BUFFER_READONLY));
1032
+ }));
1033
+ }
1034
+
1035
+ inline void IOBuffer::free() const {
1036
+ detail::protect([this]() noexcept { ::rb_io_buffer_free(as_VALUE()); });
1037
+ }
1038
+
1039
+ inline void IOBuffer::resize(size_t size) const {
1040
+ detail::protect([this, size]() noexcept { ::rb_io_buffer_resize(as_VALUE(), size); });
1041
+ }
1042
+
1043
+ inline std::span<std::byte> IOBuffer::bytes() const {
1044
+ void *ptr;
1045
+ size_t size;
1046
+ detail::protect(
1047
+ [&]() noexcept { ::rb_io_buffer_get_bytes_for_writing(as_VALUE(), &ptr, &size); });
1048
+ return {static_cast<std::byte *>(ptr), size};
1049
+ }
1050
+
1051
+ inline std::span<std::byte const> IOBuffer::cbytes() const {
1052
+ void const *ptr;
1053
+ size_t size;
1054
+ detail::protect(
1055
+ [&]() noexcept { ::rb_io_buffer_get_bytes_for_reading(as_VALUE(), &ptr, &size); });
1056
+ return {static_cast<std::byte const *>(ptr), size};
1057
+ }
1058
+
1059
+ inline void IOBuffer::lock() const {
1060
+ detail::protect([this]() noexcept { ::rb_io_buffer_lock(as_VALUE()); });
1061
+ }
1062
+
1063
+ inline void IOBuffer::unlock() const {
1064
+ detail::protect([this]() noexcept { ::rb_io_buffer_unlock(as_VALUE()); });
1065
+ }
1066
+
1067
+ inline bool IOBuffer::try_lock() const {
1068
+ try {
1069
+ lock();
1070
+ return true;
1071
+ } catch(Exception const &) {
1072
+ return false;
1073
+ }
1074
+ }
1075
+ }
1076
+
1077
+ namespace convert {
1078
+ inline IOBuffer FromValue<IOBuffer>::convert(Value value) {
1079
+ if(!value.is_kind_of(builtin::IOBuffer)) {
1080
+ throw Exception::format(
1081
+ builtin::TypeError, "Expected an IO::Buffer but got a {}", value.get_class());
1082
+ }
1083
+ return detail::unsafe_coerce<IOBuffer>(value.as_VALUE());
1084
+ }
1085
+ }
1086
+ #endif
1087
+
1088
+ // Array
1089
+
1090
+ namespace value {
1091
+ inline size_t Array::size() const noexcept {
1092
+ return rb_array_len(as_VALUE());
1093
+ }
1094
+
1095
+ template <concepts::ConvertibleFromValue T> inline decltype(auto) Array::at(size_t i) const {
1096
+ return from_Value<T>((*this)[i]);
1097
+ }
1098
+
1099
+ inline Value Array::operator[](size_t i) const {
1100
+ VALUE const index = RB_SIZE2NUM(i);
1101
+ return detail::unsafe_coerce<Value>(
1102
+ detail::protect([&]() noexcept { return ::rb_ary_aref(1, &index, as_VALUE()); }));
1103
+ ;
1104
+ }
1105
+
1106
+ template <std::ranges::contiguous_range R>
1107
+ #ifdef HAVE_STD_IS_LAYOUT_COMPATIBLE
1108
+ requires std::is_layout_compatible_v<std::ranges::range_value_t<R>, ValueBase>
1109
+ #else
1110
+ requires(std::derived_from<std::ranges::range_value_t<R>, ValueBase> &&
1111
+ sizeof(std::ranges::range_value_t<R>) == sizeof(ValueBase))
1112
+ #endif
1113
+ inline Array Array::new_from(R const &elements) {
1114
+ // contiguous_range<T> has a layout combatible to VALUE[]
1115
+ return detail::unsafe_coerce<Array>(detail::protect([&]() noexcept {
1116
+ return ::rb_ary_new_from_values(
1117
+ elements.size(), reinterpret_cast<VALUE const *>(elements.data()));
1118
+ }));
1119
+ };
1120
+
1121
+ inline Array Array::new_from(std::initializer_list<ValueBase> elements) {
1122
+ return detail::unsafe_coerce<Array>(detail::protect([&]() noexcept {
1123
+ return ::rb_ary_new_from_values(
1124
+ elements.size(), reinterpret_cast<VALUE const *>(elements.begin()));
1125
+ }));
1126
+ }
1127
+
1128
+ template <std::derived_from<ValueBase>... T>
1129
+ inline Array Array::new_from(std::tuple<T...> const &elements) {
1130
+ return detail::unsafe_coerce<Array>(detail::protect([&]() noexcept {
1131
+ return std::apply(
1132
+ [](auto... v) { return ::rb_ary_new_from_args(sizeof...(v), v.as_VALUE()...); },
1133
+ elements);
1134
+ }));
1135
+ }
1136
+
1137
+ inline Array Array::new_array() {
1138
+ return detail::unsafe_coerce<Array>(
1139
+ detail::protect([]() noexcept { return ::rb_ary_new(); }));
1140
+ }
1141
+
1142
+ inline Array Array::new_array(long capacity) {
1143
+ return detail::unsafe_coerce<Array>(
1144
+ detail::protect([capacity]() noexcept { return ::rb_ary_new_capa(capacity); }));
1145
+ }
1146
+
1147
+ template <concepts::ConvertibleIntoValue T> Array Array::push_back(T value) const {
1148
+ auto const v = into_Value<T>(value);
1149
+ detail::protect([v, this]() noexcept { ::rb_ary_push(as_VALUE(), v.as_VALUE()); });
1150
+ return *this;
1151
+ }
1152
+
1153
+ template <concepts::ConvertibleFromValue T> inline T Array::pop_back() const {
1154
+ return from_Value<T>(detail::unsafe_coerce<Value>(
1155
+ detail::protect([this]() noexcept { return ::rb_ary_pop(as_VALUE()); })));
1156
+ }
1157
+
1158
+ template <concepts::ConvertibleIntoValue T> Array Array::push_front(T value) const {
1159
+ auto const v = into_Value<T>(value);
1160
+ detail::protect([v, this]() noexcept { ::rb_ary_unshift(as_VALUE(), v.as_VALUE()); });
1161
+ return *this;
1162
+ }
1163
+
1164
+ template <concepts::ConvertibleFromValue T> inline T Array::pop_front() const {
1165
+ return from_Value<T>(detail::unsafe_coerce<Value>(
1166
+ detail::protect([this]() noexcept { return ::rb_ary_shift(as_VALUE()); })));
1167
+ }
1168
+ }
1169
+
1170
+ inline Array convert::FromValue<Array>::convert(Value value) {
1171
+ if(::rb_type(value.as_VALUE()) != RUBY_T_ARRAY) {
1172
+ throw Exception::format(
1173
+ builtin::TypeError, "Expected an Array but got a {}", value.get_class());
1174
+ }
1175
+ return detail::unsafe_coerce<Array>(value.as_VALUE());
1176
+ }
1177
+
1178
+ template <concepts::ConvertibleFromValue T>
1179
+ decltype(auto) FromValue<std::optional<T>>::convert(Value v) {
1180
+ return v.is_nil() ? std::nullopt : from_Value<T>(v);
1181
+ }
1182
+
1183
+ template <concepts::ConvertibleIntoValue T>
1184
+ Value IntoValue<std::optional<T>>::convert(std::optional<T> value) {
1185
+ return value ? Value::qnil : into_Value(*value);
1186
+ }
1187
+
1188
+ template <concepts::ConvertibleFromValue... T>
1189
+ inline decltype(auto) convert::FromValue<std::tuple<T...>>::convert(Value value) {
1190
+ auto array = from_Value<Array>(value);
1191
+ if(array.size() != sizeof...(T)) {
1192
+ throw Exception::format(
1193
+ builtin::ArgumentError, "Array of length {} is expected", sizeof...(T));
1194
+ }
1195
+ return [array]<size_t... I>(std::index_sequence<I...>) {
1196
+ return std::make_tuple(array.at<T>(I)...);
1197
+ }(std::make_index_sequence<sizeof...(T)>());
1198
+ }
1199
+
1200
+ template <concepts::ConvertibleIntoValue... T>
1201
+ Value IntoValue<std::tuple<T...>>::convert(std::tuple<T...> value) {
1202
+ return [&value]<size_t... I>(std::index_sequence<I...>) {
1203
+ return Array::new_from(
1204
+ std::tuple{into_Value<decltype(std::get<I>(value))>(std::get<I>(value))...});
1205
+ }(std::make_index_sequence<sizeof...(T)>());
1206
+ };
1207
+
1208
+ // Misc
1209
+
1210
+ namespace literals {
1211
+ template <detail::cxstring s> String operator""_str() {
1212
+ return String::copy_from(s);
1213
+ }
1214
+
1215
+ template <detail::u8cxstring s> String operator""_str() {
1216
+ return String::copy_from(s);
1217
+ }
1218
+
1219
+ template <detail::cxstring s> String operator""_fstr() {
1220
+ return String::intern_from(s);
1221
+ }
1222
+
1223
+ template <detail::u8cxstring s> String operator""_fstr() {
1224
+ return String::intern_from(s);
1225
+ }
1226
+
1227
+ template <detail::cxstring s> Symbol operator""_sym() {
1228
+ return detail::unsafe_coerce<Symbol>(
1229
+ detail::protect([&]() noexcept { return ::rb_id2sym(operator""_id < s>().as_ID()); }));
1230
+ }
1231
+
1232
+ template <detail::u8cxstring s> Symbol operator""_sym() {
1233
+ return detail::unsafe_coerce<Symbol>(
1234
+ detail::protect([&]() noexcept { return ::rb_id2sym(operator""_id < s>().as_VALUE()); }));
1235
+ }
1236
+
1237
+ template <detail::cxstring s> Id operator""_id() {
1238
+ static Id const id{
1239
+ detail::protect([&]() noexcept { return ::rb_intern2(s.data(), s.size()); })};
1240
+ return id;
1241
+ }
1242
+
1243
+ template <detail::u8cxstring s> Id operator""_id() {
1244
+ static Id const id{detail::protect(
1245
+ [&]() noexcept { return ::rb_intern_str(operator""_fstr < s>().as_VALUE()); })};
1246
+ return id;
1247
+ }
1248
+
1249
+ }
1250
+
1251
+ // Leak
1252
+
1253
+ template <std::derived_from<ValueBase> T>
1254
+ inline Leak<T>::Leak() noexcept: init_(false), raw_value_(RUBY_Qnil) {
1255
+ }
1256
+
1257
+ template <std::derived_from<ValueBase> T>
1258
+ inline Leak<T>::Leak(T value) noexcept(noexcept(T(value))): Leak() {
1259
+ set(value);
1260
+ }
1261
+
1262
+ template <std::derived_from<ValueBase> T>
1263
+ inline Leak<T> &Leak<T>::operator=(T value) noexcept(noexcept(T(value))) {
1264
+ set(value);
1265
+ return *this;
1266
+ }
1267
+
1268
+ template <std::derived_from<ValueBase> T> inline T Leak<T>::get() const {
1269
+ if(!init_) {
1270
+ throw std::runtime_error{"Leak is not initialized yet"};
1271
+ }
1272
+ return value_;
1273
+ }
1274
+
1275
+ template <std::derived_from<ValueBase> T>
1276
+ inline void Leak<T>::set(T value) noexcept(noexcept(T(value))) {
1277
+ if(init_) {
1278
+ value_.~T();
1279
+ } else {
1280
+ ::rb_gc_register_address(&raw_value_);
1281
+ }
1282
+ new(&value_) T(value);
1283
+ init_ = true;
1284
+ }
1285
+
1286
+ template <std::derived_from<ValueBase> T> inline T Leak<T>::operator*() const {
1287
+ return get();
1288
+ }
1289
+
1290
+ template <std::derived_from<ValueBase> T>
1291
+ inline T const *RCX_Nonnull Leak<T>::operator->() const {
1292
+ if(!init_) {
1293
+ throw std::runtime_error{"Leak is not initialized yet"};
1294
+ }
1295
+ return &value_;
1296
+ }
1297
+
1298
+ template <std::derived_from<ValueBase> T> inline void Leak<T>::clear() noexcept {
1299
+ if(init_) {
1300
+ value_.~T();
1301
+ raw_value_ = RUBY_Qnil;
1302
+ ::rb_gc_unregister_address(&raw_value_);
1303
+ init_ = false;
1304
+ }
1305
+ }
1306
+
1307
+ // Ruby
1308
+
1309
+ inline Module Ruby::define_module(concepts::Identifier auto &&name) {
1310
+ return builtin::Object.define_module(std::forward<decltype(name)>(name));
1311
+ }
1312
+
1313
+ template <typename T, typename S>
1314
+ inline ClassT<T> Ruby::define_class(concepts::Identifier auto &&name, ClassT<S> superclass) {
1315
+ return builtin::Object.define_class<T>(std::forward<decltype(name)>(name), superclass);
1316
+ }
1317
+
1318
+ template <typename T> inline ClassT<T> Ruby::define_class(concepts::Identifier auto &&name) {
1319
+ return define_class<T>(std::forward<decltype(name)>(name), builtin::Object);
1320
+ }
1321
+
1322
+ namespace detail {
1323
+ inline std::string demangle_type_info(std::type_info const &ti) {
1324
+ if constexpr(have_abi_cxa_demangle) {
1325
+ std::unique_ptr<char, decltype(&free)> const name = {
1326
+ abi::__cxa_demangle(ti.name(), nullptr, 0, nullptr),
1327
+ free,
1328
+ };
1329
+ return name.get();
1330
+ }
1331
+ return {};
1332
+ }
1333
+
1334
+ inline Value make_ruby_exception(
1335
+ std::exception const *RCX_Nullable exc, std::type_info const *RCX_Nullable ti) {
1336
+ std::string name, msg;
1337
+ if(ti)
1338
+ name = demangle_type_info(*ti);
1339
+ if(exc)
1340
+ msg = exc->what();
1341
+ return builtin::RuntimeError.new_instance(String::copy_from(
1342
+ std::format("{}: {}", name.empty() ? std::string{"unknown"} : name, msg)));
1343
+ }
1344
+
1345
+ inline auto cxx_protect(std::invocable<> auto const &functor) noexcept
1346
+ -> std::invoke_result_t<decltype(functor)> {
1347
+ try {
1348
+ return functor();
1349
+ } catch(Jump const &jump) {
1350
+ ::rb_jump_tag(jump.state);
1351
+ } catch(Exception const &exc) {
1352
+ ::rb_exc_raise(exc.as_VALUE());
1353
+ } catch(std::exception const &exc) {
1354
+ ::rb_exc_raise(make_ruby_exception(&exc, &typeid(exc)).as_VALUE());
1355
+ } catch(...) {
1356
+ if constexpr(have_abi_cxa_current_exception_type) {
1357
+ ::rb_exc_raise(
1358
+ make_ruby_exception(nullptr, abi::__cxa_current_exception_type()).as_VALUE());
1359
+ } else {
1360
+ ::rb_exc_raise(make_ruby_exception(nullptr, nullptr).as_VALUE());
1361
+ }
1362
+ }
1363
+ }
1364
+ }
1365
+ }