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,68 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/value.hpp>
|
|
4
|
+
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <utility>
|
|
7
|
+
|
|
8
|
+
namespace rbxx {
|
|
9
|
+
namespace detail {
|
|
10
|
+
|
|
11
|
+
class object_cell {
|
|
12
|
+
public:
|
|
13
|
+
explicit object_cell(VALUE initial) noexcept : stored_(initial) {
|
|
14
|
+
rb_gc_register_address(&stored_);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
object_cell(const object_cell&) = delete;
|
|
18
|
+
object_cell& operator=(const object_cell&) = delete;
|
|
19
|
+
|
|
20
|
+
~object_cell() noexcept { rb_gc_unregister_address(&stored_); }
|
|
21
|
+
|
|
22
|
+
[[nodiscard]] VALUE raw() const noexcept { return stored_; }
|
|
23
|
+
|
|
24
|
+
private:
|
|
25
|
+
VALUE stored_;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
} // namespace detail
|
|
29
|
+
|
|
30
|
+
/// @brief A copyable, movable RAII handle that pins a Ruby object across GC and compaction.
|
|
31
|
+
/// @code rbxx::object pinned{rbxx::value{ruby_value}}; @endcode
|
|
32
|
+
class object {
|
|
33
|
+
public:
|
|
34
|
+
/// @brief Constructs an empty handle representing nil.
|
|
35
|
+
object() noexcept = default;
|
|
36
|
+
|
|
37
|
+
/// @brief Pins the supplied Ruby value.
|
|
38
|
+
/// @code rbxx::object pinned{rbxx::value{Qtrue}}; @endcode
|
|
39
|
+
explicit object(value initial) : cell_(std::make_shared<detail::object_cell>(initial.raw())) {}
|
|
40
|
+
|
|
41
|
+
/// @brief Pins the supplied raw Ruby VALUE.
|
|
42
|
+
explicit object(VALUE initial) : object(value{initial}) {}
|
|
43
|
+
|
|
44
|
+
object(const object&) noexcept = default;
|
|
45
|
+
object(object&&) noexcept = default;
|
|
46
|
+
object& operator=(const object&) noexcept = default;
|
|
47
|
+
object& operator=(object&&) noexcept = default;
|
|
48
|
+
~object() = default;
|
|
49
|
+
|
|
50
|
+
/// @brief Returns the pinned object as a non-owning value view.
|
|
51
|
+
/// @code rbxx::value current = pinned.get(); @endcode
|
|
52
|
+
[[nodiscard]] value get() const noexcept { return value{raw()}; }
|
|
53
|
+
|
|
54
|
+
/// @brief Returns the current raw VALUE, updated after compaction.
|
|
55
|
+
[[nodiscard]] VALUE raw() const noexcept { return cell_ ? cell_->raw() : Qnil; }
|
|
56
|
+
|
|
57
|
+
/// @brief Returns whether the handle is empty or pins nil.
|
|
58
|
+
[[nodiscard]] bool is_nil() const noexcept { return NIL_P(raw()); }
|
|
59
|
+
|
|
60
|
+
/// @brief Releases this handle's ownership of the pin.
|
|
61
|
+
/// @code pinned.reset(); @endcode
|
|
62
|
+
void reset() noexcept { cell_.reset(); }
|
|
63
|
+
|
|
64
|
+
private:
|
|
65
|
+
std::shared_ptr<detail::object_cell> cell_;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
namespace rbxx::op {
|
|
4
|
+
|
|
5
|
+
/// @brief Metadata for mapping a C++ callable to a Ruby operator method.
|
|
6
|
+
struct name {
|
|
7
|
+
const char* ruby_name;
|
|
8
|
+
bool include_comparable = false;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/// @name Ruby operator names
|
|
12
|
+
/// @{
|
|
13
|
+
inline constexpr name add{"+"};
|
|
14
|
+
inline constexpr name subtract{"-"};
|
|
15
|
+
inline constexpr name multiply{"*"};
|
|
16
|
+
inline constexpr name divide{"/"};
|
|
17
|
+
inline constexpr name modulo{"%"};
|
|
18
|
+
inline constexpr name equal{"=="};
|
|
19
|
+
inline constexpr name compare{"<=>", true};
|
|
20
|
+
inline constexpr name less{"<"};
|
|
21
|
+
inline constexpr name greater{">"};
|
|
22
|
+
inline constexpr name index{"[]"};
|
|
23
|
+
inline constexpr name index_set{"[]="};
|
|
24
|
+
inline constexpr name left_shift{"<<"};
|
|
25
|
+
inline constexpr name to_s{"to_s"};
|
|
26
|
+
inline constexpr name inspect{"inspect"};
|
|
27
|
+
inline constexpr name hash{"hash"};
|
|
28
|
+
inline constexpr name call{"call"};
|
|
29
|
+
/// @}
|
|
30
|
+
|
|
31
|
+
} // namespace rbxx::op
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstddef>
|
|
4
|
+
|
|
5
|
+
namespace rbxx {
|
|
6
|
+
|
|
7
|
+
namespace policy {
|
|
8
|
+
|
|
9
|
+
/// @brief Internal value carried by a public return-value policy constant.
|
|
10
|
+
enum class kind { automatic, copy, take, reference, shared };
|
|
11
|
+
|
|
12
|
+
/// @brief Selects ownership behavior when converting a C++ return value to Ruby.
|
|
13
|
+
struct return_value_policy {
|
|
14
|
+
kind value;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/// @name Return-value policies
|
|
18
|
+
/// @{
|
|
19
|
+
inline constexpr return_value_policy automatic{kind::automatic};
|
|
20
|
+
inline constexpr return_value_policy copy{kind::copy};
|
|
21
|
+
inline constexpr return_value_policy take{kind::take};
|
|
22
|
+
inline constexpr return_value_policy reference{kind::reference};
|
|
23
|
+
inline constexpr return_value_policy shared{kind::shared};
|
|
24
|
+
/// @}
|
|
25
|
+
|
|
26
|
+
} // namespace policy
|
|
27
|
+
|
|
28
|
+
/// @brief Keeps one Ruby object alive for as long as another remains reachable.
|
|
29
|
+
/// @details Index 0 is the return value, 1 is self, and 2 onward are arguments.
|
|
30
|
+
/// @code .def("child", &Owner::child, policy::reference, keep_alive<0, 1>()) @endcode
|
|
31
|
+
template <std::size_t Nurse, std::size_t Patient> struct keep_alive {
|
|
32
|
+
static constexpr std::size_t nurse = Nurse;
|
|
33
|
+
static constexpr std::size_t patient = Patient;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/exception.hpp>
|
|
4
|
+
|
|
5
|
+
#include <exception>
|
|
6
|
+
#include <functional>
|
|
7
|
+
#include <optional>
|
|
8
|
+
#include <tuple>
|
|
9
|
+
#include <type_traits>
|
|
10
|
+
#include <utility>
|
|
11
|
+
|
|
12
|
+
namespace rbxx {
|
|
13
|
+
namespace detail {
|
|
14
|
+
|
|
15
|
+
template <typename Function, typename... Args> struct protect_payload {
|
|
16
|
+
using function_type = std::decay_t<Function>;
|
|
17
|
+
using result_type = std::invoke_result_t<function_type&, std::decay_t<Args>&...>;
|
|
18
|
+
using stored_result = std::conditional_t<std::is_void_v<result_type>, bool, result_type>;
|
|
19
|
+
|
|
20
|
+
function_type function;
|
|
21
|
+
std::tuple<std::decay_t<Args>...> args;
|
|
22
|
+
std::optional<stored_result> result;
|
|
23
|
+
std::exception_ptr cpp_exception;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
template <typename Payload> VALUE protect_trampoline(VALUE opaque) noexcept {
|
|
27
|
+
auto* payload = reinterpret_cast<Payload*>(opaque);
|
|
28
|
+
try {
|
|
29
|
+
if constexpr (std::is_void_v<typename Payload::result_type>) {
|
|
30
|
+
std::apply(payload->function, payload->args);
|
|
31
|
+
payload->result.emplace(true);
|
|
32
|
+
} else {
|
|
33
|
+
payload->result.emplace(std::apply(payload->function, payload->args));
|
|
34
|
+
}
|
|
35
|
+
} catch (...) {
|
|
36
|
+
payload->cpp_exception = std::current_exception();
|
|
37
|
+
}
|
|
38
|
+
return Qnil;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
} // namespace detail
|
|
42
|
+
|
|
43
|
+
/// @brief Invokes a Ruby C API operation through rb_protect and converts longjmp to ruby_error.
|
|
44
|
+
/// @code VALUE string = rbxx::protect(rb_utf8_str_new_cstr, "safe"); @endcode
|
|
45
|
+
template <typename Function, typename... Args> auto protect(Function&& function, Args&&... args) {
|
|
46
|
+
using payload_type = detail::protect_payload<Function, Args...>;
|
|
47
|
+
static_assert(!std::is_reference_v<typename payload_type::result_type>,
|
|
48
|
+
"rbxx::protect does not support reference return types");
|
|
49
|
+
|
|
50
|
+
payload_type payload{std::forward<Function>(function),
|
|
51
|
+
std::tuple<std::decay_t<Args>...>{std::forward<Args>(args)...}, std::nullopt,
|
|
52
|
+
nullptr};
|
|
53
|
+
int state = 0;
|
|
54
|
+
rb_protect(detail::protect_trampoline<payload_type>, reinterpret_cast<VALUE>(&payload), &state);
|
|
55
|
+
|
|
56
|
+
if (state != 0) {
|
|
57
|
+
VALUE exception = rb_errinfo();
|
|
58
|
+
rb_set_errinfo(Qnil);
|
|
59
|
+
throw ruby_error(exception);
|
|
60
|
+
}
|
|
61
|
+
if (payload.cpp_exception) {
|
|
62
|
+
std::rethrow_exception(payload.cpp_exception);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if constexpr (std::is_void_v<typename payload_type::result_type>) {
|
|
66
|
+
return;
|
|
67
|
+
} else {
|
|
68
|
+
return std::move(*payload.result);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/arg.hpp>
|
|
4
|
+
#include <rbxx/callback.hpp>
|
|
5
|
+
#include <rbxx/class.hpp>
|
|
6
|
+
#include <rbxx/data_object.hpp>
|
|
7
|
+
#include <rbxx/detail/ruby_include.hpp>
|
|
8
|
+
#include <rbxx/extension.hpp>
|
|
9
|
+
#include <rbxx/function.hpp>
|
|
10
|
+
#include <rbxx/module.hpp>
|
|
11
|
+
#include <rbxx/nogvl.hpp>
|
|
12
|
+
#include <rbxx/object.hpp>
|
|
13
|
+
#include <rbxx/operators.hpp>
|
|
14
|
+
#include <rbxx/policies.hpp>
|
|
15
|
+
#include <rbxx/protect.hpp>
|
|
16
|
+
#include <rbxx/registry.hpp>
|
|
17
|
+
#include <rbxx/stl.hpp>
|
|
18
|
+
#include <rbxx/type_caster.hpp>
|
|
19
|
+
#include <rbxx/value.hpp>
|
|
20
|
+
#include <rbxx/version.hpp>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/type_caster.hpp>
|
|
4
|
+
|
|
5
|
+
#include <functional>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <mutex>
|
|
8
|
+
#include <source_location>
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <typeindex>
|
|
11
|
+
#include <unordered_map>
|
|
12
|
+
|
|
13
|
+
namespace rbxx::detail {
|
|
14
|
+
|
|
15
|
+
struct class_info {
|
|
16
|
+
std::type_index cpp_type{typeid(void)};
|
|
17
|
+
VALUE ruby_class = Qnil;
|
|
18
|
+
const rb_data_type_t* data_type = nullptr;
|
|
19
|
+
std::function<void*(void*)> native_pointer;
|
|
20
|
+
std::unordered_map<std::type_index, std::function<void*(void*)>> conversions;
|
|
21
|
+
std::source_location registered_at = std::source_location::current();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
class type_registry {
|
|
25
|
+
public:
|
|
26
|
+
static type_registry& instance() {
|
|
27
|
+
static auto* registry = new type_registry();
|
|
28
|
+
return *registry;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
template <typename T, typename Base = void>
|
|
32
|
+
void add(VALUE ruby_class, const rb_data_type_t* data_type,
|
|
33
|
+
std::source_location location = std::source_location::current()) {
|
|
34
|
+
auto information = std::make_unique<class_info>();
|
|
35
|
+
information->cpp_type = std::type_index(typeid(T));
|
|
36
|
+
information->ruby_class = ruby_class;
|
|
37
|
+
information->data_type = data_type;
|
|
38
|
+
information->conversions.emplace(std::type_index(typeid(T)),
|
|
39
|
+
[](void* pointer) { return pointer; });
|
|
40
|
+
information->registered_at = location;
|
|
41
|
+
|
|
42
|
+
if constexpr (!std::is_void_v<Base>) {
|
|
43
|
+
class_info* base = find(std::type_index(typeid(Base)));
|
|
44
|
+
if (base == nullptr) {
|
|
45
|
+
std::string message = "rbxx: base C++ type ";
|
|
46
|
+
message += type_name<Base>();
|
|
47
|
+
message += " is not registered; call def_class<Base>() first";
|
|
48
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
49
|
+
}
|
|
50
|
+
for (const auto& [target, conversion] : base->conversions) {
|
|
51
|
+
information->conversions.emplace(target, [conversion](void* pointer) {
|
|
52
|
+
auto* derived = static_cast<T*>(pointer);
|
|
53
|
+
return conversion(static_cast<Base*>(derived));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
std::lock_guard lock(write_mutex_);
|
|
59
|
+
class_info* stored = information.get();
|
|
60
|
+
by_data_type_.insert_or_assign(data_type, stored);
|
|
61
|
+
by_type_.insert_or_assign(std::type_index(typeid(T)), std::move(information));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
template <typename T> void set_native_pointer(std::function<void*(void*)> extractor) {
|
|
65
|
+
class_info* information = find(std::type_index(typeid(T)));
|
|
66
|
+
if (information != nullptr) {
|
|
67
|
+
information->native_pointer = std::move(extractor);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
[[nodiscard]] class_info* find(std::type_index type) const noexcept {
|
|
72
|
+
auto found = by_type_.find(type);
|
|
73
|
+
return found == by_type_.end() ? nullptr : found->second.get();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
[[nodiscard]] class_info* find(const rb_data_type_t* type) const noexcept {
|
|
77
|
+
auto found = by_data_type_.find(type);
|
|
78
|
+
return found == by_data_type_.end() ? nullptr : found->second;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private:
|
|
82
|
+
std::mutex write_mutex_;
|
|
83
|
+
std::unordered_map<std::type_index, std::unique_ptr<class_info>> by_type_;
|
|
84
|
+
std::unordered_map<const rb_data_type_t*, class_info*> by_data_type_;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
template <typename T> [[noreturn]] void throw_unregistered_type() {
|
|
88
|
+
std::string message = "rbxx: C++ type ";
|
|
89
|
+
message += type_name<T>();
|
|
90
|
+
message += " is not registered; call def_class<T>() or specialize rbxx::type_caster<T>";
|
|
91
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
template <typename T> class_info& registered_class() {
|
|
95
|
+
class_info* information = type_registry::instance().find(std::type_index(typeid(T)));
|
|
96
|
+
if (information == nullptr) {
|
|
97
|
+
throw_unregistered_type<T>();
|
|
98
|
+
}
|
|
99
|
+
return *information;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
template <typename T> T& load_registered(value input) {
|
|
103
|
+
VALUE raw = input.raw();
|
|
104
|
+
if (!RB_TYPE_P(raw, T_DATA) || !RTYPEDDATA_P(raw)) {
|
|
105
|
+
std::string message = "rbxx: expected registered C++ type ";
|
|
106
|
+
message += type_name<T>();
|
|
107
|
+
message += ", got ";
|
|
108
|
+
message += rb_obj_classname(raw);
|
|
109
|
+
message += "; call def_class<T>() for the expected type";
|
|
110
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class_info* actual = type_registry::instance().find(RTYPEDDATA_TYPE(raw));
|
|
114
|
+
if (actual == nullptr || !actual->native_pointer) {
|
|
115
|
+
throw_unregistered_type<T>();
|
|
116
|
+
}
|
|
117
|
+
auto conversion = actual->conversions.find(std::type_index(typeid(T)));
|
|
118
|
+
if (conversion == actual->conversions.end()) {
|
|
119
|
+
std::string message = "rbxx: wrapped C++ type cannot be converted to ";
|
|
120
|
+
message += type_name<T>();
|
|
121
|
+
message += "; register the inheritance with def_class<Derived, Base>()";
|
|
122
|
+
message += "; actual type was registered at ";
|
|
123
|
+
message += actual->registered_at.file_name();
|
|
124
|
+
message += ":";
|
|
125
|
+
message += std::to_string(actual->registered_at.line());
|
|
126
|
+
throw ruby_error(make_exception(rb_eTypeError, message.c_str()));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void* native = actual->native_pointer(RTYPEDDATA_DATA(raw));
|
|
130
|
+
if (native == nullptr) {
|
|
131
|
+
throw ruby_error(make_exception(rb_eRuntimeError, "rbxx: C++ object is not initialized"));
|
|
132
|
+
}
|
|
133
|
+
return *static_cast<T*>(conversion->second(native));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
template <typename T> bool registered_matches(value input) noexcept {
|
|
137
|
+
if (!RB_TYPE_P(input.raw(), T_DATA) || !RTYPEDDATA_P(input.raw())) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
class_info* actual = type_registry::instance().find(RTYPEDDATA_TYPE(input.raw()));
|
|
141
|
+
return actual != nullptr &&
|
|
142
|
+
actual->conversions.contains(std::type_index(typeid(std::remove_cv_t<T>)));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
} // namespace rbxx::detail
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/stl/detail.hpp>
|
|
4
|
+
|
|
5
|
+
#include <array>
|
|
6
|
+
#include <sstream>
|
|
7
|
+
|
|
8
|
+
namespace rbxx {
|
|
9
|
+
namespace detail {
|
|
10
|
+
template <typename T, std::size_t Size>
|
|
11
|
+
struct is_non_bindable_class<std::array<T, Size>> : std::true_type {};
|
|
12
|
+
} // namespace detail
|
|
13
|
+
|
|
14
|
+
template <typename T, std::size_t Size> struct type_caster<std::array<T, Size>> {
|
|
15
|
+
static constexpr std::string_view name = "Array";
|
|
16
|
+
|
|
17
|
+
static std::array<T, Size> load(value input) {
|
|
18
|
+
VALUE array = detail::coerce_array(input);
|
|
19
|
+
if (RARRAY_LEN(array) != static_cast<long>(Size)) {
|
|
20
|
+
std::ostringstream message;
|
|
21
|
+
message << "rbxx: expected Array of length " << Size << ", got " << RARRAY_LEN(array);
|
|
22
|
+
throw ruby_error(detail::make_exception(rb_eArgError, message.str().c_str()));
|
|
23
|
+
}
|
|
24
|
+
return load_elements(array, std::make_index_sequence<Size>{});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static value dump(const std::array<T, Size>& input) { return detail::dump_array_range(input); }
|
|
28
|
+
|
|
29
|
+
static bool matches(value input) noexcept {
|
|
30
|
+
return input.is_array() && RARRAY_LEN(input.raw()) == static_cast<long>(Size);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private:
|
|
34
|
+
template <std::size_t... Index>
|
|
35
|
+
static std::array<T, Size> load_elements(VALUE array, std::index_sequence<Index...>) {
|
|
36
|
+
return {from_ruby<T>(value{RARRAY_AREF(array, static_cast<long>(Index))})...};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/stl/detail.hpp>
|
|
4
|
+
|
|
5
|
+
#include <chrono>
|
|
6
|
+
|
|
7
|
+
namespace rbxx {
|
|
8
|
+
namespace detail {
|
|
9
|
+
template <typename Rep, typename Period>
|
|
10
|
+
struct is_non_bindable_class<std::chrono::duration<Rep, Period>> : std::true_type {};
|
|
11
|
+
} // namespace detail
|
|
12
|
+
|
|
13
|
+
template <typename Rep, typename Period> struct type_caster<std::chrono::duration<Rep, Period>> {
|
|
14
|
+
using duration_type = std::chrono::duration<Rep, Period>;
|
|
15
|
+
static constexpr std::string_view name = "seconds";
|
|
16
|
+
static duration_type load(value input) {
|
|
17
|
+
const auto seconds = std::chrono::duration<double>{from_ruby<double>(input)};
|
|
18
|
+
return std::chrono::duration_cast<duration_type>(seconds);
|
|
19
|
+
}
|
|
20
|
+
static value dump(const duration_type& input) {
|
|
21
|
+
return to_ruby(std::chrono::duration<double>(input).count());
|
|
22
|
+
}
|
|
23
|
+
static bool matches(value input) noexcept { return type_caster<double>::matches(input); }
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/data_object.hpp>
|
|
4
|
+
|
|
5
|
+
#include <limits>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <type_traits>
|
|
8
|
+
|
|
9
|
+
namespace rbxx::detail {
|
|
10
|
+
|
|
11
|
+
inline VALUE coerce_array(value input) {
|
|
12
|
+
VALUE converted = protect(rb_check_array_type, input.raw());
|
|
13
|
+
if (NIL_P(converted)) {
|
|
14
|
+
throw_type_error("Array or object responding to #to_ary", input);
|
|
15
|
+
}
|
|
16
|
+
return converted;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
inline VALUE coerce_hash(value input) {
|
|
20
|
+
VALUE converted = protect(rb_check_hash_type, input.raw());
|
|
21
|
+
if (NIL_P(converted)) {
|
|
22
|
+
throw_type_error("Hash or object responding to #to_hash", input);
|
|
23
|
+
}
|
|
24
|
+
return converted;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
template <typename Range> value dump_array_range(const Range& input) {
|
|
28
|
+
const auto size = input.size();
|
|
29
|
+
if (size > static_cast<std::size_t>(std::numeric_limits<long>::max())) {
|
|
30
|
+
throw std::length_error("rbxx: container is too large for a Ruby Array");
|
|
31
|
+
}
|
|
32
|
+
return value{protect([&input, size] {
|
|
33
|
+
VALUE result = rb_ary_new_capa(static_cast<long>(size));
|
|
34
|
+
for (const auto& element : input) {
|
|
35
|
+
using element_type = std::remove_cvref_t<decltype(element)>;
|
|
36
|
+
if constexpr (std::is_floating_point_v<element_type>) {
|
|
37
|
+
rb_ary_push(result, rb_float_new(static_cast<double>(element)));
|
|
38
|
+
} else {
|
|
39
|
+
rb_ary_push(result, to_ruby(element).raw());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
})};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
template <typename Map> value dump_hash_range(const Map& input) {
|
|
47
|
+
return value{protect([&input] {
|
|
48
|
+
VALUE result = rb_hash_new();
|
|
49
|
+
for (const auto& [key, mapped] : input) {
|
|
50
|
+
rb_hash_aset(result, to_ruby(key).raw(), to_ruby(mapped).raw());
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
})};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
inline VALUE hash_pairs(VALUE hash) {
|
|
57
|
+
return protect([hash] { return rb_funcall(hash, rb_intern("to_a"), 0); });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
} // namespace rbxx::detail
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/stl/detail.hpp>
|
|
4
|
+
|
|
5
|
+
#include <filesystem>
|
|
6
|
+
|
|
7
|
+
namespace rbxx {
|
|
8
|
+
namespace detail {
|
|
9
|
+
template <> struct is_non_bindable_class<std::filesystem::path> : std::true_type {};
|
|
10
|
+
} // namespace detail
|
|
11
|
+
|
|
12
|
+
template <> struct type_caster<std::filesystem::path> {
|
|
13
|
+
static constexpr std::string_view name = "String path";
|
|
14
|
+
static std::filesystem::path load(value input) {
|
|
15
|
+
return std::filesystem::path{from_ruby<std::string>(input)};
|
|
16
|
+
}
|
|
17
|
+
static value dump(const std::filesystem::path& input) { return to_ruby(input.string()); }
|
|
18
|
+
static bool matches(value input) noexcept { return type_caster<std::string>::matches(input); }
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/stl/detail.hpp>
|
|
4
|
+
|
|
5
|
+
#include <map>
|
|
6
|
+
#include <unordered_map>
|
|
7
|
+
|
|
8
|
+
namespace rbxx {
|
|
9
|
+
namespace detail {
|
|
10
|
+
template <typename Key, typename Mapped, typename Compare, typename Allocator>
|
|
11
|
+
struct is_non_bindable_class<std::map<Key, Mapped, Compare, Allocator>> : std::true_type {};
|
|
12
|
+
template <typename Key, typename Mapped, typename Hash, typename Equal, typename Allocator>
|
|
13
|
+
struct is_non_bindable_class<std::unordered_map<Key, Mapped, Hash, Equal, Allocator>>
|
|
14
|
+
: std::true_type {};
|
|
15
|
+
|
|
16
|
+
template <typename Map> Map load_map(value input) {
|
|
17
|
+
VALUE pairs = hash_pairs(coerce_hash(input));
|
|
18
|
+
Map result;
|
|
19
|
+
const long length = RARRAY_LEN(pairs);
|
|
20
|
+
for (long index = 0; index < length; ++index) {
|
|
21
|
+
VALUE pair = RARRAY_AREF(pairs, index);
|
|
22
|
+
using key_type = typename Map::key_type;
|
|
23
|
+
using mapped_type = typename Map::mapped_type;
|
|
24
|
+
result.emplace(from_ruby<key_type>(value{RARRAY_AREF(pair, 0)}),
|
|
25
|
+
from_ruby<mapped_type>(value{RARRAY_AREF(pair, 1)}));
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
} // namespace detail
|
|
30
|
+
|
|
31
|
+
template <typename Key, typename Mapped, typename Compare, typename Allocator>
|
|
32
|
+
struct type_caster<std::map<Key, Mapped, Compare, Allocator>> {
|
|
33
|
+
using map_type = std::map<Key, Mapped, Compare, Allocator>;
|
|
34
|
+
static constexpr std::string_view name = "Hash";
|
|
35
|
+
static map_type load(value input) { return detail::load_map<map_type>(input); }
|
|
36
|
+
static value dump(const map_type& input) { return detail::dump_hash_range(input); }
|
|
37
|
+
static bool matches(value input) noexcept { return input.is_hash(); }
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
template <typename Key, typename Mapped, typename Hash, typename Equal, typename Allocator>
|
|
41
|
+
struct type_caster<std::unordered_map<Key, Mapped, Hash, Equal, Allocator>> {
|
|
42
|
+
using map_type = std::unordered_map<Key, Mapped, Hash, Equal, Allocator>;
|
|
43
|
+
static constexpr std::string_view name = "Hash";
|
|
44
|
+
static map_type load(value input) { return detail::load_map<map_type>(input); }
|
|
45
|
+
static value dump(const map_type& input) { return detail::dump_hash_range(input); }
|
|
46
|
+
static bool matches(value input) noexcept { return input.is_hash(); }
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/stl/detail.hpp>
|
|
4
|
+
|
|
5
|
+
#include <optional>
|
|
6
|
+
|
|
7
|
+
namespace rbxx {
|
|
8
|
+
namespace detail {
|
|
9
|
+
template <typename T> struct is_non_bindable_class<std::optional<T>> : std::true_type {};
|
|
10
|
+
} // namespace detail
|
|
11
|
+
|
|
12
|
+
template <typename T> struct type_caster<std::optional<T>> {
|
|
13
|
+
static constexpr std::string_view name = "Object or nil";
|
|
14
|
+
static std::optional<T> load(value input) {
|
|
15
|
+
return input.is_nil() ? std::nullopt : std::optional<T>{from_ruby<T>(input)};
|
|
16
|
+
}
|
|
17
|
+
static value dump(const std::optional<T>& input) { return input ? to_ruby(*input) : value{Qnil}; }
|
|
18
|
+
static bool matches(value input) noexcept {
|
|
19
|
+
return input.is_nil() || type_caster<T>::matches(input);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
} // namespace rbxx
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <rbxx/stl/detail.hpp>
|
|
4
|
+
|
|
5
|
+
#include <set>
|
|
6
|
+
|
|
7
|
+
namespace rbxx {
|
|
8
|
+
namespace detail {
|
|
9
|
+
template <typename T, typename Compare, typename Allocator>
|
|
10
|
+
struct is_non_bindable_class<std::set<T, Compare, Allocator>> : std::true_type {};
|
|
11
|
+
} // namespace detail
|
|
12
|
+
|
|
13
|
+
template <typename T, typename Compare, typename Allocator>
|
|
14
|
+
struct type_caster<std::set<T, Compare, Allocator>> {
|
|
15
|
+
using set_type = std::set<T, Compare, Allocator>;
|
|
16
|
+
static constexpr std::string_view name = "Array";
|
|
17
|
+
static set_type load(value input) {
|
|
18
|
+
VALUE array = detail::coerce_array(input);
|
|
19
|
+
set_type result;
|
|
20
|
+
const long length = RARRAY_LEN(array);
|
|
21
|
+
for (long index = 0; index < length; ++index) {
|
|
22
|
+
result.insert(from_ruby<T>(value{RARRAY_AREF(array, index)}));
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
static value dump(const set_type& input) { return detail::dump_array_range(input); }
|
|
27
|
+
static bool matches(value input) noexcept { return input.is_array(); }
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
} // namespace rbxx
|