#include #include #include #include "cpp_protect.hpp" #include "NativeRegistry.hpp" namespace Rice::detail { template inline void NativeIterator::define(VALUE klass, std::string method_name, Iterator_Func_T begin, Iterator_Func_T end) { // Tell Ruby to invoke the static method call on this class detail::protect(rb_define_method, klass, method_name.c_str(), (RUBY_METHOD_FUNC)&NativeIterator_T::call, 0); // Now create a NativeIterator instance and save it to the natives registry keyed on // Ruby klass and method id. There may be multiple NativeIterator instances // because the same C++ method could be mapped to multiple Ruby methods. NativeIterator_T* native = new NativeIterator_T(klass, method_name, begin, end); detail::Registries::instance.natives.add(klass, Identifier(method_name).id(), native); } template inline VALUE NativeIterator::call(VALUE self) { // Look up the native function based on the Ruby klass and method id NativeIterator_T* nativeIterator = detail::Registries::instance.natives.lookup(); return cpp_protect([&] { return nativeIterator->operator()(self); }); } template inline NativeIterator::NativeIterator(VALUE klass, std::string method_name, Iterator_Func_T begin, Iterator_Func_T end) : klass_(klass), method_name_(method_name), begin_(begin), end_(end) { } template inline VALUE NativeIterator::createRubyEnumerator(VALUE self) { auto rb_size_function = [](VALUE recv, VALUE argv, VALUE eobj) -> VALUE { // Since we can't capture VALUE self from above (because then we can't send // this lambda to rb_enumeratorize_with_size), extract it from recv return cpp_protect([&] { // Get the iterator instance using Iter_T = NativeIterator; // Class is easy VALUE klass = protect(rb_class_of, recv); // Read the method_id from an attribute we added to the enumerator instance ID method_id = protect(rb_ivar_get, eobj, rb_intern("rice_method")); Iter_T* iterator = detail::Registries::instance.natives.lookup(klass, method_id); // Get the wrapped C++ instance T* receiver = detail::From_Ruby().convert(recv); // Get the distance Iterator_T begin = std::invoke(iterator->begin_, *receiver); Iterator_T end = std::invoke(iterator->end_, *receiver); Difference_T distance = std::distance(begin, end); return detail::To_Ruby().convert(distance); }); }; VALUE method_sym = Identifier(this->method_name_).to_sym(); VALUE enumerator = protect(rb_enumeratorize_with_size, self, method_sym, 0, nullptr, rb_size_function); // Hack the enumerator object by storing name_ on the enumerator object so // the rb_size_function above has access to it ID method_id = Identifier(this->method_name_).id(); protect(rb_ivar_set, enumerator, rb_intern("rice_method"), method_id); return enumerator; } template inline VALUE NativeIterator::operator()(VALUE self) { if (!protect(rb_block_given_p)) { return createRubyEnumerator(self); } else { T* receiver = detail::From_Ruby().convert(self); Iterator_T it = std::invoke(this->begin_, *receiver); Iterator_T end = std::invoke(this->end_, *receiver); for (; it != end; ++it) { protect(rb_yield, detail::To_Ruby().convert(*it)); } return self; } } }