From fe1546867cefeb798ad936f3e6e7ed1cf68182a1 Mon Sep 17 00:00:00 2001 From: cfis Date: Sun, 8 Dec 2024 23:12:31 -0800 Subject: [PATCH] Big header cleanup - separate hpp and ipp files. Thus remove defn.hpp files and do not include .ipp files from .hpp files. Also remove any includes for other rice files (they are not standalone files, so just stop pretending). These changes allows us to use the C++ API (Array, etc) from from_ruby and to_ruby. --- include/rice/rice.hpp | 9452 +++++++++++----------- rice/Address_Registration_Guard.hpp | 75 +- rice/Address_Registration_Guard_defn.hpp | 79 - rice/Arg.hpp | 2 - rice/Constructor.hpp | 3 - rice/Data_Object.hpp | 76 +- rice/Data_Object.ipp | 5 - rice/Data_Object_defn.hpp | 85 - rice/Data_Type.hpp | 207 +- rice/Data_Type.ipp | 13 - rice/Data_Type_defn.hpp | 213 - rice/Director.hpp | 2 - rice/Enum.hpp | 5 - rice/Enum.ipp | 3 - rice/Exception.hpp | 64 +- rice/Exception.ipp | 5 - rice/Exception_defn.hpp | 68 - rice/HandlerRegistration.hpp | 2 - rice/Identifier.hpp | 2 - rice/MemoryView.hpp | 8 +- rice/MemoryView.ipp | 4 - rice/MemoryView_defn.hpp | 14 - rice/Return.hpp | 4 - rice/cpp_api/Array.hpp | 4 - rice/cpp_api/Builtin_Object.hpp | 28 +- rice/cpp_api/Builtin_Object.ipp | 5 - rice/cpp_api/Builtin_Object_defn.hpp | 33 - rice/cpp_api/Class.hpp | 63 +- rice/cpp_api/Class.ipp | 6 - rice/cpp_api/Class_defn.hpp | 68 - rice/cpp_api/Hash.hpp | 5 - rice/cpp_api/Hash.ipp | 7 - rice/cpp_api/Module.hpp | 74 +- rice/cpp_api/Module.ipp | 8 - rice/cpp_api/Module_defn.hpp | 81 - rice/cpp_api/Object.hpp | 270 +- rice/cpp_api/Object.ipp | 4 - rice/cpp_api/Object_defn.hpp | 277 - rice/cpp_api/String.hpp | 5 - rice/cpp_api/Struct.hpp | 6 - rice/cpp_api/Struct.ipp | 1 - rice/cpp_api/Symbol.hpp | 5 - rice/detail/ExceptionHandler.hpp | 74 +- rice/detail/ExceptionHandler_defn.hpp | 77 - rice/detail/HandlerRegistry.hpp | 4 - rice/detail/InstanceRegistry.hpp | 3 - rice/detail/MethodInfo.hpp | 4 - rice/detail/MethodInfo.ipp | 1 - rice/detail/Native.hpp | 2 - rice/detail/Native.ipp | 4 - rice/detail/NativeAttributeGet.hpp | 5 - rice/detail/NativeAttributeGet.ipp | 4 - rice/detail/NativeAttributeSet.hpp | 5 - rice/detail/NativeAttributeSet.ipp | 4 - rice/detail/NativeFunction.hpp | 9 - rice/detail/NativeFunction.ipp | 4 - rice/detail/NativeIterator.hpp | 10 +- rice/detail/NativeIterator.ipp | 3 - rice/detail/NativeRegistry.hpp | 4 +- rice/detail/NativeRegistry.ipp | 2 - rice/detail/Registries.hpp | 7 - rice/detail/RubyFunction.hpp | 3 - rice/detail/RubyFunction.ipp | 2 - rice/detail/TupleIterator.hpp | 10 +- rice/detail/TupleIterator.ipp | 9 - rice/detail/Type.hpp | 1 - rice/detail/Type.ipp | 2 - rice/detail/TypeRegistry.hpp | 3 - rice/detail/TypeRegistry.ipp | 3 - rice/detail/Wrapper.hpp | 4 - rice/detail/Wrapper.ipp | 1 - rice/detail/cpp_protect.hpp | 2 - rice/detail/default_allocation_func.ipp | 2 - rice/detail/from_ruby.hpp | 39 +- rice/detail/from_ruby.ipp | 11 - rice/detail/from_ruby_defn.hpp | 38 - rice/detail/to_ruby.hpp | 44 +- rice/detail/to_ruby.ipp | 2 - rice/detail/to_ruby_defn.hpp | 48 - rice/global_function.hpp | 4 - rice/global_function.ipp | 1 - rice/rice.hpp | 80 +- rice/ruby_mark.hpp | 7 +- rice/traits/method_traits.hpp | 1 - test/CMakeSettings.json | 15 + 85 files changed, 5735 insertions(+), 6164 deletions(-) delete mode 100644 rice/Address_Registration_Guard_defn.hpp delete mode 100644 rice/Data_Object_defn.hpp delete mode 100644 rice/Data_Type_defn.hpp delete mode 100644 rice/Exception_defn.hpp delete mode 100644 rice/MemoryView_defn.hpp delete mode 100644 rice/cpp_api/Builtin_Object_defn.hpp delete mode 100644 rice/cpp_api/Class_defn.hpp delete mode 100644 rice/cpp_api/Module_defn.hpp delete mode 100644 rice/cpp_api/Object_defn.hpp delete mode 100644 rice/detail/ExceptionHandler_defn.hpp delete mode 100644 rice/detail/TupleIterator.ipp delete mode 100644 rice/detail/from_ruby_defn.hpp delete mode 100644 rice/detail/to_ruby_defn.hpp create mode 100644 test/CMakeSettings.json diff --git a/include/rice/rice.hpp b/include/rice/rice.hpp index 0978e6be..4c2890c8 100644 --- a/include/rice/rice.hpp +++ b/include/rice/rice.hpp @@ -345,22 +345,20 @@ namespace Rice::detail // ========= TupleIterator.hpp ========= - -// --------- TupleIterator.ipp --------- // See https://www.cppstories.com/2022/tuple-iteration-apply/ template void for_each_tuple(Tuple_T&& tuple, Function_T&& callable) { - std::apply([&callable](auto&& ...args) - { - (callable(std::forward(args)), ...); - }, std::forward(tuple)); + std::apply([&callable](auto&& ...args) + { + (callable(std::forward(args)), ...); + }, std::forward(tuple)); } // Code for C++ to call Ruby -// ========= Exception_defn.hpp ========= +// ========= Exception.hpp ========= #include @@ -476,7 +474,8 @@ namespace Rice::detail auto protect(Function_T func, Arg_Ts...args); } -// --------- RubyFunction.ipp --------- + +// ========= RubyFunction.ipp ========= #include @@ -555,1716 +554,1700 @@ namespace Rice::detail return rubyFunction(); } } +// Type Conversion declarations -// Code for Ruby to call C++ +// ========= Type.hpp ========= -// ========= ExceptionHandler.hpp ========= +#include +#include +#include +namespace Rice::detail +{ + template + struct Type + { + static bool verify(); + }; -// --------- ExceptionHandler_defn.hpp --------- -#ifndef Rice__detail__ExceptionHandler_defn__hpp_ -#define Rice__detail__ExceptionHandler_defn__hpp_ + // Return the name of a type + std::string typeName(const std::type_info& typeInfo); + std::string typeName(const std::type_index& typeIndex); + std::string makeClassName(const std::type_info& typeInfo); -#include + template + void verifyType(); -namespace Rice::detail -{ - /* An abstract class for converting C++ exceptions to ruby exceptions. It's used - like this: + template + void verifyTypes(); +} - try - { - } - catch(...) - { - handler->handle(); - } - If an exception is thrown the handler will pass the exception up the - chain, then the last handler in the chain will throw the exception - down the chain until a lower handler can handle it, e.g.: +// ========= to_ruby.hpp ========= - try - { - return call_next_ExceptionHandler(); - } - catch(MyException const & ex) - { - throw Rice::Exception(rb_cMyException, "%s", ex.what()); + +namespace Rice +{ + namespace detail + { + //! Convert a C++ object to Ruby. + /*! If x is a pointer, wraps the pointee as a Ruby object. If x is an + * Object, returns x. + * + * If no conversion exists a compile-time error is generated. + * + * \param x the object to convert. + * \return a Ruby representation of the C++ object. + * + * Example: + * \code + * rb_p(to_ruby(42)); + * + * Foo * p_foo = new Foo(); + * rb_p(to_ruby(p_foo)); + * \endcode + */ + template + class To_Ruby; + + // Helper template function that let's users avoid having to specify the template type - its deduced + template + VALUE to_ruby(T&& x) + { + using Unqualified_T = remove_cv_recursive_t; + return To_Ruby().convert(std::forward(x)); } - Memory management. Handlers are created by the ModuleBase constructor. When the - module defines a new Ruby method, metadata is stored on the Ruby klass including - the exception handler. Since the metadata outlives the module, handlers are stored - using std::shared_ptr. Thus the Module (or its inherited children) can be destroyed - without corrupting the metadata references to the shared exception handler. */ + // Helper template function that let's users avoid having to specify the template type - its deduced + template + VALUE to_ruby(T* x) + { + using Unqualified_T = remove_cv_recursive_t; + return To_Ruby().convert(x); + } + } // detail +} // Rice - class ExceptionHandler - { - public: - ExceptionHandler() = default; - virtual ~ExceptionHandler() = default; - // Don't allow copying or assignment - ExceptionHandler(const ExceptionHandler& other) = delete; - ExceptionHandler& operator=(const ExceptionHandler& other) = delete; +// ========= from_ruby.hpp ========= - virtual VALUE handle() const = 0; - }; +namespace Rice::detail +{ + //! Convert a Ruby object to C++. + /*! If the Ruby object can be converted to an immediate value, returns a + * copy of the Ruby object. If the Ruby object is holding a C++ + * object and the type specified is a pointer to that type, returns a + * pointer to that object. + * + * Conversions from ruby to a pointer type are automatically generated + * when a type is bound using Data_Type. If no conversion exists an + * exception is thrown. + * + * \param T the C++ type to which to convert. + * \param x the Ruby object to convert. + * \return a C++ representation of the Ruby object. + * + * Example: + * \code + * Object x = INT2NUM(42); + * std::cout << From_Ruby::convert(x); + * + * Data_Object foo(new Foo); + * std::cout << *From_Ruby(foo) << std::endl; + * \endcode + */ - // The default exception handler just rethrows the exception. If there - // are other handlers in the chain, they will try to handle the rethrown - // exception. - class DefaultExceptionHandler : public ExceptionHandler + template + class From_Ruby; + + enum class Convertible: uint8_t { - public: - virtual VALUE handle() const override; + None = 0b000, + TypeCast = 0b010, + Exact = 0b110, }; +} - // An exception handler that takes a functor as an argument. The - // functor should throw a Rice::Exception to handle the exception. If - // the functor does not handle the exception, the exception will be - // re-thrown. - template - class CustomExceptionHandler : public ExceptionHandler + +// C++ API declarations + +// ========= Identifier.hpp ========= + +#include + +namespace Rice +{ + class Symbol; + + //! A wrapper for the ID type + /*! An ID is ruby's internal representation of a Symbol object. + */ + class Identifier { public: - CustomExceptionHandler(Functor_T handler, std::shared_ptr nextHandler); - virtual VALUE handle() const override; + //! Construct a new Identifier from an ID. + Identifier(ID id); + + //! Construct a new Identifier from a Symbol. + Identifier(Symbol const& symbol); + + //! Construct a new Identifier from a c string. + Identifier(char const* s); + + //! Construct a new Identifier from a string. + Identifier(std::string const& string); + + //! Return a string representation of the Identifier. + char const* c_str() const; + + //! Return a string representation of the Identifier. + std::string str() const; + + //! Return the underlying ID + ID id() const { return id_; } + + //! Return the underlying ID + operator ID() const { return id_; } + + //! Return the ID as a Symbol + VALUE to_sym() const; private: - Functor_T handler_; - std::shared_ptr nextHandler_; + ID id_; }; -} -#endif // Rice__detail__ExceptionHandler_defn__hpp_ -// --------- ExceptionHandler.ipp --------- -namespace Rice::detail +} // namespace Rice + + +// ========= Identifier.ipp ========= +namespace Rice { - inline VALUE Rice::detail::DefaultExceptionHandler::handle() const + inline Identifier::Identifier(ID id) : id_(id) { - throw; } - template - inline Rice::detail::CustomExceptionHandler:: - CustomExceptionHandler(Functor_T handler, std::shared_ptr nextHandler) - : handler_(handler), nextHandler_(nextHandler) + inline Identifier::Identifier(char const* s) : id_(rb_intern(s)) { } - template - inline VALUE Rice::detail::CustomExceptionHandler::handle() const + inline Identifier::Identifier(std::string const& s) : id_(rb_intern2(s.c_str(), s.size())) { - try - { - return this->nextHandler_->handle(); - } - catch (Exception_T const& ex) - { - handler_(ex); - throw; - } } -} + inline char const* Identifier::c_str() const + { + return detail::protect(rb_id2name, id_); + } -// ========= Arg.hpp ========= + inline std::string Identifier::str() const + { + return c_str(); + } -#include + inline VALUE Identifier::to_sym() const + { + return ID2SYM(id_); + } +} +// ========= Object.hpp ========= + +/*! \file Object.hpp + */ + +#include namespace Rice { - //! Helper for defining default arguments of a method - /*! This class exposes the ability to define the default values of a - * wrapped method. Inspired by how Boost.Python handles keyword and - * default arguments, the syntax is simple: - * - * \code - * define_method( - * "method", - * &method, - * Arg("arg1"), Arg("arg2") = 3, Arg("arg3") = true - * ); - * \endcode - * - * which means "for method &method, it takes 3 arguments - * [arg1, arg2, arg3]. Of these arguments, arg2's default is 3 - * and arg3's default is true. - * - * It may be required to explicitly cast the type of the default - * value to prevent compilation errors. + class Class; + class String; + class Array; + + //! The base class for all Objects + /*! Perhaps the name "Object" is a misnomer, because this class really + * holds an object reference, not an object. */ - class Arg + class Object { public: - //! Initialize a new Arg with the name of the argument - /*! We require the name of the argument because 1) it makes code - * easier to read and 2) hopefully Ruby gets keyword arguments - * in the future and this means Rice will be ready for it. - */ - Arg(std::string name); - - //! Set the default value for this Arg - /*! Set the default value for this argument. - * If this isn't called on this Arg, then this - * Arg is required in the method call. - * - * \param val the value to store as default - */ - template - Arg& operator=(Arg_Type val); - - //! Check if this Arg has a default value associated with it - bool hasDefaultValue() const; - - //! Return a reference to the default value associated with this Arg - /*! \return the type saved to this Arg - */ - template - Arg_Type& defaultValue(); - - //! Tell the receiving object to keep this argument alive - //! until the receiving object is freed. - Arg& keepAlive(); - - //! Returns if the argument should be kept alive - bool isKeepAlive() const; - - //! Specifies if the argument should be treated as a value - Arg& setValue(); + //! Encapsulate an existing ruby object. + Object(VALUE value = Qnil) : value_(value) {} - //! Returns if the argument should be treated as a value - bool isValue() const; + //! Destructor + virtual ~Object(); - //! Specifies C++ will take ownership of this value and Ruby shoudl not fee it - Arg& transferOwnership(); - bool isTransfer(); + // Enable copying + Object(const Object& other) = default; + Object& operator=(const Object& other) = default; - public: - const std::string name; - int32_t position = -1; + // Enable moving + Object(Object&& other); + Object& operator=(Object&& other); - private: - //! Our saved default value - std::any defaultValue_; - bool isValue_ = false; - bool isKeepAlive_ = false; - bool isTransfer_ = false; - }; -} // Rice + //! Returns false if the object is nil or false; returns true + //! otherwise. + // Having this conversion also prevents accidental conversion to + // undesired integral types (e.g. long or int) by making the + // conversion ambiguous. + bool test() const { return RTEST(value_); } + //! Returns false if the object is nil or false; returns true + //! otherwise. + operator bool() const { return test(); } -// --------- Arg.ipp --------- -namespace Rice -{ - inline Arg::Arg(std::string name) : name(name) - { - } + //! Returns true if the object is nil, false otherwise. + bool is_nil() const { return NIL_P(value_); } - template - inline Arg& Arg::operator=(Arg_Type val) - { - this->defaultValue_ = val; - return *this; - } + //! Implicit conversion to VALUE. + operator VALUE() const { return value_; } - //! Check if this Arg has a default value associated with it - inline bool Arg::hasDefaultValue() const - { - return this->defaultValue_.has_value(); - } + //! Explicitly get the encapsulated VALUE. + // Returns a const ref so that Address_Registration_Guard can access + // the address where the VALUE is stored + VALUE const volatile& value() const { return value_; } - //! Return a reference to the default value associated with this Arg - /*! \return the type saved to this Arg - */ - template - inline Arg_Type& Arg::defaultValue() - { - return std::any_cast(this->defaultValue_); - } + //! Get the class of an object. + /*! \return the object's Class. + */ + Class class_of() const; - inline Arg& Arg::keepAlive() - { - this->isKeepAlive_ = true; - return *this; - } + //! Compare this object to another object. + /*! Gets the result of self <=> other and returns the result. The + * result will be less than zero if self < other, greater than zero + * if self > other, and equal to zero if self == other. + */ + int compare(Object const& other) const; - inline bool Arg::isKeepAlive() const - { - return this->isKeepAlive_; - } + //! Return a string representation of an object. + /*! \return the result of calling to_s on the object. A String is not + * returned, because it is not possible to return an instance of a + * derived class. + */ + String to_s() const; - inline Arg& Arg::setValue() - { - isValue_ = true; - return *this; - } + //! Return the name of an object's class. + String class_name() const; - inline bool Arg::isValue() const - { - return isValue_; - } + //! Inspect the object. + /*! \return the result of calling inspect on the object. A String is + * not returned, because it is not possible to return an instance of + * a derived class. + */ + String inspect() const; - inline Arg& Arg::transferOwnership() - { - this->isTransfer_ = true; - return *this; - } + //! Freeze the object. + void freeze(); - inline bool Arg::isTransfer() - { - return this->isTransfer_; - } + //! Determine if the object is frozen. + /*! \return true if the object is frozen, false otherwise. + */ + bool is_frozen() const; + //! Evaluate the given string in the context of the object. + /*! This is equivalant to calling obj.instance_eval(s) from inside the + * interpreter. + * \return the result of the expression. + */ + Object instance_eval(String const& s); -} // Rice + //! Return the type of the underlying C object. + /*! This is equivalent to calling rb_type(obj). + * \return the type of the underlying C object (e.g. T_DATA, T_ARRAY, + * etc.). + */ + int rb_type() const; -// ========= Return.hpp ========= + //! Return the object's id + VALUE object_id() const; -#include + //! Determine whether the object is an instance of a class/module. + /*! \param klass a class or module. + * \return true if the object is an instance of the given + * class/module or one of its descendants. + */ + bool is_a(Object klass) const; -namespace Rice -{ - //! Helper for defining Return argument of a method + //! Determine if the objects responds to a method. + /*! \param id the name of the method + * \return true if the objects responds to the method, false + * otherwise. + */ + bool respond_to(Identifier id) const; - class Return - { - public: - //! Specifies Ruby should take ownership of the returned value - Return& takeOwnership(); + //! Determine whether class is the object's class. + /*! \param klass a class. + * \return true if the object is an instance of the given class. + */ + bool is_instance_of(Object klass) const; - //! Does Ruby own the returned value? - bool isOwner(); + //! Determine whether the Ruby VALUEs wrapped by this + //! object are the same object. Maps to Object::equal? + /*! \param other a Object. + */ + bool is_equal(const Object& other) const; - //! Specifies the returned value is a Ruby value - Return& setValue(); + //! Determine whether the Ruby VALUEs wrapped by this + //! object are equivalent. Maps to Object::eql? + /*! \param other a Object. + */ + bool is_eql(const Object& other) const; - //! Is the returned value a Ruby value? - bool isValue() const; + //! Set an instance variable. + /*! \param name the name of the instance variable to set (including + * the leading @ sign) + * \param value the value of the variable, which will be converted to + * a Ruby type if necessary. + */ + template + void iv_set(Identifier name, T const& value); - //! Tell the returned object to keep alive the receving object - Return& keepAlive(); + //! Get the value of an instance variable. + /*! \param name the name of the instance variable to get + * \return the value of the instance variable + */ + Object iv_get(Identifier name) const; - //! Is the returned value being kept alive? - bool isKeepAlive() const; + //! Get the value of an instance variable, but don't warn if it is + //unset. + /*! \param name the name of the instance variable to get + * \return the value of the instance variable + */ + Object attr_get(Identifier name) const; - private: - bool isKeepAlive_ = false; - bool isOwner_ = false; - bool isValue_ = false; - }; -} // Rice + //! Call the Ruby method specified by 'id' on object 'obj'. + /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to + * Ruby objects with to_ruby<>. To call methods expecting keyword arguments, + * use call_kw. + * + * E.g.: + * \code + * Rice::Object obj = x.call("foo", "one", 2); + * \endcode + * + * If a return type is specified, the return value will automatically be + * converted to that type as long as 'from_ruby' exists for that type. + * + * E.g.: + * \code + * float ret = x.call("foo", z, 42); + * \endcode + */ + template + Object call(Identifier id, Arg_Ts... args) const; + //! Call the Ruby method specified by 'id' on object 'obj'. + /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to + * Ruby objects with to_ruby<>. The final argument must be a Hash and will be treated + * as keyword arguments to the function. + * + * E.g.: + * \code + * Rice::Hash kw; + * kw[":argument"] = String("one") + * Rice::Object obj = x.call_kw("foo", kw); + * \endcode + * + * If a return type is specified, the return value will automatically be + * converted to that type as long as 'from_ruby' exists for that type. + * + * E.g.: + * \code + * float ret = x.call_kw("foo", kw); + * \endcode + */ + template + Object call_kw(Identifier id, Arg_Ts... args) const; -// --------- Return.ipp --------- -#include -#include + //! Vectorized call. + /*! Calls the method identified by id with the list of arguments + * identified by args. + * \param id the name of the method to call + * \param args the arguments to the method + * \return the return value of the method call + */ + Object vcall(Identifier id, Array args); -namespace Rice -{ - inline Return& Return::takeOwnership() - { - this->isOwner_ = true; - return *this; - } + //! Get a constant. + /*! \param name the name of the constant to get. + * \return the value of the constant. + */ + Object const_get(Identifier name) const; - inline bool Return::isOwner() - { - return this->isOwner_; - } + //! Determine whether a constant is defined. + /*! \param name the name of the constant to check. + * \return true if the constant is defined in this module or false + * otherwise. + */ + bool const_defined(Identifier name) const; - inline Return& Return::setValue() - { - this->isValue_ = true; - return *this; - } + //! Set a constant. + /*! \param name the name of the constant to set. + * \param value the value of the constant. + * \return *this + */ + inline Object const_set(Identifier name, Object value); - inline bool Return::isValue() const - { - return this->isValue_; - } + //! Set a constant if it not already set. + /*! \param name the name of the constant to set. + * \param value the value of the constant. + * \return *this + */ + inline Object const_set_maybe(Identifier name, Object value); - inline Return& Return::keepAlive() - { - this->isKeepAlive_ = true; - return *this; - } + //! Remove a constant. + /*! \param name the name of the constant to remove. + */ + void remove_const(Identifier name); - inline bool Return::isKeepAlive() const - { - return this->isKeepAlive_; - } -} // Rice + protected: + //! Set the encapsulated value. + void set_value(VALUE v); + + private: + volatile VALUE value_; + }; + std::ostream& operator<<(std::ostream& out, Object const& obj); -// ========= from_ruby.hpp ========= + bool operator==(Object const& lhs, Object const& rhs); + bool operator!=(Object const& lhs, Object const& rhs); + bool operator<(Object const& lhs, Object const& rhs); + bool operator>(Object const& lhs, Object const& rhs); + + extern Object const Nil; + extern Object const True; + extern Object const False; + extern Object const Undef; +} // namespace Rice -// --------- from_ruby_defn.hpp --------- -#ifndef Rice__detail__from_ruby_defn__hpp_ -#define Rice__detail__from_ruby_defn__hpp_ +// ========= Builtin_Object.hpp ========= -#include + +namespace Rice +{ + //! A smartpointer-like wrapper for Ruby builtin objects. + /*! A builtin object is one of Ruby's internal types, e.g. RArray or + * RString. Every builtin type structure has a corresponding integer + * type number (e.g T_ARRAY for RArray or T_STRING for RString). This + * class is a wrapper for those types of objects, primarily useful as a + * base class for other wrapper classes like Array and Hash. + */ + template + class Builtin_Object + : public Object + { + public: + //! Wrap an already allocated Ruby object. + /*! Checks to see if the object is an object of type Builtin_Type; a + * C++ exception is thrown if this is not the case. + * \param value the object to be wrapped. + */ + Builtin_Object(Object value); + + RObject& operator*() const; //!< Return a reference to obj_ + RObject* operator->() const; //!< Return a pointer to obj_ + RObject* get() const; //!< Return a pointer to obj_ + }; +} // namespace Rice -namespace Rice::detail +// ========= String.hpp ========= + + +namespace Rice { - //! Convert a Ruby object to C++. - /*! If the Ruby object can be converted to an immediate value, returns a - * copy of the Ruby object. If the Ruby object is holding a C++ - * object and the type specified is a pointer to that type, returns a - * pointer to that object. - * - * Conversions from ruby to a pointer type are automatically generated - * when a type is bound using Data_Type. If no conversion exists an - * exception is thrown. - * - * \param T the C++ type to which to convert. - * \param x the Ruby object to convert. - * \return a C++ representation of the Ruby object. + //! A Wraper for the ruby String class. + /*! This class provides a C++-style interface to ruby's String class and + * its associated rb_str_* functions. * * Example: * \code - * Object x = INT2NUM(42); - * std::cout << From_Ruby::convert(x); - * - * Data_Object foo(new Foo); - * std::cout << *From_Ruby(foo) << std::endl; + * String s(String::format("%s: %d", "foo", 42)); + * std::cout << s.length() << std::endl; * \endcode */ + class String + : public Builtin_Object + { + public: + //! Construct a new string. + String(); - template - class From_Ruby; -} + //! Wrap an existing string. + String(VALUE v); -#endif // Rice__detail__From_Ruby2_defn__hpp_ + //! Wrap an existing string. + String(Object v); -// --------- from_ruby.ipp --------- -#ifndef Rice__detail__from_ruby__ipp_ -#define Rice__detail__from_ruby__ipp_ + //! Construct a String from an Identifier. + String(Identifier id); -#include -#include + //! Construct a String from a null-terminated C string. + String(char const* s); -/* This file implements conversions from Ruby to native values fo fundamental types - such as bool, int, float, etc. It also includes conversions for chars and strings */ -namespace Rice::detail -{ - enum class Convertible: uint8_t - { - None = 0b000, - TypeCast = 0b010, - Exact = 0b110, - }; + //! Construct a String from an std::string. + String(std::string const& s); - inline Convertible operator&(Convertible left, Convertible right) - { - return static_cast(static_cast(left) & static_cast(right)); - } + //! Construct a String from an std::string_view. + String(std::string_view const& s); - inline Convertible operator|(Convertible left, Convertible right) - { - return static_cast(static_cast(left) | static_cast(right)); - } + //! Format a string using printf-style formatting. + template + static inline String format(char const* fmt, Arg_Ts&&...args); - inline bool operator<(Convertible left, Convertible right) - { - return static_cast(left) < static_cast(right); - } + //! Get the length of the String. + /*! \return the length of the string. + */ + size_t length() const; - // =========== short ============ - template<> - class From_Ruby - { - public: - From_Ruby() = default; + //! Get the character at the given index. + /*! \param index the desired index. + * \return the character at the given index. + */ + char operator[](ptrdiff_t index) const; - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Return a pointer to the beginning of the underlying C string. + char const* c_str() const; - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Return a copy of the string as an std::string. + std::string str() const; - short convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - return protect(rb_num2short_inline, value); - } - } - - private: - Arg* arg_ = nullptr; + //! Create an Identifier from the String. + /*! Calls rb_intern to create an ID. + * \return an Identifier holding the ID returned from rb_intern. + */ + Identifier intern() const; }; +} // namespace Rice - template<> - class From_Ruby + +// ========= Array.hpp ========= + +#include + +namespace Rice +{ + //! A wrapper for the ruby Array class. + /*! This class provides a C++-style interface to ruby's Array class and + * its associated rb_ary_* functions. + * Example: + * \code + * Array a; + * a.push(String("some string")); + * a.push(42); + * \endcode + */ + class Array + : public Builtin_Object { public: - From_Ruby() = default; + //! Construct a new array + Array(); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Wrap an existing array + /*! \param v a ruby object, which must be of type T_ARRAY. + */ + Array(Object v); - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Wrap an existing array + /*! \param v a ruby object, which must be of type T_ARRAY. + */ + Array(VALUE v); - short& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = protect(rb_num2short_inline, value); - return this->converted_; - } - } + //! Construct an array from a sequence. + /*! \param begin an iterator to the beginning of the sequence. + * \param end an iterator to the end of the sequence. + */ + template + Array(Iter_T begin, Iter_T end); - private: - Arg* arg_ = nullptr; - short converted_ = 0; - }; + //! Construct an Array from a C array. + /*! \param a a C array of type T and size n. + */ + template + Array(T const (&a)[n]); - template<> - class From_Ruby - { public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Return the size of the array. + long size() const; - short* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = protect(rb_num2short_inline, value); - return &this->converted_; - } - } + //! Return the element at the given index. + /*! \param index The index of the desired element. The index may be + * negative, to indicate an offset from the end of the array. If the + * index is out of bounds, this function has undefined behavior. + * \return the element at the given index. + */ + Object operator[](long index) const; private: - short converted_ = 0; - }; + //! A helper class so array[index]=value can work. + class Proxy; - // =========== int ============ - template<> - class From_Ruby - { public: - From_Ruby() = default; + //! Return a reference to the element at the given index. + /*! \param index The index of the desired element. The index may be + * negative, to indicate an offset from the end of the array. If the + * index is out of bounds, this function has undefined behavior. + * \return the element at the given index. + */ + Proxy operator[](long index); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Push an element onto the end of the array + /*! \param v an object to push onto the array. + * \return the object which was pushed onto the array. + */ + template + Object push(T const& obj); - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; + //! Pop an element from the end of the array + /*! \return the object which was popped from the array, or Qnil if + * the array was empty. + */ + Object pop(); - // This case is for Enums which are defined as Ruby classes. Some C++ apis - // will take a int parameter but really what we have is an Enum - case RUBY_T_DATA: - { - static ID id = protect(rb_intern, "to_int"); - if (protect(rb_respond_to, value, id)) - { - return Convertible::TypeCast; - } - else - { - return Convertible::None; - } + //! Unshift an element onto the beginning of the array + /*! \param v an object to unshift onto the array. + * \return the object which was unshifted onto the array. + */ + template + Object unshift(T const& obj); - break; - } - default: - return Convertible::None; - } - } + //! Shift an element from the beginning of the array + /*! \return the object which was shifted from the array. + */ + Object shift(); - int convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - // rb_num2long_inline will call to_int for RUBY_T_DATA objects - return (int)protect(rb_num2long_inline, value); - } - } - private: - Arg* arg_ = nullptr; + template + class Iterator; + + long position_of(long index) const; + + public: + //! An iterator. + typedef Iterator iterator; + + //! A const iterator. + typedef Iterator const_iterator; + + //! Return an iterator to the beginning of the array. + iterator begin(); + + //! Return a const iterator to the beginning of the array. + const_iterator begin() const; + + //! Return an iterator to the end of the array. + iterator end(); + + //! Return a const iterator to the end of the array. + const_iterator end() const; }; - template<> - class From_Ruby + //! A helper class so array[index]=value can work. + class Array::Proxy { public: - From_Ruby() = default; + //! Construct a new Proxy + Proxy(Array array, long index); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Implicit conversion to Object. + operator Object() const; - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Explicit conversion to VALUE. + VALUE value() const; - int& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = (int)protect(rb_num2long_inline, value); - return this->converted_; - } - } + //! Assignment operator. + template + Object operator=(T const& value); private: - Arg* arg_ = nullptr; - int converted_ = 0; + Array array_; + long index_; }; - template<> - class From_Ruby + //! A helper class for implementing iterators for a Array. + // TODO: This really should be a random-access iterator. + template + class Array::Iterator { public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + using iterator_category = std::forward_iterator_tag; + using value_type = Value_T; + using difference_type = long; + using pointer = Object*; + using reference = Value_T&; - int* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = (int)protect(rb_num2long_inline, value); - return &this->converted_; - } - } + Iterator(Array_Ptr_T array, long index); + + template + Iterator(Iterator const& rhs); + + template + Iterator& operator=(Iterator const& rhs); + + Iterator& operator++(); + Iterator operator++(int); + Value_T operator*(); + Object* operator->(); + + template + bool operator==(Iterator const& rhs) const; + + template + bool operator!=(Iterator const& rhs) const; + + Array_Ptr_T array() const; + long index() const; private: - int converted_; + Array_Ptr_T array_; + long index_; + + Object tmp_; }; +} // namespace Rice - // =========== long ============ - template<> - class From_Ruby + +// ========= Hash.hpp ========= + +#include +#include + +namespace Rice +{ + //! A wrapper for the ruby Hash class. + //! This class provides a C++-style interface to ruby's Hash class and + //! its associated rb_hash_* functions. + //! Example: + //! \code + //! Hash h; + //! h[42] = String("foo"); + //! h[10] = String("bar"); + //! std::cout << String(h[42]) << std::endl; + //! \endcode + class Hash: public Builtin_Object { public: - From_Ruby() = default; + //! Construct a new hash. + Hash(); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Wrap an existing hash. + /*! \param v the hash to wrap. + */ + Hash(Object v); - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Return the number of elements in the hash. + size_t size() const; - long convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - return protect(rb_num2long_inline, value); - } - } - private: - Arg* arg_ = nullptr; - }; + //! A helper class so hash[key]=value can work. + class Proxy; - template<> - class From_Ruby - { public: - From_Ruby() = default; + //! Get the value for the given key. + /*! \param key the key whose value should be retrieved from the hash. + * \return the value associated with the given key. + */ + template + Proxy const operator[](Key_T const& key) const; - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Get the value for the given key. + /*! \param key the key whose value should be retrieved from the hash. + * \return the value associated with the given key. + */ + template + Proxy operator[](Key_T const& key); - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Get the value for the given key + /*! The returned value is converted to the type given by Value_T. + * \param key the key whose value should be retrieved from the hash. + * \return the value associated with the given key, converted to C++ + * type Value_T. + */ + template + Value_T get(Key_T const& key); - long& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = protect(rb_num2long_inline, value); - return this->converted_; - } - } + //! A helper class for dereferencing iterators + class Entry; - private: - Arg* arg_ = nullptr; - long converted_ = 0; - }; + //! A helper class for implementing iterators for a Hash. + template + class Iterator; - template<> - class From_Ruby - { public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } - - long* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = protect(rb_num2long_inline, value); - return &this->converted_; - } - } + //! An iterator. + typedef Iterator iterator; - private: - long converted_ = 0; - }; + //! A const iterator. + typedef Iterator const_iterator; - // =========== long long ============ - template<> - class From_Ruby - { public: - From_Ruby() = default; + //! Return an iterator to the beginning of the hash. + iterator begin(); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Return a const iterator to the beginning of the hash. + const_iterator begin() const; - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - case RUBY_T_BIGNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Return an iterator to the end of the hash. + iterator end(); - long long convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - return protect(rb_num2ll_inline, value); - } - } - - private: - Arg* arg_ = nullptr; + //! Return a const to the end of the hash. + const_iterator end() const; }; - template<> - class From_Ruby + //! A helper class so hash[key]=value can work. + class Hash::Proxy { public: - From_Ruby() = default; + //! Construct a new Proxy. + Proxy(Hash* hash, Object key); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Implicit conversion to Object. + operator Object() const; - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - case RUBY_T_BIGNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Explicit conversion to VALUE. + VALUE value() const; - long long& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = protect(rb_num2ll_inline, value); - return this->converted_; - } - } + //! Assignment operator. + template + Object operator=(T const& value); private: - Arg* arg_ = nullptr; - long long converted_ = 0; + Hash* hash_; + Object key_; }; - template<> - class From_Ruby + //! A helper class for dereferencing iterators + /*! This class is intended to look similar to an std::pair. + */ + class Hash::Entry { public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - case RUBY_T_BIGNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Construct a new Entry. + Entry(Hash* hash, Object key); - long long* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = protect(rb_num2ll_inline, value); - return &this->converted_; - } - } + //! Copy constructor. + Entry(Entry const& entry); - private: - long long converted_ = 0; + Object const key; //!< The key + Object const& first; //!< An alias for the key + + Proxy value; //!< The value + Proxy& second; //!< An alias for the value + + Entry& operator=(Entry const& rhs); + + friend bool operator<(Entry const& lhs, Entry const& rhs); }; - // =========== unsigned short ============ - template<> - class From_Ruby + bool operator<(Hash::Entry const& lhs, Hash::Entry const& rhs); + + //! A helper class for implementing iterators for a Hash. + template + class Hash::Iterator { public: - From_Ruby() = default; + using iterator_category = std::input_iterator_tag; + using value_type = Value_T; + using difference_type = long; + using pointer = Object*; + using reference = Value_T&; - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Construct a new Iterator. + Iterator(Hash_Ptr_T hash); - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Construct a new Iterator with a given start-at index point + Iterator(Hash_Ptr_T hash, int start_at); - unsigned short convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - return protect(rb_num2ushort, value); - } - } - - private: - Arg* arg_ = nullptr; - }; + //! Construct an Iterator from another Iterator of a different const + //! qualification. + template + Iterator(Iterator_T const& iterator); - template<> - class From_Ruby - { - public: - From_Ruby() = default; + //! Preincrement operator. + Iterator& operator++(); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Postincrement operator. + Iterator operator++(int); - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Dereference operator. + Value_T operator*(); - unsigned short& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = protect(rb_num2ushort, value); - return this->converted_; - } - } + //! Dereference operator. + Value_T* operator->(); + + //! Equality operator. + bool operator==(Iterator const& rhs) const; + + //! Inequality operator. + bool operator!=(Iterator const& rhs) const; + + template + friend class Hash::Iterator; + + protected: + Object current_key(); + + Array hash_keys(); private: - Arg* arg_ = nullptr; - unsigned short converted_ = 0; + Hash_Ptr_T hash_; + long current_index_; + VALUE keys_; + + mutable typename std::remove_const::type tmp_; }; +} // namespace Rice - template<> - class From_Ruby + + +// ========= Symbol.hpp ========= + +#include + +namespace Rice +{ + //! A wrapper for ruby's Symbol class. + /*! Symbols are internal identifiers in ruby. They are singletons and + * can be thought of as frozen strings. They differ from an Identifier + * in that they are in fact real Objects, but they can be converted + * back and forth between Identifier and Symbol. + */ + class Symbol + : public Object { public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Wrap an existing symbol. + Symbol(VALUE v); - unsigned short* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = protect(rb_num2ushort, value); - return &this->converted_; - } - } + //! Wrap an existing symbol. + Symbol(Object v); - private: - unsigned short converted_ = 0; - }; + //! Construct a Symbol from an Identifier. + Symbol(Identifier id); - // =========== unsigned int ============ - template<> - class From_Ruby - { - public: - From_Ruby() = default; + //! Construct a Symbol from a null-terminated C string. + Symbol(char const* s = ""); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Construct a Symbol from an std::string. + Symbol(std::string const& s); - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Construct a Symbol from an std::string_view. + Symbol(std::string_view const& s); - unsigned int convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - return (unsigned int)protect(rb_num2ulong_inline, value); - } - } - - private: - Arg* arg_ = nullptr; + //! Return a string representation of the Symbol. + char const* c_str() const; + + //! Return a string representation of the Symbol. + std::string str() const; + + //! Return the Symbol as an Identifier. + Identifier to_id() const; }; +} // namespace Rice - template<> - class From_Ruby - { - public: - From_Ruby() = default; - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } - unsigned int& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = (unsigned int)protect(rb_num2ulong_inline, value); - return this->converted_; - } - } +// ========= Arg.hpp ========= - private: - Arg* arg_ = nullptr; - unsigned int converted_ = 0; - }; +#include - template<> - class From_Ruby +namespace Rice +{ + //! Helper for defining default arguments of a method + /*! This class exposes the ability to define the default values of a + * wrapped method. Inspired by how Boost.Python handles keyword and + * default arguments, the syntax is simple: + * + * \code + * define_method( + * "method", + * &method, + * Arg("arg1"), Arg("arg2") = 3, Arg("arg3") = true + * ); + * \endcode + * + * which means "for method &method, it takes 3 arguments + * [arg1, arg2, arg3]. Of these arguments, arg2's default is 3 + * and arg3's default is true. + * + * It may be required to explicitly cast the type of the default + * value to prevent compilation errors. + */ + class Arg { public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Initialize a new Arg with the name of the argument + /*! We require the name of the argument because 1) it makes code + * easier to read and 2) hopefully Ruby gets keyword arguments + * in the future and this means Rice will be ready for it. + */ + Arg(std::string name); - unsigned int* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = (unsigned int)protect(rb_num2ulong_inline, value); - return &this->converted_; - } - } + //! Set the default value for this Arg + /*! Set the default value for this argument. + * If this isn't called on this Arg, then this + * Arg is required in the method call. + * + * \param val the value to store as default + */ + template + Arg& operator=(Arg_Type val); - private: - unsigned int converted_ = 0; - }; + //! Check if this Arg has a default value associated with it + bool hasDefaultValue() const; - // =========== unsigned long ============ - template<> - class From_Ruby - { - public: - From_Ruby() = default; + //! Return a reference to the default value associated with this Arg + /*! \return the type saved to this Arg + */ + template + Arg_Type& defaultValue(); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + //! Tell the receiving object to keep this argument alive + //! until the receiving object is freed. + Arg& keepAlive(); + + //! Returns if the argument should be kept alive + bool isKeepAlive() const; - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Specifies if the argument should be treated as a value + Arg& setValue(); - unsigned long convert(VALUE value) - { - if (this->arg_ && this->arg_->isValue()) - { - return (unsigned long)value; - } - else if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - return protect(rb_num2ulong_inline, value); - } - } - - private: - Arg* arg_ = nullptr; - }; + //! Returns if the argument should be treated as a value + bool isValue() const; - template<> - class From_Ruby - { - public: - From_Ruby() = default; + //! Specifies C++ will take ownership of this value and Ruby shoudl not fee it + Arg& transferOwnership(); + bool isTransfer(); - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } - - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } - - unsigned long& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = protect(rb_num2ulong_inline, value); - return this->converted_; - } - } + public: + const std::string name; + int32_t position = -1; private: - Arg* arg_ = nullptr; - unsigned long converted_ = 0; + //! Our saved default value + std::any defaultValue_; + bool isValue_ = false; + bool isKeepAlive_ = false; + bool isTransfer_ = false; }; +} // Rice - template<> - class From_Ruby + +// ========= Arg.ipp ========= +namespace Rice +{ + inline Arg::Arg(std::string name) : name(name) { - public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + } - unsigned long* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = protect(rb_num2ulong_inline, value); - return &this->converted_; - } - } + template + inline Arg& Arg::operator=(Arg_Type val) + { + this->defaultValue_ = val; + return *this; + } - private: - unsigned long converted_ = 0; - }; + //! Check if this Arg has a default value associated with it + inline bool Arg::hasDefaultValue() const + { + return this->defaultValue_.has_value(); + } - // =========== unsigned long long ============ - template<> - class From_Ruby + //! Return a reference to the default value associated with this Arg + /*! \return the type saved to this Arg + */ + template + inline Arg_Type& Arg::defaultValue() { - public: - From_Ruby() = default; + return std::any_cast(this->defaultValue_); + } - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + inline Arg& Arg::keepAlive() + { + this->isKeepAlive_ = true; + return *this; + } - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + inline bool Arg::isKeepAlive() const + { + return this->isKeepAlive_; + } - unsigned long long convert(VALUE value) - { - if (this->arg_ && this->arg_->isValue()) - { - return value; - } - else if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - return protect(rb_num2ull, value); - } - } - - private: - Arg* arg_ = nullptr; - }; + inline Arg& Arg::setValue() + { + isValue_ = true; + return *this; + } - template<> - class From_Ruby + inline bool Arg::isValue() const { - public: - From_Ruby() = default; + return isValue_; + } - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + inline Arg& Arg::transferOwnership() + { + this->isTransfer_ = true; + return *this; + } - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + inline bool Arg::isTransfer() + { + return this->isTransfer_; + } - unsigned long long& convert(VALUE value) - { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) - { - return this->arg_->defaultValue(); - } - else - { - this->converted_ = protect(rb_num2ull, value); - return this->converted_; - } - } - private: - Arg* arg_ = nullptr; - unsigned long long converted_ = 0; - }; +} // Rice +// ========= Return.hpp ========= - template<> - class From_Ruby +namespace Rice +{ + //! Helper for defining Return argument of a method + + class Return { public: - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) - { - case RUBY_T_FIXNUM: - return Convertible::Exact; - break; - default: - return Convertible::None; - } - } + //! Specifies Ruby should take ownership of the returned value + Return& takeOwnership(); - unsigned long long* convert(VALUE value) - { - if (value == Qnil) - { - return nullptr; - } - else - { - this->converted_ = protect(rb_num2ull, value); - return &this->converted_; - } - } + //! Does Ruby own the returned value? + bool isOwner(); + + //! Specifies the returned value is a Ruby value + Return& setValue(); + + //! Is the returned value a Ruby value? + bool isValue() const; + + //! Tell the returned object to keep alive the receving object + Return& keepAlive(); + + //! Is the returned value being kept alive? + bool isKeepAlive() const; private: - unsigned long long converted_ = 0; + bool isKeepAlive_ = false; + bool isOwner_ = false; + bool isValue_ = false; }; +} // Rice - // =========== bool ============ - template<> - class From_Ruby + +// ========= Return.ipp ========= +#include +#include + +namespace Rice +{ + inline Return& Return::takeOwnership() { - public: - From_Ruby() = default; + this->isOwner_ = true; + return *this; + } - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } + inline bool Return::isOwner() + { + return this->isOwner_; + } - Convertible is_convertible(VALUE value) + inline Return& Return::setValue() + { + this->isValue_ = true; + return *this; + } + + inline bool Return::isValue() const + { + return this->isValue_; + } + + inline Return& Return::keepAlive() + { + this->isKeepAlive_ = true; + return *this; + } + + inline bool Return::isKeepAlive() const + { + return this->isKeepAlive_; + } +} // Rice + +// ========= to_ruby.ipp ========= + +namespace Rice +{ + namespace detail + { + template<> + class To_Ruby { - switch (rb_type(value)) + public: + VALUE convert(void const*) { - case RUBY_T_TRUE: - return Convertible::Exact; - break; - case RUBY_T_FALSE: - return Convertible::Exact; - break; - case RUBY_T_NIL: - return Convertible::TypeCast; - break; - default: - return Convertible::None; + throw std::runtime_error("Converting from void pointer is not implemented"); + return Qnil; } - } + }; - bool convert(VALUE value) + template<> + class To_Ruby { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + public: + VALUE convert(std::nullptr_t const) { - return this->arg_->defaultValue(); + return Qnil; } - else + }; + + template<> + class To_Ruby + { + public: + VALUE convert(short const& x) { - return RTEST(value); +#ifdef rb_int2num_inline + return protect(rb_int2num_inline, (int)x); +#else + return RB_INT2NUM(x); +#endif } - } - - private: - Arg* arg_ = nullptr; - }; - - template<> - class From_Ruby - { - public: - From_Ruby() = default; + }; - explicit From_Ruby(Arg* arg) : arg_(arg) + template<> + class To_Ruby { - } + public: + VALUE convert(short const& x) + { +#ifdef rb_int2num_inline + return protect(rb_int2num_inline, (int)x); +#else + return RB_INT2NUM(x); +#endif + } + }; - Convertible is_convertible(VALUE value) + template<> + class To_Ruby { - switch (rb_type(value)) + public: + VALUE convert(int const& x) { - case RUBY_T_TRUE: - return Convertible::Exact; - break; - case RUBY_T_FALSE: - return Convertible::Exact; - break; - case RUBY_T_NIL: - return Convertible::TypeCast; - break; - default: - return Convertible::None; +#ifdef rb_int2num_inline + return protect(rb_int2num_inline, (int)x); +#else + return RB_INT2NUM(x); +#endif } - } + }; - bool& convert(VALUE value) + template<> + class To_Ruby { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + public: + VALUE convert(int const& x) { - return this->arg_->defaultValue(); +#ifdef rb_int2num_inline + return protect(rb_int2num_inline, (int)x); +#else + return RB_INT2NUM(x); +#endif } - else + }; + + template<> + class To_Ruby + { + public: + VALUE convert(long const& x) { - this->converted_ = RTEST(value); - return this->converted_; + return protect(rb_long2num_inline, x); } - } - - private: - Arg* arg_ = nullptr; - bool converted_ = false; - }; + }; - template<> - class From_Ruby - { - public: - Convertible is_convertible(VALUE value) + template<> + class To_Ruby { - switch (rb_type(value)) + public: + VALUE convert(long const& x) { - case RUBY_T_TRUE: - return Convertible::Exact; - break; - case RUBY_T_FALSE: - return Convertible::Exact; - break; - case RUBY_T_NIL: - return Convertible::TypeCast; - break; - default: - return Convertible::None; + return protect(rb_long2num_inline, x); } - } + }; - bool* convert(VALUE value) + template<> + class To_Ruby { - if (value == Qnil) + public: + VALUE convert(long long const& x) { - return nullptr; + return protect(rb_ll2inum, x); } - else + }; + + template<> + class To_Ruby + { + public: + VALUE convert(long long const& x) { - this->converted_ = RTEST(value); - return &this->converted_; + return protect(rb_ll2inum, x); } - } - - private: - bool converted_ = false; - }; + }; - // =========== char ============ - template - inline T charFromRuby(VALUE value) - { - switch (rb_type(value)) + template<> + class To_Ruby { - case T_STRING: + public: + VALUE convert(unsigned short const& x) { - if (RSTRING_LEN(value) == 1) - { - return RSTRING_PTR(value)[0]; - } - else - { - throw std::invalid_argument("from_ruby: string must have length 1"); - } - break; +#ifdef rb_int2num_inline + return protect(rb_uint2num_inline, (unsigned int)x); +#else + return RB_UINT2NUM(x); +#endif } - case T_FIXNUM: + }; + + template<> + class To_Ruby + { + public: + VALUE convert(unsigned short const& x) { - return From_Ruby().convert(value) & 0xff; - break; +#ifdef rb_int2num_inline + return protect(rb_uint2num_inline, (unsigned int)x); +#else + return RB_UINT2NUM(x); +#endif } - default: + }; + + template<> + class To_Ruby + { + public: + VALUE convert(unsigned int const& x) { - throw Exception(rb_eTypeError, "wrong argument type %s (expected % s)", - detail::protect(rb_obj_classname, value), "char type"); +#ifdef rb_int2num_inline + return protect(rb_uint2num_inline, (unsigned int)x); +#else + return RB_UINT2NUM(x); +#endif } - } - } + }; - template<> - class From_Ruby - { - public: - From_Ruby() = default; - - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } - - Convertible is_convertible(VALUE value) + template<> + class To_Ruby { - switch (rb_type(value)) + public: + VALUE convert(unsigned int const& x) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - break; - default: - return Convertible::None; +#ifdef rb_int2num_inline + return protect(rb_uint2num_inline, (unsigned int)x); +#else + return RB_UINT2NUM(x); +#endif } - } + }; - char convert(VALUE value) + template<> + class To_Ruby { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + public: + To_Ruby() = default; + + explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) { - return this->arg_->defaultValue(); } - else + + VALUE convert(unsigned long const& x) { - return charFromRuby(value); + if (this->returnInfo_ && this->returnInfo_->isValue()) + { + return x; + } + else + { + return protect(rb_ulong2num_inline, x); + } } - } - - private: - Arg* arg_ = nullptr; - }; - template<> - class From_Ruby - { - public: - From_Ruby() = default; + private: + Return* returnInfo_ = nullptr; + }; - explicit From_Ruby(Arg* arg) : arg_(arg) + template<> + class To_Ruby { - } + public: + To_Ruby() = default; - Convertible is_convertible(VALUE value) - { - switch (rb_type(value)) + explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - break; - default: - return Convertible::None; } - } - char& convert(VALUE value) + VALUE convert(unsigned long const& x) + { + if (this->returnInfo_ && this->returnInfo_->isValue()) + { + return x; + } + else + { + return protect(rb_ulong2num_inline, x); + } + } + + private: + Return* returnInfo_ = nullptr; + }; + + template<> + class To_Ruby { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + public: + To_Ruby() = default; + + explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) { - return this->arg_->defaultValue(); } - else + + VALUE convert(unsigned long long const& x) { - this->converted_ = charFromRuby(value); - return this->converted_; + if (this->returnInfo_ && this->returnInfo_->isValue()) + { + return x; + } + else + { + return protect(rb_ull2inum, (unsigned long long)x); + } } - } - private: - Arg* arg_ = nullptr; - char converted_ = 0; - }; + private: + Return* returnInfo_ = nullptr; + }; - template<> - class From_Ruby - { - public: - Convertible is_convertible(VALUE value) + template<> + class To_Ruby { - switch (rb_type(value)) + public: + To_Ruby() = default; + + explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - break; - default: - return Convertible::None; } - } - char* convert(VALUE value) - { - if (value == Qnil) + VALUE convert(unsigned long long const& x) { - return nullptr; + if (this->returnInfo_ && this->returnInfo_->isValue()) + { + return x; + } + else + { + return protect(rb_ull2inum, (unsigned long long)x); + } } - else + + private: + Return* returnInfo_ = nullptr; + }; + + template<> + class To_Ruby + { + public: + VALUE convert(float const& x) { - detail::protect(rb_check_type, value, (int)T_STRING); - return RSTRING_PTR(value); + return protect(rb_float_new, (double)x); } - } - }; - - // This is mostly for testing. NativeFunction removes const before calling From_Ruby - template<> - class From_Ruby - { - public: - Convertible is_convertible(VALUE value) + }; + + template<> + class To_Ruby { - switch (rb_type(value)) + public: + VALUE convert(float const& x) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - break; - default: - return Convertible::None; + return protect(rb_float_new, (double)x); } - } + }; - char const* convert(VALUE value) + template<> + class To_Ruby { - if (value == Qnil) + public: + VALUE convert(double const& x) { - return nullptr; + return protect(rb_float_new, x); } - else + }; + + template<> + class To_Ruby + { + public: + VALUE convert(double const& x) { - detail::protect(rb_check_type, value, (int)T_STRING); - return RSTRING_PTR(value); + return protect(rb_float_new, x); } - } - }; - - // =========== unsigned char ============ - template<> - class From_Ruby - { - public: - From_Ruby() = default; + }; - explicit From_Ruby(Arg* arg) : arg_(arg) + template<> + class To_Ruby { - } + public: + VALUE convert(bool const& x) + { + return x ? Qtrue : Qfalse; + } + }; - Convertible is_convertible(VALUE value) + template<> + class To_Ruby { - switch (rb_type(value)) + public: + VALUE convert(bool const& x) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - break; - default: - return Convertible::None; + return x ? Qtrue : Qfalse; } - } + }; - unsigned char convert(VALUE value) + template<> + class To_Ruby { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + public: + VALUE convert(char const& x) { - return this->arg_->defaultValue(); + return To_Ruby().convert(x); } - else + }; + + template<> + class To_Ruby + { + public: + VALUE convert(char const& x) { - return charFromRuby(value); + return To_Ruby().convert(x); } - } + }; - private: - Arg* arg_ = nullptr; - }; + template<> + class To_Ruby + { + public: + VALUE convert(unsigned char const& x) + { + return To_Ruby().convert(x); + } + }; + + template<> + class To_Ruby + { + public: + VALUE convert(unsigned char const& x) + { + return To_Ruby().convert(x); + } + }; + + template<> + class To_Ruby + { + public: + VALUE convert(signed char const& x) + { + return To_Ruby().convert(x); + } + }; + + template<> + class To_Ruby + { + public: + VALUE convert(signed char const& x) + { + return To_Ruby().convert(x); + } + }; + + template<> + class To_Ruby + { + public: + VALUE convert(char const* x) + { + if (strlen(x) > 0 && x[0] == ':') + { + size_t symbolLength = strlen(x) - 1; + char* symbol = new char[symbolLength]; + strncpy(symbol, x + 1, symbolLength); + ID id = protect(rb_intern2, symbol, (long)symbolLength); + delete[] symbol; + return protect(rb_id2sym, id); + } + else + { + return protect(rb_str_new2, x); + } + } + }; + + template + class To_Ruby + { + public: + VALUE convert(char const x[]) + { + if (N > 0 && x[0] == ':') + { + // N count includes a NULL character at the end of the string + constexpr size_t symbolLength = N - 1; + char symbol[symbolLength]; + strncpy(symbol, x + 1, symbolLength); + ID id = protect(rb_intern, symbol); + return protect(rb_id2sym, id); + } + else + { + return protect(rb_str_new2, x); + } + } + }; + } +} +// ========= from_ruby.ipp ========= + +#include +#include + +/* This file implements conversions from Ruby to native values fo fundamental types + such as bool, int, float, etc. It also includes conversions for chars and strings */ +namespace Rice::detail +{ + inline Convertible operator&(Convertible left, Convertible right) + { + return static_cast(static_cast(left) & static_cast(right)); + } + + inline Convertible operator|(Convertible left, Convertible right) + { + return static_cast(static_cast(left) | static_cast(right)); + } + + inline bool operator<(Convertible left, Convertible right) + { + return static_cast(left) < static_cast(right); + } + // =========== short ============ template<> - class From_Ruby + class From_Ruby { public: From_Ruby() = default; @@ -2277,38 +2260,32 @@ namespace Rice::detail { switch (rb_type(value)) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - break; - default: - return Convertible::None; + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } } - unsigned char& convert(VALUE value) + short convert(VALUE value) { if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return this->arg_->defaultValue(); + return this->arg_->defaultValue(); } else { - this->converted_ = charFromRuby(value); - return this->converted_; + return protect(rb_num2short_inline, value); } } - + private: Arg* arg_ = nullptr; - unsigned char converted_ = 0; }; template<> - class From_Ruby + class From_Ruby { public: From_Ruby() = default; @@ -2321,81 +2298,68 @@ namespace Rice::detail { switch (rb_type(value)) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - break; - default: - return Convertible::None; + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } } - unsigned char* convert(VALUE value) + short& convert(VALUE value) { - if (value == Qnil) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return nullptr; + return this->arg_->defaultValue(); } else { - detail::protect(rb_check_type, value, (int)T_STRING); - return reinterpret_cast(RSTRING_PTR(value)); + this->converted_ = protect(rb_num2short_inline, value); + return this->converted_; } } private: Arg* arg_ = nullptr; + short converted_ = 0; }; - // =========== signed char ============ template<> - class From_Ruby + class From_Ruby { public: - From_Ruby() = default; - - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } - Convertible is_convertible(VALUE value) { switch (rb_type(value)) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - // This is for C++ chars which are converted to Ruby integers case RUBY_T_FIXNUM: - return Convertible::TypeCast; + return Convertible::Exact; break; default: return Convertible::None; } } - signed char convert(VALUE value) + short* convert(VALUE value) { - if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + if (value == Qnil) { - return this->arg_->defaultValue(); + return nullptr; } else { - return charFromRuby(value); + this->converted_ = protect(rb_num2short_inline, value); + return &this->converted_; } } - + private: - Arg* arg_ = nullptr; + short converted_ = 0; }; - // =========== double ============ + // =========== int ============ template<> - class From_Ruby + class From_Ruby { public: From_Ruby() = default; @@ -2408,23 +2372,41 @@ namespace Rice::detail { switch (rb_type(value)) { - case RUBY_T_FLOAT: + case RUBY_T_FIXNUM: return Convertible::Exact; break; + + // This case is for Enums which are defined as Ruby classes. Some C++ apis + // will take a int parameter but really what we have is an Enum + case RUBY_T_DATA: + { + static ID id = protect(rb_intern, "to_int"); + if (protect(rb_respond_to, value, id)) + { + return Convertible::TypeCast; + } + else + { + return Convertible::None; + } + + break; + } default: return Convertible::None; } } - double convert(VALUE value) + int convert(VALUE value) { if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return this->arg_->defaultValue(); + return this->arg_->defaultValue(); } else { - return protect(rb_num2dbl, value); + // rb_num2long_inline will call to_int for RUBY_T_DATA objects + return (int)protect(rb_num2long_inline, value); } } @@ -2433,7 +2415,7 @@ namespace Rice::detail }; template<> - class From_Ruby + class From_Ruby { public: From_Ruby() = default; @@ -2446,7 +2428,7 @@ namespace Rice::detail { switch (rb_type(value)) { - case RUBY_T_FLOAT: + case RUBY_T_FIXNUM: return Convertible::Exact; break; default: @@ -2454,33 +2436,33 @@ namespace Rice::detail } } - double& convert(VALUE value) + int& convert(VALUE value) { if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return this->arg_->defaultValue(); + return this->arg_->defaultValue(); } else { - this->converted_ = protect(rb_num2dbl, value); + this->converted_ = (int)protect(rb_num2long_inline, value); return this->converted_; } } private: Arg* arg_ = nullptr; - double converted_; + int converted_ = 0; }; template<> - class From_Ruby + class From_Ruby { public: Convertible is_convertible(VALUE value) { switch (rb_type(value)) { - case RUBY_T_FLOAT: + case RUBY_T_FIXNUM: return Convertible::Exact; break; default: @@ -2488,7 +2470,7 @@ namespace Rice::detail } } - double* convert(VALUE value) + int* convert(VALUE value) { if (value == Qnil) { @@ -2496,18 +2478,18 @@ namespace Rice::detail } else { - this->converted_ = protect(rb_num2dbl, value); + this->converted_ = (int)protect(rb_num2long_inline, value); return &this->converted_; } } private: - double converted_; + int converted_; }; - // =========== float ============ + // =========== long ============ template<> - class From_Ruby + class From_Ruby { public: From_Ruby() = default; @@ -2520,27 +2502,23 @@ namespace Rice::detail { switch (rb_type(value)) { - case RUBY_T_FLOAT: + case RUBY_T_FIXNUM: return Convertible::Exact; break; - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - case RUBY_T_BIGNUM: - return Convertible::TypeCast; default: return Convertible::None; } } - float convert(VALUE value) + long convert(VALUE value) { if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return this->arg_->defaultValue(); + return this->arg_->defaultValue(); } else { - return (float)protect(rb_num2dbl, value); + return protect(rb_num2long_inline, value); } } @@ -2549,7 +2527,7 @@ namespace Rice::detail }; template<> - class From_Ruby + class From_Ruby { public: From_Ruby() = default; @@ -2562,57 +2540,49 @@ namespace Rice::detail { switch (rb_type(value)) { - case RUBY_T_FLOAT: + case RUBY_T_FIXNUM: return Convertible::Exact; break; - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - case RUBY_T_BIGNUM: - return Convertible::TypeCast; default: return Convertible::None; } } - float& convert(VALUE value) + long& convert(VALUE value) { if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return this->arg_->defaultValue(); + return this->arg_->defaultValue(); } else { - this->converted_ = (float)protect(rb_num2dbl, value); + this->converted_ = protect(rb_num2long_inline, value); return this->converted_; } } private: Arg* arg_ = nullptr; - float converted_; + long converted_ = 0; }; template<> - class From_Ruby + class From_Ruby { public: Convertible is_convertible(VALUE value) { switch (rb_type(value)) { - case RUBY_T_FLOAT: + case RUBY_T_FIXNUM: return Convertible::Exact; break; - case RUBY_T_FIXNUM: - return Convertible::TypeCast; - case RUBY_T_BIGNUM: - return Convertible::TypeCast; default: return Convertible::None; } } - float* convert(VALUE value) + long* convert(VALUE value) { if (value == Qnil) { @@ -2620,1077 +2590,1375 @@ namespace Rice::detail } else { - this->converted_ = (float)protect(rb_num2dbl, value); + this->converted_ = protect(rb_num2long_inline, value); return &this->converted_; } } private: - float converted_; + long converted_ = 0; }; -} -#endif // Rice__detail__from_ruby__ipp_ - - - -// ========= to_ruby.hpp ========= - - -// --------- to_ruby_defn.hpp --------- -#ifndef Rice__detail__to_ruby_defn__hpp_ -#define Rice__detail__to_ruby_defn__hpp_ - -namespace Rice -{ - namespace detail + // =========== long long ============ + template<> + class From_Ruby { - //! Convert a C++ object to Ruby. - /*! If x is a pointer, wraps the pointee as a Ruby object. If x is an - * Object, returns x. - * - * If no conversion exists a compile-time error is generated. - * - * \param x the object to convert. - * \return a Ruby representation of the C++ object. - * - * Example: - * \code - * rb_p(to_ruby(42)); - * - * Foo * p_foo = new Foo(); - * rb_p(to_ruby(p_foo)); - * \endcode - */ - template - class To_Ruby; - - // Helper template function that let's users avoid having to specify the template type - its deduced - template - VALUE to_ruby(T&& x) - { - using Unqualified_T = remove_cv_recursive_t; - return To_Ruby().convert(std::forward(x)); - } + public: + From_Ruby() = default; - // Helper template function that let's users avoid having to specify the template type - its deduced - template - VALUE to_ruby(T* x) + explicit From_Ruby(Arg* arg) : arg_(arg) { - using Unqualified_T = remove_cv_recursive_t; - return To_Ruby().convert(x); } - } // detail -} // Rice - -#endif // Rice__detail__to_ruby_defn__hpp_ -// --------- to_ruby.ipp --------- - -namespace Rice -{ - namespace detail - { - template<> - class To_Ruby + Convertible is_convertible(VALUE value) { - public: - VALUE convert(void const*) + switch (rb_type(value)) { - throw std::runtime_error("Converting from void pointer is not implemented"); - return Qnil; + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + case RUBY_T_BIGNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template<> - class To_Ruby + long long convert(VALUE value) { - public: - VALUE convert(std::nullptr_t const) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return Qnil; + return this->arg_->defaultValue(); } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(short const& x) + else { -#ifdef rb_int2num_inline - return protect(rb_int2num_inline, (int)x); -#else - return RB_INT2NUM(x); -#endif + return protect(rb_num2ll_inline, value); } - }; + } + + private: + Arg* arg_ = nullptr; + }; - template<> - class To_Ruby - { - public: - VALUE convert(short const& x) - { -#ifdef rb_int2num_inline - return protect(rb_int2num_inline, (int)x); -#else - return RB_INT2NUM(x); -#endif - } - }; + template<> + class From_Ruby + { + public: + From_Ruby() = default; - template<> - class To_Ruby + explicit From_Ruby(Arg* arg) : arg_(arg) { - public: - VALUE convert(int const& x) - { -#ifdef rb_int2num_inline - return protect(rb_int2num_inline, (int)x); -#else - return RB_INT2NUM(x); -#endif - } - }; + } - template<> - class To_Ruby + Convertible is_convertible(VALUE value) { - public: - VALUE convert(int const& x) + switch (rb_type(value)) { -#ifdef rb_int2num_inline - return protect(rb_int2num_inline, (int)x); -#else - return RB_INT2NUM(x); -#endif + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + case RUBY_T_BIGNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template<> - class To_Ruby + long long& convert(VALUE value) { - public: - VALUE convert(long const& x) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return protect(rb_long2num_inline, x); + return this->arg_->defaultValue(); } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(long const& x) + else { - return protect(rb_long2num_inline, x); + this->converted_ = protect(rb_num2ll_inline, value); + return this->converted_; } - }; + } - template<> - class To_Ruby + private: + Arg* arg_ = nullptr; + long long converted_ = 0; + }; + + template<> + class From_Ruby + { + public: + Convertible is_convertible(VALUE value) { - public: - VALUE convert(long long const& x) + switch (rb_type(value)) { - return protect(rb_ll2inum, x); + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + case RUBY_T_BIGNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template<> - class To_Ruby + long long* convert(VALUE value) { - public: - VALUE convert(long long const& x) + if (value == Qnil) { - return protect(rb_ll2inum, x); + return nullptr; } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(unsigned short const& x) + else { -#ifdef rb_int2num_inline - return protect(rb_uint2num_inline, (unsigned int)x); -#else - return RB_UINT2NUM(x); -#endif + this->converted_ = protect(rb_num2ll_inline, value); + return &this->converted_; } - }; + } - template<> - class To_Ruby - { - public: - VALUE convert(unsigned short const& x) - { -#ifdef rb_int2num_inline - return protect(rb_uint2num_inline, (unsigned int)x); -#else - return RB_UINT2NUM(x); -#endif - } - }; + private: + long long converted_ = 0; + }; - template<> - class To_Ruby + // =========== unsigned short ============ + template<> + class From_Ruby + { + public: + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) { - public: - VALUE convert(unsigned int const& x) - { -#ifdef rb_int2num_inline - return protect(rb_uint2num_inline, (unsigned int)x); -#else - return RB_UINT2NUM(x); -#endif - } - }; + } - template<> - class To_Ruby + Convertible is_convertible(VALUE value) { - public: - VALUE convert(unsigned int const& x) + switch (rb_type(value)) { -#ifdef rb_int2num_inline - return protect(rb_uint2num_inline, (unsigned int)x); -#else - return RB_UINT2NUM(x); -#endif + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template<> - class To_Ruby + unsigned short convert(VALUE value) { - public: - To_Ruby() = default; - - explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { + return this->arg_->defaultValue(); } - - VALUE convert(unsigned long const& x) + else { - if (this->returnInfo_ && this->returnInfo_->isValue()) - { - return x; - } - else - { - return protect(rb_ulong2num_inline, x); - } + return protect(rb_num2ushort, value); } + } + + private: + Arg* arg_ = nullptr; + }; - private: - Return* returnInfo_ = nullptr; - }; + template<> + class From_Ruby + { + public: + From_Ruby() = default; - template<> - class To_Ruby + explicit From_Ruby(Arg* arg) : arg_(arg) { - public: - To_Ruby() = default; + } - explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) { + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } + } - VALUE convert(unsigned long const& x) + unsigned short& convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - if (this->returnInfo_ && this->returnInfo_->isValue()) - { - return x; - } - else - { - return protect(rb_ulong2num_inline, x); - } + return this->arg_->defaultValue(); + } + else + { + this->converted_ = protect(rb_num2ushort, value); + return this->converted_; } + } - private: - Return* returnInfo_ = nullptr; - }; + private: + Arg* arg_ = nullptr; + unsigned short converted_ = 0; + }; - template<> - class To_Ruby + template<> + class From_Ruby + { + public: + Convertible is_convertible(VALUE value) { - public: - To_Ruby() = default; - - explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) + switch (rb_type(value)) { + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } + } - VALUE convert(unsigned long long const& x) + unsigned short* convert(VALUE value) + { + if (value == Qnil) { - if (this->returnInfo_ && this->returnInfo_->isValue()) - { - return x; - } - else - { - return protect(rb_ull2inum, (unsigned long long)x); - } + return nullptr; + } + else + { + this->converted_ = protect(rb_num2ushort, value); + return &this->converted_; } + } - private: - Return* returnInfo_ = nullptr; - }; + private: + unsigned short converted_ = 0; + }; - template<> - class To_Ruby + // =========== unsigned int ============ + template<> + class From_Ruby + { + public: + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) { - public: - To_Ruby() = default; + } - explicit To_Ruby(Return* returnInfo) : returnInfo_(returnInfo) + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) { + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } + } - VALUE convert(unsigned long long const& x) + unsigned int convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - if (this->returnInfo_ && this->returnInfo_->isValue()) - { - return x; - } - else - { - return protect(rb_ull2inum, (unsigned long long)x); - } + return this->arg_->defaultValue(); + } + else + { + return (unsigned int)protect(rb_num2ulong_inline, value); } + } + + private: + Arg* arg_ = nullptr; + }; - private: - Return* returnInfo_ = nullptr; - }; + template<> + class From_Ruby + { + public: + From_Ruby() = default; - template<> - class To_Ruby + explicit From_Ruby(Arg* arg) : arg_(arg) { - public: - VALUE convert(float const& x) - { - return protect(rb_float_new, (double)x); - } - }; + } - template<> - class To_Ruby + Convertible is_convertible(VALUE value) { - public: - VALUE convert(float const& x) + switch (rb_type(value)) { - return protect(rb_float_new, (double)x); + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template<> - class To_Ruby + unsigned int& convert(VALUE value) { - public: - VALUE convert(double const& x) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return protect(rb_float_new, x); + return this->arg_->defaultValue(); } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(double const& x) + else { - return protect(rb_float_new, x); + this->converted_ = (unsigned int)protect(rb_num2ulong_inline, value); + return this->converted_; } - }; + } - template<> - class To_Ruby + private: + Arg* arg_ = nullptr; + unsigned int converted_ = 0; + }; + + template<> + class From_Ruby + { + public: + Convertible is_convertible(VALUE value) { - public: - VALUE convert(bool const& x) + switch (rb_type(value)) { - return x ? Qtrue : Qfalse; + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template<> - class To_Ruby + unsigned int* convert(VALUE value) { - public: - VALUE convert(bool const& x) + if (value == Qnil) { - return x ? Qtrue : Qfalse; + return nullptr; } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(char const& x) + else { - return To_Ruby().convert(x); + this->converted_ = (unsigned int)protect(rb_num2ulong_inline, value); + return &this->converted_; } - }; + } - template<> - class To_Ruby + private: + unsigned int converted_ = 0; + }; + + // =========== unsigned long ============ + template<> + class From_Ruby + { + public: + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) { - public: - VALUE convert(char const& x) - { - return To_Ruby().convert(x); - } - }; + } - template<> - class To_Ruby + Convertible is_convertible(VALUE value) { - public: - VALUE convert(unsigned char const& x) + switch (rb_type(value)) { - return To_Ruby().convert(x); + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template<> - class To_Ruby + unsigned long convert(VALUE value) { - public: - VALUE convert(unsigned char const& x) + if (this->arg_ && this->arg_->isValue()) { - return To_Ruby().convert(x); + return (unsigned long)value; } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(signed char const& x) + else if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - return To_Ruby().convert(x); + return this->arg_->defaultValue(); } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(signed char const& x) + else { - return To_Ruby().convert(x); + return protect(rb_num2ulong_inline, value); } - }; + } + + private: + Arg* arg_ = nullptr; + }; - template<> - class To_Ruby + template<> + class From_Ruby + { + public: + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) { - public: - VALUE convert(char const* x) + } + + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) { - if (strlen(x) > 0 && x[0] == ':') - { - size_t symbolLength = strlen(x) - 1; - char* symbol = new char[symbolLength]; - strncpy(symbol, x + 1, symbolLength); - ID id = protect(rb_intern2, symbol, (long)symbolLength); - delete[] symbol; - return protect(rb_id2sym, id); - } - else - { - return protect(rb_str_new2, x); - } + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; } - }; + } - template - class To_Ruby + unsigned long& convert(VALUE value) { - public: - VALUE convert(char const x[]) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - if (N > 0 && x[0] == ':') - { - // N count includes a NULL character at the end of the string - constexpr size_t symbolLength = N - 1; - char symbol[symbolLength]; - strncpy(symbol, x + 1, symbolLength); - ID id = protect(rb_intern, symbol); - return protect(rb_id2sym, id); - } - else - { - return protect(rb_str_new2, x); - } + return this->arg_->defaultValue(); } - }; - } -} - - -// ========= Native.hpp ========= - - -namespace Rice::detail -{ - class Native; - - class Resolved - { - public: - inline bool operator<(Resolved other); - inline bool operator>(Resolved other); + else + { + this->converted_ = protect(rb_num2ulong_inline, value); + return this->converted_; + } + } - Convertible convertible; - double parameterMatch; - Native* native; + private: + Arg* arg_ = nullptr; + unsigned long converted_ = 0; }; - class Native + template<> + class From_Ruby { public: - static VALUE resolve(int argc, VALUE* argv, VALUE self); - public: - virtual ~Native() = default; - VALUE call(int argc, VALUE* argv, VALUE self); - - virtual Resolved matches(int argc, VALUE* argv, VALUE self) = 0; - virtual VALUE operator()(int argc, VALUE* argv, VALUE self) = 0; - }; -} - + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } -// ========= Type.hpp ========= + unsigned long* convert(VALUE value) + { + if (value == Qnil) + { + return nullptr; + } + else + { + this->converted_ = protect(rb_num2ulong_inline, value); + return &this->converted_; + } + } -#include -#include -#include + private: + unsigned long converted_ = 0; + }; -namespace Rice::detail -{ - template - struct Type + // =========== unsigned long long ============ + template<> + class From_Ruby { - static bool verify(); - }; + public: + From_Ruby() = default; - // Return the name of a type - std::string typeName(const std::type_info& typeInfo); - std::string typeName(const std::type_index& typeIndex); - std::string makeClassName(const std::type_info& typeInfo); + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } - template - void verifyType(); + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } - template - void verifyTypes(); -} + unsigned long long convert(VALUE value) + { + if (this->arg_ && this->arg_->isValue()) + { + return value; + } + else if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + return protect(rb_num2ull, value); + } + } + + private: + Arg* arg_ = nullptr; + }; + template<> + class From_Ruby + { + public: + From_Ruby() = default; -// ========= TypeRegistry.hpp ========= + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } -#include -#include -#include -#include -#include -#include + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } + unsigned long long& convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + this->converted_ = protect(rb_num2ull, value); + return this->converted_; + } + } -/* The type registry keeps track of all C++ types wrapped by Rice. When a native function returns - an instance of a class/struct we look up its type to verity that it has been registered. - - We have to do this to support C++ inheritance. If a C++ function returns a pointer/reference - to an Abstract class, the actual returned object will be a Child class. However, all we know - from the C++ method signature is that it is an Absract class - thus the need for a registry.*/ + private: + Arg* arg_ = nullptr; + unsigned long long converted_ = 0; + }; -namespace Rice::detail -{ - class TypeRegistry + template<> + class From_Ruby { public: - template - void add(VALUE klass, rb_data_type_t* rbType); + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FIXNUM: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } - template - void remove(); + unsigned long long* convert(VALUE value) + { + if (value == Qnil) + { + return nullptr; + } + else + { + this->converted_ = protect(rb_num2ull, value); + return &this->converted_; + } + } - template - bool isDefined(); + private: + unsigned long long converted_ = 0; + }; - template - bool verify(); - - template - std::pair figureType(const T& object); + // =========== bool ============ + template<> + class From_Ruby + { + public: + From_Ruby() = default; - // Validate unverified types and throw an exception if any exist. This is mostly for unit tests. - void validateUnverifiedTypes(); - // Clear unverified types. This is mostly for unit tests - void clearUnverifiedTypes(); + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } - public: - // If true an exception will be thrown for unvalidated types. If false then a messages - // will be sent to stderr - bool isStrict = true; + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_TRUE: + return Convertible::Exact; + break; + case RUBY_T_FALSE: + return Convertible::Exact; + break; + case RUBY_T_NIL: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } + } + bool convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + return RTEST(value); + } + } + private: - std::optional> lookup(const std::type_info& typeInfo); - void raiseUnverifiedType(const std::string& typeName); - - std::unordered_map> registry_{}; - std::set unverified_{}; - bool verified_ = true; + Arg* arg_ = nullptr; }; -} - - -// --------- TypeRegistry.ipp --------- -#include -#include -#include -#include - -namespace Rice::detail -{ - template - inline void TypeRegistry::add(VALUE klass, rb_data_type_t* rbType) + template<> + class From_Ruby { - std::type_index key(typeid(T)); - registry_[key] = std::pair(klass, rbType); - } + public: + From_Ruby() = default; - /* Special case void. Rice defines classes using the class name not a pointer to the - class. Thus define_class is more consistent with Rice then - define_class. However, the types of void and void* are different so we need - this special case. - - It is possible to support define_class, but it requires changing the static - assertions on Data_Type and Data_Object and thus seems less desirable (and less - consistent as mentioned above).*/ - template <> - inline void TypeRegistry::add(VALUE klass, rb_data_type_t* rbType) - { - // The special case, use void* - std::type_index key(typeid(void*)); - registry_[key] = std::pair(klass, rbType); - } - - template - inline void TypeRegistry::remove() - { - std::type_index key(typeid(T)); - registry_.erase(key); - } - - template - inline bool TypeRegistry::isDefined() - { - std::type_index key(typeid(T)); - auto iter = registry_.find(key); - return iter != registry_.end(); - } - - // Special case void. See comment for add above. - template <> - inline bool TypeRegistry::isDefined() - { - std::type_index key(typeid(void*)); - auto iter = registry_.find(key); - return iter != registry_.end(); - } + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } - template - inline bool TypeRegistry::verify() - { - if (isDefined()) + Convertible is_convertible(VALUE value) { - return true; + switch (rb_type(value)) + { + case RUBY_T_TRUE: + return Convertible::Exact; + break; + case RUBY_T_FALSE: + return Convertible::Exact; + break; + case RUBY_T_NIL: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } } - else + + bool& convert(VALUE value) { - const std::type_info& typeInfo = typeid(T); - std::type_index key(typeInfo); - this->unverified_.insert(key); - this->verified_ = false; - return false; + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + this->converted_ = RTEST(value); + return this->converted_; + } } - } - inline std::optional> TypeRegistry::lookup(const std::type_info& typeInfo) - { - std::type_index key(typeInfo); - auto iter = registry_.find(key); + private: + Arg* arg_ = nullptr; + bool converted_ = false; + }; - if (iter == registry_.end()) + template<> + class From_Ruby + { + public: + Convertible is_convertible(VALUE value) { - return std::nullopt; + switch (rb_type(value)) + { + case RUBY_T_TRUE: + return Convertible::Exact; + break; + case RUBY_T_FALSE: + return Convertible::Exact; + break; + case RUBY_T_NIL: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } } - else + + bool* convert(VALUE value) { - return iter->second; + if (value == Qnil) + { + return nullptr; + } + else + { + this->converted_ = RTEST(value); + return &this->converted_; + } } - } - template - inline std::pair TypeRegistry::figureType(const T& object) + private: + bool converted_ = false; + }; + + // =========== char ============ + template + inline T charFromRuby(VALUE value) { - if (!this->verified_) + switch (rb_type(value)) { - this->validateUnverifiedTypes(); + case T_STRING: + { + if (RSTRING_LEN(value) == 1) + { + return RSTRING_PTR(value)[0]; + } + else + { + throw std::invalid_argument("from_ruby: string must have length 1"); + } + break; + } + case T_FIXNUM: + { + return From_Ruby().convert(value) & 0xff; + break; + } + default: + { + throw Exception(rb_eTypeError, "wrong argument type %s (expected % s)", + detail::protect(rb_obj_classname, value), "char type"); + } } + } - // First check and see if the actual type of the object is registered - std::optional> result = lookup(typeid(object)); + template<> + class From_Ruby + { + public: + From_Ruby() = default; - if (result) + explicit From_Ruby(Arg* arg) : arg_(arg) { - return result.value(); } - // If not, then we are willing to accept an ancestor class specified by T. This is needed - // to support Directors. Classes inherited from Directors are never actually registered - // with Rice - and what we really want it to return the C++ class they inherit from. - const std::type_info& typeInfo = typeid(T); - result = lookup(typeInfo); - if (result) + Convertible is_convertible(VALUE value) { - return result.value(); + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } } - raiseUnverifiedType(detail::typeName(typeInfo)); - // Make the compiler happy - return std::pair(Qnil, nullptr); - } - - inline void TypeRegistry::validateUnverifiedTypes() - { - // Loop over the unverified types and delete each on that is found in the registry - // the registry and raise an exception for the first one that is not - for (auto iter = this->unverified_.begin(); iter != this->unverified_.end(); ) + char convert(VALUE value) { - const std::type_index& typeIndex = *iter; - bool isDefined = this->registry_.find(typeIndex) != this->registry_.end(); - if (isDefined) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - iter = this->unverified_.erase(iter); + return this->arg_->defaultValue(); } else { - iter++; + return charFromRuby(value); } } + + private: + Arg* arg_ = nullptr; + }; - if (this->unverified_.empty()) - { - this->verified_ = true; - return; - } - - std::stringstream stream; - stream << "The following types are not registered with Rice:" << "\n"; + template<> + class From_Ruby + { + public: + From_Ruby() = default; - for (const std::type_index& typeIndex : this->unverified_) + explicit From_Ruby(Arg* arg) : arg_(arg) { - stream << " " << typeName(typeIndex) << "\n"; } - if (this->isStrict) + Convertible is_convertible(VALUE value) { - throw std::invalid_argument(stream.str()); + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } } - else + + char& convert(VALUE value) { - std::cerr << stream.str() << std::flush; + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + this->converted_ = charFromRuby(value); + return this->converted_; + } } - } - inline void TypeRegistry::clearUnverifiedTypes() - { - this->unverified_.clear(); - } + private: + Arg* arg_ = nullptr; + char converted_ = 0; + }; - inline void TypeRegistry::raiseUnverifiedType(const std::string& typeName) + template<> + class From_Ruby { - std::string message = "Type is not registered with Rice: " + typeName; - throw std::invalid_argument(message); - } -} + public: + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } + } -// ========= InstanceRegistry.hpp ========= + char* convert(VALUE value) + { + if (value == Qnil) + { + return nullptr; + } + else + { + detail::protect(rb_check_type, value, (int)T_STRING); + return RSTRING_PTR(value); + } + } + }; + + // This is mostly for testing. NativeFunction removes const before calling From_Ruby + template<> + class From_Ruby + { + public: + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } + } -#include + char const* convert(VALUE value) + { + if (value == Qnil) + { + return nullptr; + } + else + { + detail::protect(rb_check_type, value, (int)T_STRING); + return RSTRING_PTR(value); + } + } + }; -namespace Rice::detail -{ - class InstanceRegistry + // =========== unsigned char ============ + template<> + class From_Ruby { public: - template - VALUE lookup(T& cppInstance); + From_Ruby() = default; - template - VALUE lookup(T* cppInstance); - VALUE lookup(void* cppInstance); + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } - void add(void* cppInstance, VALUE rubyInstance); - void remove(void* cppInstance); - void clear(); + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } + } - public: - bool isEnabled = false; + unsigned char convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + return charFromRuby(value); + } + } private: - std::map objectMap_; + Arg* arg_ = nullptr; }; -} // namespace Rice::detail - - -// --------- InstanceRegistry.ipp --------- -#include -namespace Rice::detail -{ - template - inline VALUE InstanceRegistry::lookup(T& cppInstance) - { - return this->lookup((void*)&cppInstance); - } - - template - inline VALUE InstanceRegistry::lookup(T* cppInstance) - { - return this->lookup((void*)cppInstance); - } - - inline VALUE InstanceRegistry::lookup(void* cppInstance) + template<> + class From_Ruby { - if (!this->isEnabled) - return Qnil; + public: + From_Ruby() = default; - auto it = this->objectMap_.find(cppInstance); - if (it != this->objectMap_.end()) + explicit From_Ruby(Arg* arg) : arg_(arg) { - return it->second; } - else + + Convertible is_convertible(VALUE value) { - return Qnil; + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } } - } - inline void InstanceRegistry::add(void* cppInstance, VALUE rubyInstance) - { - if (this->isEnabled) + unsigned char& convert(VALUE value) { - this->objectMap_[cppInstance] = rubyInstance; + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + this->converted_ = charFromRuby(value); + return this->converted_; + } } - } - inline void InstanceRegistry::remove(void* cppInstance) - { - this->objectMap_.erase(cppInstance); - } + private: + Arg* arg_ = nullptr; + unsigned char converted_ = 0; + }; - inline void InstanceRegistry::clear() + template<> + class From_Ruby { - this->objectMap_.clear(); - } -} // namespace + public: + From_Ruby() = default; + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } + } -// ========= HandlerRegistry.hpp ========= + unsigned char* convert(VALUE value) + { + if (value == Qnil) + { + return nullptr; + } + else + { + detail::protect(rb_check_type, value, (int)T_STRING); + return reinterpret_cast(RSTRING_PTR(value)); + } + } + private: + Arg* arg_ = nullptr; + }; -namespace Rice::detail -{ - class HandlerRegistry + // =========== signed char ============ + template<> + class From_Ruby { public: - //! Define an exception handler. - /*! Whenever an exception of type Exception_T is thrown from a - * function defined on this class, the supplied functor will be called to - * translate the exception into a ruby exception. - * \param Exception_T a template parameter indicating the type of - * exception to be translated. - * \param functor a functor to be called to translate the exception - * into a ruby exception. This functor should re-throw the exception - * as an Exception. - * Example: - * \code - * Class rb_cFoo; - * - * void translate_my_exception(MyException const& ex) - * { - * throw Rice::Exception(rb_eRuntimeError, ex.what_without_backtrace()); - * } - * - * extern "C" - * void Init_MyExtension() - * { - * rb_cFoo = define_class("Foo"); - * register_handler(translate_my_exception); - * } - * \endcode - */ - template - HandlerRegistry& add(Functor_T functor); + From_Ruby() = default; - std::shared_ptr handler() const; + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } - private: - mutable std::shared_ptr handler_ = std::make_shared(); + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + // This is for C++ chars which are converted to Ruby integers + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } + } + signed char convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + return charFromRuby(value); + } + } + + private: + Arg* arg_ = nullptr; }; -} // namespace Rice::detail + // =========== double ============ + template<> + class From_Ruby + { + public: + From_Ruby() = default; -// --------- HandlerRegistry.ipp --------- -#include + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } -namespace Rice::detail -{ - template - inline HandlerRegistry& HandlerRegistry::add(Functor_T functor) - { - // Create a new exception handler and pass ownership of the current handler to it (they - // get chained together). Then take ownership of the new handler. - this->handler_ = std::make_shared>( - functor, std::move(this->handler_)); + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FLOAT: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } - return *this; - } + double convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + return protect(rb_num2dbl, value); + } + } + + private: + Arg* arg_ = nullptr; + }; - inline std::shared_ptr HandlerRegistry::handler() const + template<> + class From_Ruby { - return this->handler_; - } -} // namespace - + public: + From_Ruby() = default; + explicit From_Ruby(Arg* arg) : arg_(arg) + { + } -// ========= NativeRegistry.hpp ========= + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FLOAT: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } -#include -#include + double& convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + this->converted_ = protect(rb_num2dbl, value); + return this->converted_; + } + } + private: + Arg* arg_ = nullptr; + double converted_; + }; -/* The Native Registry tracks C++ instance that are used to invoke C++ methods for Ruby. - These objects include instances of the NativeFunction, NativeIterator, NativeAttributeGet - and NativeAttributeSet Each instance is specialized to call a specific C++ function, method - or attribute that is exposed to Ruby. - - The registry stores these C++ instances using a map of vectors. The map is keyed on the - the Ruby class (VALUE) and method id (ID). The value is a vector of Native pointers stored - in a std::unique_ptr. Thus the registry takes ownership of the pointers when calling - code adds them to the registry. The value is a vector to support C++ method overloading. - - Note - when an existing Ruby class is redefined using rb_define_class, its VALUE stays the same - but all its methods and fields are reset. Thus any call to rb_define_class must be followed - by calling the reset method on the registry. Although redefinition shouldn't happen in - production code it happens in many places in the unit tests. */ - -namespace Rice::detail -{ - class NativeRegistry + template<> + class From_Ruby { public: - void add(VALUE klass, ID methodId, std::unique_ptr& native); - void reset(VALUE klass); - const std::vector>& lookup(VALUE klass, ID methodId); + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FLOAT: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } + + double* convert(VALUE value) + { + if (value == Qnil) + { + return nullptr; + } + else + { + this->converted_ = protect(rb_num2dbl, value); + return &this->converted_; + } + } private: - // Key - Ruby klass/method - // Value - Vector of Native pointers owned by the registry (thus wrapped in std::unique_ptr) - std::map, std::vector>> natives_ = {}; + double converted_; }; -} - -// --------- NativeRegistry.ipp --------- -namespace Rice::detail -{ - inline void NativeRegistry::add(VALUE klass, ID methodId, std::unique_ptr& native) + // =========== float ============ + template<> + class From_Ruby { - if (rb_type(klass) == T_ICLASS) + public: + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) { - klass = detail::protect(rb_class_of, klass); } - // Create the key - std::pair key = std::make_pair(klass, methodId); - - // Lookup items for method - std::vector>& natives = this->natives_[key]; - - natives.push_back(std::move(native)); - } + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FLOAT: + return Convertible::Exact; + break; + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + case RUBY_T_BIGNUM: + return Convertible::TypeCast; + default: + return Convertible::None; + } + } - inline void NativeRegistry::reset(VALUE klass) - { - for (auto iter = this->natives_.begin(); iter != this->natives_.end();) + float convert(VALUE value) { - // Iter points to a std::pair, std::vector - if (iter->first.first == klass) + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) { - iter = this->natives_.erase(iter); + return this->arg_->defaultValue(); } else { - ++iter; + return (float)protect(rb_num2dbl, value); } } - } - inline const std::vector>& NativeRegistry::lookup(VALUE klass, ID methodId) + private: + Arg* arg_ = nullptr; + }; + + template<> + class From_Ruby { - if (rb_type(klass) == T_ICLASS) + public: + From_Ruby() = default; + + explicit From_Ruby(Arg* arg) : arg_(arg) { - klass = detail::protect(rb_class_of, klass); } - // Create the key - std::pair key = std::make_pair(klass, methodId); - - // Lookup items for method - return this->natives_[key]; - } + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FLOAT: + return Convertible::Exact; + break; + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + case RUBY_T_BIGNUM: + return Convertible::TypeCast; + default: + return Convertible::None; + } + } + + float& convert(VALUE value) + { + if (value == Qnil && this->arg_ && this->arg_->hasDefaultValue()) + { + return this->arg_->defaultValue(); + } + else + { + this->converted_ = (float)protect(rb_num2dbl, value); + return this->converted_; + } + } + + private: + Arg* arg_ = nullptr; + float converted_; + }; + + template<> + class From_Ruby + { + public: + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_FLOAT: + return Convertible::Exact; + break; + case RUBY_T_FIXNUM: + return Convertible::TypeCast; + case RUBY_T_BIGNUM: + return Convertible::TypeCast; + default: + return Convertible::None; + } + } + + float* convert(VALUE value) + { + if (value == Qnil) + { + return nullptr; + } + else + { + this->converted_ = (float)protect(rb_num2dbl, value); + return &this->converted_; + } + } + + private: + float converted_; + }; } +// Registries + +// ========= TypeRegistry.hpp ========= + +#include +#include +#include +#include +#include +#include -// ========= Registries.hpp ========= +/* The type registry keeps track of all C++ types wrapped by Rice. When a native function returns + an instance of a class/struct we look up its type to verity that it has been registered. + + We have to do this to support C++ inheritance. If a C++ function returns a pointer/reference + to an Abstract class, the actual returned object will be a Child class. However, all we know + from the C++ method signature is that it is an Absract class - thus the need for a registry.*/ namespace Rice::detail { - class Registries + class TypeRegistry { public: - static Registries instance; + template + void add(VALUE klass, rb_data_type_t* rbType); + + template + void remove(); + + template + bool isDefined(); + + template + bool verify(); + + template + std::pair figureType(const T& object); + + // Validate unverified types and throw an exception if any exist. This is mostly for unit tests. + void validateUnverifiedTypes(); + // Clear unverified types. This is mostly for unit tests + void clearUnverifiedTypes(); public: - HandlerRegistry handlers; - InstanceRegistry instances; - NativeRegistry natives; - TypeRegistry types; - }; -} + // If true an exception will be thrown for unvalidated types. If false then a messages + // will be sent to stderr + bool isStrict = true; + private: + std::optional> lookup(const std::type_info& typeInfo); + void raiseUnverifiedType(const std::string& typeName); -// --------- Registries.ipp --------- -namespace Rice::detail -{ - //Initialize static variables here. - inline Registries Registries::instance; + std::unordered_map> registry_{}; + std::set unverified_{}; + bool verified_ = true; + }; } @@ -3852,2812 +4120,2316 @@ namespace Rice::detail } } -// ========= cpp_protect.hpp ========= - -#include +// ========= TypeRegistry.ipp ========= +#include #include - -#if __has_include() - #include - namespace fs = std::filesystem; -#elif __has_include() - #include - namespace fs = std::experimental::filesystem; -#else - #error "no filesystem include found :'(" -#endif +#include +#include namespace Rice::detail { - template - auto cpp_protect(Callable_T&& func) + template + inline void TypeRegistry::add(VALUE klass, rb_data_type_t* rbType) { - try - { - return func(); - } - catch (...) - { - try - { - detail::Registries::instance.handlers.handler()->handle(); - } - catch (::Rice::Exception const& ex) - { - rb_exc_raise(ex.value()); - } - catch (::Rice::Jump_Tag const& ex) - { - rb_jump_tag(ex.tag); - } - catch (std::bad_alloc const& ex) - { - /* This won't work quite right if the rb_exc_new2 fails; not - much we can do about that, since Ruby doesn't give us access - to a pre-allocated NoMemoryError object */ - rb_exc_raise(rb_exc_new2(rb_eNoMemError, ex.what())); - } - catch (std::domain_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eFloatDomainError, ex.what())); - } - catch (std::invalid_argument const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eArgError, ex.what())); - } - catch (fs::filesystem_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eIOError, ex.what())); - } - catch (std::length_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eRuntimeError, ex.what())); - } - catch (std::out_of_range const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); - } - catch (std::overflow_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); - } - catch (std::range_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); - } - catch (std::regex_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eRegexpError, ex.what())); - } - catch (std::system_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eSystemCallError, ex.what())); - } - catch (std::underflow_error const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); - } - catch (std::exception const& ex) - { - rb_exc_raise(rb_exc_new2(rb_eRuntimeError, ex.what())); - } - catch (...) - { - rb_exc_raise(rb_exc_new2(rb_eRuntimeError, "Unknown C++ exception thrown")); - } - throw std::runtime_error("Should never get here - just making compilers happy"); - } + std::type_index key(typeid(T)); + registry_[key] = std::pair(klass, rbType); } -} - -// ========= Wrapper.hpp ========= + /* Special case void. Rice defines classes using the class name not a pointer to the + class. Thus define_class is more consistent with Rice then + define_class. However, the types of void and void* are different so we need + this special case. + + It is possible to support define_class, but it requires changing the static + assertions on Data_Type and Data_Object and thus seems less desirable (and less + consistent as mentioned above).*/ + template <> + inline void TypeRegistry::add(VALUE klass, rb_data_type_t* rbType) + { + // The special case, use void* + std::type_index key(typeid(void*)); + registry_[key] = std::pair(klass, rbType); + } -namespace Rice -{ -namespace detail -{ + template + inline void TypeRegistry::remove() + { + std::type_index key(typeid(T)); + registry_.erase(key); + } -class Wrapper -{ -public: - Wrapper(bool isOwner = false); - virtual ~Wrapper() = default; - virtual void* get() = 0; - - void ruby_mark(); - void addKeepAlive(VALUE value); - void setOwner(bool value); - -protected: - bool isOwner_ = false; - -private: - // We use a vector for speed and memory locality versus a set which does - // not scale well when getting to tens of thousands of objects (not expecting - // that to happen...but just in case) - std::vector keepAlive_; -}; - -template -VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T& data, bool isOwner); - -template -VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T* data, bool isOwner); - -template -T* unwrap(VALUE value, rb_data_type_t* rb_type, bool transferOwnership); - -Wrapper* getWrapper(VALUE value, rb_data_type_t* rb_type); - -template -void replace(VALUE value, rb_data_type_t* rb_type, T* data, bool isOwner); - -Wrapper* getWrapper(VALUE value); - -} // namespace detail -} // namespace Rice - - -// --------- Wrapper.ipp --------- -#include - -namespace Rice::detail -{ - inline Wrapper::Wrapper(bool isOwner): isOwner_(isOwner) - { - } - - inline void Wrapper::ruby_mark() - { - for (VALUE value : this->keepAlive_) - { - rb_gc_mark(value); - } - } - - inline void Wrapper::addKeepAlive(VALUE value) + template + inline bool TypeRegistry::isDefined() { - this->keepAlive_.push_back(value); + std::type_index key(typeid(T)); + auto iter = registry_.find(key); + return iter != registry_.end(); } - inline void Wrapper::setOwner(bool value) + // Special case void. See comment for add above. + template <> + inline bool TypeRegistry::isDefined() { - this->isOwner_ = value; + std::type_index key(typeid(void*)); + auto iter = registry_.find(key); + return iter != registry_.end(); } template - class WrapperValue : public Wrapper + inline bool TypeRegistry::verify() { - public: - WrapperValue(T& data): data_(std::move(data)) + if (isDefined()) { + return true; } - - ~WrapperValue() + else { - Registries::instance.instances.remove(this->get()); + const std::type_info& typeInfo = typeid(T); + std::type_index key(typeInfo); + this->unverified_.insert(key); + this->verified_ = false; + return false; } + } - void* get() override + inline std::optional> TypeRegistry::lookup(const std::type_info& typeInfo) + { + std::type_index key(typeInfo); + auto iter = registry_.find(key); + + if (iter == registry_.end()) { - return (void*)&this->data_; + return std::nullopt; } - - private: - T data_; - }; + else + { + return iter->second; + } + } template - class WrapperReference : public Wrapper + inline std::pair TypeRegistry::figureType(const T& object) { - public: - WrapperReference(T& data): data_(data) + if (!this->verified_) { + this->validateUnverifiedTypes(); } - ~WrapperReference() + // First check and see if the actual type of the object is registered + std::optional> result = lookup(typeid(object)); + + if (result) { - Registries::instance.instances.remove(this->get()); + return result.value(); } - void* get() override + // If not, then we are willing to accept an ancestor class specified by T. This is needed + // to support Directors. Classes inherited from Directors are never actually registered + // with Rice - and what we really want it to return the C++ class they inherit from. + const std::type_info& typeInfo = typeid(T); + result = lookup(typeInfo); + if (result) { - return (void*)&this->data_; + return result.value(); } - private: - T& data_; - }; + raiseUnverifiedType(detail::typeName(typeInfo)); + // Make the compiler happy + return std::pair(Qnil, nullptr); + } - template - class WrapperPointer : public Wrapper + inline void TypeRegistry::validateUnverifiedTypes() { - public: - WrapperPointer(T* data, bool isOwner) : Wrapper(isOwner), data_(data) - { - } - - ~WrapperPointer() + // Loop over the unverified types and delete each on that is found in the registry + // the registry and raise an exception for the first one that is not + for (auto iter = this->unverified_.begin(); iter != this->unverified_.end(); ) { - Registries::instance.instances.remove(this->get()); - - if constexpr (std::is_destructible_v) + const std::type_index& typeIndex = *iter; + bool isDefined = this->registry_.find(typeIndex) != this->registry_.end(); + if (isDefined) { - if (this->isOwner_) - { - delete this->data_; - } + iter = this->unverified_.erase(iter); + } + else + { + iter++; } } - void* get() override + if (this->unverified_.empty()) { - return (void*)this->data_; + this->verified_ = true; + return; } - private: - T* data_ = nullptr; - }; - - // ---- Helper Functions ------- - template - inline VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T& data, bool isOwner) - { - VALUE result = Registries::instance.instances.lookup(&data); - - if (result != Qnil) - return result; - - Wrapper* wrapper = nullptr; + std::stringstream stream; + stream << "The following types are not registered with Rice:" << "\n"; - // Is this a pointer but cannot be copied? For example a std::unique_ptr - if constexpr (!std::is_void_v && !std::is_copy_constructible_v) - { - wrapper = new Wrapper_T(std::move(data)); - result = TypedData_Wrap_Struct(klass, rb_type, wrapper); - } - // Is this a pointer or smart pointer like std::shared_ptr - else if constexpr (!std::is_void_v) + for (const std::type_index& typeIndex : this->unverified_) { - wrapper = new Wrapper_T(data); - result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + stream << " " << typeName(typeIndex) << "\n"; } - // Is this a pointer and it cannot copied? This is for std::unique_ptr - // If ruby is the owner than copy the object - else if (isOwner) + + if (this->isStrict) { - wrapper = new WrapperValue(data); - result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + throw std::invalid_argument(stream.str()); } - // Ruby is not the owner so just wrap the reference else { - wrapper = new WrapperReference(data); - result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + std::cerr << stream.str() << std::flush; } + } - Registries::instance.instances.add(wrapper->get(), result); - - return result; - }; + inline void TypeRegistry::clearUnverifiedTypes() + { + this->unverified_.clear(); + } - template - inline VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T* data, bool isOwner) + inline void TypeRegistry::raiseUnverifiedType(const std::string& typeName) { - VALUE result = Registries::instance.instances.lookup(data); + std::string message = "Type is not registered with Rice: " + typeName; + throw std::invalid_argument(message); + } +} +// ========= InstanceRegistry.hpp ========= - if (result != Qnil) - return result; +#include - Wrapper* wrapper = nullptr; +namespace Rice::detail +{ + class InstanceRegistry + { + public: + template + VALUE lookup(T& cppInstance); - if constexpr (!std::is_void_v) - { - wrapper = new Wrapper_T(data); - result = TypedData_Wrap_Struct(klass, rb_type, wrapper); - } - else - { - wrapper = new WrapperPointer(data, isOwner); - result = TypedData_Wrap_Struct(klass, rb_type, wrapper); - } + template + VALUE lookup(T* cppInstance); + VALUE lookup(void* cppInstance); - Registries::instance.instances.add(wrapper->get(), result); - return result; - }; + void add(void* cppInstance, VALUE rubyInstance); + void remove(void* cppInstance); + void clear(); - template - inline T* unwrap(VALUE value, rb_data_type_t* rb_type, bool transferOwnership) - { - Wrapper* wrapper = getWrapper(value, rb_type); - if (transferOwnership) - wrapper->setOwner(false); + public: + bool isEnabled = false; - if (wrapper == nullptr) - { - std::string message = "Wrapped C++ object is nil. Did you override " + - std::string(detail::protect(rb_obj_classname, value)) + - "#initialize and forget to call super?"; + private: + std::map objectMap_; + }; +} // namespace Rice::detail - throw std::runtime_error(message); - } - return static_cast(wrapper->get()); - } - - inline Wrapper* getWrapper(VALUE value, rb_data_type_t* rb_type) - { - Wrapper* wrapper = nullptr; - TypedData_Get_Struct(value, Wrapper, rb_type, wrapper); - return wrapper; - } - inline Wrapper* getWrapper(VALUE value) - { - // Turn off spurious warning on g++ 12 -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif - return RTYPEDDATA_P(value) ? static_cast(RTYPEDDATA_DATA(value)) : nullptr; -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - } +// ========= InstanceRegistry.ipp ========= +#include +namespace Rice::detail +{ template - inline void replace(VALUE value, rb_data_type_t* rb_type, T* data, bool isOwner) + inline VALUE InstanceRegistry::lookup(T& cppInstance) { - WrapperPointer* wrapper = nullptr; - TypedData_Get_Struct(value, WrapperPointer, rb_type, wrapper); - if (wrapper) - { - Registries::instance.instances.remove(wrapper->get()); - delete wrapper; - } - - wrapper = new WrapperPointer(data, isOwner); - RTYPEDDATA_DATA(value) = wrapper; - - Registries::instance.instances.add(data, value); + return this->lookup((void*)&cppInstance); } -} // namespace - - - -// ========= MethodInfo.hpp ========= - -#include - -namespace Rice -{ - class MethodInfo - { - public: - template - MethodInfo(size_t argCount, const Arg_Ts&...args); - - /** - * Get the rb_scan_args format string for this - * list of arguments. - */ - std::string formatString(); - - /** - * Add a defined Arg to this list of Arguments - */ - void addArg(const Arg& arg); - - Arg& arg(size_t pos); - - // Iterator support - std::vector::iterator begin(); - std::vector::iterator end(); - - Return returnInfo; - - private: - template - void processArg(const Arg_T& arg); - - std::vector args_; - }; -} - -// --------- MethodInfo.ipp --------- -#include -namespace Rice -{ - template - inline MethodInfo::MethodInfo(size_t argCount, const Arg_Ts&...args) + template + inline VALUE InstanceRegistry::lookup(T* cppInstance) { - // Process the passed in arguments - (this->processArg(args), ...); - - // Fill in any missing arguments - for (size_t i = this->args_.size(); i < argCount; i++) - { - Arg arg("arg_" + std::to_string(i)); - this->addArg(arg); - } - - // TODO - so hacky but update the Arg positions - for (uint32_t i = 0; i < this->args_.size(); i++) - { - this->args_[i].position = i; - } + return this->lookup((void*)cppInstance); } - template - inline void MethodInfo::processArg(const Arg_T& arg) + inline VALUE InstanceRegistry::lookup(void* cppInstance) { - if constexpr (std::is_same_v) + if (!this->isEnabled) + return Qnil; + + auto it = this->objectMap_.find(cppInstance); + if (it != this->objectMap_.end()) { - this->addArg(arg); + return it->second; } else { - this->returnInfo = arg; + return Qnil; } } - inline void MethodInfo::addArg(const Arg& arg) - { - this->args_.push_back(arg); - } - - inline std::string MethodInfo::formatString() + inline void InstanceRegistry::add(void* cppInstance, VALUE rubyInstance) { - size_t required = 0; - size_t optional = 0; - - for (const Arg& arg : this->args_) + if (this->isEnabled) { - if (arg.hasDefaultValue()) - { - optional++; - } - else - { - required++; - } + this->objectMap_[cppInstance] = rubyInstance; } - - return std::to_string(required) + std::to_string(optional); - } - - inline Arg& MethodInfo::arg(size_t pos) - { - return args_[pos]; } - inline std::vector::iterator MethodInfo::begin() + inline void InstanceRegistry::remove(void* cppInstance) { - return this->args_.begin(); + this->objectMap_.erase(cppInstance); } - inline std::vector::iterator MethodInfo::end() + inline void InstanceRegistry::clear() { - return this->args_.end(); + this->objectMap_.clear(); } -} +} // namespace -// ========= Identifier.hpp ========= +// ========= ExceptionHandler.hpp ========= -#include +#include -namespace Rice +namespace Rice::detail { - class Symbol; - - //! A wrapper for the ID type - /*! An ID is ruby's internal representation of a Symbol object. - */ - class Identifier - { - public: - //! Construct a new Identifier from an ID. - Identifier(ID id); - - //! Construct a new Identifier from a Symbol. - Identifier(Symbol const& symbol); - - //! Construct a new Identifier from a c string. - Identifier(char const* s); + /* An abstract class for converting C++ exceptions to ruby exceptions. It's used + like this: - //! Construct a new Identifier from a string. - Identifier(std::string const& string); + try + { + } + catch(...) + { + handler->handle(); + } - //! Return a string representation of the Identifier. - char const* c_str() const; + If an exception is thrown the handler will pass the exception up the + chain, then the last handler in the chain will throw the exception + down the chain until a lower handler can handle it, e.g.: - //! Return a string representation of the Identifier. - std::string str() const; + try + { + return call_next_ExceptionHandler(); + } + catch(MyException const & ex) + { + throw Rice::Exception(rb_cMyException, "%s", ex.what()); + } - //! Return the underlying ID - ID id() const { return id_; } + Memory management. Handlers are created by the ModuleBase constructor. When the + module defines a new Ruby method, metadata is stored on the Ruby klass including + the exception handler. Since the metadata outlives the module, handlers are stored + using std::shared_ptr. Thus the Module (or its inherited children) can be destroyed + without corrupting the metadata references to the shared exception handler. */ - //! Return the underlying ID - operator ID() const { return id_; } + class ExceptionHandler + { + public: + ExceptionHandler() = default; + virtual ~ExceptionHandler() = default; - //! Return the ID as a Symbol - VALUE to_sym() const; + // Don't allow copying or assignment + ExceptionHandler(const ExceptionHandler& other) = delete; + ExceptionHandler& operator=(const ExceptionHandler& other) = delete; - private: - ID id_; + virtual VALUE handle() const = 0; }; -} // namespace Rice - -// --------- Identifier.ipp --------- -namespace Rice -{ - inline Identifier::Identifier(ID id) : id_(id) + // The default exception handler just rethrows the exception. If there + // are other handlers in the chain, they will try to handle the rethrown + // exception. + class DefaultExceptionHandler : public ExceptionHandler { - } + public: + virtual VALUE handle() const override; + }; - inline Identifier::Identifier(char const* s) : id_(rb_intern(s)) - { - } - - inline Identifier::Identifier(std::string const& s) : id_(rb_intern2(s.c_str(), s.size())) - { - } - - inline char const* Identifier::c_str() const - { - return detail::protect(rb_id2name, id_); - } - - inline std::string Identifier::str() const + // An exception handler that takes a functor as an argument. The + // functor should throw a Rice::Exception to handle the exception. If + // the functor does not handle the exception, the exception will be + // re-thrown. + template + class CustomExceptionHandler : public ExceptionHandler { - return c_str(); - } + public: + CustomExceptionHandler(Functor_T handler, std::shared_ptr nextHandler); + virtual VALUE handle() const override; - inline VALUE Identifier::to_sym() const - { - return ID2SYM(id_); - } + private: + Functor_T handler_; + std::shared_ptr nextHandler_; + }; } -// ========= Exception.ipp ========= +// ========= HandlerRegistry.hpp ========= -namespace Rice +namespace Rice::detail { - inline Exception::Exception(VALUE exception) : exception_(exception) + class HandlerRegistry { - } + public: + //! Define an exception handler. + /*! Whenever an exception of type Exception_T is thrown from a + * function defined on this class, the supplied functor will be called to + * translate the exception into a ruby exception. + * \param Exception_T a template parameter indicating the type of + * exception to be translated. + * \param functor a functor to be called to translate the exception + * into a ruby exception. This functor should re-throw the exception + * as an Exception. + * Example: + * \code + * Class rb_cFoo; + * + * void translate_my_exception(MyException const& ex) + * { + * throw Rice::Exception(rb_eRuntimeError, ex.what_without_backtrace()); + * } + * + * extern "C" + * void Init_MyExtension() + * { + * rb_cFoo = define_class("Foo"); + * register_handler(translate_my_exception); + * } + * \endcode + */ + template + HandlerRegistry& add(Functor_T functor); - template - inline Exception::Exception(const Exception& other, char const* fmt, Arg_Ts&&...args) - : Exception(other.class_of(), fmt, std::forward(args)...) - { - } + std::shared_ptr handler() const; - template - inline Exception::Exception(const VALUE exceptionClass, char const* fmt, Arg_Ts&&...args) - { -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-security" -#endif + private: + mutable std::shared_ptr handler_ = std::make_shared(); - size_t size = std::snprintf(nullptr, 0, fmt, std::forward(args)...); - this->message_ = std::string(size, '\0'); + }; +} // namespace Rice::detail - // size+1 avoids truncating the string. Otherwise snprintf writes n - 1 characters - // to allow space for null character but we don't need that since std::string - // will add a null character internally at n + 1 - std::snprintf(&this->message_[0], size + 1, fmt, std::forward(args)...); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - // Now create the Ruby exception - this->exception_ = detail::protect(rb_exc_new2, exceptionClass, this->message_.c_str()); - } +// ========= HandlerRegistry.ipp ========= +#include - inline char const* Exception::what() const noexcept +namespace Rice::detail +{ + template + inline HandlerRegistry& HandlerRegistry::add(Functor_T functor) { - if (this->message_.empty()) - { - // This isn't protected because if it fails then either we could eat the exception - // (not good) or crash the program (better) - VALUE rubyMessage = rb_funcall(this->exception_, rb_intern("message"), 0); - this->message_ = std::string(RSTRING_PTR(rubyMessage), RSTRING_LEN(rubyMessage)); - } - return this->message_.c_str(); + // Create a new exception handler and pass ownership of the current handler to it (they + // get chained together). Then take ownership of the new handler. + this->handler_ = std::make_shared>( + functor, std::move(this->handler_)); + + return *this; } - inline VALUE Exception::class_of() const + inline std::shared_ptr HandlerRegistry::handler() const { - return detail::protect(rb_class_of, this->exception_); + return this->handler_; } +} // namespace - inline VALUE Exception::value() const +// ========= HandlerRegistration.hpp ========= + + +namespace Rice +{ + // Register exception handler + template + detail::HandlerRegistry register_handler(Functor_T functor) { - return this->exception_; + return detail::Registries::instance.handlers.add(std::forward(functor)); } } -// ========= Native.ipp ========= +// ========= ExceptionHandler.ipp ========= namespace Rice::detail { - inline bool Resolved::operator<(Resolved other) + inline VALUE Rice::detail::DefaultExceptionHandler::handle() const { - if (this->convertible != other.convertible) - { - return this->convertible < other.convertible; - } - else - { - return this->parameterMatch < other.parameterMatch; - } + throw; } - inline bool Resolved::operator>(Resolved other) + template + inline Rice::detail::CustomExceptionHandler:: + CustomExceptionHandler(Functor_T handler, std::shared_ptr nextHandler) + : handler_(handler), nextHandler_(nextHandler) { - if (this->convertible != other.convertible) + } + + template + inline VALUE Rice::detail::CustomExceptionHandler::handle() const + { + try { - return this->convertible > other.convertible; + return this->nextHandler_->handle(); } - else + catch (Exception_T const& ex) { - return this->parameterMatch > other.parameterMatch; + handler_(ex); + throw; } } +} - inline VALUE Native::resolve(int argc, VALUE* argv, VALUE self) - { - /* This method is called from Ruby and is responsible for determining the correct - Native object (ie, NativeFunction, NativeIterator, NativeAttributeGet and - NativeAttributeSet) that shoudl be used to invoke the underlying C++ code. - Most of the time there will be a single Native object registered for a C++ function, - method, constructor, iterator or attribute. However, there can be multiple Natives - when a C++ function/method/construtor is overloaded. - - In that case, the code iterates over each Native and calls its matches method. The matches - method returns a Resolved object which includes a Convertible field and parameterMatch field. - The Convertible field is an enum that specifies if the types of the values supplied by Ruby - match the types of the C++ function parameters. Allowed values include can be Exact (example Ruby into to C++ int), - TypeCast (example Ruby into to C++ float) or None (cannot be converted). - - The parameterMatch field is simply the number or arguments provided by Ruby divided by the - number of arguments required by C++. These numbers can be different because C++ method - parameters can have default values. +// ========= Native.hpp ========= - Taking these two values into account, the method sorts the Natives and picks the one with the - highest score (Convertible::Exact and 1.0 for parameterMatch). Thus given these two C++ functions: - void some_method(int a); - void some_mtehod(int a, float b = 2.0). +namespace Rice::detail +{ + class Native; - A call from ruby of some_method(1) will exactly match both signatures, but the first one - will be chosen because the parameterMatch will be 1.0 for the first overload but 0.5 - for the second. */ + class Resolved + { + public: + inline bool operator<(Resolved other); + inline bool operator>(Resolved other); - Native* native = nullptr; + Convertible convertible; + double parameterMatch; + Native* native; + }; - ID methodId; - VALUE klass; - if (!rb_frame_method_id_and_class(&methodId, &klass)) - { - rb_raise(rb_eRuntimeError, "Cannot get method id and class for function"); - } + class Native + { + public: + static VALUE resolve(int argc, VALUE* argv, VALUE self); + public: + virtual ~Native() = default; + VALUE call(int argc, VALUE* argv, VALUE self); - // Execute the function but make sure to catch any C++ exceptions! - return cpp_protect([&] - { - const std::vector>& natives = Registries::instance.natives.lookup(klass, methodId); + virtual Resolved matches(int argc, VALUE* argv, VALUE self) = 0; + virtual VALUE operator()(int argc, VALUE* argv, VALUE self) = 0; + }; +} - if (natives.size() == 1) - { - native = natives.front().get(); - } - else if (natives.size() == 0) - { - Identifier identifier(methodId); - rb_raise(rb_eArgError, "Could not find method call for %s#%s", rb_class2name(klass), identifier.c_str()); - } - else - { - // Loop over every native to see how well they match the Ruby parameters - std::vector resolves; - std::transform(natives.begin(), natives.end(), - std::back_inserter(resolves), - [&](const std::unique_ptr& native) - { - return native->matches(argc, argv, self); - }); - // Now sort from best to worst - std::sort(resolves.begin(), resolves.end(), std::greater{}); +// ========= NativeRegistry.hpp ========= - // Get the best one - Resolved resolved = resolves.front(); +#include +#include +#include - // Did it match? - if (resolved.convertible != Convertible::None) - { - native = resolved.native; - } - else - { - Identifier identifier(methodId); - rb_raise(rb_eArgError, "Could not resolve method call for %s#%s", rb_class2name(klass), identifier.c_str()); - } - } +/* The Native Registry tracks C++ instance that are used to invoke C++ methods for Ruby. + These objects include instances of the NativeFunction, NativeIterator, NativeAttributeGet + and NativeAttributeSet Each instance is specialized to call a specific C++ function, method + or attribute that is exposed to Ruby. + + The registry stores these C++ instances using a map of vectors. The map is keyed on the + the Ruby class (VALUE) and method id (ID). The value is a vector of Native pointers stored + in a std::unique_ptr. Thus the registry takes ownership of the pointers when calling + code adds them to the registry. The value is a vector to support C++ method overloading. + + Note - when an existing Ruby class is redefined using rb_define_class, its VALUE stays the same + but all its methods and fields are reset. Thus any call to rb_define_class must be followed + by calling the reset method on the registry. Although redefinition shouldn't happen in + production code it happens in many places in the unit tests. */ + +namespace Rice::detail +{ + class NativeRegistry + { + public: + void add(VALUE klass, ID methodId, std::unique_ptr& native); + void reset(VALUE klass); + const std::vector>& lookup(VALUE klass, ID methodId); - // Call the C++ function - return (*native)(argc, argv, self); - }); - } + private: + // Key - Ruby klass/method + // Value - Vector of Native pointers owned by the registry (thus wrapped in std::unique_ptr) + std::map, std::vector>> natives_ = {}; + }; } -// ========= NativeAttributeGet.hpp ========= +// ========= NativeRegistry.ipp ========= -namespace Rice +namespace Rice::detail { - enum class AttrAccess - { - ReadWrite, - Read, - Write - }; - - namespace detail + inline void NativeRegistry::add(VALUE klass, ID methodId, std::unique_ptr& native) { - template - class NativeAttributeGet: Native + if (rb_type(klass) == T_ICLASS) { - public: - using NativeAttribute_T = NativeAttributeGet; - - using T = typename attribute_traits::attr_type; - using Receiver_T = typename attribute_traits::class_type; - using To_Ruby_T = remove_cv_recursive_t; + klass = detail::protect(rb_class_of, klass); + } - public: - // Register attribute getter with Ruby - static void define(VALUE klass, std::string name, Attribute_T attribute); + // Create the key + std::pair key = std::make_pair(klass, methodId); - public: - // Disallow creating/copying/moving - NativeAttributeGet() = delete; - NativeAttributeGet(const NativeAttribute_T&) = delete; - NativeAttributeGet(NativeAttribute_T&&) = delete; - void operator=(const NativeAttribute_T&) = delete; - void operator=(NativeAttribute_T&&) = delete; + // Lookup items for method + std::vector>& natives = this->natives_[key]; - Resolved matches(int argc, VALUE* argv, VALUE self) override; - VALUE operator()(int argc, VALUE* argv, VALUE self) override; + natives.push_back(std::move(native)); + } - protected: - NativeAttributeGet(VALUE klass, std::string name, Attribute_T attr); + inline void NativeRegistry::reset(VALUE klass) + { + for (auto iter = this->natives_.begin(); iter != this->natives_.end();) + { + // Iter points to a std::pair, std::vector + if (iter->first.first == klass) + { + iter = this->natives_.erase(iter); + } + else + { + ++iter; + } + } + } + + inline const std::vector>& NativeRegistry::lookup(VALUE klass, ID methodId) + { + if (rb_type(klass) == T_ICLASS) + { + klass = detail::protect(rb_class_of, klass); + } - private: - VALUE klass_; - std::string name_; - Attribute_T attribute_; - }; - } // detail -} // Rice + // Create the key + std::pair key = std::make_pair(klass, methodId); + // Lookup items for method + return this->natives_[key]; + } +} -// --------- NativeAttributeGet.ipp --------- -#include -#include +// ========= Registries.hpp ========= namespace Rice::detail { - template - void NativeAttributeGet::define(VALUE klass, std::string name, Attribute_T attribute) + class Registries { - // Create a NativeAttributeGet that Ruby will call to read/write C++ variables - NativeAttribute_T* nativeAttribute = new NativeAttribute_T(klass, name, std::forward(attribute)); - std::unique_ptr native(nativeAttribute); + public: + static Registries instance; - detail::protect(rb_define_method, klass, name.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); + public: + HandlerRegistry handlers; + InstanceRegistry instances; + NativeRegistry natives; + TypeRegistry types; + }; +} - // Add to native registry. Since attributes cannot be overridden, there is no need to set the - // matches or calls function pointer. Instead Ruby can call the static call method defined on - // this class (&NativeAttribute_T::get). - Identifier identifier(name); - detail::Registries::instance.natives.add(klass, identifier.id(), native); + +// ========= Registries.ipp ========= +namespace Rice::detail +{ + //Initialize static variables here. + inline Registries Registries::instance; +} + +// Code for Ruby to call C++ + +// ========= Exception.ipp ========= + +namespace Rice +{ + inline Exception::Exception(VALUE exception) : exception_(exception) + { } - template - inline Resolved NativeAttributeGet::matches(int argc, VALUE* argv, VALUE self) + template + inline Exception::Exception(const Exception& other, char const* fmt, Arg_Ts&&...args) + : Exception(other.class_of(), fmt, std::forward(args)...) { - if (argc == 0) - return Resolved { Convertible::Exact, 1, this }; - else - return Resolved{ Convertible::None, 0, this }; } - - template - NativeAttributeGet::NativeAttributeGet(VALUE klass, std::string name, Attribute_T attribute) - : klass_(klass), name_(name), attribute_(attribute) + + template + inline Exception::Exception(const VALUE exceptionClass, char const* fmt, Arg_Ts&&...args) { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" +#endif + + size_t size = std::snprintf(nullptr, 0, fmt, std::forward(args)...); + this->message_ = std::string(size, '\0'); + + // size+1 avoids truncating the string. Otherwise snprintf writes n - 1 characters + // to allow space for null character but we don't need that since std::string + // will add a null character internally at n + 1 + std::snprintf(&this->message_[0], size + 1, fmt, std::forward(args)...); + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + // Now create the Ruby exception + this->exception_ = detail::protect(rb_exc_new2, exceptionClass, this->message_.c_str()); } - template - inline VALUE NativeAttributeGet::operator()(int argc, VALUE* argv, VALUE self) + inline char const* Exception::what() const noexcept { - if constexpr (std::is_member_object_pointer_v) - { - Receiver_T* nativeSelf = From_Ruby().convert(self); - return To_Ruby().convert(nativeSelf->*attribute_); - } - else + if (this->message_.empty()) { - return To_Ruby().convert(*attribute_); + // This isn't protected because if it fails then either we could eat the exception + // (not good) or crash the program (better) + VALUE rubyMessage = rb_funcall(this->exception_, rb_intern("message"), 0); + this->message_ = std::string(RSTRING_PTR(rubyMessage), RSTRING_LEN(rubyMessage)); } + return this->message_.c_str(); } -} // Rice - - -// ========= NativeAttributeSet.hpp ========= + inline VALUE Exception::class_of() const + { + return detail::protect(rb_class_of, this->exception_); + } -namespace Rice -{ - namespace detail + inline VALUE Exception::value() const { - template - class NativeAttributeSet: Native - { - public: - using NativeAttribute_T = NativeAttributeSet; - using Attr_T = typename attribute_traits::attr_type; - using T_Unqualified = remove_cv_recursive_t; - using Receiver_T = typename attribute_traits::class_type; + return this->exception_; + } +} - public: - // Register attribute getter/setter with Ruby - static void define(VALUE klass, std::string name, Attribute_T attribute); +// ========= cpp_protect.hpp ========= - public: - // Disallow creating/copying/moving - NativeAttributeSet() = delete; - NativeAttributeSet(const NativeAttribute_T&) = delete; - NativeAttributeSet(NativeAttribute_T&&) = delete; - void operator=(const NativeAttribute_T&) = delete; - void operator=(NativeAttribute_T&&) = delete; +#include +#include - Resolved matches(int argc, VALUE* argv, VALUE self) override; - VALUE operator()(int argc, VALUE* argv, VALUE self) override; +#if __has_include() + #include + namespace fs = std::filesystem; +#elif __has_include() + #include + namespace fs = std::experimental::filesystem; +#else + #error "no filesystem include found :'(" +#endif - protected: - NativeAttributeSet(VALUE klass, std::string name, Attribute_T attr); - private: - VALUE klass_; - std::string name_; - Attribute_T attribute_; - }; - } // detail -} // Rice +namespace Rice::detail +{ + template + auto cpp_protect(Callable_T&& func) + { + try + { + return func(); + } + catch (...) + { + try + { + detail::Registries::instance.handlers.handler()->handle(); + } + catch (::Rice::Exception const& ex) + { + rb_exc_raise(ex.value()); + } + catch (::Rice::Jump_Tag const& ex) + { + rb_jump_tag(ex.tag); + } + catch (std::bad_alloc const& ex) + { + /* This won't work quite right if the rb_exc_new2 fails; not + much we can do about that, since Ruby doesn't give us access + to a pre-allocated NoMemoryError object */ + rb_exc_raise(rb_exc_new2(rb_eNoMemError, ex.what())); + } + catch (std::domain_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eFloatDomainError, ex.what())); + } + catch (std::invalid_argument const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eArgError, ex.what())); + } + catch (fs::filesystem_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eIOError, ex.what())); + } + catch (std::length_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eRuntimeError, ex.what())); + } + catch (std::out_of_range const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); + } + catch (std::overflow_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); + } + catch (std::range_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); + } + catch (std::regex_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eRegexpError, ex.what())); + } + catch (std::system_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eSystemCallError, ex.what())); + } + catch (std::underflow_error const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eRangeError, ex.what())); + } + catch (std::exception const& ex) + { + rb_exc_raise(rb_exc_new2(rb_eRuntimeError, ex.what())); + } + catch (...) + { + rb_exc_raise(rb_exc_new2(rb_eRuntimeError, "Unknown C++ exception thrown")); + } + throw std::runtime_error("Should never get here - just making compilers happy"); + } + } +} +// ========= Wrapper.hpp ========= -// --------- NativeAttributeSet.ipp --------- -#include -#include +namespace Rice +{ +namespace detail +{ -namespace Rice::detail +class Wrapper { - template - void NativeAttributeSet::define(VALUE klass, std::string name, Attribute_T attribute) - { - // Create a NativeAttributeSet that Ruby will call to read/write C++ variables - NativeAttribute_T* nativeAttribute = new NativeAttribute_T(klass, name, std::forward(attribute)); - std::unique_ptr native(nativeAttribute); +public: + Wrapper(bool isOwner = false); + virtual ~Wrapper() = default; + virtual void* get() = 0; - // Define the write method name - std::string setter = name + "="; + void ruby_mark(); + void addKeepAlive(VALUE value); + void setOwner(bool value); - // Tell Ruby to invoke the static method write to get the attribute value - detail::protect(rb_define_method, klass, setter.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); +protected: + bool isOwner_ = false; - // Add to native registry. Since attributes cannot be overridden, there is no need to set the - // matches or calls function pointer. Instead Ruby can call the static call method defined on - // this class (&NativeAttribute_T::set). - Identifier identifier(setter); - detail::Registries::instance.natives.add(klass, identifier.id(), native); +private: + // We use a vector for speed and memory locality versus a set which does + // not scale well when getting to tens of thousands of objects (not expecting + // that to happen...but just in case) + std::vector keepAlive_; +}; + +template +VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T& data, bool isOwner); + +template +VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T* data, bool isOwner); + +template +T* unwrap(VALUE value, rb_data_type_t* rb_type, bool transferOwnership); + +Wrapper* getWrapper(VALUE value, rb_data_type_t* rb_type); + +template +void replace(VALUE value, rb_data_type_t* rb_type, T* data, bool isOwner); + +Wrapper* getWrapper(VALUE value); + +} // namespace detail +} // namespace Rice + + + +// ========= Wrapper.ipp ========= +#include + +namespace Rice::detail +{ + inline Wrapper::Wrapper(bool isOwner): isOwner_(isOwner) + { } - template - NativeAttributeSet::NativeAttributeSet(VALUE klass, std::string name, Attribute_T attribute) - : klass_(klass), name_(name), attribute_(attribute) + inline void Wrapper::ruby_mark() + { + for (VALUE value : this->keepAlive_) + { + rb_gc_mark(value); + } + } + + inline void Wrapper::addKeepAlive(VALUE value) { + this->keepAlive_.push_back(value); } - template - inline Resolved NativeAttributeSet::matches(int argc, VALUE* argv, VALUE self) + inline void Wrapper::setOwner(bool value) { - if (argc == 1) - return Resolved{ Convertible::Exact, 1, this }; - else - return Resolved{ Convertible::None, 0, this }; + this->isOwner_ = value; } - template - inline VALUE NativeAttributeSet::operator()(int argc, VALUE* argv, VALUE self) + template + class WrapperValue : public Wrapper { - if constexpr (std::is_fundamental_v> && std::is_pointer_v) + public: + WrapperValue(T& data): data_(std::move(data)) { - static_assert(true, "An fundamental value, such as an integer, cannot be assigned to an attribute that is a pointer."); } - else if constexpr (std::is_same_v, std::string> && std::is_pointer_v) + + ~WrapperValue() { - static_assert(true, "An string cannot be assigned to an attribute that is a pointer."); + Registries::instance.instances.remove(this->get()); } - if (argc != 1) + void* get() override { - throw std::runtime_error("Incorrect number of parameters for setting attribute. Attribute: " + this->name_); + return (void*)&this->data_; } - VALUE value = argv[0]; - - if constexpr (!std::is_null_pointer_v && - !std::is_const_v && - (std::is_fundamental_v || std::is_assignable_v)) + private: + T data_; + }; + + template + class WrapperReference : public Wrapper + { + public: + WrapperReference(T& data): data_(data) { - Receiver_T* nativeSelf = From_Ruby().convert(self); - nativeSelf->*attribute_ = From_Ruby().convert(value); } - else if constexpr (std::is_null_pointer_v && - !std::is_const_v && - (std::is_fundamental_v || std::is_assignable_v)) + + ~WrapperReference() { - *attribute_ = From_Ruby().convert(value); + Registries::instance.instances.remove(this->get()); } - else + + void* get() override { - // Should never get here because define_attr won't compile this code, but just in case! - throw std::invalid_argument("Could not set attribute. Attribute: " + this->name_); + return (void*)&this->data_; } - return value; - } -} // Rice + private: + T& data_; + }; + template + class WrapperPointer : public Wrapper + { + public: + WrapperPointer(T* data, bool isOwner) : Wrapper(isOwner), data_(data) + { + } -// ========= NativeFunction.hpp ========= + ~WrapperPointer() + { + Registries::instance.instances.remove(this->get()); + if constexpr (std::is_destructible_v) + { + if (this->isOwner_) + { + delete this->data_; + } + } + } -namespace Rice::detail -{ - //! The NativeFunction class calls C++ functions/methods/lambdas on behalf of Ruby - /*! The NativeFunction class is an intermediate between Ruby and C++. Every method - * defined in Rice is associated with a NativeFuntion instance that is stored in - * a unordered_map maintained by the MethodData class. The key is the Ruby class - * and method. - * - * When Ruby calls into C++ it invokes the static NativeFunction.call method. This - * method then looks up the NativeFunction instance and calls its ->() operator. - * - * The instance then converts each of the arguments passed from Ruby into their - * C++ equivalents. It then retrieves the C++ object (if there is one, Ruby could - * be calling a free standing method or lambda). Then it calls the C++ method - * and gets back the result. If there is a result (so not void), it is converted - * from a C++ object to a Ruby object and returned back to Ruby. - * - * This class make heavy use of C++ Template metaprogramming to determine - * the types and parameters a method takes. It then uses that information - * to perform type conversion Ruby to C++. - * - * @tparam Receiver_T - The type of C++ class wrapped by Ruby. Althought NativeFunction - * can derive the C++ class (Receiver_T), it can differ per member function. For example, - * std::map has a size() method but that is actually implemented on an ancestor class _Tree. - * Thus Receiver_T is std::map but Function_T::Receiver_T is _Tree. This breaks Rice in two ways. - * First, _Tree is not a registered type. Second, Rice would return a _Tree instance back to - * C++ and not a std::map. - * @tparam Function_T - A template that represents the C++ function - * to call. This typename is automatically deduced by the compiler. - * @tparam IsMethod - A boolean specifying whether the function has - * a self parameter or not. Rice differentiates these two cases by - * calling them methods (self) or functions (no self). - */ - - template - class NativeFunction: Native - { - public: - using NativeFunction_T = NativeFunction; - - // We remove const to avoid an explosion of To_Ruby specializations and Ruby doesn't - // have the concept of constants anyways - using Return_T = typename function_traits::return_type; - using Receiver_T = typename method_traits::Class_T; - using Arg_Ts = typename method_traits::Arg_Ts; - static constexpr std::size_t arity = method_traits::arity; - using From_Ruby_Args_Ts = typename tuple_map::type; - using To_Ruby_T = remove_cv_recursive_t; - - // Register function with Ruby - static void define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo); + void* get() override + { + return (void*)this->data_; + } - public: - // Disallow creating/copying/moving - NativeFunction() = delete; - NativeFunction(const NativeFunction_T&) = delete; - NativeFunction(NativeFunction_T&&) = delete; - void operator=(const NativeFunction_T&) = delete; - void operator=(NativeFunction_T&&) = delete; + private: + T* data_ = nullptr; + }; - Resolved matches(int argc, VALUE* argv, VALUE self) override; - VALUE operator()(int argc, VALUE* argv, VALUE self) override; + // ---- Helper Functions ------- + template + inline VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T& data, bool isOwner) + { + VALUE result = Registries::instance.instances.lookup(&data); - protected: - NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo); + if (result != Qnil) + return result; - private: - template - From_Ruby createFromRuby(); - - // Create NativeArgs which are used to convert values from Ruby to C++ - template - From_Ruby_Args_Ts createFromRuby(std::index_sequence& indices); + Wrapper* wrapper = nullptr; - // Convert C++ value to Ruby - To_Ruby createToRuby(); - - // Convert Ruby argv pointer to Ruby values - std::vector getRubyValues(int argc, VALUE* argv); + // Is this a pointer but cannot be copied? For example a std::unique_ptr + if constexpr (!std::is_void_v && !std::is_copy_constructible_v) + { + wrapper = new Wrapper_T(std::move(data)); + result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + } + // Is this a pointer or smart pointer like std::shared_ptr + else if constexpr (!std::is_void_v) + { + wrapper = new Wrapper_T(data); + result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + } + // Is this a pointer and it cannot copied? This is for std::unique_ptr + // If ruby is the owner than copy the object + else if (isOwner) + { + wrapper = new WrapperValue(data); + result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + } + // Ruby is not the owner so just wrap the reference + else + { + wrapper = new WrapperReference(data); + result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + } - template - Arg_T getNativeValue(std::vector& values); + Registries::instance.instances.add(wrapper->get(), result); - // Convert Ruby values to C++ values - template - Arg_Ts getNativeValues(std::vector& values, std::index_sequence& indices); + return result; + }; - // Figure out what self is - Receiver_T getReceiver(VALUE self); + template + inline VALUE wrap(VALUE klass, rb_data_type_t* rb_type, T* data, bool isOwner) + { + VALUE result = Registries::instance.instances.lookup(data); - // Throw an exception when wrapper cannot be extracted - [[noreturn]] void noWrapper(const VALUE klass, const std::string& wrapper); + if (result != Qnil) + return result; - // Do we need to keep alive any arguments? - void checkKeepAlive(VALUE self, VALUE returnValue, std::vector& rubyValues); + Wrapper* wrapper = nullptr; - // Call the underlying C++ function - VALUE invokeNativeFunction(Arg_Ts&& nativeArgs); - VALUE invokeNativeMethod(VALUE self, Arg_Ts&& nativeArgs); + if constexpr (!std::is_void_v) + { + wrapper = new Wrapper_T(data); + result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + } + else + { + wrapper = new WrapperPointer(data, isOwner); + result = TypedData_Wrap_Struct(klass, rb_type, wrapper); + } - private: - VALUE klass_; - std::string method_name_; - Function_T function_; - From_Ruby_Args_Ts fromRubys_; - To_Ruby toRuby_; - std::unique_ptr methodInfo_; + Registries::instance.instances.add(wrapper->get(), result); + return result; }; -} - -// --------- NativeFunction.ipp --------- -#include -#include -#include -#include - -namespace Rice::detail -{ - template - void NativeFunction::define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo) + template + inline T* unwrap(VALUE value, rb_data_type_t* rb_type, bool transferOwnership) { - // Have we defined this method yet in Ruby? - Identifier identifier(method_name); - const std::vector>& natives = Registries::instance.natives.lookup(klass, identifier.id()); - if (natives.empty()) + Wrapper* wrapper = getWrapper(value, rb_type); + if (transferOwnership) + wrapper->setOwner(false); + + if (wrapper == nullptr) { - // Tell Ruby to invoke the static resolved method defined above - detail::protect(rb_define_method, klass, method_name.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); + std::string message = "Wrapped C++ object is nil. Did you override " + + std::string(detail::protect(rb_obj_classname, value)) + + "#initialize and forget to call super?"; + + throw std::runtime_error(message); } - // Create a NativeFunction instance and save it to the NativeRegistry. There may be multiple - // NativeFunction instances for a specific method because C++ supports method overloading. - NativeFunction_T* nativeFunction = new NativeFunction_T(klass, method_name, std::forward(function), methodInfo); - std::unique_ptr native(nativeFunction); - detail::Registries::instance.natives.add(klass, identifier.id(), native); + return static_cast(wrapper->get()); } - - template - NativeFunction::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo) - : klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo) + + inline Wrapper* getWrapper(VALUE value, rb_data_type_t* rb_type) { - // Create a tuple of NativeArgs that will convert the Ruby values to native values. For - // builtin types NativeArgs will keep a copy of the native value so that it - // can be passed by reference or pointer to the native function. For non-builtin types - // it will just pass the value through. - auto indices = std::make_index_sequence>{}; - this->fromRubys_ = this->createFromRuby(indices); - - this->toRuby_ = this->createToRuby(); + Wrapper* wrapper = nullptr; + TypedData_Get_Struct(value, Wrapper, rb_type, wrapper); + return wrapper; } - template - To_Ruby::To_Ruby_T> NativeFunction::createToRuby() + inline Wrapper* getWrapper(VALUE value) { - // Does the From_Ruby instantiation work with ReturnInfo? - if constexpr (std::is_constructible_v, Return*>) - { - return To_Ruby(&this->methodInfo_->returnInfo); - } - else - { - return To_Ruby(); - } + // Turn off spurious warning on g++ 12 +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + return RTYPEDDATA_P(value) ? static_cast(RTYPEDDATA_DATA(value)) : nullptr; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif } - template - template - From_Ruby NativeFunction::createFromRuby() + template + inline void replace(VALUE value, rb_data_type_t* rb_type, T* data, bool isOwner) { - // Does the From_Ruby instantiation work with Arg? - if constexpr (std::is_constructible_v, Arg*>) - { - return From_Ruby(&this->methodInfo_->arg(I)); - } - else + WrapperPointer* wrapper = nullptr; + TypedData_Get_Struct(value, WrapperPointer, rb_type, wrapper); + if (wrapper) { - return From_Ruby(); + Registries::instance.instances.remove(wrapper->get()); + delete wrapper; } - } - template - template - typename NativeFunction::From_Ruby_Args_Ts NativeFunction::createFromRuby(std::index_sequence& indices) - { - return std::make_tuple(createFromRuby::type>, I>()...); + wrapper = new WrapperPointer(data, isOwner); + RTYPEDDATA_DATA(value) = wrapper; + + Registries::instance.instances.add(data, value); } +} // namespace - template - Resolved NativeFunction::matches(int argc, VALUE* argv, VALUE self) - { - // Return false if Ruby provided more arguments than the C++ method takes - if (argc > arity) - return Resolved{ Convertible::None, 0, this }; +// ========= MethodInfo.hpp ========= - Resolved result { Convertible::Exact, 1, this }; - - MethodInfo* methodInfo = this->methodInfo_.get(); - int index = 0; +#include - // Loop over each FromRuby instance - for_each_tuple(this->fromRubys_, - [&](auto& fromRuby) - { - Convertible convertible = Convertible::None; +namespace Rice +{ + class MethodInfo + { + public: + template + MethodInfo(size_t argCount, const Arg_Ts&...args); - Arg& arg = methodInfo->arg(index); + /** + * Get the rb_scan_args format string for this + * list of arguments. + */ + std::string formatString(); - // Is a VALUE being passed directly to C++ ? - if (arg.isValue() && index < argc) - { - convertible = Convertible::Exact; - } - // If index is less than argc then check with FromRuby if the VALUE is convertible - // to C++. - else if (index < argc) - { - VALUE value = argv[index]; - convertible = fromRuby.is_convertible(value); - } - // Last check if a default value has been set - else if (arg.hasDefaultValue()) - { - convertible = Convertible::Exact; - } + /** + * Add a defined Arg to this list of Arguments + */ + void addArg(const Arg& arg); - result.convertible = result.convertible & convertible; + Arg& arg(size_t pos); - index++; - }); + // Iterator support + std::vector::iterator begin(); + std::vector::iterator end(); - if (arity > 0) - result.parameterMatch = (double)argc / arity; + Return returnInfo; - return result; - } + private: + template + void processArg(const Arg_T& arg); - template - std::vector NativeFunction::getRubyValues(int argc, VALUE* argv) - { - // Setup a tuple for the leading rb_scan_args arguments - std::string scanFormat = this->methodInfo_->formatString(); - std::tuple rbScanArgs = std::forward_as_tuple(argc, argv, scanFormat.c_str()); + std::vector args_; + }; +} - // Create a vector to store the VALUEs that will be returned by rb_scan_args - std::vector rbScanValues(std::tuple_size_v, Qnil); +// ========= MethodInfo.ipp ========= +#include - // Convert the vector to an array so it can be concatenated to a tuple. As importantly - // fill it with pointers to rbScanValues - std::array> rbScanValuePointers; - std::transform(rbScanValues.begin(), rbScanValues.end(), rbScanValuePointers.begin(), - [](VALUE& value) - { - return &value; - }); +namespace Rice +{ + template + inline MethodInfo::MethodInfo(size_t argCount, const Arg_Ts&...args) + { + // Process the passed in arguments + (this->processArg(args), ...); - // Combine the tuples and call rb_scan_args - std::apply(rb_scan_args, std::tuple_cat(rbScanArgs, rbScanValuePointers)); + // Fill in any missing arguments + for (size_t i = this->args_.size(); i < argCount; i++) + { + Arg arg("arg_" + std::to_string(i)); + this->addArg(arg); + } - return rbScanValues; + // TODO - so hacky but update the Arg positions + for (uint32_t i = 0; i < this->args_.size(); i++) + { + this->args_[i].position = i; + } } - template - template - Arg_T NativeFunction::getNativeValue(std::vector& values) + template + inline void MethodInfo::processArg(const Arg_T& arg) { - /* In general the compiler will convert T to const T, but that does not work for converting - T** to const T** (see see https://isocpp.org/wiki/faq/const-correctness#constptrptr-conversion) - which comes up in the OpenCV bindings. - - An alternative solution is updating From_Ruby#convert to become a templated function that specifies - the return type. That works but requires a lot more code changes for this one case and is not - backwards compatible. */ - if constexpr (is_pointer_pointer_v && !std::is_convertible_v, Arg_T>) + if constexpr (std::is_same_v) { - return (Arg_T)std::get(this->fromRubys_).convert(values[I]); + this->addArg(arg); } else { - return std::get(this->fromRubys_).convert(values[I]); + this->returnInfo = arg; } } - template - template - typename NativeFunction::Arg_Ts NativeFunction::getNativeValues(std::vector& values, - std::index_sequence& indices) + inline void MethodInfo::addArg(const Arg& arg) { - /* Loop over each value returned from Ruby and convert it to the appropriate C++ type based - on the arguments (Arg_Ts) required by the C++ function. Arg_T may have const/volatile while - the associated From_Ruby template parameter will not. Thus From_Ruby produces non-const values - which we let the compiler convert to const values as needed. This works except for - T** -> const T**, see comment in getNativeValue method. */ - return std::forward_as_tuple(this->getNativeValue, I>(values)...); + this->args_.push_back(arg); } - template - typename NativeFunction::Receiver_T NativeFunction::getReceiver(VALUE self) + inline std::string MethodInfo::formatString() { - // There is no self parameter - if constexpr (std::is_same_v) - { - return nullptr; - } - // Self parameter is a Ruby VALUE so no conversion is needed - else if constexpr (std::is_same_v) + size_t required = 0; + size_t optional = 0; + + for (const Arg& arg : this->args_) { - return self; + if (arg.hasDefaultValue()) + { + optional++; + } + else + { + required++; + } } - /* This case happens when a class wrapped by Rice is calling a method - defined on an ancestor class. For example, the std::map size method - is defined on _Tree not map. Rice needs to know the actual type - that was wrapped so it can correctly extract the C++ object from - the Ruby object. */ - else if constexpr (!std::is_same_v, Class_T> && - std::is_base_of_v, Class_T>) + + return std::to_string(required) + std::to_string(optional); + } + + inline Arg& MethodInfo::arg(size_t pos) + { + return args_[pos]; + } + + inline std::vector::iterator MethodInfo::begin() + { + return this->args_.begin(); + } + + inline std::vector::iterator MethodInfo::end() + { + return this->args_.end(); + } +} + +// ========= Native.ipp ========= + +namespace Rice::detail +{ + inline bool Resolved::operator<(Resolved other) + { + if (this->convertible != other.convertible) { - Class_T* instance = From_Ruby().convert(self); - return dynamic_cast(instance); + return this->convertible < other.convertible; } - // Self parameter could be derived from Object or it is an C++ instance and - // needs to be unwrapped from Ruby else { - return From_Ruby().convert(self); + return this->parameterMatch < other.parameterMatch; } } - template - VALUE NativeFunction::invokeNativeFunction(Arg_Ts&& nativeArgs) + inline bool Resolved::operator>(Resolved other) { - if constexpr (std::is_void_v) + if (this->convertible != other.convertible) { - std::apply(this->function_, std::forward(nativeArgs)); - return Qnil; + return this->convertible > other.convertible; } else { - // Call the native method and get the result - Return_T nativeResult = std::apply(this->function_, std::forward(nativeArgs)); - - // Return the result - return this->toRuby_.convert(nativeResult); + return this->parameterMatch > other.parameterMatch; } } - template - VALUE NativeFunction::invokeNativeMethod(VALUE self, Arg_Ts&& nativeArgs) + inline VALUE Native::resolve(int argc, VALUE* argv, VALUE self) { - Receiver_T receiver = this->getReceiver(self); - auto selfAndNativeArgs = std::tuple_cat(std::forward_as_tuple(receiver), std::forward(nativeArgs)); + /* This method is called from Ruby and is responsible for determining the correct + Native object (ie, NativeFunction, NativeIterator, NativeAttributeGet and + NativeAttributeSet) that shoudl be used to invoke the underlying C++ code. + Most of the time there will be a single Native object registered for a C++ function, + method, constructor, iterator or attribute. However, there can be multiple Natives + when a C++ function/method/construtor is overloaded. - if constexpr (std::is_void_v) + In that case, the code iterates over each Native and calls its matches method. The matches + method returns a Resolved object which includes a Convertible field and parameterMatch field. + The Convertible field is an enum that specifies if the types of the values supplied by Ruby + match the types of the C++ function parameters. Allowed values include can be Exact (example Ruby into to C++ int), + TypeCast (example Ruby into to C++ float) or None (cannot be converted). + + The parameterMatch field is simply the number or arguments provided by Ruby divided by the + number of arguments required by C++. These numbers can be different because C++ method + parameters can have default values. + + Taking these two values into account, the method sorts the Natives and picks the one with the + highest score (Convertible::Exact and 1.0 for parameterMatch). Thus given these two C++ functions: + + void some_method(int a); + void some_mtehod(int a, float b = 2.0). + + A call from ruby of some_method(1) will exactly match both signatures, but the first one + will be chosen because the parameterMatch will be 1.0 for the first overload but 0.5 + for the second. */ + + Native* native = nullptr; + + ID methodId; + VALUE klass; + if (!rb_frame_method_id_and_class(&methodId, &klass)) { - std::apply(this->function_, std::forward(selfAndNativeArgs)); - return Qnil; + rb_raise(rb_eRuntimeError, "Cannot get method id and class for function"); } - else + + // Execute the function but make sure to catch any C++ exceptions! + return cpp_protect([&] { - Return_T nativeResult = std::apply(this->function_, std::forward(selfAndNativeArgs)); + const std::vector>& natives = Registries::instance.natives.lookup(klass, methodId); - // Special handling if the method returns self. If so we do not want - // to create a new Ruby wrapper object and instead return self. - if constexpr (std::is_same_v, intrinsic_type>) - { - if constexpr (std::is_pointer_v && std::is_pointer_v) - { - if (nativeResult == receiver) - return self; - } - else if constexpr (std::is_pointer_v && std::is_reference_v) - { - if (nativeResult == &receiver) - return self; - } - else if constexpr (std::is_reference_v && std::is_pointer_v) - { - if (&nativeResult == receiver) - return self; - } - else if constexpr (std::is_reference_v && std::is_reference_v) - { - if (&nativeResult == &receiver) - return self; - } - } - - return this->toRuby_.convert(nativeResult); - } - } - - template - void NativeFunction::noWrapper(const VALUE klass, const std::string& wrapper) - { - std::stringstream message; - - message << "When calling the method `"; - message << this->method_name_; - message << "' we could not find the wrapper for the '"; - message << rb_obj_classname(klass); - message << "' "; - message << wrapper; - message << " type. You should not use keepAlive() on a Return or Arg that is a builtin Rice type."; - - throw std::runtime_error(message.str()); - } - - template - void NativeFunction::checkKeepAlive(VALUE self, VALUE returnValue, std::vector& rubyValues) - { - // selfWrapper will be nullptr if this(self) is a builtin type and not an external(wrapped) type - // it is highly unlikely that keepAlive is used in this case but we check anyway - Wrapper* selfWrapper = getWrapper(self); - - // Check function arguments - for (const Arg& arg : (*this->methodInfo_)) - { - if (arg.isKeepAlive()) + if (natives.size() == 1) { - if (selfWrapper == nullptr) - { - noWrapper(self, "self"); - } - selfWrapper->addKeepAlive(rubyValues[arg.position]); + native = natives.front().get(); } - } - - // Check return value - if (this->methodInfo_->returnInfo.isKeepAlive()) - { - if (selfWrapper == nullptr) + else if (natives.size() == 0) { - noWrapper(self, "self"); + Identifier identifier(methodId); + rb_raise(rb_eArgError, "Could not find method call for %s#%s", rb_class2name(klass), identifier.c_str()); } - - // returnWrapper will be nullptr if returnValue is a built-in type and not an external(wrapped) type - Wrapper* returnWrapper = getWrapper(returnValue); - if (returnWrapper == nullptr) + else { - noWrapper(returnValue, "return"); - } - returnWrapper->addKeepAlive(self); - } - } - - template - VALUE NativeFunction::operator()(int argc, VALUE* argv, VALUE self) - { - // Get the ruby values - std::vector rubyValues = this->getRubyValues(argc, argv); - - auto indices = std::make_index_sequence>{}; + // Loop over every native to see how well they match the Ruby parameters + std::vector resolves; + std::transform(natives.begin(), natives.end(), + std::back_inserter(resolves), + [&](const std::unique_ptr& native) + { + return native->matches(argc, argv, self); + }); - // Convert the Ruby values to native values - Arg_Ts nativeValues = this->getNativeValues(rubyValues, indices); + // Now sort from best to worst + std::sort(resolves.begin(), resolves.end(), std::greater{}); - // Now call the native method - VALUE result = Qnil; - if constexpr (std::is_same_v) - { - result = this->invokeNativeFunction(std::forward(nativeValues)); - } - else - { - result = this->invokeNativeMethod(self, std::forward(nativeValues)); - } + // Get the best one + Resolved resolved = resolves.front(); - // Check if any function arguments or return values need to have their lifetimes tied to the receiver - this->checkKeepAlive(self, result, rubyValues); + // Did it match? + if (resolved.convertible != Convertible::None) + { + native = resolved.native; + } + else + { + // Special case == to make the RubyMine debugger work, it liks calling == with a Module as + // the other argument, thus breaking if C++ operator== is implemented. + Identifier identifier(methodId); + if (identifier.str() == "==") + { + return detail::protect(rb_call_super, argc, argv); + } + else + { + rb_raise(rb_eArgError, "Could not resolve method call for %s#%s", rb_class2name(klass), identifier.c_str()); + } + } + } - return result; + // Call the C++ function + return (*native)(argc, argv, self); + }); } } - -// ========= NativeIterator.hpp ========= -#ifndef Rice_NativeIterator__hpp_ -#define Rice_NativeIterator__hpp_ +// ========= NativeAttributeGet.hpp ========= -namespace Rice::detail +namespace Rice { - template - class NativeIterator: Native + enum class AttrAccess { - public: - using NativeIterator_T = NativeIterator; - using Iterator_T = typename function_traits::return_type; - using Value_T = typename std::iterator_traits::value_type; - using Reference_T = typename std::iterator_traits::reference; - using Difference_T = typename std::iterator_traits::difference_type; - using To_Ruby_T = remove_cv_recursive_t; - - public: - // Register function with Ruby - void static define(VALUE klass, std::string method_name, Iterator_Func_T begin, Iterator_Func_T end); - - public: - // Disallow creating/copying/moving - NativeIterator() = delete; - NativeIterator(const NativeIterator_T&) = delete; - NativeIterator(NativeIterator_T&&) = delete; - void operator=(const NativeIterator_T&) = delete; - void operator=(NativeIterator_T&&) = delete; - - Resolved matches(int argc, VALUE* argv, VALUE self) override; - VALUE operator()(int argc, VALUE* argv, VALUE self) override; - - protected: - NativeIterator(VALUE klass, std::string method_name, Iterator_Func_T begin, Iterator_Func_T end); - - private: - VALUE createRubyEnumerator(VALUE self); - - private: - VALUE klass_; - std::string method_name_; - Iterator_Func_T begin_; - Iterator_Func_T end_; + ReadWrite, + Read, + Write }; -} - -// --------- NativeIterator.ipp --------- -#include -#include -#include - - -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 resolveIterator static method defined above - detail::protect(rb_define_method, klass, method_name.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); - - // Create a NativeIterator instance and save it to the NativeRegistry. There may be multiple - // NativeFunction instances for a specific method because C++ supports method overloading. - NativeIterator_T* nativeIterator = new NativeIterator_T(klass, method_name, begin, end); - std::unique_ptr native(nativeIterator); - - Identifier identifier(method_name); - detail::Registries::instance.natives.add(klass, identifier.id(), native); - } - - 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 Resolved NativeIterator::matches(int argc, VALUE* argv, VALUE self) - { - return Resolved{ Convertible::Exact, 1.0, this }; - } - template - inline VALUE NativeIterator::createRubyEnumerator(VALUE self) + namespace detail { - auto rb_size_function = [](VALUE recv, VALUE argv, VALUE eobj) -> VALUE + template + class NativeAttributeGet: Native { - // 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 - // Class is easy - VALUE klass = protect(rb_class_of, recv); - // Read the method_id from an attribute we added to the enumerator instance - Identifier identifier = protect(rb_ivar_get, eobj, rb_intern("rice_method")); - - const std::vector>& natives = detail::Registries::instance.natives.lookup(klass, identifier.id()); - NativeIterator_T* iterator = static_cast(natives.back().get()); - - // 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); - }); - }; - - Identifier identifier(this->method_name_); - VALUE enumerator = protect(rb_enumeratorize_with_size, self, identifier.to_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 - protect(rb_ivar_set, enumerator, rb_intern("rice_method"), identifier.id()); + public: + using NativeAttribute_T = NativeAttributeGet; - return enumerator; - } - - template - inline VALUE NativeIterator::operator()(int argc, VALUE* argv, 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; - } - } -} -#endif // Rice_NativeIterator__hpp_ -// ========= HandlerRegistration.hpp ========= - - -namespace Rice -{ - // Register exception handler - template - detail::HandlerRegistry register_handler(Functor_T functor) - { - return detail::Registries::instance.handlers.add(std::forward(functor)); - } -} - -// C++ classes for using the Ruby API - -// ========= Object.hpp ========= - - -// --------- Object_defn.hpp --------- -#ifndef Rice__Object_defn__hpp_ -#define Rice__Object_defn__hpp_ - -/*! \file Object.hpp - */ - - -#include -#include - -namespace Rice -{ - class Class; - class String; - class Array; - - //! The base class for all Objects - /*! Perhaps the name "Object" is a misnomer, because this class really - * holds an object reference, not an object. - */ - class Object - { - public: - //! Encapsulate an existing ruby object. - Object(VALUE value = Qnil) : value_(value) {} - - //! Destructor - virtual ~Object(); - - // Enable copying - Object(const Object& other) = default; - Object& operator=(const Object& other) = default; - - // Enable moving - Object(Object&& other); - Object& operator=(Object&& other); - - //! Returns false if the object is nil or false; returns true - //! otherwise. - // Having this conversion also prevents accidental conversion to - // undesired integral types (e.g. long or int) by making the - // conversion ambiguous. - bool test() const { return RTEST(value_); } - - //! Returns false if the object is nil or false; returns true - //! otherwise. - operator bool() const { return test(); } - - //! Returns true if the object is nil, false otherwise. - bool is_nil() const { return NIL_P(value_); } - - //! Implicit conversion to VALUE. - operator VALUE() const { return value_; } - - //! Explicitly get the encapsulated VALUE. - // Returns a const ref so that Address_Registration_Guard can access - // the address where the VALUE is stored - VALUE const volatile& value() const { return value_; } - - //! Get the class of an object. - /*! \return the object's Class. - */ - Class class_of() const; - - //! Compare this object to another object. - /*! Gets the result of self <=> other and returns the result. The - * result will be less than zero if self < other, greater than zero - * if self > other, and equal to zero if self == other. - */ - int compare(Object const& other) const; - - //! Return a string representation of an object. - /*! \return the result of calling to_s on the object. A String is not - * returned, because it is not possible to return an instance of a - * derived class. - */ - String to_s() const; - - //! Return the name of an object's class. - String class_name() const; - - //! Inspect the object. - /*! \return the result of calling inspect on the object. A String is - * not returned, because it is not possible to return an instance of - * a derived class. - */ - String inspect() const; - - //! Freeze the object. - void freeze(); - - //! Determine if the object is frozen. - /*! \return true if the object is frozen, false otherwise. - */ - bool is_frozen() const; - - //! Evaluate the given string in the context of the object. - /*! This is equivalant to calling obj.instance_eval(s) from inside the - * interpreter. - * \return the result of the expression. - */ - Object instance_eval(String const& s); - - //! Return the type of the underlying C object. - /*! This is equivalent to calling rb_type(obj). - * \return the type of the underlying C object (e.g. T_DATA, T_ARRAY, - * etc.). - */ - int rb_type() const; - - //! Return the object's id - VALUE object_id() const; - - //! Determine whether the object is an instance of a class/module. - /*! \param klass a class or module. - * \return true if the object is an instance of the given - * class/module or one of its descendants. - */ - bool is_a(Object klass) const; - - //! Determine if the objects responds to a method. - /*! \param id the name of the method - * \return true if the objects responds to the method, false - * otherwise. - */ - bool respond_to(Identifier id) const; - - //! Determine whether class is the object's class. - /*! \param klass a class. - * \return true if the object is an instance of the given class. - */ - bool is_instance_of(Object klass) const; - - //! Determine whether the Ruby VALUEs wrapped by this - //! object are the same object. Maps to Object::equal? - /*! \param other a Object. - */ - bool is_equal(const Object& other) const; - - //! Determine whether the Ruby VALUEs wrapped by this - //! object are equivalent. Maps to Object::eql? - /*! \param other a Object. - */ - bool is_eql(const Object& other) const; - - //! Set an instance variable. - /*! \param name the name of the instance variable to set (including - * the leading @ sign) - * \param value the value of the variable, which will be converted to - * a Ruby type if necessary. - */ - template - void iv_set(Identifier name, T const& value); - - //! Get the value of an instance variable. - /*! \param name the name of the instance variable to get - * \return the value of the instance variable - */ - Object iv_get(Identifier name) const; - - //! Get the value of an instance variable, but don't warn if it is - //unset. - /*! \param name the name of the instance variable to get - * \return the value of the instance variable - */ - Object attr_get(Identifier name) const; - - //! Call the Ruby method specified by 'id' on object 'obj'. - /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to - * Ruby objects with to_ruby<>. To call methods expecting keyword arguments, - * use call_kw. - * - * E.g.: - * \code - * Rice::Object obj = x.call("foo", "one", 2); - * \endcode - * - * If a return type is specified, the return value will automatically be - * converted to that type as long as 'from_ruby' exists for that type. - * - * E.g.: - * \code - * float ret = x.call("foo", z, 42); - * \endcode - */ - template - Object call(Identifier id, Arg_Ts... args) const; - - //! Call the Ruby method specified by 'id' on object 'obj'. - /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to - * Ruby objects with to_ruby<>. The final argument must be a Hash and will be treated - * as keyword arguments to the function. - * - * E.g.: - * \code - * Rice::Hash kw; - * kw[":argument"] = String("one") - * Rice::Object obj = x.call_kw("foo", kw); - * \endcode - * - * If a return type is specified, the return value will automatically be - * converted to that type as long as 'from_ruby' exists for that type. - * - * E.g.: - * \code - * float ret = x.call_kw("foo", kw); - * \endcode - */ - template - Object call_kw(Identifier id, Arg_Ts... args) const; - - //! Vectorized call. - /*! Calls the method identified by id with the list of arguments - * identified by args. - * \param id the name of the method to call - * \param args the arguments to the method - * \return the return value of the method call - */ - Object vcall(Identifier id, Array args); - - //! Get a constant. - /*! \param name the name of the constant to get. - * \return the value of the constant. - */ - Object const_get(Identifier name) const; - - //! Determine whether a constant is defined. - /*! \param name the name of the constant to check. - * \return true if the constant is defined in this module or false - * otherwise. - */ - bool const_defined(Identifier name) const; - - //! Set a constant. - /*! \param name the name of the constant to set. - * \param value the value of the constant. - * \return *this - */ - inline Object const_set(Identifier name, Object value); - - //! Set a constant if it not already set. - /*! \param name the name of the constant to set. - * \param value the value of the constant. - * \return *this - */ - inline Object const_set_maybe(Identifier name, Object value); - - //! Remove a constant. - /*! \param name the name of the constant to remove. - */ - void remove_const(Identifier name); - - protected: - //! Set the encapsulated value. - void set_value(VALUE v); - - private: - volatile VALUE value_; - }; - - std::ostream& operator<<(std::ostream& out, Object const& obj); - - bool operator==(Object const& lhs, Object const& rhs); - bool operator!=(Object const& lhs, Object const& rhs); - bool operator<(Object const& lhs, Object const& rhs); - bool operator>(Object const& lhs, Object const& rhs); - - extern Object const Nil; - extern Object const True; - extern Object const False; - extern Object const Undef; -} // namespace Rice + using T = typename attribute_traits::attr_type; + using Receiver_T = typename attribute_traits::class_type; + using To_Ruby_T = remove_cv_recursive_t; -#endif // Rice__Object_defn__hpp_ + public: + // Register attribute getter with Ruby + static void define(VALUE klass, std::string name, Attribute_T attribute); -// --------- Object.ipp --------- -#ifndef Rice__Object__ipp_ -#define Rice__Object__ipp_ + public: + // Disallow creating/copying/moving + NativeAttributeGet() = delete; + NativeAttributeGet(const NativeAttribute_T&) = delete; + NativeAttributeGet(NativeAttribute_T&&) = delete; + void operator=(const NativeAttribute_T&) = delete; + void operator=(NativeAttribute_T&&) = delete; -namespace Rice -{ - inline const Object Nil(Qnil); - inline const Object True(Qtrue); - inline const Object False(Qfalse); - inline const Object Undef(Qundef); + Resolved matches(int argc, VALUE* argv, VALUE self) override; + VALUE operator()(int argc, VALUE* argv, VALUE self) override; - // Ruby auto detects VALUEs in the stack, so when an Object gets deleted make sure - // to clean up in case it is on the stack - inline Object::~Object() - { - this->value_ = Qnil; - } + protected: + NativeAttributeGet(VALUE klass, std::string name, Attribute_T attr); - // Move constructor - inline Object::Object(Object&& other) - { - this->value_ = other.value_; - other.value_ = Qnil; - } + private: + VALUE klass_; + std::string name_; + Attribute_T attribute_; + }; + } // detail +} // Rice - // Move assignment - inline Object& Object::operator=(Object&& other) - { - this->value_ = other.value_; - other.value_ = Qnil; - return *this; - } - template - inline Object Object::call(Identifier id, Arg_Ts... args) const - { - /* IMPORTANT - We store VALUEs in an array that is a local variable. - That allows the Ruby garbage collector to find them when scanning - the stack and thus mark them. If instead we use a vector, then Ruby's GC - can't find the VALUEs and may garbage collect them before they are sent - to the destination method resulting in a segmentation fault. This is - easy to duplicate by setting GC.stress to true and calling a constructor - that takes multiple values like a std::pair wrapper. */ - std::array values = { detail::To_Ruby>().convert(args)... }; - return detail::protect(rb_funcallv, value(), id.id(), (int)values.size(), (const VALUE*)values.data()); - } +// ========= NativeAttributeGet.ipp ========= +#include +#include - template - inline Object Object::call_kw(Identifier id, Arg_Ts... args) const - { - /* IMPORTANT - See call() above */ - std::array values = { detail::To_Ruby>().convert(args)... }; - return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_KEYWORDS); - } - template - inline void Object::iv_set(Identifier name, T const& value) +namespace Rice::detail +{ + template + void NativeAttributeGet::define(VALUE klass, std::string name, Attribute_T attribute) { - detail::protect(rb_ivar_set, this->value(), name.id(), detail::To_Ruby().convert(value)); - } + // Create a NativeAttributeGet that Ruby will call to read/write C++ variables + NativeAttribute_T* nativeAttribute = new NativeAttribute_T(klass, name, std::forward(attribute)); + std::unique_ptr native(nativeAttribute); - inline int Object::compare(Object const& other) const - { - Object result = call("<=>", other); - return detail::From_Ruby().convert(result); - } + detail::protect(rb_define_method, klass, name.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); - inline bool Object::is_equal(const Object& other) const - { - VALUE result = detail::protect(rb_equal, this->value_, other.value_); - return RB_TEST(result); + // Add to native registry. Since attributes cannot be overridden, there is no need to set the + // matches or calls function pointer. Instead Ruby can call the static call method defined on + // this class (&NativeAttribute_T::get). + Identifier identifier(name); + detail::Registries::instance.natives.add(klass, identifier.id(), native); } - inline bool Object::is_eql(const Object& other) const + template + inline Resolved NativeAttributeGet::matches(int argc, VALUE* argv, VALUE self) { - VALUE result = detail::protect(rb_eql, this->value_, other.value_); - return RB_TEST(result); + if (argc == 0) + return Resolved { Convertible::Exact, 1, this }; + else + return Resolved{ Convertible::None, 0, this }; } - - inline void Object::freeze() + + template + NativeAttributeGet::NativeAttributeGet(VALUE klass, std::string name, Attribute_T attribute) + : klass_(klass), name_(name), attribute_(attribute) { - detail::protect(rb_obj_freeze, value()); } - inline bool Object::is_frozen() const + template + inline VALUE NativeAttributeGet::operator()(int argc, VALUE* argv, VALUE self) { - return RB_OBJ_FROZEN(value()); + if constexpr (std::is_member_object_pointer_v) + { + Receiver_T* nativeSelf = From_Ruby().convert(self); + return To_Ruby().convert(nativeSelf->*attribute_); + } + else + { + return To_Ruby().convert(*attribute_); + } } +} // Rice - inline int Object::rb_type() const - { - return ::rb_type(this->value()); - } +// ========= NativeAttributeSet.hpp ========= - inline VALUE Object::object_id() const - { - return detail::protect(rb_obj_id, this->value()); - } - inline bool Object::is_a(Object klass) const +namespace Rice +{ + namespace detail { - VALUE result = detail::protect(rb_obj_is_kind_of, this->value(), klass.value()); - return RB_TEST(result); - } + template + class NativeAttributeSet: Native + { + public: + using NativeAttribute_T = NativeAttributeSet; + using Attr_T = typename attribute_traits::attr_type; + using T_Unqualified = remove_cv_recursive_t; + using Receiver_T = typename attribute_traits::class_type; - inline bool Object::respond_to(Identifier id) const - { - return bool(rb_respond_to(this->value(), id.id())); - } + public: + // Register attribute getter/setter with Ruby + static void define(VALUE klass, std::string name, Attribute_T attribute); - inline bool Object::is_instance_of(Object klass) const - { - VALUE result = detail::protect(rb_obj_is_instance_of, this->value(), klass.value()); - return RB_TEST(result); - } + public: + // Disallow creating/copying/moving + NativeAttributeSet() = delete; + NativeAttributeSet(const NativeAttribute_T&) = delete; + NativeAttributeSet(NativeAttribute_T&&) = delete; + void operator=(const NativeAttribute_T&) = delete; + void operator=(NativeAttribute_T&&) = delete; - inline Object Object::iv_get(Identifier name) const - { - return detail::protect(rb_ivar_get, this->value(), name.id()); - } + Resolved matches(int argc, VALUE* argv, VALUE self) override; + VALUE operator()(int argc, VALUE* argv, VALUE self) override; - inline Object Object::attr_get(Identifier name) const - { - return detail::protect(rb_attr_get, this->value(), name.id()); - } + protected: + NativeAttributeSet(VALUE klass, std::string name, Attribute_T attr); - inline void Object::set_value(VALUE v) - { - value_ = v; - } + private: + VALUE klass_; + std::string name_; + Attribute_T attribute_; + }; + } // detail +} // Rice - inline Object Object::const_get(Identifier name) const - { - return detail::protect(rb_const_get, this->value(), name.id()); - } - inline bool Object::const_defined(Identifier name) const - { - size_t result = detail::protect(rb_const_defined, this->value(), name.id()); - return bool(result); - } +// ========= NativeAttributeSet.ipp ========= +#include +#include - inline Object Object::const_set(Identifier name, Object value) - { - detail::protect(rb_const_set, this->value(), name.id(), value.value()); - return value; - } - inline Object Object::const_set_maybe(Identifier name, Object value) +namespace Rice::detail +{ + template + void NativeAttributeSet::define(VALUE klass, std::string name, Attribute_T attribute) { - if (!this->const_defined(name)) - { - this->const_set(name, value); - } - return value; - } + // Create a NativeAttributeSet that Ruby will call to read/write C++ variables + NativeAttribute_T* nativeAttribute = new NativeAttribute_T(klass, name, std::forward(attribute)); + std::unique_ptr native(nativeAttribute); - inline void Object::remove_const(Identifier name) - { - detail::protect(rb_mod_remove_const, this->value(), name.to_sym()); - } + // Define the write method name + std::string setter = name + "="; - inline bool operator==(Object const& lhs, Object const& rhs) - { - VALUE result = detail::protect(rb_equal, lhs.value(), rhs.value()); - return result == Qtrue ? true : false; - } + // Tell Ruby to invoke the static method write to get the attribute value + detail::protect(rb_define_method, klass, setter.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); - inline bool operator!=(Object const& lhs, Object const& rhs) - { - return !(lhs == rhs); + // Add to native registry. Since attributes cannot be overridden, there is no need to set the + // matches or calls function pointer. Instead Ruby can call the static call method defined on + // this class (&NativeAttribute_T::set). + Identifier identifier(setter); + detail::Registries::instance.natives.add(klass, identifier.id(), native); } - inline bool operator<(Object const& lhs, Object const& rhs) + template + NativeAttributeSet::NativeAttributeSet(VALUE klass, std::string name, Attribute_T attribute) + : klass_(klass), name_(name), attribute_(attribute) { - Object result = lhs.call("<", rhs); - return result.test(); } - inline bool operator>(Object const& lhs, Object const& rhs) + template + inline Resolved NativeAttributeSet::matches(int argc, VALUE* argv, VALUE self) { - Object result = lhs.call(">", rhs); - return result.test(); + if (argc == 1) + return Resolved{ Convertible::Exact, 1, this }; + else + return Resolved{ Convertible::None, 0, this }; } -} -namespace Rice::detail -{ - template<> - struct Type + template + inline VALUE NativeAttributeSet::operator()(int argc, VALUE* argv, VALUE self) { - static bool verify() + if constexpr (std::is_fundamental_v> && std::is_pointer_v) { - return true; + static_assert(true, "An fundamental value, such as an integer, cannot be assigned to an attribute that is a pointer."); } - }; - - template<> - class To_Ruby - { - public: - static VALUE convert(Object const& x) + else if constexpr (std::is_same_v, std::string> && std::is_pointer_v) { - return x.value(); + static_assert(true, "An string cannot be assigned to an attribute that is a pointer."); } - }; - template<> - class To_Ruby - { - public: - static VALUE convert(Object const& x) + if (argc != 1) { - return x.value(); + throw std::runtime_error("Incorrect number of parameters for setting attribute. Attribute: " + this->name_); } - }; - template<> - class From_Ruby - { - public: - Convertible is_convertible(VALUE value) + VALUE value = argv[0]; + + if constexpr (!std::is_null_pointer_v && + !std::is_const_v && + (std::is_fundamental_v || std::is_assignable_v)) { - switch (rb_type(value)) - { - case RUBY_T_OBJECT: - return Convertible::Exact; - break; - default: - return Convertible::None; - } + Receiver_T* nativeSelf = From_Ruby().convert(self); + nativeSelf->*attribute_ = From_Ruby().convert(value); } - - Object convert(VALUE value) + else if constexpr (std::is_null_pointer_v && + !std::is_const_v && + (std::is_fundamental_v || std::is_assignable_v)) { - return Object(value); + *attribute_ = From_Ruby().convert(value); + } + else + { + // Should never get here because define_attr won't compile this code, but just in case! + throw std::invalid_argument("Could not set attribute. Attribute: " + this->name_); } - }; -} -#endif // Rice__Object__ipp_ - - - -// ========= Builtin_Object.hpp ========= + return value; + } +} // Rice -// --------- Builtin_Object_defn.hpp --------- -#ifndef Rice__Builtin_Object_defn__hpp_ -#define Rice__Builtin_Object_defn__hpp_ +// ========= NativeFunction.hpp ========= -namespace Rice +namespace Rice::detail { - //! A smartpointer-like wrapper for Ruby builtin objects. - /*! A builtin object is one of Ruby's internal types, e.g. RArray or - * RString. Every builtin type structure has a corresponding integer - * type number (e.g T_ARRAY for RArray or T_STRING for RString). This - * class is a wrapper for those types of objects, primarily useful as a - * base class for other wrapper classes like Array and Hash. + //! The NativeFunction class calls C++ functions/methods/lambdas on behalf of Ruby + /*! The NativeFunction class is an intermediate between Ruby and C++. Every method + * defined in Rice is associated with a NativeFuntion instance that is stored in + * a unordered_map maintained by the MethodData class. The key is the Ruby class + * and method. + * + * When Ruby calls into C++ it invokes the static NativeFunction.call method. This + * method then looks up the NativeFunction instance and calls its ->() operator. + * + * The instance then converts each of the arguments passed from Ruby into their + * C++ equivalents. It then retrieves the C++ object (if there is one, Ruby could + * be calling a free standing method or lambda). Then it calls the C++ method + * and gets back the result. If there is a result (so not void), it is converted + * from a C++ object to a Ruby object and returned back to Ruby. + * + * This class make heavy use of C++ Template metaprogramming to determine + * the types and parameters a method takes. It then uses that information + * to perform type conversion Ruby to C++. + * + * @tparam Receiver_T - The type of C++ class wrapped by Ruby. Althought NativeFunction + * can derive the C++ class (Receiver_T), it can differ per member function. For example, + * std::map has a size() method but that is actually implemented on an ancestor class _Tree. + * Thus Receiver_T is std::map but Function_T::Receiver_T is _Tree. This breaks Rice in two ways. + * First, _Tree is not a registered type. Second, Rice would return a _Tree instance back to + * C++ and not a std::map. + * @tparam Function_T - A template that represents the C++ function + * to call. This typename is automatically deduced by the compiler. + * @tparam IsMethod - A boolean specifying whether the function has + * a self parameter or not. Rice differentiates these two cases by + * calling them methods (self) or functions (no self). */ - template - class Builtin_Object - : public Object + + template + class NativeFunction: Native { public: - //! Wrap an already allocated Ruby object. - /*! Checks to see if the object is an object of type Builtin_Type; a - * C++ exception is thrown if this is not the case. - * \param value the object to be wrapped. - */ - Builtin_Object(Object value); + using NativeFunction_T = NativeFunction; - RObject& operator*() const; //!< Return a reference to obj_ - RObject* operator->() const; //!< Return a pointer to obj_ - RObject* get() const; //!< Return a pointer to obj_ + // We remove const to avoid an explosion of To_Ruby specializations and Ruby doesn't + // have the concept of constants anyways + using Return_T = typename function_traits::return_type; + using Receiver_T = typename method_traits::Class_T; + using Arg_Ts = typename method_traits::Arg_Ts; + static constexpr std::size_t arity = method_traits::arity; + using From_Ruby_Args_Ts = typename tuple_map::type; + using To_Ruby_T = remove_cv_recursive_t; + + // Register function with Ruby + static void define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo); + + public: + // Disallow creating/copying/moving + NativeFunction() = delete; + NativeFunction(const NativeFunction_T&) = delete; + NativeFunction(NativeFunction_T&&) = delete; + void operator=(const NativeFunction_T&) = delete; + void operator=(NativeFunction_T&&) = delete; + + Resolved matches(int argc, VALUE* argv, VALUE self) override; + VALUE operator()(int argc, VALUE* argv, VALUE self) override; + + protected: + NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo); + + private: + template + From_Ruby createFromRuby(); + + // Create NativeArgs which are used to convert values from Ruby to C++ + template + From_Ruby_Args_Ts createFromRuby(std::index_sequence& indices); + + // Convert C++ value to Ruby + To_Ruby createToRuby(); + + // Convert Ruby argv pointer to Ruby values + std::vector getRubyValues(int argc, VALUE* argv); + + template + Arg_T getNativeValue(std::vector& values); + + // Convert Ruby values to C++ values + template + Arg_Ts getNativeValues(std::vector& values, std::index_sequence& indices); + + // Figure out what self is + Receiver_T getReceiver(VALUE self); + + // Throw an exception when wrapper cannot be extracted + [[noreturn]] void noWrapper(const VALUE klass, const std::string& wrapper); + + // Do we need to keep alive any arguments? + void checkKeepAlive(VALUE self, VALUE returnValue, std::vector& rubyValues); + + // Call the underlying C++ function + VALUE invokeNativeFunction(Arg_Ts&& nativeArgs); + VALUE invokeNativeMethod(VALUE self, Arg_Ts&& nativeArgs); + + private: + VALUE klass_; + std::string method_name_; + Function_T function_; + From_Ruby_Args_Ts fromRubys_; + To_Ruby toRuby_; + std::unique_ptr methodInfo_; }; -} // namespace Rice +} -#endif // Rice__Builtin_Object_defn__hpp_ -// --------- Builtin_Object.ipp --------- -#ifndef Rice__Builtin_Object__ipp_ -#define Rice__Builtin_Object__ipp_ +// ========= NativeFunction.ipp ========= +#include #include +#include +#include -namespace Rice + +namespace Rice::detail { - namespace detail + template + void NativeFunction::define(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo) { - inline VALUE check_type(Object value, int type) + // Have we defined this method yet in Ruby? + Identifier identifier(method_name); + const std::vector>& natives = Registries::instance.natives.lookup(klass, identifier.id()); + if (natives.empty()) { - detail::protect(rb_check_type, value.value(), type); - return Qnil; + // Tell Ruby to invoke the static resolved method defined above + detail::protect(rb_define_method, klass, method_name.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); } + + // Create a NativeFunction instance and save it to the NativeRegistry. There may be multiple + // NativeFunction instances for a specific method because C++ supports method overloading. + NativeFunction_T* nativeFunction = new NativeFunction_T(klass, method_name, std::forward(function), methodInfo); + std::unique_ptr native(nativeFunction); + detail::Registries::instance.natives.add(klass, identifier.id(), native); } - template - inline Builtin_Object::Builtin_Object(Object value) : Object(value) + template + NativeFunction::NativeFunction(VALUE klass, std::string method_name, Function_T function, MethodInfo* methodInfo) + : klass_(klass), method_name_(method_name), function_(function), methodInfo_(methodInfo) { - detail::check_type(value, Builtin_Type); + // Create a tuple of NativeArgs that will convert the Ruby values to native values. For + // builtin types NativeArgs will keep a copy of the native value so that it + // can be passed by reference or pointer to the native function. For non-builtin types + // it will just pass the value through. + auto indices = std::make_index_sequence>{}; + this->fromRubys_ = this->createFromRuby(indices); + + this->toRuby_ = this->createToRuby(); } - template - inline RObject& Builtin_Object::operator*() const + template + To_Ruby::To_Ruby_T> NativeFunction::createToRuby() { - return *ROBJECT(this->value()); + // Does the From_Ruby instantiation work with ReturnInfo? + if constexpr (std::is_constructible_v, Return*>) + { + return To_Ruby(&this->methodInfo_->returnInfo); + } + else + { + return To_Ruby(); + } } - template - inline RObject* Builtin_Object::operator->() const + template + template + From_Ruby NativeFunction::createFromRuby() { - return ROBJECT(this->value()); + // Does the From_Ruby instantiation work with Arg? + if constexpr (std::is_constructible_v, Arg*>) + { + return From_Ruby(&this->methodInfo_->arg(I)); + } + else + { + return From_Ruby(); + } } - template - inline RObject* Builtin_Object::get() const + template + template + typename NativeFunction::From_Ruby_Args_Ts NativeFunction::createFromRuby(std::index_sequence& indices) { - return ROBJECT(this->value()); + return std::make_tuple(createFromRuby::type>, I>()...); } -} // namespace Rice - -#endif // Rice__Builtin_Object__ipp_ - -// ========= String.hpp ========= - -namespace Rice -{ - //! A Wraper for the ruby String class. - /*! This class provides a C++-style interface to ruby's String class and - * its associated rb_str_* functions. - * - * Example: - * \code - * String s(String::format("%s: %d", "foo", 42)); - * std::cout << s.length() << std::endl; - * \endcode - */ - class String - : public Builtin_Object + template + Resolved NativeFunction::matches(int argc, VALUE* argv, VALUE self) { - public: - //! Construct a new string. - String(); - - //! Wrap an existing string. - String(VALUE v); + // Return false if Ruby provided more arguments than the C++ method takes + if (argc > arity) + return Resolved{ Convertible::None, 0, this }; - //! Wrap an existing string. - String(Object v); + Resolved result { Convertible::Exact, 1, this }; - //! Construct a String from an Identifier. - String(Identifier id); + MethodInfo* methodInfo = this->methodInfo_.get(); + int index = 0; - //! Construct a String from a null-terminated C string. - String(char const* s); + // Loop over each FromRuby instance + for_each_tuple(this->fromRubys_, + [&](auto& fromRuby) + { + Convertible convertible = Convertible::None; - //! Construct a String from an std::string. - String(std::string const& s); + Arg& arg = methodInfo->arg(index); - //! Construct a String from an std::string_view. - String(std::string_view const& s); + // Is a VALUE being passed directly to C++ ? + if (arg.isValue() && index < argc) + { + convertible = Convertible::Exact; + } + // If index is less than argc then check with FromRuby if the VALUE is convertible + // to C++. + else if (index < argc) + { + VALUE value = argv[index]; + convertible = fromRuby.is_convertible(value); + } + // Last check if a default value has been set + else if (arg.hasDefaultValue()) + { + convertible = Convertible::Exact; + } - //! Format a string using printf-style formatting. - template - static inline String format(char const* fmt, Arg_Ts&&...args); + result.convertible = result.convertible & convertible; - //! Get the length of the String. - /*! \return the length of the string. - */ - size_t length() const; + index++; + }); - //! Get the character at the given index. - /*! \param index the desired index. - * \return the character at the given index. - */ - char operator[](ptrdiff_t index) const; + if (arity > 0) + result.parameterMatch = (double)argc / arity; - //! Return a pointer to the beginning of the underlying C string. - char const* c_str() const; + return result; + } - //! Return a copy of the string as an std::string. - std::string str() const; + template + std::vector NativeFunction::getRubyValues(int argc, VALUE* argv) + { + // Setup a tuple for the leading rb_scan_args arguments + std::string scanFormat = this->methodInfo_->formatString(); + std::tuple rbScanArgs = std::forward_as_tuple(argc, argv, scanFormat.c_str()); - //! Create an Identifier from the String. - /*! Calls rb_intern to create an ID. - * \return an Identifier holding the ID returned from rb_intern. - */ - Identifier intern() const; - }; -} // namespace Rice + // Create a vector to store the VALUEs that will be returned by rb_scan_args + std::vector rbScanValues(std::tuple_size_v, Qnil); + // Convert the vector to an array so it can be concatenated to a tuple. As importantly + // fill it with pointers to rbScanValues + std::array> rbScanValuePointers; + std::transform(rbScanValues.begin(), rbScanValues.end(), rbScanValuePointers.begin(), + [](VALUE& value) + { + return &value; + }); -// --------- String.ipp --------- -namespace Rice -{ - inline String::String() : Builtin_Object(detail::protect(rb_str_new2, "")) - { - } + // Combine the tuples and call rb_scan_args + std::apply(rb_scan_args, std::tuple_cat(rbScanArgs, rbScanValuePointers)); - inline String::String(VALUE v) : Builtin_Object(v) - { + return rbScanValues; } - inline String::String(Object v) : Builtin_Object(v) + template + template + Arg_T NativeFunction::getNativeValue(std::vector& values) { + /* In general the compiler will convert T to const T, but that does not work for converting + T** to const T** (see see https://isocpp.org/wiki/faq/const-correctness#constptrptr-conversion) + which comes up in the OpenCV bindings. + + An alternative solution is updating From_Ruby#convert to become a templated function that specifies + the return type. That works but requires a lot more code changes for this one case and is not + backwards compatible. */ + if constexpr (is_pointer_pointer_v && !std::is_convertible_v, Arg_T>) + { + return (Arg_T)std::get(this->fromRubys_).convert(values[I]); + } + else + { + return std::get(this->fromRubys_).convert(values[I]); + } } - inline String::String(char const* s) : Builtin_Object(detail::protect(rb_str_new2, s)) + template + template + typename NativeFunction::Arg_Ts NativeFunction::getNativeValues(std::vector& values, + std::index_sequence& indices) { + /* Loop over each value returned from Ruby and convert it to the appropriate C++ type based + on the arguments (Arg_Ts) required by the C++ function. Arg_T may have const/volatile while + the associated From_Ruby template parameter will not. Thus From_Ruby produces non-const values + which we let the compiler convert to const values as needed. This works except for + T** -> const T**, see comment in getNativeValue method. */ + return std::forward_as_tuple(this->getNativeValue, I>(values)...); } - inline String::String(std::string const& s) : Builtin_Object(detail::protect(rb_str_new, s.data(), (long)s.length())) + template + typename NativeFunction::Receiver_T NativeFunction::getReceiver(VALUE self) { + // There is no self parameter + if constexpr (std::is_same_v) + { + return nullptr; + } + // Self parameter is a Ruby VALUE so no conversion is needed + else if constexpr (std::is_same_v) + { + return self; + } + /* This case happens when a class wrapped by Rice is calling a method + defined on an ancestor class. For example, the std::map size method + is defined on _Tree not map. Rice needs to know the actual type + that was wrapped so it can correctly extract the C++ object from + the Ruby object. */ + else if constexpr (!std::is_same_v, Class_T> && + std::is_base_of_v, Class_T>) + { + Class_T* instance = From_Ruby().convert(self); + return dynamic_cast(instance); + } + // Self parameter could be derived from Object or it is an C++ instance and + // needs to be unwrapped from Ruby + else + { + return From_Ruby().convert(self); + } } - inline String::String(std::string_view const& s) : Builtin_Object(detail::protect(rb_str_new, s.data(), (long)s.length())) + template + VALUE NativeFunction::invokeNativeFunction(Arg_Ts&& nativeArgs) { - } + if constexpr (std::is_void_v) + { + std::apply(this->function_, std::forward(nativeArgs)); + return Qnil; + } + else + { + // Call the native method and get the result + Return_T nativeResult = std::apply(this->function_, std::forward(nativeArgs)); - inline String::String(Identifier id) : Builtin_Object(detail::protect(rb_str_new2, id.c_str())) - { + // Return the result + return this->toRuby_.convert(nativeResult); + } } - template - inline String String::format(char const* fmt, Arg_Ts&&...args) + template + VALUE NativeFunction::invokeNativeMethod(VALUE self, Arg_Ts&& nativeArgs) { - size_t size = std::snprintf(nullptr, 0, fmt, std::forward(args)...); - std::string temp(size, '\0'); + Receiver_T receiver = this->getReceiver(self); + auto selfAndNativeArgs = std::tuple_cat(std::forward_as_tuple(receiver), std::forward(nativeArgs)); - // size+1 avoids trunctaing the string. Otherwise snprintf writes n - 1 characters - // to allow space for null character but we don't need that since std::string - // will add a null character internally at n + 1 - std::snprintf(&temp[0], size + 1, fmt, std::forward(args)...); + if constexpr (std::is_void_v) + { + std::apply(this->function_, std::forward(selfAndNativeArgs)); + return Qnil; + } + else + { + Return_T nativeResult = std::apply(this->function_, std::forward(selfAndNativeArgs)); - String s = String(temp.c_str()); - return s; - } + // Special handling if the method returns self. If so we do not want + // to create a new Ruby wrapper object and instead return self. + if constexpr (std::is_same_v, intrinsic_type>) + { + if constexpr (std::is_pointer_v && std::is_pointer_v) + { + if (nativeResult == receiver) + return self; + } + else if constexpr (std::is_pointer_v && std::is_reference_v) + { + if (nativeResult == &receiver) + return self; + } + else if constexpr (std::is_reference_v && std::is_pointer_v) + { + if (&nativeResult == receiver) + return self; + } + else if constexpr (std::is_reference_v && std::is_reference_v) + { + if (&nativeResult == &receiver) + return self; + } + } - inline size_t String::length() const - { - return RSTRING_LEN(value()); + return this->toRuby_.convert(nativeResult); + } } - inline char String::operator[](ptrdiff_t index) const + template + void NativeFunction::noWrapper(const VALUE klass, const std::string& wrapper) { - return RSTRING_PTR(value())[index]; - } + std::stringstream message; - inline char const* String::c_str() const - { - return RSTRING_PTR(value()); - } + message << "When calling the method `"; + message << this->method_name_; + message << "' we could not find the wrapper for the '"; + message << rb_obj_classname(klass); + message << "' "; + message << wrapper; + message << " type. You should not use keepAlive() on a Return or Arg that is a builtin Rice type."; - inline std::string String::str() const - { - return std::string(RSTRING_PTR(value()), length()); + throw std::runtime_error(message.str()); } - inline Identifier String::intern() const + template + void NativeFunction::checkKeepAlive(VALUE self, VALUE returnValue, std::vector& rubyValues) { - return rb_intern(c_str()); - } -} + // selfWrapper will be nullptr if this(self) is a builtin type and not an external(wrapped) type + // it is highly unlikely that keepAlive is used in this case but we check anyway + Wrapper* selfWrapper = getWrapper(self); -namespace Rice::detail -{ - template<> - struct Type - { - static bool verify() - { - return true; - } - }; - - template<> - class To_Ruby - { - public: - VALUE convert(String const& x) + // Check function arguments + for (const Arg& arg : (*this->methodInfo_)) { - return x.value(); + if (arg.isKeepAlive()) + { + if (selfWrapper == nullptr) + { + noWrapper(self, "self"); + } + selfWrapper->addKeepAlive(rubyValues[arg.position]); + } } - }; - template<> - class From_Ruby - { - public: - Convertible is_convertible(VALUE value) + // Check return value + if (this->methodInfo_->returnInfo.isKeepAlive()) { - switch (rb_type(value)) + if (selfWrapper == nullptr) { - case RUBY_T_STRING: - return Convertible::Exact; - break; - default: - return Convertible::None; + noWrapper(self, "self"); } - } - String convert(VALUE value) - { - return String(value); + // returnWrapper will be nullptr if returnValue is a built-in type and not an external(wrapped) type + Wrapper* returnWrapper = getWrapper(returnValue); + if (returnWrapper == nullptr) + { + noWrapper(returnValue, "return"); + } + returnWrapper->addKeepAlive(self); } - }; -} - -// ========= Array.hpp ========= - -#include - -namespace Rice -{ - //! A wrapper for the ruby Array class. - /*! This class provides a C++-style interface to ruby's Array class and - * its associated rb_ary_* functions. - * Example: - * \code - * Array a; - * a.push(String("some string")); - * a.push(42); - * \endcode - */ - class Array - : public Builtin_Object - { - public: - //! Construct a new array - Array(); - - //! Wrap an existing array - /*! \param v a ruby object, which must be of type T_ARRAY. - */ - Array(Object v); - - //! Wrap an existing array - /*! \param v a ruby object, which must be of type T_ARRAY. - */ - Array(VALUE v); - - //! Construct an array from a sequence. - /*! \param begin an iterator to the beginning of the sequence. - * \param end an iterator to the end of the sequence. - */ - template - Array(Iter_T begin, Iter_T end); - - //! Construct an Array from a C array. - /*! \param a a C array of type T and size n. - */ - template - Array(T const (&a)[n]); - - public: - //! Return the size of the array. - long size() const; - - //! Return the element at the given index. - /*! \param index The index of the desired element. The index may be - * negative, to indicate an offset from the end of the array. If the - * index is out of bounds, this function has undefined behavior. - * \return the element at the given index. - */ - Object operator[](long index) const; - - private: - //! A helper class so array[index]=value can work. - class Proxy; - - public: - //! Return a reference to the element at the given index. - /*! \param index The index of the desired element. The index may be - * negative, to indicate an offset from the end of the array. If the - * index is out of bounds, this function has undefined behavior. - * \return the element at the given index. - */ - Proxy operator[](long index); - - //! Push an element onto the end of the array - /*! \param v an object to push onto the array. - * \return the object which was pushed onto the array. - */ - template - Object push(T const& obj); - - //! Pop an element from the end of the array - /*! \return the object which was popped from the array, or Qnil if - * the array was empty. - */ - Object pop(); - - //! Unshift an element onto the beginning of the array - /*! \param v an object to unshift onto the array. - * \return the object which was unshifted onto the array. - */ - template - Object unshift(T const& obj); - - //! Shift an element from the beginning of the array - /*! \return the object which was shifted from the array. - */ - Object shift(); - - private: - template - class Iterator; - - long position_of(long index) const; - - public: - //! An iterator. - typedef Iterator iterator; - - //! A const iterator. - typedef Iterator const_iterator; - - //! Return an iterator to the beginning of the array. - iterator begin(); - - //! Return a const iterator to the beginning of the array. - const_iterator begin() const; - - //! Return an iterator to the end of the array. - iterator end(); - - //! Return a const iterator to the end of the array. - const_iterator end() const; - }; + } - //! A helper class so array[index]=value can work. - class Array::Proxy + template + VALUE NativeFunction::operator()(int argc, VALUE* argv, VALUE self) { - public: - //! Construct a new Proxy - Proxy(Array array, long index); + // Get the ruby values + std::vector rubyValues = this->getRubyValues(argc, argv); - //! Implicit conversion to Object. - operator Object() const; + auto indices = std::make_index_sequence>{}; - //! Explicit conversion to VALUE. - VALUE value() const; + // Convert the Ruby values to native values + Arg_Ts nativeValues = this->getNativeValues(rubyValues, indices); - //! Assignment operator. - template - Object operator=(T const& value); + // Now call the native method + VALUE result = Qnil; + if constexpr (std::is_same_v) + { + result = this->invokeNativeFunction(std::forward(nativeValues)); + } + else + { + result = this->invokeNativeMethod(self, std::forward(nativeValues)); + } - private: - Array array_; - long index_; - }; + // Check if any function arguments or return values need to have their lifetimes tied to the receiver + this->checkKeepAlive(self, result, rubyValues); - //! A helper class for implementing iterators for a Array. - // TODO: This really should be a random-access iterator. - template - class Array::Iterator - { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = Value_T; - using difference_type = long; - using pointer = Object*; - using reference = Value_T&; + return result; + } +} - Iterator(Array_Ptr_T array, long index); +// ========= NativeIterator.hpp ========= - template - Iterator(Iterator const& rhs); - template - Iterator& operator=(Iterator const& rhs); +namespace Rice::detail +{ + template + class NativeIterator: Native + { + public: + using NativeIterator_T = NativeIterator; + using Iterator_T = typename function_traits::return_type; + using Value_T = typename std::iterator_traits::value_type; + using Reference_T = typename std::iterator_traits::reference; + using Difference_T = typename std::iterator_traits::difference_type; + using To_Ruby_T = remove_cv_recursive_t; - Iterator& operator++(); - Iterator operator++(int); - Value_T operator*(); - Object* operator->(); + public: + // Register function with Ruby + void static define(VALUE klass, std::string method_name, Iterator_Func_T begin, Iterator_Func_T end); - template - bool operator==(Iterator const& rhs) const; + public: + // Disallow creating/copying/moving + NativeIterator() = delete; + NativeIterator(const NativeIterator_T&) = delete; + NativeIterator(NativeIterator_T&&) = delete; + void operator=(const NativeIterator_T&) = delete; + void operator=(NativeIterator_T&&) = delete; - template - bool operator!=(Iterator const& rhs) const; + Resolved matches(int argc, VALUE* argv, VALUE self) override; + VALUE operator()(int argc, VALUE* argv, VALUE self) override; - Array_Ptr_T array() const; - long index() const; + protected: + NativeIterator(VALUE klass, std::string method_name, Iterator_Func_T begin, Iterator_Func_T end); private: - Array_Ptr_T array_; - long index_; + VALUE createRubyEnumerator(VALUE self); - Object tmp_; + private: + VALUE klass_; + std::string method_name_; + Iterator_Func_T begin_; + Iterator_Func_T end_; }; -} // namespace Rice +} -// --------- Array.ipp --------- -#ifndef Rice__Array__ipp_ -#define Rice__Array__ipp_ +// ========= NativeIterator.ipp ========= +#include +#include +#include -namespace Rice + +namespace Rice::detail { - inline Array::Array() : Builtin_Object(detail::protect(rb_ary_new)) + template + inline void NativeIterator::define(VALUE klass, std::string method_name, Iterator_Func_T begin, Iterator_Func_T end) { + // Tell Ruby to invoke the resolveIterator static method defined above + detail::protect(rb_define_method, klass, method_name.c_str(), (RUBY_METHOD_FUNC)&Native::resolve, -1); + + // Create a NativeIterator instance and save it to the NativeRegistry. There may be multiple + // NativeFunction instances for a specific method because C++ supports method overloading. + NativeIterator_T* nativeIterator = new NativeIterator_T(klass, method_name, begin, end); + std::unique_ptr native(nativeIterator); + + Identifier identifier(method_name); + detail::Registries::instance.natives.add(klass, identifier.id(), native); } - inline Array::Array(Object v) : Builtin_Object(v) + 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) { } - inline Array::Array(VALUE v) : Builtin_Object(v) + template + inline Resolved NativeIterator::matches(int argc, VALUE* argv, VALUE self) { + return Resolved{ Convertible::Exact, 1.0, this }; } - template - inline Array::Array(Iter_T it, Iter_T end) : Builtin_Object(detail::protect(rb_ary_new)) + template + inline VALUE NativeIterator::createRubyEnumerator(VALUE self) { - for (; it != end; ++it) + auto rb_size_function = [](VALUE recv, VALUE argv, VALUE eobj) -> VALUE { - push(*it); - } + // 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 + // Class is easy + VALUE klass = protect(rb_class_of, recv); + // Read the method_id from an attribute we added to the enumerator instance + Identifier identifier = protect(rb_ivar_get, eobj, rb_intern("rice_method")); + + const std::vector>& natives = detail::Registries::instance.natives.lookup(klass, identifier.id()); + NativeIterator_T* iterator = static_cast(natives.back().get()); + + // 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); + }); + }; + + Identifier identifier(this->method_name_); + VALUE enumerator = protect(rb_enumeratorize_with_size, self, identifier.to_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 + protect(rb_ivar_set, enumerator, rb_intern("rice_method"), identifier.id()); + + return enumerator; } - template - inline Array::Array(T const (&a)[n]) : Builtin_Object(detail::protect(rb_ary_new)) + template + inline VALUE NativeIterator::operator()(int argc, VALUE* argv, VALUE self) { - for (long j = 0; j < n; ++j) + if (!protect(rb_block_given_p)) { - push(a[j]); + 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; } } +} +// C++ API definitions - inline long Array::size() const +// ========= Object.ipp ========= +namespace Rice +{ + inline const Object Nil(Qnil); + inline const Object True(Qtrue); + inline const Object False(Qfalse); + inline const Object Undef(Qundef); + + // Ruby auto detects VALUEs in the stack, so when an Object gets deleted make sure + // to clean up in case it is on the stack + inline Object::~Object() { - return RARRAY_LEN(this->value()); + this->value_ = Qnil; } - inline Object Array::operator[](long index) const + // Move constructor + inline Object::Object(Object&& other) { - return detail::protect(rb_ary_entry, value(), position_of(index)); + this->value_ = other.value_; + other.value_ = Qnil; } - inline Array::Proxy Array::operator[](long index) + // Move assignment + inline Object& Object::operator=(Object&& other) { - return Proxy(*this, position_of(index)); + this->value_ = other.value_; + other.value_ = Qnil; + return *this; } - template - inline Object Array::push(T const& obj) + template + inline Object Object::call(Identifier id, Arg_Ts... args) const { - return detail::protect(rb_ary_push, value(), detail::To_Ruby().convert(obj)); + /* IMPORTANT - We store VALUEs in an array that is a local variable. + That allows the Ruby garbage collector to find them when scanning + the stack and thus mark them. If instead we use a vector, then Ruby's GC + can't find the VALUEs and may garbage collect them before they are sent + to the destination method resulting in a segmentation fault. This is + easy to duplicate by setting GC.stress to true and calling a constructor + that takes multiple values like a std::pair wrapper. */ + std::array values = { detail::To_Ruby>().convert(args)... }; + return detail::protect(rb_funcallv, value(), id.id(), (int)values.size(), (const VALUE*)values.data()); } - inline Object Array::pop() + template + inline Object Object::call_kw(Identifier id, Arg_Ts... args) const { - return detail::protect(rb_ary_pop, value()); + /* IMPORTANT - See call() above */ + std::array values = { detail::To_Ruby>().convert(args)... }; + return detail::protect(rb_funcallv_kw, value(), id.id(), (int)values.size(), (const VALUE*)values.data(), RB_PASS_KEYWORDS); } template - inline Object Array::unshift(T const& obj) + inline void Object::iv_set(Identifier name, T const& value) { - return detail::protect(rb_ary_unshift, value(), detail::To_Ruby().convert(obj)); + detail::protect(rb_ivar_set, this->value(), name.id(), detail::To_Ruby().convert(value)); } - inline Object Array::shift() + inline int Object::compare(Object const& other) const { - return detail::protect(rb_ary_shift, value()); + Object result = call("<=>", other); + return detail::From_Ruby().convert(result); } - inline long Array::position_of(long index) const + inline bool Object::is_equal(const Object& other) const { - if (index < 0) - { - return size() + index; - } - else - { - return static_cast(index); - } + VALUE result = detail::protect(rb_equal, this->value_, other.value_); + return RB_TEST(result); } - inline Array::Proxy::Proxy(Array array, long index) - : array_(array) - , index_(index) + inline bool Object::is_eql(const Object& other) const { + VALUE result = detail::protect(rb_eql, this->value_, other.value_); + return RB_TEST(result); } - inline Array::Proxy::operator Object() const + inline void Object::freeze() { - return detail::protect(rb_ary_entry, array_.value(), index_); + detail::protect(rb_obj_freeze, value()); } - inline VALUE Array::Proxy::value() const + inline bool Object::is_frozen() const { - return detail::protect(rb_ary_entry, array_.value(), index_); + return RB_OBJ_FROZEN(value()); } - template - Object Array::Proxy::operator=(T const& value) + inline int Object::rb_type() const { - Object o = detail::To_Ruby().convert(value); - detail::protect(rb_ary_store, array_.value(), index_, o.value()); - return o; + return ::rb_type(this->value()); } - template - inline Array::Iterator::Iterator(Array_Ptr_T array, long index) : - array_(array), index_(index) + inline VALUE Object::object_id() const + { + return detail::protect(rb_obj_id, this->value()); + } + + inline bool Object::is_a(Object klass) const { + VALUE result = detail::protect(rb_obj_is_kind_of, this->value(), klass.value()); + return RB_TEST(result); } - template - template - inline - Array::Iterator::Iterator(Iterator const& rhs) : - array_(rhs.array()) , index_(rhs.index()), tmp_() + inline bool Object::respond_to(Identifier id) const { + return bool(rb_respond_to(this->value(), id.id())); } - template - template - inline Array::Iterator& Array::Iterator::operator=(Iterator const& rhs) + inline bool Object::is_instance_of(Object klass) const { - array_ = rhs.array_; - index_ = rhs.index_; - return *this; + VALUE result = detail::protect(rb_obj_is_instance_of, this->value(), klass.value()); + return RB_TEST(result); } - template - inline Array::Iterator& Array::Iterator::operator++() + inline Object Object::iv_get(Identifier name) const { - ++index_; - return *this; + return detail::protect(rb_ivar_get, this->value(), name.id()); } - template - inline Array::Iterator Array::Iterator::operator++(int) + inline Object Object::attr_get(Identifier name) const { - Array copy(*this); - ++(*this); - return *this; + return detail::protect(rb_attr_get, this->value(), name.id()); } - template - inline Value_T Array::Iterator::operator*() + inline void Object::set_value(VALUE v) { - return (*array_)[index_]; + value_ = v; } - template - inline Object* Array::Iterator::operator->() + inline Object Object::const_get(Identifier name) const { - tmp_ = (*array_)[index_]; - return &tmp_; + return detail::protect(rb_const_get, this->value(), name.id()); } - template - template - inline bool Array::Iterator::operator==(Iterator const& rhs) const + inline bool Object::const_defined(Identifier name) const { - return array_->value() == rhs.array_->value() && index_ == rhs.index_; + size_t result = detail::protect(rb_const_defined, this->value(), name.id()); + return bool(result); } - template - template - inline bool Array::Iterator::operator!=(Iterator const& rhs) const + inline Object Object::const_set(Identifier name, Object value) { - return !(*this == rhs); + detail::protect(rb_const_set, this->value(), name.id(), value.value()); + return value; } - template - Array_Ptr_T Array::Iterator::array() const + inline Object Object::const_set_maybe(Identifier name, Object value) { - return array_; + if (!this->const_defined(name)) + { + this->const_set(name, value); + } + return value; } - template - long Array::Iterator::index() const + inline void Object::remove_const(Identifier name) { - return index_; + detail::protect(rb_mod_remove_const, this->value(), name.to_sym()); } - inline Array::iterator Array::begin() + inline bool operator==(Object const& lhs, Object const& rhs) { - return iterator(this, 0); + VALUE result = detail::protect(rb_equal, lhs.value(), rhs.value()); + return result == Qtrue ? true : false; } - inline Array::const_iterator Array::begin() const + inline bool operator!=(Object const& lhs, Object const& rhs) { - return const_iterator(this, 0); + return !(lhs == rhs); } - inline Array::iterator Array::end() + inline bool operator<(Object const& lhs, Object const& rhs) { - return iterator(this, size()); + Object result = lhs.call("<", rhs); + return result.test(); } - inline Array::const_iterator Array::end() const + inline bool operator>(Object const& lhs, Object const& rhs) { - return const_iterator(this, size()); + Object result = lhs.call(">", rhs); + return result.test(); } } namespace Rice::detail { template<> - struct Type + struct Type { static bool verify() { @@ -6666,44 +6438,34 @@ namespace Rice::detail }; template<> - class To_Ruby + class To_Ruby { public: - VALUE convert(Array const& x) + static VALUE convert(Object const& x) { return x.value(); } }; template<> - class To_Ruby + class To_Ruby { public: - VALUE convert(Array const& x) + static VALUE convert(Object const& x) { return x.value(); } }; template<> - class To_Ruby - { - public: - VALUE convert(Array const* x) - { - return x->value(); - } - }; - - template<> - class From_Ruby + class From_Ruby { public: Convertible is_convertible(VALUE value) { switch (rb_type(value)) { - case RUBY_T_ARRAY: + case RUBY_T_OBJECT: return Convertible::Exact; break; default: @@ -6711,210 +6473,437 @@ namespace Rice::detail } } - Array convert(VALUE value) + Object convert(VALUE value) { - return Array(value); + return Object(value); } }; } -#endif // Rice__Array__ipp_ -// ========= Hash.hpp ========= +// ========= Builtin_Object.ipp ========= +#include -#include -#include +namespace Rice +{ + namespace detail + { + inline VALUE check_type(Object value, int type) + { + detail::protect(rb_check_type, value.value(), type); + return Qnil; + } + } + + template + inline Builtin_Object::Builtin_Object(Object value) : Object(value) + { + detail::check_type(value, Builtin_Type); + } + + template + inline RObject& Builtin_Object::operator*() const + { + return *ROBJECT(this->value()); + } + + template + inline RObject* Builtin_Object::operator->() const + { + return ROBJECT(this->value()); + } + + template + inline RObject* Builtin_Object::get() const + { + return ROBJECT(this->value()); + } +} // namespace Rice +// ========= String.ipp ========= namespace Rice { - //! A wrapper for the ruby Hash class. - //! This class provides a C++-style interface to ruby's Hash class and - //! its associated rb_hash_* functions. - //! Example: - //! \code - //! Hash h; - //! h[42] = String("foo"); - //! h[10] = String("bar"); - //! std::cout << String(h[42]) << std::endl; - //! \endcode - class Hash: public Builtin_Object + inline String::String() : Builtin_Object(detail::protect(rb_str_new2, "")) { - public: - //! Construct a new hash. - Hash(); + } - //! Wrap an existing hash. - /*! \param v the hash to wrap. - */ - Hash(Object v); + inline String::String(VALUE v) : Builtin_Object(v) + { + } - //! Return the number of elements in the hash. - size_t size() const; + inline String::String(Object v) : Builtin_Object(v) + { + } - private: - //! A helper class so hash[key]=value can work. - class Proxy; + inline String::String(char const* s) : Builtin_Object(detail::protect(rb_str_new2, s)) + { + } + + inline String::String(std::string const& s) : Builtin_Object(detail::protect(rb_str_new, s.data(), (long)s.length())) + { + } + + inline String::String(std::string_view const& s) : Builtin_Object(detail::protect(rb_str_new, s.data(), (long)s.length())) + { + } + + inline String::String(Identifier id) : Builtin_Object(detail::protect(rb_str_new2, id.c_str())) + { + } + + template + inline String String::format(char const* fmt, Arg_Ts&&...args) + { + size_t size = std::snprintf(nullptr, 0, fmt, std::forward(args)...); + std::string temp(size, '\0'); + + // size+1 avoids trunctaing the string. Otherwise snprintf writes n - 1 characters + // to allow space for null character but we don't need that since std::string + // will add a null character internally at n + 1 + std::snprintf(&temp[0], size + 1, fmt, std::forward(args)...); + + String s = String(temp.c_str()); + return s; + } + + inline size_t String::length() const + { + return RSTRING_LEN(value()); + } + + inline char String::operator[](ptrdiff_t index) const + { + return RSTRING_PTR(value())[index]; + } + + inline char const* String::c_str() const + { + return RSTRING_PTR(value()); + } + + inline std::string String::str() const + { + return std::string(RSTRING_PTR(value()), length()); + } + + inline Identifier String::intern() const + { + return rb_intern(c_str()); + } +} + +namespace Rice::detail +{ + template<> + struct Type + { + static bool verify() + { + return true; + } + }; + + template<> + class To_Ruby + { + public: + VALUE convert(String const& x) + { + return x.value(); + } + }; + template<> + class From_Ruby + { public: - //! Get the value for the given key. - /*! \param key the key whose value should be retrieved from the hash. - * \return the value associated with the given key. - */ - template - Proxy const operator[](Key_T const& key) const; + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_STRING: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } - //! Get the value for the given key. - /*! \param key the key whose value should be retrieved from the hash. - * \return the value associated with the given key. - */ - template - Proxy operator[](Key_T const& key); + String convert(VALUE value) + { + return String(value); + } + }; +} +// ========= Array.ipp ========= - //! Get the value for the given key - /*! The returned value is converted to the type given by Value_T. - * \param key the key whose value should be retrieved from the hash. - * \return the value associated with the given key, converted to C++ - * type Value_T. - */ - template - Value_T get(Key_T const& key); +namespace Rice +{ + inline Array::Array() : Builtin_Object(detail::protect(rb_ary_new)) + { + } - //! A helper class for dereferencing iterators - class Entry; + inline Array::Array(Object v) : Builtin_Object(v) + { + } - //! A helper class for implementing iterators for a Hash. - template - class Iterator; + inline Array::Array(VALUE v) : Builtin_Object(v) + { + } - public: - //! An iterator. - typedef Iterator iterator; + template + inline Array::Array(Iter_T it, Iter_T end) : Builtin_Object(detail::protect(rb_ary_new)) + { + for (; it != end; ++it) + { + push(*it); + } + } - //! A const iterator. - typedef Iterator const_iterator; + template + inline Array::Array(T const (&a)[n]) : Builtin_Object(detail::protect(rb_ary_new)) + { + for (long j = 0; j < n; ++j) + { + push(a[j]); + } + } - public: - //! Return an iterator to the beginning of the hash. - iterator begin(); + inline long Array::size() const + { + return RARRAY_LEN(this->value()); + } - //! Return a const iterator to the beginning of the hash. - const_iterator begin() const; + inline Object Array::operator[](long index) const + { + return detail::protect(rb_ary_entry, value(), position_of(index)); + } - //! Return an iterator to the end of the hash. - iterator end(); + inline Array::Proxy Array::operator[](long index) + { + return Proxy(*this, position_of(index)); + } - //! Return a const to the end of the hash. - const_iterator end() const; - }; + template + inline Object Array::push(T const& obj) + { + return detail::protect(rb_ary_push, value(), detail::To_Ruby().convert(obj)); + } - //! A helper class so hash[key]=value can work. - class Hash::Proxy + inline Object Array::pop() { - public: - //! Construct a new Proxy. - Proxy(Hash* hash, Object key); + return detail::protect(rb_ary_pop, value()); + } - //! Implicit conversion to Object. - operator Object() const; + template + inline Object Array::unshift(T const& obj) + { + return detail::protect(rb_ary_unshift, value(), detail::To_Ruby().convert(obj)); + } - //! Explicit conversion to VALUE. - VALUE value() const; + inline Object Array::shift() + { + return detail::protect(rb_ary_shift, value()); + } - //! Assignment operator. - template - Object operator=(T const& value); + inline long Array::position_of(long index) const + { + if (index < 0) + { + return size() + index; + } + else + { + return static_cast(index); + } + } - private: - Hash* hash_; - Object key_; - }; + inline Array::Proxy::Proxy(Array array, long index) + : array_(array) + , index_(index) + { + } - //! A helper class for dereferencing iterators - /*! This class is intended to look similar to an std::pair. - */ - class Hash::Entry + inline Array::Proxy::operator Object() const { - public: - //! Construct a new Entry. - Entry(Hash* hash, Object key); + return detail::protect(rb_ary_entry, array_.value(), index_); + } - //! Copy constructor. - Entry(Entry const& entry); + inline VALUE Array::Proxy::value() const + { + return detail::protect(rb_ary_entry, array_.value(), index_); + } - Object const key; //!< The key - Object const& first; //!< An alias for the key + template + Object Array::Proxy::operator=(T const& value) + { + Object o = detail::To_Ruby().convert(value); + detail::protect(rb_ary_store, array_.value(), index_, o.value()); + return o; + } - Proxy value; //!< The value - Proxy& second; //!< An alias for the value + template + inline Array::Iterator::Iterator(Array_Ptr_T array, long index) : + array_(array), index_(index) + { + } - Entry& operator=(Entry const& rhs); + template + template + inline + Array::Iterator::Iterator(Iterator const& rhs) : + array_(rhs.array()) , index_(rhs.index()), tmp_() + { + } - friend bool operator<(Entry const& lhs, Entry const& rhs); - }; + template + template + inline Array::Iterator& Array::Iterator::operator=(Iterator const& rhs) + { + array_ = rhs.array_; + index_ = rhs.index_; + return *this; + } - bool operator<(Hash::Entry const& lhs, Hash::Entry const& rhs); + template + inline Array::Iterator& Array::Iterator::operator++() + { + ++index_; + return *this; + } - //! A helper class for implementing iterators for a Hash. - template - class Hash::Iterator + template + inline Array::Iterator Array::Iterator::operator++(int) { - public: - using iterator_category = std::input_iterator_tag; - using value_type = Value_T; - using difference_type = long; - using pointer = Object*; - using reference = Value_T&; + Array copy(*this); + ++(*this); + return *this; + } - //! Construct a new Iterator. - Iterator(Hash_Ptr_T hash); + template + inline Value_T Array::Iterator::operator*() + { + return (*array_)[index_]; + } - //! Construct a new Iterator with a given start-at index point - Iterator(Hash_Ptr_T hash, int start_at); + template + inline Object* Array::Iterator::operator->() + { + tmp_ = (*array_)[index_]; + return &tmp_; + } - //! Construct an Iterator from another Iterator of a different const - //! qualification. - template - Iterator(Iterator_T const& iterator); + template + template + inline bool Array::Iterator::operator==(Iterator const& rhs) const + { + return array_->value() == rhs.array_->value() && index_ == rhs.index_; + } - //! Preincrement operator. - Iterator& operator++(); + template + template + inline bool Array::Iterator::operator!=(Iterator const& rhs) const + { + return !(*this == rhs); + } - //! Postincrement operator. - Iterator operator++(int); + template + Array_Ptr_T Array::Iterator::array() const + { + return array_; + } - //! Dereference operator. - Value_T operator*(); + template + long Array::Iterator::index() const + { + return index_; + } - //! Dereference operator. - Value_T* operator->(); + inline Array::iterator Array::begin() + { + return iterator(this, 0); + } - //! Equality operator. - bool operator==(Iterator const& rhs) const; + inline Array::const_iterator Array::begin() const + { + return const_iterator(this, 0); + } - //! Inequality operator. - bool operator!=(Iterator const& rhs) const; + inline Array::iterator Array::end() + { + return iterator(this, size()); + } - template - friend class Hash::Iterator; + inline Array::const_iterator Array::end() const + { + return const_iterator(this, size()); + } +} - protected: - Object current_key(); +namespace Rice::detail +{ + template<> + struct Type + { + static bool verify() + { + return true; + } + }; - Array hash_keys(); + template<> + class To_Ruby + { + public: + VALUE convert(Array const& x) + { + return x.value(); + } + }; - private: - Hash_Ptr_T hash_; - long current_index_; - VALUE keys_; + template<> + class To_Ruby + { + public: + VALUE convert(Array const& x) + { + return x.value(); + } + }; - mutable typename std::remove_const::type tmp_; + template<> + class To_Ruby + { + public: + VALUE convert(Array const* x) + { + return x->value(); + } }; -} // namespace Rice + template<> + class From_Ruby + { + public: + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_ARRAY: + return Convertible::Exact; + break; + default: + return Convertible::None; + } + } -// --------- Hash.ipp --------- -#ifndef Rice__Hash__ipp_ -#define Rice__Hash__ipp_ + Array convert(VALUE value) + { + return Array(value); + } + }; +} +// ========= Hash.ipp ========= #include namespace Rice @@ -7173,56 +7162,7 @@ namespace Rice::detail }; } -#endif // Rice__Hash__ipp_ - - -// ========= Symbol.hpp ========= - -#include - -namespace Rice -{ - //! A wrapper for ruby's Symbol class. - /*! Symbols are internal identifiers in ruby. They are singletons and - * can be thought of as frozen strings. They differ from an Identifier - * in that they are in fact real Objects, but they can be converted - * back and forth between Identifier and Symbol. - */ - class Symbol - : public Object - { - public: - //! Wrap an existing symbol. - Symbol(VALUE v); - - //! Wrap an existing symbol. - Symbol(Object v); - - //! Construct a Symbol from an Identifier. - Symbol(Identifier id); - - //! Construct a Symbol from a null-terminated C string. - Symbol(char const* s = ""); - - //! Construct a Symbol from an std::string. - Symbol(std::string const& s); - - //! Construct a Symbol from an std::string_view. - Symbol(std::string_view const& s); - - //! Return a string representation of the Symbol. - char const* c_str() const; - - //! Return a string representation of the Symbol. - std::string str() const; - - //! Return the Symbol as an Identifier. - Identifier to_id() const; - }; -} // namespace Rice - - -// --------- Symbol.ipp --------- +// ========= Symbol.ipp ========= namespace Rice { inline Symbol::Symbol(VALUE value) : Object(value) @@ -7295,6 +7235,21 @@ namespace Rice::detail class From_Ruby { public: + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_SYMBOL: + return Convertible::Exact; + break; + case RUBY_T_STRING: + return Convertible::TypeCast; + break; + default: + return Convertible::None; + } + } + Symbol convert(VALUE value) { return Symbol(value); @@ -7303,15 +7258,9 @@ namespace Rice::detail } - // ========= Module.hpp ========= -// --------- Module_defn.hpp --------- -#ifndef Rice__Module_defn__hpp_ -#define Rice__Module_defn__hpp_ - - namespace Rice { template @@ -7489,7 +7438,6 @@ inline auto& define_module_function(std::string name, Function_T&& func, const A define_singleton_function(name, std::forward(func), args...); return *this; } - protected: template void wrap_native_call(VALUE klass, std::string name, Function_T&& function, MethodInfo* methodInfo); @@ -7511,11 +7459,8 @@ inline auto& define_module_function(std::string name, Function_T&& func, const A */ Module anonymous_module(); } -#endif // Rice__Module_defn__hpp_ -// --------- Module.ipp --------- -#ifndef Rice__Module__ipp_ -#define Rice__Module__ipp_ +// ========= Module.ipp ========= namespace Rice { @@ -7625,19 +7570,11 @@ namespace Rice::detail } }; } -#endif // Rice__Module__ipp_ - // ========= Class.hpp ========= - -// --------- Class_defn.hpp --------- -#ifndef Rice__Class_defn__hpp_ -#define Rice__Class_defn__hpp_ - #include - /*! * \example inheritance/animals.cpp * \example callbacks/sample_callbacks.cpp @@ -7824,11 +7761,8 @@ inline auto& define_module_function(std::string name, Function_T&& func, const A Class anonymous_class(); } // namespace Rice -#endif // Rice__Class_defn__hpp_ -// --------- Class.ipp --------- -#ifndef Rice__Class__ipp_ -#define Rice__Class__ipp_ +// ========= Class.ipp ========= namespace Rice { @@ -7912,7 +7846,6 @@ namespace Rice::detail } }; } -#endif // Rice__Class__ipp_ // ========= Struct.hpp ========= @@ -8027,7 +7960,7 @@ namespace Rice } // namespace Rice -// --------- Struct.ipp --------- +// ========= Struct.ipp ========= namespace Rice { @@ -8124,11 +8057,6 @@ namespace Rice::detail // ========= Address_Registration_Guard.hpp ========= -// --------- Address_Registration_Guard_defn.hpp --------- -#ifndef Rice__Address_Registration_Guard_defn__hpp_ -#define Rice__Address_Registration_Guard_defn__hpp_ - - namespace Rice { //! A guard to register a given address with the GC. @@ -8201,8 +8129,8 @@ namespace Rice }; } // namespace Rice -#endif // Rice__Address_Registration_Guard_defn__hpp_ -// --------- Address_Registration_Guard.ipp --------- + +// ========= Address_Registration_Guard.ipp ========= namespace Rice { inline Address_Registration_Guard::Address_Registration_Guard(VALUE* address) : address_(address) @@ -8283,7 +8211,6 @@ namespace Rice enabled = false; } } // Rice - // ========= global_function.hpp ========= @@ -8305,19 +8232,16 @@ namespace Rice } // Rice -// --------- global_function.ipp --------- +// ========= global_function.ipp ========= template void Rice::define_global_function(char const * name, Function_T&& func, Arg_Ts const& ...args) { Module(rb_mKernel).define_module_function(name, std::forward(func), args...); } - // Code involved in creating custom DataTypes (ie, Ruby classes that wrap C++ classes) // ========= ruby_mark.hpp ========= -#ifndef ruby_mark__hpp -#define ruby_mark__hpp //! Default function to call to mark a data object. /*! This function can be specialized for a particular type to override @@ -8330,7 +8254,7 @@ namespace Rice { } } -#endif // ruby_mark__hpp + // ========= default_allocation_func.hpp ========= @@ -8386,11 +8310,6 @@ namespace Rice // ========= Data_Type.hpp ========= - -// --------- Data_Type_defn.hpp --------- -#ifndef Rice__Data_Type_defn__hpp_ -#define Rice__Data_Type_defn__hpp_ - #include namespace Rice @@ -8533,7 +8452,7 @@ namespace Rice template Data_Type& define_singleton_attr(std::string name, Attribute_T attribute, AttrAccess access = AttrAccess::ReadWrite); - // Include these methods to call methods from Module but return + // Include these methods to call methods from Module but return // an instance of the current classes. This is an alternative to // using CRTP. @@ -8660,7 +8579,6 @@ inline auto& define_module_function(std::string name, Function_T&& func, const A define_singleton_function(name, std::forward(func), args...); return *this; } - protected: //! Bind a Data_Type to a VALUE. /*! Throws an exception if the Data_Type is already bound to a @@ -8723,11 +8641,7 @@ inline auto& define_module_function(std::string name, Function_T&& func, const A } -#endif // Rice__Data_Type_defn__hpp_ -// --------- Data_Type.ipp --------- -#ifndef Rice__Data_Type__ipp_ -#define Rice__Data_Type__ipp_ - +// ========= Data_Type.ipp ========= #include @@ -9043,10 +8957,8 @@ namespace Rice detail::NativeFunction::define(klass, name, std::forward(function), methodInfo); } } -#endif // ========= default_allocation_func.ipp ========= - namespace Rice::detail { template @@ -9101,14 +9013,6 @@ namespace Rice // ========= Data_Object.hpp ========= - -// --------- Data_Object_defn.hpp --------- -#ifndef Rice__Data_Object_defn__hpp_ -#define Rice__Data_Object_defn__hpp_ - -#include - - /*! \file * \brief Provides a helper class for wrapping and unwrapping C++ * objects as Ruby objects. @@ -9183,13 +9087,8 @@ namespace Rice }; } // namespace Rice -#endif // Rice__Data_Object_defn__hpp_ - - -// --------- Data_Object.ipp --------- -#ifndef Rice__Data_Object__ipp_ -#define Rice__Data_Object__ipp_ +// ========= Data_Object.ipp ========= #include @@ -9776,6 +9675,18 @@ namespace Rice::detail static_assert(!std::is_fundamental_v>, "Data_Object cannot be used with fundamental types"); public: + Convertible is_convertible(VALUE value) + { + switch (rb_type(value)) + { + case RUBY_T_DATA: + return Data_Type::is_descendant(value) ? Convertible::Exact : Convertible::None; + break; + default: + return Convertible::None; + } + } + static Data_Object convert(VALUE value) { return Data_Object(value); @@ -9832,13 +9743,9 @@ namespace Rice::detail Arg* arg_ = nullptr; }; } -#endif // Rice__Data_Object__ipp_ - // ========= Enum.hpp ========= -#include - namespace Rice { /*! @@ -9901,7 +9808,7 @@ namespace Rice } // namespace Rice -// --------- Enum.ipp --------- +// ========= Enum.ipp ========= #include @@ -10044,16 +9951,8 @@ namespace Rice return Enum(name, module); } } - // ========= MemoryView.hpp ========= - -// --------- MemoryView_defn.hpp --------- -#ifndef Rice__MemoryView_defn__hpp_ -#define Rice__MemoryView_defn__hpp_ - -#include - namespace Rice { class MemoryView @@ -10061,55 +9960,6 @@ namespace Rice }; } -#endif // Rice__MemoryView_defn__hpp_ -// --------- MemoryView.ipp --------- -#ifndef Rice__MemoryView__ipp_ -#define Rice__MemoryView__ipp_ - -namespace Rice -{ -} - -namespace Rice::detail -{ - template<> - class To_Ruby - { - public: - VALUE convert(unsigned char** x) - { - std::runtime_error("To_Ruby unsigned char** is not implemented yet"); - return Qnil; - } - }; - - template<> - class From_Ruby - { - public: - From_Ruby() = default; - - explicit From_Ruby(Arg* arg) : arg_(arg) - { - } - - Convertible is_convertible(VALUE value) - { - std::runtime_error("From_Ruby unsigned char** is not implemented yet"); - return Convertible::None; - } - - unsigned char** convert(VALUE value) - { - std::runtime_error("From_Ruby unsigned char** is not implemented yet"); - return nullptr; - } - - private: - Arg* arg_ = nullptr; - }; -} -#endif // Rice__MemoryView__ipp_ // Dependent on Module, Class, Array and String diff --git a/rice/Address_Registration_Guard.hpp b/rice/Address_Registration_Guard.hpp index f7656d5f..dc62034b 100644 --- a/rice/Address_Registration_Guard.hpp +++ b/rice/Address_Registration_Guard.hpp @@ -1,7 +1,76 @@ #ifndef Rice__Address_Registration_Guard__hpp_ #define Rice__Address_Registration_Guard__hpp_ -#include "Address_Registration_Guard_defn.hpp" -#include "Address_Registration_Guard.ipp" +namespace Rice +{ + //! A guard to register a given address with the GC. + /*! Calls rb_gc_register_address upon construction and + * rb_gc_unregister_address upon destruction. + * For example: + * \code + * Class Foo + * { + * public: + * Foo() + * : string_(rb_str_new2()) + * , guard_(&string_); + * + * private: + * VALUE string_; + * Address_Registration_Guard guard_; + * }; + * \endcode + */ + class Address_Registration_Guard + { + public: + //! Register an address with the GC. + /* \param address The address to register with the GC. The address + * must point to a valid ruby object (RObject). + */ + Address_Registration_Guard(VALUE* address); -#endif // Rice__Address_Registration_Guard__hpp_ + //! Register an Object with the GC. + /*! \param object The Object to register with the GC. The object must + * not be destroyed before the Address_Registration_Guard is + * destroyed. + */ + Address_Registration_Guard(Object* object); + + //! Unregister an address/Object with the GC. + /*! Destruct an Address_Registration_Guard. The address registered + * with the Address_Registration_Guard when it was constructed will + * be unregistered from the GC. + */ + ~Address_Registration_Guard(); + + // Disable copying + Address_Registration_Guard(Address_Registration_Guard const& other) = delete; + Address_Registration_Guard& operator=(Address_Registration_Guard const& other) = delete; + + // Enable moving + Address_Registration_Guard(Address_Registration_Guard&& other); + Address_Registration_Guard& operator=(Address_Registration_Guard&& other); + + //! Get the address that is registered with the GC. + VALUE* address() const; + + /** Called during Ruby's exit process since we should not call + * rb_gc unregister_address there + */ + static void disable(); + + private: + inline static bool enabled = true; + inline static bool exit_handler_registered = false; + static void registerExitHandler(); + + private: + void registerAddress() const; + void unregisterAddress(); + + VALUE* address_ = nullptr; + }; +} // namespace Rice + +#endif // Rice__Address_Registration_Guard__hpp_ \ No newline at end of file diff --git a/rice/Address_Registration_Guard_defn.hpp b/rice/Address_Registration_Guard_defn.hpp deleted file mode 100644 index 1fabe1e1..00000000 --- a/rice/Address_Registration_Guard_defn.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef Rice__Address_Registration_Guard_defn__hpp_ -#define Rice__Address_Registration_Guard_defn__hpp_ - -#include "cpp_api/Object_defn.hpp" -#include "detail/ruby.hpp" - -namespace Rice -{ - //! A guard to register a given address with the GC. - /*! Calls rb_gc_register_address upon construction and - * rb_gc_unregister_address upon destruction. - * For example: - * \code - * Class Foo - * { - * public: - * Foo() - * : string_(rb_str_new2()) - * , guard_(&string_); - * - * private: - * VALUE string_; - * Address_Registration_Guard guard_; - * }; - * \endcode - */ - class Address_Registration_Guard - { - public: - //! Register an address with the GC. - /* \param address The address to register with the GC. The address - * must point to a valid ruby object (RObject). - */ - Address_Registration_Guard(VALUE* address); - - //! Register an Object with the GC. - /*! \param object The Object to register with the GC. The object must - * not be destroyed before the Address_Registration_Guard is - * destroyed. - */ - Address_Registration_Guard(Object* object); - - //! Unregister an address/Object with the GC. - /*! Destruct an Address_Registration_Guard. The address registered - * with the Address_Registration_Guard when it was constructed will - * be unregistered from the GC. - */ - ~Address_Registration_Guard(); - - // Disable copying - Address_Registration_Guard(Address_Registration_Guard const& other) = delete; - Address_Registration_Guard& operator=(Address_Registration_Guard const& other) = delete; - - // Enable moving - Address_Registration_Guard(Address_Registration_Guard&& other); - Address_Registration_Guard& operator=(Address_Registration_Guard&& other); - - //! Get the address that is registered with the GC. - VALUE* address() const; - - /** Called during Ruby's exit process since we should not call - * rb_gc unregister_address there - */ - static void disable(); - - private: - inline static bool enabled = true; - inline static bool exit_handler_registered = false; - static void registerExitHandler(); - - private: - void registerAddress() const; - void unregisterAddress(); - - VALUE* address_ = nullptr; - }; -} // namespace Rice - -#endif // Rice__Address_Registration_Guard_defn__hpp_ \ No newline at end of file diff --git a/rice/Arg.hpp b/rice/Arg.hpp index dca100fa..406230a1 100644 --- a/rice/Arg.hpp +++ b/rice/Arg.hpp @@ -84,6 +84,4 @@ namespace Rice }; } // Rice -#include "Arg.ipp" - #endif // Rice__Arg__hpp_ \ No newline at end of file diff --git a/rice/Constructor.hpp b/rice/Constructor.hpp index 0c10c151..86aa372a 100644 --- a/rice/Constructor.hpp +++ b/rice/Constructor.hpp @@ -1,9 +1,6 @@ #ifndef Rice__Constructor__hpp_ #define Rice__Constructor__hpp_ -#include "detail/Wrapper.hpp" -#include "cpp_api/Object_defn.hpp" - namespace Rice { //! Define a Type's Constructor and it's arguments. diff --git a/rice/Data_Object.hpp b/rice/Data_Object.hpp index 7ac26555..a8ac3eba 100644 --- a/rice/Data_Object.hpp +++ b/rice/Data_Object.hpp @@ -1,8 +1,78 @@ #ifndef Rice__Data_Object__hpp_ #define Rice__Data_Object__hpp_ -#include "Data_Object_defn.hpp" -#include "Data_Object.ipp" +/*! \file + * \brief Provides a helper class for wrapping and unwrapping C++ + * objects as Ruby objects. + */ -#endif // Rice__Data_Object__hpp_ +namespace Rice +{ + //! A smartpointer-like wrapper for Ruby data objects. + /*! A data object is a ruby object of type T_DATA, which is usually + * created by using the Data_Wrap_Struct or Data_Make_Struct macro. + * This class wraps creation of the data structure, providing a + * type-safe object-oriented interface to the underlying C interface. + * This class works in conjunction with the Data_Type class to ensure + * type safety. + * + * Example: + * \code + * class Foo { }; + * ... + * Data_Type rb_cFoo = define_class("Foo"); + * ... + * // Wrap: + * Data_Object foo1(new Foo); + * + * // Get value to return: + * VALUE v = foo1.value() + * + * // Unwrap: + * Data_Object foo2(v, rb_cFoo); + * \endcode + */ + template + class Data_Object : public Object + { + static_assert(!std::is_pointer_v); + static_assert(!std::is_reference_v); + static_assert(!std::is_const_v); + static_assert(!std::is_volatile_v); + static_assert(!std::is_void_v); + + public: + static T* from_ruby(VALUE value, bool transferOwnership = false); + + public: + //! Wrap a C++ object. + /*! This constructor is analogous to calling Data_Wrap_Struct. Be + * careful not to call this function more than once for the same + * pointer (in general, it should only be called for newly + * constructed objects that need to be managed by Ruby's garbage + * collector). + * \param obj the object to wrap. + * \param isOwner Should the Data_Object take ownership of the object? + * \param klass the Ruby class to use for the newly created Ruby + * object. + */ + Data_Object(T* obj, bool isOwner = false, Class klass = Data_Type::klass()); + Data_Object(T& obj, bool isOwner = false, Class klass = Data_Type::klass()); + + //! Unwrap a Ruby object. + /*! This constructor is analogous to calling Data_Get_Struct. Uses + * Data_Type::klass as the class of the object. + * \param value the Ruby object to unwrap. + */ + Data_Object(Object value); + T& operator*() const; //!< Return a reference to obj_ + T* operator->() const; //!< Return a pointer to obj_ + T* get() const; //!< Return a pointer to obj_ + + private: + static void check_ruby_type(VALUE value); + }; +} // namespace Rice + +#endif // Rice__Data_Object__hpp_ diff --git a/rice/Data_Object.ipp b/rice/Data_Object.ipp index 986bc950..b878a8b4 100644 --- a/rice/Data_Object.ipp +++ b/rice/Data_Object.ipp @@ -1,7 +1,3 @@ -#ifndef Rice__Data_Object__ipp_ -#define Rice__Data_Object__ipp_ - -#include "Data_Type_defn.hpp" #include @@ -656,4 +652,3 @@ namespace Rice::detail Arg* arg_ = nullptr; }; } -#endif // Rice__Data_Object__ipp_ \ No newline at end of file diff --git a/rice/Data_Object_defn.hpp b/rice/Data_Object_defn.hpp deleted file mode 100644 index 3b866b60..00000000 --- a/rice/Data_Object_defn.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef Rice__Data_Object_defn__hpp_ -#define Rice__Data_Object_defn__hpp_ - -#include - -#include "detail/to_ruby.hpp" -#include "detail/ruby.hpp" -#include "cpp_api/Object_defn.hpp" - -/*! \file - * \brief Provides a helper class for wrapping and unwrapping C++ - * objects as Ruby objects. - */ - -namespace Rice -{ - //! A smartpointer-like wrapper for Ruby data objects. - /*! A data object is a ruby object of type T_DATA, which is usually - * created by using the Data_Wrap_Struct or Data_Make_Struct macro. - * This class wraps creation of the data structure, providing a - * type-safe object-oriented interface to the underlying C interface. - * This class works in conjunction with the Data_Type class to ensure - * type safety. - * - * Example: - * \code - * class Foo { }; - * ... - * Data_Type rb_cFoo = define_class("Foo"); - * ... - * // Wrap: - * Data_Object foo1(new Foo); - * - * // Get value to return: - * VALUE v = foo1.value() - * - * // Unwrap: - * Data_Object foo2(v, rb_cFoo); - * \endcode - */ - template - class Data_Object : public Object - { - static_assert(!std::is_pointer_v); - static_assert(!std::is_reference_v); - static_assert(!std::is_const_v); - static_assert(!std::is_volatile_v); - static_assert(!std::is_void_v); - - public: - static T* from_ruby(VALUE value, bool transferOwnership = false); - - public: - //! Wrap a C++ object. - /*! This constructor is analogous to calling Data_Wrap_Struct. Be - * careful not to call this function more than once for the same - * pointer (in general, it should only be called for newly - * constructed objects that need to be managed by Ruby's garbage - * collector). - * \param obj the object to wrap. - * \param isOwner Should the Data_Object take ownership of the object? - * \param klass the Ruby class to use for the newly created Ruby - * object. - */ - Data_Object(T* obj, bool isOwner = false, Class klass = Data_Type::klass()); - Data_Object(T& obj, bool isOwner = false, Class klass = Data_Type::klass()); - - //! Unwrap a Ruby object. - /*! This constructor is analogous to calling Data_Get_Struct. Uses - * Data_Type::klass as the class of the object. - * \param value the Ruby object to unwrap. - */ - Data_Object(Object value); - - T& operator*() const; //!< Return a reference to obj_ - T* operator->() const; //!< Return a pointer to obj_ - T* get() const; //!< Return a pointer to obj_ - - private: - static void check_ruby_type(VALUE value); - }; -} // namespace Rice - -#endif // Rice__Data_Object_defn__hpp_ - diff --git a/rice/Data_Type.hpp b/rice/Data_Type.hpp index c977f052..476137b9 100644 --- a/rice/Data_Type.hpp +++ b/rice/Data_Type.hpp @@ -1,7 +1,208 @@ #ifndef Rice__Data_Type__hpp_ #define Rice__Data_Type__hpp_ -#include "Data_Type_defn.hpp" -#include "Data_Type.ipp" +#include -#endif // Rice__Data_Type__hpp_ \ No newline at end of file +namespace Rice +{ + //! A mechanism for binding ruby types to C++ types. + /*! This class binds run-time types (Ruby VALUEs) to compile-time types + * (C++ types). The binding can occur only once. + */ + template + class Data_Type : public Class + { + static_assert(std::is_same_v, T>); + + public: + using type = T; + + //! Default constructor which does not bind. + /*! No member functions must be called on this Data_Type except bind, + * until the type is bound. + */ + Data_Type(); + + //! Constructor which takes a Module. + /*! Binds the type to the given VALUE according to the rules given + * above. + * \param klass the module to which to bind. + */ + Data_Type(Module const & v); + + //! Destructor. + virtual ~Data_Type(); + + //! Return the Ruby class. + /*! \return the ruby class to which the type is bound. + */ + static Class klass(); + + //! Return the Ruby data type. + static rb_data_type_t* ruby_data_type(); + + //! Assignment operator which takes a Module + /*! \param klass must be the class to which this data type is already + * bound. + * \return *this + */ + virtual Data_Type & operator=(Module const & klass); + + /*! Creates a singleton method allocate and an instance method called + * initialize which together create a new instance of the class. The + * allocate method allocates memory for the object reference and the + * initialize method constructs the object. + * \param constructor an object that has a static member function + * construct() that constructs a new instance of T and sets the object's data + * member to point to the new instance. A helper class Constructor + * is provided that does precisely this. + * \param args a list of Arg instance used to define default parameters (optional) + * + * For example: + * \code + * define_class("Foo") + * .define_constructor(Constructor()); + * \endcode + */ + template + Data_Type& define_constructor(Constructor_T constructor, Arg_Ts const& ...args); + + /*! Runs a function that should define this Data_Types methods and attributes. + * This is useful when creating classes from a C++ class template. + * + * \param builder A function that addes methods/attributes to this class + * + * For example: + * \code + * void builder(Data_Type>& klass) + * { + * klass.define_method... + * return klass; + * } + * + * define_class<>>("Matrix") + * .build(&builder); + * + * \endcode + */ + template + Data_Type& define(Func_T func); + + //! Register a Director class for this class. + /*! For any class that uses Rice::Director to enable polymorphism + * across the languages, you need to register that director proxy + * class with this method. Not doing so will cause the resulting + * library to die at run time when it tries to convert the base + * type into the Director proxy type. + * + * This method takes no methodInfo, just needs the type of the + * Director proxy class. + * + * For example: + * \code + * class FooDirector : public Foo, public Rice::Director { + * ... + * }; + * + * define_class("Foo") + * .define_director() + * .define_constructor(Constructor()); + * \endcode + */ + template + Data_Type& define_director(); + + //! Determine if the type is bound. + /*! \return true if the object is bound, false otherwise. + */ + static bool is_bound(); + static void check_is_bound(); + + // This is only for testing - DO NOT USE!!! + static void unbind(); + + static bool is_descendant(VALUE value); + + //! Define an iterator. + /*! Essentially this is a conversion from a C++-style begin/end + * iterator to a Ruby-style \#each iterator. + * \param begin a member function pointer to a function that returns + * an iterator to the beginning of the sequence. + * \param end a member function pointer to a function that returns an + * iterator to the end of the sequence. + * \param name the name of the iterator. + * \return *this + */ + + template + Data_Type& define_iterator(Iterator_Func_T begin, Iterator_Func_T end, std::string name = "each"); + + template + Data_Type& define_attr(std::string name, Attribute_T attribute, AttrAccess access = AttrAccess::ReadWrite); + + template + Data_Type& define_singleton_attr(std::string name, Attribute_T attribute, AttrAccess access = AttrAccess::ReadWrite); + + #include "cpp_api/shared_methods.hpp" + protected: + //! Bind a Data_Type to a VALUE. + /*! Throws an exception if the Data_Type is already bound to a + * different class. Any existing instances of the Data_Type will be + * bound after this function returns. + * \param klass the ruby type to which to bind. + * \return *this + */ + template + static Data_Type bind(const Module& klass); + + template + friend Rice::Data_Type define_class_under(Object module, char const * name); + + template + friend Rice::Data_Type define_class(char const * name); + + template + void wrap_native_call(VALUE klass, std::string name, Function_T&& function, MethodInfo* methodInfo); + + private: + template + friend class Data_Type; + + static inline VALUE klass_ = Qnil; + + // Typed Data support + static inline rb_data_type_t* rb_data_type_ = nullptr; + + typedef std::set *> Instances; + + static Instances & unbound_instances() + { + static Instances unbound_instances; + return unbound_instances; + } + }; + + //! Define a new data class in the namespace given by module. + /*! By default the class will inherit from Ruby's rb_cObject. This + * can be overriden via the Base_T template parameter. Note that + * Base_T must already have been registered. + * \param T the C++ type of the wrapped class. + * \param module the the Module in which to define the class. + * \return the new class. + */ + template + Data_Type define_class_under(Object module, char const* name); + + //! Define a new data class in the default namespace. + /*! By default the class will inherit from Ruby's rb_cObject. This + * can be overriden via the Base_T template parameter. Note that + * Base_T must already have been registered. + * \param T the C++ type of the wrapped class. + * \param module the the Module in which to define the class. + * \return the new class. + */ + template + Data_Type define_class(char const* name); +} + +#endif // Rice__Data_Type__hpp_ diff --git a/rice/Data_Type.ipp b/rice/Data_Type.ipp index 79f428f7..0f62475c 100644 --- a/rice/Data_Type.ipp +++ b/rice/Data_Type.ipp @@ -1,15 +1,3 @@ -#ifndef Rice__Data_Type__ipp_ -#define Rice__Data_Type__ipp_ - -#include "traits/attribute_traits.hpp" -#include "traits/method_traits.hpp" -#include "detail/NativeAttributeGet.hpp" -#include "detail/NativeAttributeSet.hpp" -#include "detail/default_allocation_func.hpp" -#include "detail/TypeRegistry.hpp" -#include "detail/Wrapper.hpp" -#include "detail/NativeIterator.hpp" -#include "ruby_mark.hpp" #include @@ -325,4 +313,3 @@ namespace Rice detail::NativeFunction::define(klass, name, std::forward(function), methodInfo); } } -#endif \ No newline at end of file diff --git a/rice/Data_Type_defn.hpp b/rice/Data_Type_defn.hpp deleted file mode 100644 index e3df102d..00000000 --- a/rice/Data_Type_defn.hpp +++ /dev/null @@ -1,213 +0,0 @@ -#ifndef Rice__Data_Type_defn__hpp_ -#define Rice__Data_Type_defn__hpp_ - -#include "cpp_api/Class_defn.hpp" -#include "detail/ruby.hpp" -#include - -namespace Rice -{ - //! A mechanism for binding ruby types to C++ types. - /*! This class binds run-time types (Ruby VALUEs) to compile-time types - * (C++ types). The binding can occur only once. - */ - template - class Data_Type : public Class - { - static_assert(std::is_same_v, T>); - - public: - using type = T; - - //! Default constructor which does not bind. - /*! No member functions must be called on this Data_Type except bind, - * until the type is bound. - */ - Data_Type(); - - //! Constructor which takes a Module. - /*! Binds the type to the given VALUE according to the rules given - * above. - * \param klass the module to which to bind. - */ - Data_Type(Module const & v); - - //! Destructor. - virtual ~Data_Type(); - - //! Return the Ruby class. - /*! \return the ruby class to which the type is bound. - */ - static Class klass(); - - //! Return the Ruby data type. - static rb_data_type_t* ruby_data_type(); - - //! Assignment operator which takes a Module - /*! \param klass must be the class to which this data type is already - * bound. - * \return *this - */ - virtual Data_Type & operator=(Module const & klass); - - /*! Creates a singleton method allocate and an instance method called - * initialize which together create a new instance of the class. The - * allocate method allocates memory for the object reference and the - * initialize method constructs the object. - * \param constructor an object that has a static member function - * construct() that constructs a new instance of T and sets the object's data - * member to point to the new instance. A helper class Constructor - * is provided that does precisely this. - * \param args a list of Arg instance used to define default parameters (optional) - * - * For example: - * \code - * define_class("Foo") - * .define_constructor(Constructor()); - * \endcode - */ - template - Data_Type& define_constructor(Constructor_T constructor, Arg_Ts const& ...args); - - /*! Runs a function that should define this Data_Types methods and attributes. - * This is useful when creating classes from a C++ class template. - * - * \param builder A function that addes methods/attributes to this class - * - * For example: - * \code - * void builder(Data_Type>& klass) - * { - * klass.define_method... - * return klass; - * } - * - * define_class<>>("Matrix") - * .build(&builder); - * - * \endcode - */ - template - Data_Type& define(Func_T func); - - //! Register a Director class for this class. - /*! For any class that uses Rice::Director to enable polymorphism - * across the languages, you need to register that director proxy - * class with this method. Not doing so will cause the resulting - * library to die at run time when it tries to convert the base - * type into the Director proxy type. - * - * This method takes no methodInfo, just needs the type of the - * Director proxy class. - * - * For example: - * \code - * class FooDirector : public Foo, public Rice::Director { - * ... - * }; - * - * define_class("Foo") - * .define_director() - * .define_constructor(Constructor()); - * \endcode - */ - template - Data_Type& define_director(); - - //! Determine if the type is bound. - /*! \return true if the object is bound, false otherwise. - */ - static bool is_bound(); - static void check_is_bound(); - - // This is only for testing - DO NOT USE!!! - static void unbind(); - - static bool is_descendant(VALUE value); - - //! Define an iterator. - /*! Essentially this is a conversion from a C++-style begin/end - * iterator to a Ruby-style \#each iterator. - * \param begin a member function pointer to a function that returns - * an iterator to the beginning of the sequence. - * \param end a member function pointer to a function that returns an - * iterator to the end of the sequence. - * \param name the name of the iterator. - * \return *this - */ - - template - Data_Type& define_iterator(Iterator_Func_T begin, Iterator_Func_T end, std::string name = "each"); - - template - Data_Type& define_attr(std::string name, Attribute_T attribute, AttrAccess access = AttrAccess::ReadWrite); - - template - Data_Type& define_singleton_attr(std::string name, Attribute_T attribute, AttrAccess access = AttrAccess::ReadWrite); - - #include "cpp_api/shared_methods.hpp" - - protected: - //! Bind a Data_Type to a VALUE. - /*! Throws an exception if the Data_Type is already bound to a - * different class. Any existing instances of the Data_Type will be - * bound after this function returns. - * \param klass the ruby type to which to bind. - * \return *this - */ - template - static Data_Type bind(const Module& klass); - - template - friend Rice::Data_Type define_class_under(Object module, char const * name); - - template - friend Rice::Data_Type define_class(char const * name); - - template - void wrap_native_call(VALUE klass, std::string name, Function_T&& function, MethodInfo* methodInfo); - - private: - template - friend class Data_Type; - - static inline VALUE klass_ = Qnil; - - // Typed Data support - static inline rb_data_type_t* rb_data_type_ = nullptr; - - typedef std::set *> Instances; - - static Instances & unbound_instances() - { - static Instances unbound_instances; - return unbound_instances; - } - }; - - //! Define a new data class in the namespace given by module. - /*! By default the class will inherit from Ruby's rb_cObject. This - * can be overriden via the Base_T template parameter. Note that - * Base_T must already have been registered. - * \param T the C++ type of the wrapped class. - * \param module the the Module in which to define the class. - * \return the new class. - */ - template - Data_Type define_class_under(Object module, char const* name); - - //! Define a new data class in the default namespace. - /*! By default the class will inherit from Ruby's rb_cObject. This - * can be overriden via the Base_T template parameter. Note that - * Base_T must already have been registered. - * \param T the C++ type of the wrapped class. - * \param module the the Module in which to define the class. - * \return the new class. - */ - template - Data_Type define_class(char const* name); -} - -#include "Data_Type.ipp" - -#endif // Rice__Data_Type_defn__hpp_ \ No newline at end of file diff --git a/rice/Director.hpp b/rice/Director.hpp index 8cf53f50..4b02cf4f 100644 --- a/rice/Director.hpp +++ b/rice/Director.hpp @@ -1,8 +1,6 @@ #ifndef Rice__Director__hpp_ #define Rice__Director__hpp_ -#include "cpp_api/Object.hpp" - namespace Rice { /** diff --git a/rice/Enum.hpp b/rice/Enum.hpp index 4be90ab5..baea01ff 100644 --- a/rice/Enum.hpp +++ b/rice/Enum.hpp @@ -1,9 +1,6 @@ #ifndef Rice__Enum__hpp_ #define Rice__Enum__hpp_ -#include "Data_Type.hpp" -#include - namespace Rice { /*! @@ -65,6 +62,4 @@ namespace Rice Enum define_enum_under(char const* name, Module module ); } // namespace Rice -#include "Enum.ipp" - #endif // Rice__Enum__hpp_ \ No newline at end of file diff --git a/rice/Enum.ipp b/rice/Enum.ipp index 9147782c..e9945bd6 100644 --- a/rice/Enum.ipp +++ b/rice/Enum.ipp @@ -1,6 +1,3 @@ -#include "detail/TypeRegistry.hpp" -#include "Data_Object.hpp" -#include "cpp_api/String.hpp" #include diff --git a/rice/Exception.hpp b/rice/Exception.hpp index 1c3808a5..98522f05 100644 --- a/rice/Exception.hpp +++ b/rice/Exception.hpp @@ -1,7 +1,67 @@ #ifndef Rice__Exception__hpp_ #define Rice__Exception__hpp_ -#include "Exception_defn.hpp" -#include "Exception.ipp" +#include + +namespace Rice +{ + //! A placeholder for Ruby exceptions. + /*! You can use this to safely throw a Ruby exception using C++ syntax: + * \code + * VALUE foo(VALUE self) { + * RUBY_TRY { + * throw Rice::Exception(rb_eMyException, "uh oh!"); + * RUBY_CATCH + * } + * \endcode + */ + class Exception + : public std::exception + { + public: + //! Construct a Exception with a Ruby exception instance + explicit Exception(VALUE exception); + + //! Construct a Exception with printf-style formatting. + /*! \param exc either an exception object or a class that inherits + * from Exception. + * \param fmt a printf-style format string + * \param ... the arguments to the format string. + */ + template + Exception(const Exception& other, char const* fmt, Arg_Ts&&...args); + + //! Construct a Exception with printf-style formatting. + /*! \param exc either an exception object or a class that inherits + * from Exception. + * \param fmt a printf-style format string + * \param ... the arguments to the format string. + */ + template + Exception(const VALUE exceptionType, char const* fmt, Arg_Ts&&...args); + + //! Destructor + virtual ~Exception() noexcept = default; + + //! Get message as a char const *. + /*! If message is a non-string object, then this function will attempt + * to throw an exception (which it can't do because of the no-throw + * specification). + * \return the underlying C pointer of the underlying message object. + */ + virtual char const* what() const noexcept override; + + //! Returns the Ruby exception class + VALUE class_of() const; + + //! Returns an instance of a Ruby exception + VALUE value() const; + + private: + // TODO: Do we need to tell the Ruby gc about an exception instance? + mutable VALUE exception_ = Qnil; + mutable std::string message_; + }; +} // namespace Rice #endif // Rice__Exception__hpp_ \ No newline at end of file diff --git a/rice/Exception.ipp b/rice/Exception.ipp index 12705353..1e25b6a9 100644 --- a/rice/Exception.ipp +++ b/rice/Exception.ipp @@ -1,7 +1,3 @@ -#ifndef Rice__Exception__ipp_ -#define Rice__Exception__ipp_ - -#include "detail/from_ruby.hpp" namespace Rice { @@ -61,4 +57,3 @@ namespace Rice return this->exception_; } } -#endif // Rice__Exception__ipp_ \ No newline at end of file diff --git a/rice/Exception_defn.hpp b/rice/Exception_defn.hpp deleted file mode 100644 index 4ddc7734..00000000 --- a/rice/Exception_defn.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef Rice__Exception_defn__hpp_ -#define Rice__Exception_defn__hpp_ - -#include -#include "detail/ruby.hpp" - -namespace Rice -{ - //! A placeholder for Ruby exceptions. - /*! You can use this to safely throw a Ruby exception using C++ syntax: - * \code - * VALUE foo(VALUE self) { - * RUBY_TRY { - * throw Rice::Exception(rb_eMyException, "uh oh!"); - * RUBY_CATCH - * } - * \endcode - */ - class Exception - : public std::exception - { - public: - //! Construct a Exception with a Ruby exception instance - explicit Exception(VALUE exception); - - //! Construct a Exception with printf-style formatting. - /*! \param exc either an exception object or a class that inherits - * from Exception. - * \param fmt a printf-style format string - * \param ... the arguments to the format string. - */ - template - Exception(const Exception& other, char const* fmt, Arg_Ts&&...args); - - //! Construct a Exception with printf-style formatting. - /*! \param exc either an exception object or a class that inherits - * from Exception. - * \param fmt a printf-style format string - * \param ... the arguments to the format string. - */ - template - Exception(const VALUE exceptionType, char const* fmt, Arg_Ts&&...args); - - //! Destructor - virtual ~Exception() noexcept = default; - - //! Get message as a char const *. - /*! If message is a non-string object, then this function will attempt - * to throw an exception (which it can't do because of the no-throw - * specification). - * \return the underlying C pointer of the underlying message object. - */ - virtual char const* what() const noexcept override; - - //! Returns the Ruby exception class - VALUE class_of() const; - - //! Returns an instance of a Ruby exception - VALUE value() const; - - private: - // TODO: Do we need to tell the Ruby gc about an exception instance? - mutable VALUE exception_ = Qnil; - mutable std::string message_; - }; -} // namespace Rice - -#endif // Rice__Exception_defn__hpp_ \ No newline at end of file diff --git a/rice/HandlerRegistration.hpp b/rice/HandlerRegistration.hpp index 59ad0faf..7a83dbcb 100644 --- a/rice/HandlerRegistration.hpp +++ b/rice/HandlerRegistration.hpp @@ -1,8 +1,6 @@ #ifndef Rice__HandlerRegistration__hpp_ #define Rice__HandlerRegistration__hpp_ -#include "detail/HandlerRegistry.hpp" - namespace Rice { // Register exception handler diff --git a/rice/Identifier.hpp b/rice/Identifier.hpp index 2316255a..4ab2a7de 100644 --- a/rice/Identifier.hpp +++ b/rice/Identifier.hpp @@ -45,6 +45,4 @@ namespace Rice }; } // namespace Rice -#include "Identifier.ipp" - #endif // Rice__Identifier__hpp_ diff --git a/rice/MemoryView.hpp b/rice/MemoryView.hpp index 45d43982..8440429b 100644 --- a/rice/MemoryView.hpp +++ b/rice/MemoryView.hpp @@ -1,7 +1,11 @@ #ifndef Rice__MemoryView__hpp_ #define Rice__MemoryView__hpp_ -#include "MemoryView_defn.hpp" -#include "MemoryView.ipp" +namespace Rice +{ + class MemoryView + { + }; +} #endif // Rice__MemoryView__hpp_ \ No newline at end of file diff --git a/rice/MemoryView.ipp b/rice/MemoryView.ipp index cb858528..5a8fd1ad 100644 --- a/rice/MemoryView.ipp +++ b/rice/MemoryView.ipp @@ -1,6 +1,3 @@ -#ifndef Rice__MemoryView__ipp_ -#define Rice__MemoryView__ipp_ - namespace Rice { } @@ -44,4 +41,3 @@ namespace Rice::detail Arg* arg_ = nullptr; }; } -#endif // Rice__MemoryView__ipp_ \ No newline at end of file diff --git a/rice/MemoryView_defn.hpp b/rice/MemoryView_defn.hpp deleted file mode 100644 index eefe87ff..00000000 --- a/rice/MemoryView_defn.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef Rice__MemoryView_defn__hpp_ -#define Rice__MemoryView_defn__hpp_ - -#include -#include "detail/ruby.hpp" - -namespace Rice -{ - class MemoryView - { - }; -} - -#endif // Rice__MemoryView_defn__hpp_ \ No newline at end of file diff --git a/rice/Return.hpp b/rice/Return.hpp index 9f395bc6..677151ab 100644 --- a/rice/Return.hpp +++ b/rice/Return.hpp @@ -1,8 +1,6 @@ #ifndef Rice__Return__hpp_ #define Rice__Return__hpp_ -#include - namespace Rice { //! Helper for defining Return argument of a method @@ -35,6 +33,4 @@ namespace Rice }; } // Rice -#include "Return.ipp" - #endif // Rice__Return__hpp_ diff --git a/rice/cpp_api/Array.hpp b/rice/cpp_api/Array.hpp index a2ce274c..d49ec91d 100644 --- a/rice/cpp_api/Array.hpp +++ b/rice/cpp_api/Array.hpp @@ -1,8 +1,6 @@ #ifndef Rice__Array__hpp_ #define Rice__Array__hpp_ -#include "Builtin_Object.hpp" -#include "../detail/ruby.hpp" #include namespace Rice @@ -187,6 +185,4 @@ namespace Rice }; } // namespace Rice -#include "Array.ipp" - #endif // Rice__Array__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Builtin_Object.hpp b/rice/cpp_api/Builtin_Object.hpp index bc8a5168..45a0a25d 100644 --- a/rice/cpp_api/Builtin_Object.hpp +++ b/rice/cpp_api/Builtin_Object.hpp @@ -1,7 +1,31 @@ #ifndef Rice__Builtin_Object__hpp_ #define Rice__Builtin_Object__hpp_ -#include "Builtin_Object_defn.hpp" -#include "Builtin_Object.ipp" +namespace Rice +{ + //! A smartpointer-like wrapper for Ruby builtin objects. + /*! A builtin object is one of Ruby's internal types, e.g. RArray or + * RString. Every builtin type structure has a corresponding integer + * type number (e.g T_ARRAY for RArray or T_STRING for RString). This + * class is a wrapper for those types of objects, primarily useful as a + * base class for other wrapper classes like Array and Hash. + */ + template + class Builtin_Object + : public Object + { + public: + //! Wrap an already allocated Ruby object. + /*! Checks to see if the object is an object of type Builtin_Type; a + * C++ exception is thrown if this is not the case. + * \param value the object to be wrapped. + */ + Builtin_Object(Object value); + + RObject& operator*() const; //!< Return a reference to obj_ + RObject* operator->() const; //!< Return a pointer to obj_ + RObject* get() const; //!< Return a pointer to obj_ + }; +} // namespace Rice #endif // Rice__Builtin_Object__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Builtin_Object.ipp b/rice/cpp_api/Builtin_Object.ipp index cff14fd2..12bae2ec 100644 --- a/rice/cpp_api/Builtin_Object.ipp +++ b/rice/cpp_api/Builtin_Object.ipp @@ -1,6 +1,3 @@ -#ifndef Rice__Builtin_Object__ipp_ -#define Rice__Builtin_Object__ipp_ - #include namespace Rice @@ -38,5 +35,3 @@ namespace Rice return ROBJECT(this->value()); } } // namespace Rice - -#endif // Rice__Builtin_Object__ipp_ \ No newline at end of file diff --git a/rice/cpp_api/Builtin_Object_defn.hpp b/rice/cpp_api/Builtin_Object_defn.hpp deleted file mode 100644 index 7db1e332..00000000 --- a/rice/cpp_api/Builtin_Object_defn.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef Rice__Builtin_Object_defn__hpp_ -#define Rice__Builtin_Object_defn__hpp_ - -#include "Object_defn.hpp" - -namespace Rice -{ - //! A smartpointer-like wrapper for Ruby builtin objects. - /*! A builtin object is one of Ruby's internal types, e.g. RArray or - * RString. Every builtin type structure has a corresponding integer - * type number (e.g T_ARRAY for RArray or T_STRING for RString). This - * class is a wrapper for those types of objects, primarily useful as a - * base class for other wrapper classes like Array and Hash. - */ - template - class Builtin_Object - : public Object - { - public: - //! Wrap an already allocated Ruby object. - /*! Checks to see if the object is an object of type Builtin_Type; a - * C++ exception is thrown if this is not the case. - * \param value the object to be wrapped. - */ - Builtin_Object(Object value); - - RObject& operator*() const; //!< Return a reference to obj_ - RObject* operator->() const; //!< Return a pointer to obj_ - RObject* get() const; //!< Return a pointer to obj_ - }; -} // namespace Rice - -#endif // Rice__Builtin_Object_defn__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Class.hpp b/rice/cpp_api/Class.hpp index 2fd10831..73421465 100644 --- a/rice/cpp_api/Class.hpp +++ b/rice/cpp_api/Class.hpp @@ -1,7 +1,66 @@ #ifndef Rice__Class__hpp_ #define Rice__Class__hpp_ -#include "Class_defn.hpp" -#include "Class.ipp" +#include + +/*! + * \example inheritance/animals.cpp + * \example callbacks/sample_callbacks.cpp + */ + +namespace Rice +{ + //! A helper for defining a Class and its methods. + /*! This class provides a C++-style interface to ruby's Class class and + * for defining methods on that class. + */ + class Class: public Module + { + public: + //! Default construct a new class wrapper and initialize it to + //! rb_cObject. + Class() = default; + + //! Construct a new class wrapper from a ruby object of type T_CLASS. + Class(VALUE v); + + //! Disallow creation of an instance from Ruby code. + /*! Undefines the singleton method allocate (or new, if using a + * version of ruby prior to 1.7) and the instance method initialize. + */ + Class & undef_creation_funcs(); + + // Create a new instance + template + Object create(Arg_Ts ...args); + + //! Class name + /*! \return std::string. + */ + const std::string name(); + + #include "shared_methods.hpp" + }; + + //! Define a new class in the namespace given by module. + /*! \param module the Module in which to define the class. + * \param name the name of the class. + * \param superclass the base class to use. + * \return the new class. + */ + Class define_class_under(Object module, char const * name, const Class& superclass = rb_cObject); + + //! Define a new class in the default namespace. + /*! \param name the name of the class. + * \param superclass the base class to use. + * \return the new class. + */ + Class define_class(char const * name, const Class& superclass = rb_cObject); + + //! Create a new anonymous class. + /*! \return the new class. + */ + Class anonymous_class(); +} // namespace Rice #endif // Rice__Class__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Class.ipp b/rice/cpp_api/Class.ipp index 1276a069..eaa87ded 100644 --- a/rice/cpp_api/Class.ipp +++ b/rice/cpp_api/Class.ipp @@ -1,8 +1,3 @@ -#ifndef Rice__Class__ipp_ -#define Rice__Class__ipp_ - -#include "../Exception.hpp" -#include "../Data_Type_defn.hpp" namespace Rice { @@ -86,4 +81,3 @@ namespace Rice::detail } }; } -#endif // Rice__Class__ipp_ \ No newline at end of file diff --git a/rice/cpp_api/Class_defn.hpp b/rice/cpp_api/Class_defn.hpp deleted file mode 100644 index 31cab2c6..00000000 --- a/rice/cpp_api/Class_defn.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef Rice__Class_defn__hpp_ -#define Rice__Class_defn__hpp_ - -#include - -#include "Module_defn.hpp" - -/*! - * \example inheritance/animals.cpp - * \example callbacks/sample_callbacks.cpp - */ - -namespace Rice -{ - //! A helper for defining a Class and its methods. - /*! This class provides a C++-style interface to ruby's Class class and - * for defining methods on that class. - */ - class Class: public Module - { - public: - //! Default construct a new class wrapper and initialize it to - //! rb_cObject. - Class() = default; - - //! Construct a new class wrapper from a ruby object of type T_CLASS. - Class(VALUE v); - - //! Disallow creation of an instance from Ruby code. - /*! Undefines the singleton method allocate (or new, if using a - * version of ruby prior to 1.7) and the instance method initialize. - */ - Class & undef_creation_funcs(); - - // Create a new instance - template - Object create(Arg_Ts ...args); - - //! Class name - /*! \return std::string. - */ - const std::string name(); - - #include "shared_methods.hpp" - }; - - //! Define a new class in the namespace given by module. - /*! \param module the Module in which to define the class. - * \param name the name of the class. - * \param superclass the base class to use. - * \return the new class. - */ - Class define_class_under(Object module, char const * name, const Class& superclass = rb_cObject); - - //! Define a new class in the default namespace. - /*! \param name the name of the class. - * \param superclass the base class to use. - * \return the new class. - */ - Class define_class(char const * name, const Class& superclass = rb_cObject); - - //! Create a new anonymous class. - /*! \return the new class. - */ - Class anonymous_class(); -} // namespace Rice - -#endif // Rice__Class_defn__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Hash.hpp b/rice/cpp_api/Hash.hpp index 1f663222..0c5baa71 100644 --- a/rice/cpp_api/Hash.hpp +++ b/rice/cpp_api/Hash.hpp @@ -1,9 +1,6 @@ #ifndef Rice__Hash__hpp_ #define Rice__Hash__hpp_ -#include "Builtin_Object_defn.hpp" -#include "Array.hpp" -#include "../detail/ruby.hpp" #include #include @@ -193,7 +190,5 @@ namespace Rice }; } // namespace Rice -#include "Hash.ipp" - #endif // Rice__Hash__hpp_ diff --git a/rice/cpp_api/Hash.ipp b/rice/cpp_api/Hash.ipp index 3149f457..2b5a0c52 100644 --- a/rice/cpp_api/Hash.ipp +++ b/rice/cpp_api/Hash.ipp @@ -1,8 +1,3 @@ -#ifndef Rice__Hash__ipp_ -#define Rice__Hash__ipp_ - -#include "../Exception.hpp" -#include "String.hpp" #include namespace Rice @@ -260,5 +255,3 @@ namespace Rice::detail } }; } - -#endif // Rice__Hash__ipp_ \ No newline at end of file diff --git a/rice/cpp_api/Module.hpp b/rice/cpp_api/Module.hpp index 1737d21a..199dfbde 100644 --- a/rice/cpp_api/Module.hpp +++ b/rice/cpp_api/Module.hpp @@ -1,8 +1,76 @@ #ifndef Rice__Module__hpp_ #define Rice__Module__hpp_ -#include "Module_defn.hpp" -#include "Module.ipp" +namespace Rice +{ + template + void validateType(); -#endif // Rice__builtin__Module__hpp_ + //! A helper for defining a Module and its methods. + /*! This class provides a C++-style interface to ruby's Module class and + * for defining methods on that module. + * + * Many of the methods are defined in Module_impl.hpp so that they can + * return a reference to the most derived type. + */ + // TODO: we can't inherit from Builtin_Object, because Class needs + // type T_CLASS and Module needs type T_MODULE + class Module : public Object + { + public: + //! Default construct a Module and initialize it to rb_cObject. + Module(); + //! Construct a Module from an existing Module object. + Module(VALUE v); + + //! Construct a Module from an string that references a Module + Module(std::string name, Object under = rb_cObject); + + //! Return the name of the module. + String name() const; + + //! Return an array containing the Module's ancestors. + /*! You will need to include Array.hpp to use this function. + */ + Array ancestors() const; + + //! Return the module's singleton class. + /*! You will need to include Class.hpp to use this function. + */ + Class singleton_class() const; + + //! Evaluate the given string in the context of the module. + /*! This is equivalant to calling obj.module_eval(s) from inside the + * interpreter. + * \return the result of the expression. + */ + Object module_eval(String const& s); + + //! Define a constant + template + Module& define_constant(std::string name, Constant_T value); + + #include "shared_methods.hpp" + protected: + template + void wrap_native_call(VALUE klass, std::string name, Function_T&& function, MethodInfo* methodInfo); + }; + + //! Define a new module in the namespace given by module. + /*! \param module the module in which to define the new module. + * \param name the name of the new module. + */ + Module define_module_under(Object module, char const * name); + + //! Define a new module in the default namespace. + /*! \param name the name of the new module. + */ + Module define_module(char const * name); + + //! Create a new anonymous module. + /*! \return the new module. + */ + Module anonymous_module(); +} +#endif // Rice__Module__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Module.ipp b/rice/cpp_api/Module.ipp index 1110a69d..f3d78134 100644 --- a/rice/cpp_api/Module.ipp +++ b/rice/cpp_api/Module.ipp @@ -1,10 +1,3 @@ -#ifndef Rice__Module__ipp_ -#define Rice__Module__ipp_ - -#include "../traits/rice_traits.hpp" -#include "../detail/Type.hpp" -#include "../detail/NativeFunction.hpp" -#include "../Exception.hpp" namespace Rice { @@ -114,4 +107,3 @@ namespace Rice::detail } }; } -#endif // Rice__Module__ipp_ \ No newline at end of file diff --git a/rice/cpp_api/Module_defn.hpp b/rice/cpp_api/Module_defn.hpp deleted file mode 100644 index 45a07245..00000000 --- a/rice/cpp_api/Module_defn.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef Rice__Module_defn__hpp_ -#define Rice__Module_defn__hpp_ - -#include "../detail/ExceptionHandler_defn.hpp" -#include "../detail/ruby.hpp" -#include "Object_defn.hpp" - -namespace Rice -{ - template - void validateType(); - - //! A helper for defining a Module and its methods. - /*! This class provides a C++-style interface to ruby's Module class and - * for defining methods on that module. - * - * Many of the methods are defined in Module_impl.hpp so that they can - * return a reference to the most derived type. - */ - // TODO: we can't inherit from Builtin_Object, because Class needs - // type T_CLASS and Module needs type T_MODULE - class Module : public Object - { - public: - //! Default construct a Module and initialize it to rb_cObject. - Module(); - - //! Construct a Module from an existing Module object. - Module(VALUE v); - - //! Construct a Module from an string that references a Module - Module(std::string name, Object under = rb_cObject); - - //! Return the name of the module. - String name() const; - - //! Return an array containing the Module's ancestors. - /*! You will need to include Array.hpp to use this function. - */ - Array ancestors() const; - - //! Return the module's singleton class. - /*! You will need to include Class.hpp to use this function. - */ - Class singleton_class() const; - - //! Evaluate the given string in the context of the module. - /*! This is equivalant to calling obj.module_eval(s) from inside the - * interpreter. - * \return the result of the expression. - */ - Object module_eval(String const& s); - - //! Define a constant - template - Module& define_constant(std::string name, Constant_T value); - - #include "shared_methods.hpp" - - protected: - template - void wrap_native_call(VALUE klass, std::string name, Function_T&& function, MethodInfo* methodInfo); - }; - - //! Define a new module in the namespace given by module. - /*! \param module the module in which to define the new module. - * \param name the name of the new module. - */ - Module define_module_under(Object module, char const * name); - - //! Define a new module in the default namespace. - /*! \param name the name of the new module. - */ - Module define_module(char const * name); - - //! Create a new anonymous module. - /*! \return the new module. - */ - Module anonymous_module(); -} -#endif // Rice__Module_defn__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Object.hpp b/rice/cpp_api/Object.hpp index 0d6e894f..d7131e31 100644 --- a/rice/cpp_api/Object.hpp +++ b/rice/cpp_api/Object.hpp @@ -1,8 +1,272 @@ #ifndef Rice__Object__hpp_ #define Rice__Object__hpp_ -#include "Object_defn.hpp" -#include "Object.ipp" +/*! \file Object.hpp + */ -#endif // Rice__Object__hpp_ +#include + +namespace Rice +{ + class Class; + class String; + class Array; + + //! The base class for all Objects + /*! Perhaps the name "Object" is a misnomer, because this class really + * holds an object reference, not an object. + */ + class Object + { + public: + //! Encapsulate an existing ruby object. + Object(VALUE value = Qnil) : value_(value) {} + + //! Destructor + virtual ~Object(); + + // Enable copying + Object(const Object& other) = default; + Object& operator=(const Object& other) = default; + + // Enable moving + Object(Object&& other); + Object& operator=(Object&& other); + + //! Returns false if the object is nil or false; returns true + //! otherwise. + // Having this conversion also prevents accidental conversion to + // undesired integral types (e.g. long or int) by making the + // conversion ambiguous. + bool test() const { return RTEST(value_); } + + //! Returns false if the object is nil or false; returns true + //! otherwise. + operator bool() const { return test(); } + + //! Returns true if the object is nil, false otherwise. + bool is_nil() const { return NIL_P(value_); } + + //! Implicit conversion to VALUE. + operator VALUE() const { return value_; } + + //! Explicitly get the encapsulated VALUE. + // Returns a const ref so that Address_Registration_Guard can access + // the address where the VALUE is stored + VALUE const volatile& value() const { return value_; } + + //! Get the class of an object. + /*! \return the object's Class. + */ + Class class_of() const; + + //! Compare this object to another object. + /*! Gets the result of self <=> other and returns the result. The + * result will be less than zero if self < other, greater than zero + * if self > other, and equal to zero if self == other. + */ + int compare(Object const& other) const; + + //! Return a string representation of an object. + /*! \return the result of calling to_s on the object. A String is not + * returned, because it is not possible to return an instance of a + * derived class. + */ + String to_s() const; + + //! Return the name of an object's class. + String class_name() const; + + //! Inspect the object. + /*! \return the result of calling inspect on the object. A String is + * not returned, because it is not possible to return an instance of + * a derived class. + */ + String inspect() const; + + //! Freeze the object. + void freeze(); + + //! Determine if the object is frozen. + /*! \return true if the object is frozen, false otherwise. + */ + bool is_frozen() const; + + //! Evaluate the given string in the context of the object. + /*! This is equivalant to calling obj.instance_eval(s) from inside the + * interpreter. + * \return the result of the expression. + */ + Object instance_eval(String const& s); + + //! Return the type of the underlying C object. + /*! This is equivalent to calling rb_type(obj). + * \return the type of the underlying C object (e.g. T_DATA, T_ARRAY, + * etc.). + */ + int rb_type() const; + + //! Return the object's id + VALUE object_id() const; + + //! Determine whether the object is an instance of a class/module. + /*! \param klass a class or module. + * \return true if the object is an instance of the given + * class/module or one of its descendants. + */ + bool is_a(Object klass) const; + //! Determine if the objects responds to a method. + /*! \param id the name of the method + * \return true if the objects responds to the method, false + * otherwise. + */ + bool respond_to(Identifier id) const; + + //! Determine whether class is the object's class. + /*! \param klass a class. + * \return true if the object is an instance of the given class. + */ + bool is_instance_of(Object klass) const; + + //! Determine whether the Ruby VALUEs wrapped by this + //! object are the same object. Maps to Object::equal? + /*! \param other a Object. + */ + bool is_equal(const Object& other) const; + + //! Determine whether the Ruby VALUEs wrapped by this + //! object are equivalent. Maps to Object::eql? + /*! \param other a Object. + */ + bool is_eql(const Object& other) const; + + //! Set an instance variable. + /*! \param name the name of the instance variable to set (including + * the leading @ sign) + * \param value the value of the variable, which will be converted to + * a Ruby type if necessary. + */ + template + void iv_set(Identifier name, T const& value); + + //! Get the value of an instance variable. + /*! \param name the name of the instance variable to get + * \return the value of the instance variable + */ + Object iv_get(Identifier name) const; + + //! Get the value of an instance variable, but don't warn if it is + //unset. + /*! \param name the name of the instance variable to get + * \return the value of the instance variable + */ + Object attr_get(Identifier name) const; + + //! Call the Ruby method specified by 'id' on object 'obj'. + /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to + * Ruby objects with to_ruby<>. To call methods expecting keyword arguments, + * use call_kw. + * + * E.g.: + * \code + * Rice::Object obj = x.call("foo", "one", 2); + * \endcode + * + * If a return type is specified, the return value will automatically be + * converted to that type as long as 'from_ruby' exists for that type. + * + * E.g.: + * \code + * float ret = x.call("foo", z, 42); + * \endcode + */ + template + Object call(Identifier id, Arg_Ts... args) const; + + //! Call the Ruby method specified by 'id' on object 'obj'. + /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to + * Ruby objects with to_ruby<>. The final argument must be a Hash and will be treated + * as keyword arguments to the function. + * + * E.g.: + * \code + * Rice::Hash kw; + * kw[":argument"] = String("one") + * Rice::Object obj = x.call_kw("foo", kw); + * \endcode + * + * If a return type is specified, the return value will automatically be + * converted to that type as long as 'from_ruby' exists for that type. + * + * E.g.: + * \code + * float ret = x.call_kw("foo", kw); + * \endcode + */ + template + Object call_kw(Identifier id, Arg_Ts... args) const; + + //! Vectorized call. + /*! Calls the method identified by id with the list of arguments + * identified by args. + * \param id the name of the method to call + * \param args the arguments to the method + * \return the return value of the method call + */ + Object vcall(Identifier id, Array args); + + //! Get a constant. + /*! \param name the name of the constant to get. + * \return the value of the constant. + */ + Object const_get(Identifier name) const; + + //! Determine whether a constant is defined. + /*! \param name the name of the constant to check. + * \return true if the constant is defined in this module or false + * otherwise. + */ + bool const_defined(Identifier name) const; + + //! Set a constant. + /*! \param name the name of the constant to set. + * \param value the value of the constant. + * \return *this + */ + inline Object const_set(Identifier name, Object value); + + //! Set a constant if it not already set. + /*! \param name the name of the constant to set. + * \param value the value of the constant. + * \return *this + */ + inline Object const_set_maybe(Identifier name, Object value); + + //! Remove a constant. + /*! \param name the name of the constant to remove. + */ + void remove_const(Identifier name); + + protected: + //! Set the encapsulated value. + void set_value(VALUE v); + + private: + volatile VALUE value_; + }; + + std::ostream& operator<<(std::ostream& out, Object const& obj); + + bool operator==(Object const& lhs, Object const& rhs); + bool operator!=(Object const& lhs, Object const& rhs); + bool operator<(Object const& lhs, Object const& rhs); + bool operator>(Object const& lhs, Object const& rhs); + + extern Object const Nil; + extern Object const True; + extern Object const False; + extern Object const Undef; +} // namespace Rice + +#endif // Rice__Object__hpp_ diff --git a/rice/cpp_api/Object.ipp b/rice/cpp_api/Object.ipp index d0d32f2d..bca734e8 100644 --- a/rice/cpp_api/Object.ipp +++ b/rice/cpp_api/Object.ipp @@ -1,6 +1,3 @@ -#ifndef Rice__Object__ipp_ -#define Rice__Object__ipp_ - namespace Rice { inline const Object Nil(Qnil); @@ -236,4 +233,3 @@ namespace Rice::detail } }; } -#endif // Rice__Object__ipp_ diff --git a/rice/cpp_api/Object_defn.hpp b/rice/cpp_api/Object_defn.hpp deleted file mode 100644 index 882039af..00000000 --- a/rice/cpp_api/Object_defn.hpp +++ /dev/null @@ -1,277 +0,0 @@ -#ifndef Rice__Object_defn__hpp_ -#define Rice__Object_defn__hpp_ - -/*! \file Object.hpp - */ - -#include "../Identifier.hpp" -#include "../detail/from_ruby_defn.hpp" -#include "../detail/to_ruby_defn.hpp" - -#include -#include - -namespace Rice -{ - class Class; - class String; - class Array; - - //! The base class for all Objects - /*! Perhaps the name "Object" is a misnomer, because this class really - * holds an object reference, not an object. - */ - class Object - { - public: - //! Encapsulate an existing ruby object. - Object(VALUE value = Qnil) : value_(value) {} - - //! Destructor - virtual ~Object(); - - // Enable copying - Object(const Object& other) = default; - Object& operator=(const Object& other) = default; - - // Enable moving - Object(Object&& other); - Object& operator=(Object&& other); - - //! Returns false if the object is nil or false; returns true - //! otherwise. - // Having this conversion also prevents accidental conversion to - // undesired integral types (e.g. long or int) by making the - // conversion ambiguous. - bool test() const { return RTEST(value_); } - - //! Returns false if the object is nil or false; returns true - //! otherwise. - operator bool() const { return test(); } - - //! Returns true if the object is nil, false otherwise. - bool is_nil() const { return NIL_P(value_); } - - //! Implicit conversion to VALUE. - operator VALUE() const { return value_; } - - //! Explicitly get the encapsulated VALUE. - // Returns a const ref so that Address_Registration_Guard can access - // the address where the VALUE is stored - VALUE const volatile& value() const { return value_; } - - //! Get the class of an object. - /*! \return the object's Class. - */ - Class class_of() const; - - //! Compare this object to another object. - /*! Gets the result of self <=> other and returns the result. The - * result will be less than zero if self < other, greater than zero - * if self > other, and equal to zero if self == other. - */ - int compare(Object const& other) const; - - //! Return a string representation of an object. - /*! \return the result of calling to_s on the object. A String is not - * returned, because it is not possible to return an instance of a - * derived class. - */ - String to_s() const; - - //! Return the name of an object's class. - String class_name() const; - - //! Inspect the object. - /*! \return the result of calling inspect on the object. A String is - * not returned, because it is not possible to return an instance of - * a derived class. - */ - String inspect() const; - - //! Freeze the object. - void freeze(); - - //! Determine if the object is frozen. - /*! \return true if the object is frozen, false otherwise. - */ - bool is_frozen() const; - - //! Evaluate the given string in the context of the object. - /*! This is equivalant to calling obj.instance_eval(s) from inside the - * interpreter. - * \return the result of the expression. - */ - Object instance_eval(String const& s); - - //! Return the type of the underlying C object. - /*! This is equivalent to calling rb_type(obj). - * \return the type of the underlying C object (e.g. T_DATA, T_ARRAY, - * etc.). - */ - int rb_type() const; - - //! Return the object's id - VALUE object_id() const; - - //! Determine whether the object is an instance of a class/module. - /*! \param klass a class or module. - * \return true if the object is an instance of the given - * class/module or one of its descendants. - */ - bool is_a(Object klass) const; - - //! Determine if the objects responds to a method. - /*! \param id the name of the method - * \return true if the objects responds to the method, false - * otherwise. - */ - bool respond_to(Identifier id) const; - - //! Determine whether class is the object's class. - /*! \param klass a class. - * \return true if the object is an instance of the given class. - */ - bool is_instance_of(Object klass) const; - - //! Determine whether the Ruby VALUEs wrapped by this - //! object are the same object. Maps to Object::equal? - /*! \param other a Object. - */ - bool is_equal(const Object& other) const; - - //! Determine whether the Ruby VALUEs wrapped by this - //! object are equivalent. Maps to Object::eql? - /*! \param other a Object. - */ - bool is_eql(const Object& other) const; - - //! Set an instance variable. - /*! \param name the name of the instance variable to set (including - * the leading @ sign) - * \param value the value of the variable, which will be converted to - * a Ruby type if necessary. - */ - template - void iv_set(Identifier name, T const& value); - - //! Get the value of an instance variable. - /*! \param name the name of the instance variable to get - * \return the value of the instance variable - */ - Object iv_get(Identifier name) const; - - //! Get the value of an instance variable, but don't warn if it is - //unset. - /*! \param name the name of the instance variable to get - * \return the value of the instance variable - */ - Object attr_get(Identifier name) const; - - //! Call the Ruby method specified by 'id' on object 'obj'. - /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to - * Ruby objects with to_ruby<>. To call methods expecting keyword arguments, - * use call_kw. - * - * E.g.: - * \code - * Rice::Object obj = x.call("foo", "one", 2); - * \endcode - * - * If a return type is specified, the return value will automatically be - * converted to that type as long as 'from_ruby' exists for that type. - * - * E.g.: - * \code - * float ret = x.call("foo", z, 42); - * \endcode - */ - template - Object call(Identifier id, Arg_Ts... args) const; - - //! Call the Ruby method specified by 'id' on object 'obj'. - /*! Pass in arguments (arg1, arg2, ...). The arguments will be converted to - * Ruby objects with to_ruby<>. The final argument must be a Hash and will be treated - * as keyword arguments to the function. - * - * E.g.: - * \code - * Rice::Hash kw; - * kw[":argument"] = String("one") - * Rice::Object obj = x.call_kw("foo", kw); - * \endcode - * - * If a return type is specified, the return value will automatically be - * converted to that type as long as 'from_ruby' exists for that type. - * - * E.g.: - * \code - * float ret = x.call_kw("foo", kw); - * \endcode - */ - template - Object call_kw(Identifier id, Arg_Ts... args) const; - - //! Vectorized call. - /*! Calls the method identified by id with the list of arguments - * identified by args. - * \param id the name of the method to call - * \param args the arguments to the method - * \return the return value of the method call - */ - Object vcall(Identifier id, Array args); - - //! Get a constant. - /*! \param name the name of the constant to get. - * \return the value of the constant. - */ - Object const_get(Identifier name) const; - - //! Determine whether a constant is defined. - /*! \param name the name of the constant to check. - * \return true if the constant is defined in this module or false - * otherwise. - */ - bool const_defined(Identifier name) const; - - //! Set a constant. - /*! \param name the name of the constant to set. - * \param value the value of the constant. - * \return *this - */ - inline Object const_set(Identifier name, Object value); - - //! Set a constant if it not already set. - /*! \param name the name of the constant to set. - * \param value the value of the constant. - * \return *this - */ - inline Object const_set_maybe(Identifier name, Object value); - - //! Remove a constant. - /*! \param name the name of the constant to remove. - */ - void remove_const(Identifier name); - - protected: - //! Set the encapsulated value. - void set_value(VALUE v); - - private: - volatile VALUE value_; - }; - - std::ostream& operator<<(std::ostream& out, Object const& obj); - - bool operator==(Object const& lhs, Object const& rhs); - bool operator!=(Object const& lhs, Object const& rhs); - bool operator<(Object const& lhs, Object const& rhs); - bool operator>(Object const& lhs, Object const& rhs); - - extern Object const Nil; - extern Object const True; - extern Object const False; - extern Object const Undef; -} // namespace Rice - -#endif // Rice__Object_defn__hpp_ diff --git a/rice/cpp_api/String.hpp b/rice/cpp_api/String.hpp index 57a79d44..a3d4537d 100644 --- a/rice/cpp_api/String.hpp +++ b/rice/cpp_api/String.hpp @@ -1,9 +1,6 @@ #ifndef Rice__String__hpp_ #define Rice__String__hpp_ -#include "../Identifier.hpp" -#include "Builtin_Object_defn.hpp" - namespace Rice { //! A Wraper for the ruby String class. @@ -70,6 +67,4 @@ namespace Rice }; } // namespace Rice -#include "String.ipp" - #endif // Rice__String__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Struct.hpp b/rice/cpp_api/Struct.hpp index 8003520a..099451bf 100644 --- a/rice/cpp_api/Struct.hpp +++ b/rice/cpp_api/Struct.hpp @@ -1,10 +1,6 @@ #ifndef Rice__ruby_struct__hpp_ #define Rice__ruby_struct__hpp_ -#include "Class.hpp" -#include "Array.hpp" -#include "Hash.hpp" - namespace Rice { //! A wrapper for creating Struct classes. @@ -114,6 +110,4 @@ namespace Rice } // namespace Rice -#include "Struct.ipp" - #endif // Rice__ruby_struct__hpp_ \ No newline at end of file diff --git a/rice/cpp_api/Struct.ipp b/rice/cpp_api/Struct.ipp index 3a3c9562..4bcbbeee 100644 --- a/rice/cpp_api/Struct.ipp +++ b/rice/cpp_api/Struct.ipp @@ -1,4 +1,3 @@ -#include "Symbol.hpp" namespace Rice { diff --git a/rice/cpp_api/Symbol.hpp b/rice/cpp_api/Symbol.hpp index 0f3e3088..0a6f99d3 100644 --- a/rice/cpp_api/Symbol.hpp +++ b/rice/cpp_api/Symbol.hpp @@ -1,9 +1,6 @@ #ifndef Rice__Symbol__hpp_ #define Rice__Symbol__hpp_ -#include "../Identifier.hpp" -#include "Object_defn.hpp" -#include "../detail/ruby.hpp" #include namespace Rice @@ -47,7 +44,5 @@ namespace Rice }; } // namespace Rice -#include "Symbol.ipp" - #endif // Rice__Symbol__hpp_ diff --git a/rice/detail/ExceptionHandler.hpp b/rice/detail/ExceptionHandler.hpp index 9ebd786f..e8c652cb 100644 --- a/rice/detail/ExceptionHandler.hpp +++ b/rice/detail/ExceptionHandler.hpp @@ -1,8 +1,76 @@ #ifndef Rice__detail__ExceptionHandler__hpp_ #define Rice__detail__ExceptionHandler__hpp_ -#include "ExceptionHandler_defn.hpp" -#include "ExceptionHandler.ipp" +#include -#endif // Rice__detail__ExceptionHandler__hpp_ +namespace Rice::detail +{ + /* An abstract class for converting C++ exceptions to ruby exceptions. It's used + like this: + try + { + } + catch(...) + { + handler->handle(); + } + + If an exception is thrown the handler will pass the exception up the + chain, then the last handler in the chain will throw the exception + down the chain until a lower handler can handle it, e.g.: + + try + { + return call_next_ExceptionHandler(); + } + catch(MyException const & ex) + { + throw Rice::Exception(rb_cMyException, "%s", ex.what()); + } + + Memory management. Handlers are created by the ModuleBase constructor. When the + module defines a new Ruby method, metadata is stored on the Ruby klass including + the exception handler. Since the metadata outlives the module, handlers are stored + using std::shared_ptr. Thus the Module (or its inherited children) can be destroyed + without corrupting the metadata references to the shared exception handler. */ + + class ExceptionHandler + { + public: + ExceptionHandler() = default; + virtual ~ExceptionHandler() = default; + + // Don't allow copying or assignment + ExceptionHandler(const ExceptionHandler& other) = delete; + ExceptionHandler& operator=(const ExceptionHandler& other) = delete; + + virtual VALUE handle() const = 0; + }; + + // The default exception handler just rethrows the exception. If there + // are other handlers in the chain, they will try to handle the rethrown + // exception. + class DefaultExceptionHandler : public ExceptionHandler + { + public: + virtual VALUE handle() const override; + }; + + // An exception handler that takes a functor as an argument. The + // functor should throw a Rice::Exception to handle the exception. If + // the functor does not handle the exception, the exception will be + // re-thrown. + template + class CustomExceptionHandler : public ExceptionHandler + { + public: + CustomExceptionHandler(Functor_T handler, std::shared_ptr nextHandler); + virtual VALUE handle() const override; + + private: + Functor_T handler_; + std::shared_ptr nextHandler_; + }; +} +#endif // Rice__detail__ExceptionHandler__hpp_ \ No newline at end of file diff --git a/rice/detail/ExceptionHandler_defn.hpp b/rice/detail/ExceptionHandler_defn.hpp deleted file mode 100644 index 48c28747..00000000 --- a/rice/detail/ExceptionHandler_defn.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef Rice__detail__ExceptionHandler_defn__hpp_ -#define Rice__detail__ExceptionHandler_defn__hpp_ - -#include -#include "ruby.hpp" - -namespace Rice::detail -{ - /* An abstract class for converting C++ exceptions to ruby exceptions. It's used - like this: - - try - { - } - catch(...) - { - handler->handle(); - } - - If an exception is thrown the handler will pass the exception up the - chain, then the last handler in the chain will throw the exception - down the chain until a lower handler can handle it, e.g.: - - try - { - return call_next_ExceptionHandler(); - } - catch(MyException const & ex) - { - throw Rice::Exception(rb_cMyException, "%s", ex.what()); - } - - Memory management. Handlers are created by the ModuleBase constructor. When the - module defines a new Ruby method, metadata is stored on the Ruby klass including - the exception handler. Since the metadata outlives the module, handlers are stored - using std::shared_ptr. Thus the Module (or its inherited children) can be destroyed - without corrupting the metadata references to the shared exception handler. */ - - class ExceptionHandler - { - public: - ExceptionHandler() = default; - virtual ~ExceptionHandler() = default; - - // Don't allow copying or assignment - ExceptionHandler(const ExceptionHandler& other) = delete; - ExceptionHandler& operator=(const ExceptionHandler& other) = delete; - - virtual VALUE handle() const = 0; - }; - - // The default exception handler just rethrows the exception. If there - // are other handlers in the chain, they will try to handle the rethrown - // exception. - class DefaultExceptionHandler : public ExceptionHandler - { - public: - virtual VALUE handle() const override; - }; - - // An exception handler that takes a functor as an argument. The - // functor should throw a Rice::Exception to handle the exception. If - // the functor does not handle the exception, the exception will be - // re-thrown. - template - class CustomExceptionHandler : public ExceptionHandler - { - public: - CustomExceptionHandler(Functor_T handler, std::shared_ptr nextHandler); - virtual VALUE handle() const override; - - private: - Functor_T handler_; - std::shared_ptr nextHandler_; - }; -} -#endif // Rice__detail__ExceptionHandler_defn__hpp_ \ No newline at end of file diff --git a/rice/detail/HandlerRegistry.hpp b/rice/detail/HandlerRegistry.hpp index 0c182a5b..74c6cc72 100644 --- a/rice/detail/HandlerRegistry.hpp +++ b/rice/detail/HandlerRegistry.hpp @@ -1,8 +1,6 @@ #ifndef Rice__detail__HandlerRegistry__hpp_ #define Rice__detail__HandlerRegistry__hpp_ -#include "ExceptionHandler.hpp" - namespace Rice::detail { class HandlerRegistry @@ -45,7 +43,5 @@ namespace Rice::detail }; } // namespace Rice::detail -#include "HandlerRegistry.ipp" - #endif // Rice__detail__HandlerRegistry__hpp_ diff --git a/rice/detail/InstanceRegistry.hpp b/rice/detail/InstanceRegistry.hpp index c31731c5..e8b87477 100644 --- a/rice/detail/InstanceRegistry.hpp +++ b/rice/detail/InstanceRegistry.hpp @@ -2,7 +2,6 @@ #define Rice__detail__InstanceRegistry__hpp_ #include -#include "ruby.hpp" namespace Rice::detail { @@ -28,7 +27,5 @@ namespace Rice::detail }; } // namespace Rice::detail -#include "InstanceRegistry.ipp" - #endif // Rice__detail__InstanceRegistry__hpp_ diff --git a/rice/detail/MethodInfo.hpp b/rice/detail/MethodInfo.hpp index 5837f115..cbe7f745 100644 --- a/rice/detail/MethodInfo.hpp +++ b/rice/detail/MethodInfo.hpp @@ -2,8 +2,6 @@ #define Rice__MethodInfo__hpp_ #include -#include "../Arg.hpp" -#include "../Return.hpp" namespace Rice { @@ -39,6 +37,4 @@ namespace Rice std::vector args_; }; } -#include "MethodInfo.ipp" - #endif // Rice__MethodInfo__hpp_ \ No newline at end of file diff --git a/rice/detail/MethodInfo.ipp b/rice/detail/MethodInfo.ipp index d86cca31..7c574f38 100644 --- a/rice/detail/MethodInfo.ipp +++ b/rice/detail/MethodInfo.ipp @@ -1,5 +1,4 @@ #include -#include "from_ruby_defn.hpp" namespace Rice { diff --git a/rice/detail/Native.hpp b/rice/detail/Native.hpp index cb1d2f3e..f9b058b8 100644 --- a/rice/detail/Native.hpp +++ b/rice/detail/Native.hpp @@ -1,8 +1,6 @@ #ifndef Rice__detail__Native__hpp_ #define Rice__detail__Native__hpp_ -#include "from_ruby.hpp" - namespace Rice::detail { class Native; diff --git a/rice/detail/Native.ipp b/rice/detail/Native.ipp index d3a35e79..3a82c9a2 100644 --- a/rice/detail/Native.ipp +++ b/rice/detail/Native.ipp @@ -1,7 +1,3 @@ -#include "Native.hpp" -#include "../Identifier.hpp" -#include "cpp_protect.hpp" -#include "Registries.hpp" namespace Rice::detail { diff --git a/rice/detail/NativeAttributeGet.hpp b/rice/detail/NativeAttributeGet.hpp index 56255256..8402c960 100644 --- a/rice/detail/NativeAttributeGet.hpp +++ b/rice/detail/NativeAttributeGet.hpp @@ -1,9 +1,6 @@ #ifndef Rice__detail__Native_Attribute_Get__hpp_ #define Rice__detail__Native_Attribute_Get__hpp_ -#include "Native.hpp" -#include "../traits/attribute_traits.hpp" - namespace Rice { enum class AttrAccess @@ -51,6 +48,4 @@ namespace Rice } // detail } // Rice -#include "NativeAttributeGet.ipp" - #endif // Rice__detail__Native_Attribute_Get__hpp_ diff --git a/rice/detail/NativeAttributeGet.ipp b/rice/detail/NativeAttributeGet.ipp index 16f1a5ae..6a1f7b4a 100644 --- a/rice/detail/NativeAttributeGet.ipp +++ b/rice/detail/NativeAttributeGet.ipp @@ -1,10 +1,6 @@ #include #include -#include "../traits/rice_traits.hpp" -#include "NativeRegistry.hpp" -#include "to_ruby_defn.hpp" -#include "cpp_protect.hpp" namespace Rice::detail { diff --git a/rice/detail/NativeAttributeSet.hpp b/rice/detail/NativeAttributeSet.hpp index 7140ca36..6ec14c5d 100644 --- a/rice/detail/NativeAttributeSet.hpp +++ b/rice/detail/NativeAttributeSet.hpp @@ -1,9 +1,6 @@ #ifndef Rice__detail__Native_Attribute_Set__hpp_ #define Rice__detail__Native_Attribute_Set__hpp_ -#include "Native.hpp" -#include "../traits/attribute_traits.hpp" - namespace Rice { namespace detail @@ -43,6 +40,4 @@ namespace Rice } // detail } // Rice -#include "NativeAttributeSet.ipp" - #endif // Rice__detail__Native_Attribute_Set__hpp_ diff --git a/rice/detail/NativeAttributeSet.ipp b/rice/detail/NativeAttributeSet.ipp index 284b3031..b51567a3 100644 --- a/rice/detail/NativeAttributeSet.ipp +++ b/rice/detail/NativeAttributeSet.ipp @@ -1,10 +1,6 @@ #include #include -#include "../traits/rice_traits.hpp" -#include "NativeRegistry.hpp" -#include "to_ruby_defn.hpp" -#include "cpp_protect.hpp" namespace Rice::detail { diff --git a/rice/detail/NativeFunction.hpp b/rice/detail/NativeFunction.hpp index 4a40cb21..b68d1fb5 100644 --- a/rice/detail/NativeFunction.hpp +++ b/rice/detail/NativeFunction.hpp @@ -1,14 +1,6 @@ #ifndef Rice__detail__Native_Function__hpp_ #define Rice__detail__Native_Function__hpp_ -#include "ruby.hpp" -#include "ExceptionHandler_defn.hpp" -#include "MethodInfo.hpp" -#include "../traits/function_traits.hpp" -#include "../traits/method_traits.hpp" -#include "../traits/rice_traits.hpp" -#include "from_ruby.hpp" - namespace Rice::detail { //! The NativeFunction class calls C++ functions/methods/lambdas on behalf of Ruby @@ -118,6 +110,5 @@ namespace Rice::detail std::unique_ptr methodInfo_; }; } -#include "NativeFunction.ipp" #endif // Rice__detail__Native_Function__hpp_ diff --git a/rice/detail/NativeFunction.ipp b/rice/detail/NativeFunction.ipp index cb815c59..d1bc67fb 100644 --- a/rice/detail/NativeFunction.ipp +++ b/rice/detail/NativeFunction.ipp @@ -3,10 +3,6 @@ #include #include -#include "cpp_protect.hpp" -#include "TupleIterator.hpp" -#include "to_ruby_defn.hpp" -#include "NativeRegistry.hpp" namespace Rice::detail { diff --git a/rice/detail/NativeIterator.hpp b/rice/detail/NativeIterator.hpp index 458ae22c..4fff8d1d 100644 --- a/rice/detail/NativeIterator.hpp +++ b/rice/detail/NativeIterator.hpp @@ -1,8 +1,5 @@ -#ifndef Rice_NativeIterator__hpp_ -#define Rice_NativeIterator__hpp_ - -#include "../traits/function_traits.hpp" -#include "Native.hpp" +#ifndef Rice__NativeIterator__hpp_ +#define Rice__NativeIterator__hpp_ namespace Rice::detail { @@ -45,6 +42,5 @@ namespace Rice::detail Iterator_Func_T end_; }; } -#include "NativeIterator.ipp" -#endif // Rice_NativeIterator__hpp_ \ No newline at end of file +#endif // Rice__NativeIterator__hpp_ diff --git a/rice/detail/NativeIterator.ipp b/rice/detail/NativeIterator.ipp index e0b1f766..93b8e03c 100644 --- a/rice/detail/NativeIterator.ipp +++ b/rice/detail/NativeIterator.ipp @@ -2,9 +2,6 @@ #include #include -#include "cpp_protect.hpp" -#include "from_ruby.hpp" -#include "NativeRegistry.hpp" namespace Rice::detail { diff --git a/rice/detail/NativeRegistry.hpp b/rice/detail/NativeRegistry.hpp index f85d65be..2a2b5269 100644 --- a/rice/detail/NativeRegistry.hpp +++ b/rice/detail/NativeRegistry.hpp @@ -3,8 +3,7 @@ #include #include - -#include "Native.hpp" +#include /* The Native Registry tracks C++ instance that are used to invoke C++ methods for Ruby. These objects include instances of the NativeFunction, NativeIterator, NativeAttributeGet @@ -36,6 +35,5 @@ namespace Rice::detail std::map, std::vector>> natives_ = {}; }; } -#include "NativeRegistry.ipp" #endif // Rice__detail__NativeRegistry__hpp diff --git a/rice/detail/NativeRegistry.ipp b/rice/detail/NativeRegistry.ipp index 343b8dcd..58cad00b 100644 --- a/rice/detail/NativeRegistry.ipp +++ b/rice/detail/NativeRegistry.ipp @@ -1,5 +1,3 @@ -#include "RubyFunction.hpp" -#include "../Identifier.hpp" namespace Rice::detail { diff --git a/rice/detail/Registries.hpp b/rice/detail/Registries.hpp index 71170e53..a677906a 100644 --- a/rice/detail/Registries.hpp +++ b/rice/detail/Registries.hpp @@ -1,11 +1,6 @@ #ifndef Rice__Registries__hpp_ #define Rice__Registries__hpp_ -#include "HandlerRegistry.hpp" -#include "InstanceRegistry.hpp" -#include "NativeRegistry.hpp" -#include "TypeRegistry.hpp" - namespace Rice::detail { class Registries @@ -21,6 +16,4 @@ namespace Rice::detail }; } -#include "Registries.ipp" - #endif // Rice__Registries__hpp_ diff --git a/rice/detail/RubyFunction.hpp b/rice/detail/RubyFunction.hpp index d99b26d9..a88cd707 100644 --- a/rice/detail/RubyFunction.hpp +++ b/rice/detail/RubyFunction.hpp @@ -1,8 +1,6 @@ #ifndef Rice__detail__ruby_function__hpp_ #define Rice__detail__ruby_function__hpp_ -#include "ruby.hpp" - namespace Rice::detail { /* This is functor class that wraps calls to a Ruby C API method. It is needed because @@ -29,6 +27,5 @@ namespace Rice::detail template auto protect(Function_T func, Arg_Ts...args); } -#include "RubyFunction.ipp" #endif // Rice__detail__ruby_function__hpp_ \ No newline at end of file diff --git a/rice/detail/RubyFunction.ipp b/rice/detail/RubyFunction.ipp index 2e744b82..f0ecf0fd 100644 --- a/rice/detail/RubyFunction.ipp +++ b/rice/detail/RubyFunction.ipp @@ -1,5 +1,3 @@ -#include "Jump_Tag.hpp" -#include "../Exception_defn.hpp" #include diff --git a/rice/detail/TupleIterator.hpp b/rice/detail/TupleIterator.hpp index 52cc983b..a64888cc 100644 --- a/rice/detail/TupleIterator.hpp +++ b/rice/detail/TupleIterator.hpp @@ -1,6 +1,14 @@ #ifndef Rice__stl__tuple_iterator__hpp_ #define Rice__stl__tuple_iterator__hpp_ -#include "TupleIterator.ipp" +// See https://www.cppstories.com/2022/tuple-iteration-apply/ +template +void for_each_tuple(Tuple_T&& tuple, Function_T&& callable) +{ + std::apply([&callable](auto&& ...args) + { + (callable(std::forward(args)), ...); + }, std::forward(tuple)); +} #endif // Rice__stl__tuple_iterator__hpp_ diff --git a/rice/detail/TupleIterator.ipp b/rice/detail/TupleIterator.ipp deleted file mode 100644 index 974ba4dc..00000000 --- a/rice/detail/TupleIterator.ipp +++ /dev/null @@ -1,9 +0,0 @@ -// See https://www.cppstories.com/2022/tuple-iteration-apply/ -template -void for_each_tuple(Tuple_T&& tuple, Function_T&& callable) -{ - std::apply([&callable](auto&& ...args) - { - (callable(std::forward(args)), ...); - }, std::forward(tuple)); -} diff --git a/rice/detail/Type.hpp b/rice/detail/Type.hpp index c510cabc..9d70354c 100644 --- a/rice/detail/Type.hpp +++ b/rice/detail/Type.hpp @@ -4,7 +4,6 @@ #include #include #include -#include "../traits/rice_traits.hpp" namespace Rice::detail { diff --git a/rice/detail/Type.ipp b/rice/detail/Type.ipp index 2d4729af..943a1145 100644 --- a/rice/detail/Type.ipp +++ b/rice/detail/Type.ipp @@ -1,5 +1,3 @@ -#include "../traits/rice_traits.hpp" -#include "Registries.hpp" #include #include diff --git a/rice/detail/TypeRegistry.hpp b/rice/detail/TypeRegistry.hpp index 87a16e72..ae0f66ab 100644 --- a/rice/detail/TypeRegistry.hpp +++ b/rice/detail/TypeRegistry.hpp @@ -8,7 +8,6 @@ #include #include -#include "ruby.hpp" /* The type registry keeps track of all C++ types wrapped by Rice. When a native function returns an instance of a class/struct we look up its type to verity that it has been registered. @@ -57,6 +56,4 @@ namespace Rice::detail }; } -#include "TypeRegistry.ipp" - #endif // Rice__TypeRegistry__hpp_ diff --git a/rice/detail/TypeRegistry.ipp b/rice/detail/TypeRegistry.ipp index 76db03d4..2a74b60d 100644 --- a/rice/detail/TypeRegistry.ipp +++ b/rice/detail/TypeRegistry.ipp @@ -3,9 +3,6 @@ #include #include -#include "ruby.hpp" -#include "../traits/rice_traits.hpp" -#include "Type.hpp" namespace Rice::detail { diff --git a/rice/detail/Wrapper.hpp b/rice/detail/Wrapper.hpp index 92adfe09..cd5924c1 100644 --- a/rice/detail/Wrapper.hpp +++ b/rice/detail/Wrapper.hpp @@ -1,8 +1,6 @@ #ifndef Rice__detail__Wrapper__hpp_ #define Rice__detail__Wrapper__hpp_ -#include "ruby.hpp" - namespace Rice { namespace detail @@ -48,7 +46,5 @@ Wrapper* getWrapper(VALUE value); } // namespace detail } // namespace Rice -#include "Wrapper.ipp" - #endif // Rice__detail__Wrapper__hpp_ diff --git a/rice/detail/Wrapper.ipp b/rice/detail/Wrapper.ipp index 11a4236f..7ca96b4f 100644 --- a/rice/detail/Wrapper.ipp +++ b/rice/detail/Wrapper.ipp @@ -1,5 +1,4 @@ #include -#include "InstanceRegistry.hpp" namespace Rice::detail { diff --git a/rice/detail/cpp_protect.hpp b/rice/detail/cpp_protect.hpp index 4956acce..8f333e62 100644 --- a/rice/detail/cpp_protect.hpp +++ b/rice/detail/cpp_protect.hpp @@ -14,8 +14,6 @@ #error "no filesystem include found :'(" #endif -#include "Jump_Tag.hpp" -#include "../Exception_defn.hpp" namespace Rice::detail { diff --git a/rice/detail/default_allocation_func.ipp b/rice/detail/default_allocation_func.ipp index 838fe553..4467820c 100644 --- a/rice/detail/default_allocation_func.ipp +++ b/rice/detail/default_allocation_func.ipp @@ -1,5 +1,3 @@ -#include "../Data_Object.hpp" - namespace Rice::detail { template diff --git a/rice/detail/from_ruby.hpp b/rice/detail/from_ruby.hpp index dd51b8c9..6af56b85 100644 --- a/rice/detail/from_ruby.hpp +++ b/rice/detail/from_ruby.hpp @@ -1,8 +1,41 @@ #ifndef Rice__detail__from_ruby__hpp_ #define Rice__detail__from_ruby__hpp_ -#include "from_ruby_defn.hpp" -#include "from_ruby.ipp" +namespace Rice::detail +{ + //! Convert a Ruby object to C++. + /*! If the Ruby object can be converted to an immediate value, returns a + * copy of the Ruby object. If the Ruby object is holding a C++ + * object and the type specified is a pointer to that type, returns a + * pointer to that object. + * + * Conversions from ruby to a pointer type are automatically generated + * when a type is bound using Data_Type. If no conversion exists an + * exception is thrown. + * + * \param T the C++ type to which to convert. + * \param x the Ruby object to convert. + * \return a C++ representation of the Ruby object. + * + * Example: + * \code + * Object x = INT2NUM(42); + * std::cout << From_Ruby::convert(x); + * + * Data_Object foo(new Foo); + * std::cout << *From_Ruby(foo) << std::endl; + * \endcode + */ -#endif // Rice__detail__from_ruby__hpp_ + template + class From_Ruby; + enum class Convertible: uint8_t + { + None = 0b000, + TypeCast = 0b010, + Exact = 0b110, + }; +} + +#endif // Rice__detail__From_Ruby2__hpp_ diff --git a/rice/detail/from_ruby.ipp b/rice/detail/from_ruby.ipp index ad9bc92d..a8384770 100644 --- a/rice/detail/from_ruby.ipp +++ b/rice/detail/from_ruby.ipp @@ -3,22 +3,11 @@ #include #include -#include "../Exception_defn.hpp" -#include "../Arg.hpp" -#include "../Identifier.hpp" -#include "RubyFunction.hpp" /* This file implements conversions from Ruby to native values fo fundamental types such as bool, int, float, etc. It also includes conversions for chars and strings */ namespace Rice::detail { - enum class Convertible: uint8_t - { - None = 0b000, - TypeCast = 0b010, - Exact = 0b110, - }; - inline Convertible operator&(Convertible left, Convertible right) { return static_cast(static_cast(left) & static_cast(right)); diff --git a/rice/detail/from_ruby_defn.hpp b/rice/detail/from_ruby_defn.hpp deleted file mode 100644 index 4ed95875..00000000 --- a/rice/detail/from_ruby_defn.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef Rice__detail__from_ruby_defn__hpp_ -#define Rice__detail__from_ruby_defn__hpp_ - -#include - -#include "ruby.hpp" - -namespace Rice::detail -{ - //! Convert a Ruby object to C++. - /*! If the Ruby object can be converted to an immediate value, returns a - * copy of the Ruby object. If the Ruby object is holding a C++ - * object and the type specified is a pointer to that type, returns a - * pointer to that object. - * - * Conversions from ruby to a pointer type are automatically generated - * when a type is bound using Data_Type. If no conversion exists an - * exception is thrown. - * - * \param T the C++ type to which to convert. - * \param x the Ruby object to convert. - * \return a C++ representation of the Ruby object. - * - * Example: - * \code - * Object x = INT2NUM(42); - * std::cout << From_Ruby::convert(x); - * - * Data_Object foo(new Foo); - * std::cout << *From_Ruby(foo) << std::endl; - * \endcode - */ - - template - class From_Ruby; -} - -#endif // Rice__detail__From_Ruby2_defn__hpp_ diff --git a/rice/detail/to_ruby.hpp b/rice/detail/to_ruby.hpp index fdd27962..f006490d 100644 --- a/rice/detail/to_ruby.hpp +++ b/rice/detail/to_ruby.hpp @@ -1,8 +1,46 @@ #ifndef Rice__detail__to_ruby__hpp_ #define Rice__detail__to_ruby__hpp_ -#include "to_ruby_defn.hpp" -#include "to_ruby.ipp" +namespace Rice +{ + namespace detail + { + //! Convert a C++ object to Ruby. + /*! If x is a pointer, wraps the pointee as a Ruby object. If x is an + * Object, returns x. + * + * If no conversion exists a compile-time error is generated. + * + * \param x the object to convert. + * \return a Ruby representation of the C++ object. + * + * Example: + * \code + * rb_p(to_ruby(42)); + * + * Foo * p_foo = new Foo(); + * rb_p(to_ruby(p_foo)); + * \endcode + */ + template + class To_Ruby; + + // Helper template function that let's users avoid having to specify the template type - its deduced + template + VALUE to_ruby(T&& x) + { + using Unqualified_T = remove_cv_recursive_t; + return To_Ruby().convert(std::forward(x)); + } -#endif // Rice__detail__to_ruby__hpp_ + // Helper template function that let's users avoid having to specify the template type - its deduced + template + VALUE to_ruby(T* x) + { + using Unqualified_T = remove_cv_recursive_t; + return To_Ruby().convert(x); + } + } // detail +} // Rice +#endif // Rice__detail__to_ruby__hpp_ diff --git a/rice/detail/to_ruby.ipp b/rice/detail/to_ruby.ipp index d4dcd6ea..b9d0c19c 100644 --- a/rice/detail/to_ruby.ipp +++ b/rice/detail/to_ruby.ipp @@ -1,5 +1,3 @@ -#include "RubyFunction.hpp" -#include "../Return.hpp" namespace Rice { diff --git a/rice/detail/to_ruby_defn.hpp b/rice/detail/to_ruby_defn.hpp deleted file mode 100644 index 527bc457..00000000 --- a/rice/detail/to_ruby_defn.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef Rice__detail__to_ruby_defn__hpp_ -#define Rice__detail__to_ruby_defn__hpp_ - -#include "../traits/rice_traits.hpp" - -namespace Rice -{ - namespace detail - { - //! Convert a C++ object to Ruby. - /*! If x is a pointer, wraps the pointee as a Ruby object. If x is an - * Object, returns x. - * - * If no conversion exists a compile-time error is generated. - * - * \param x the object to convert. - * \return a Ruby representation of the C++ object. - * - * Example: - * \code - * rb_p(to_ruby(42)); - * - * Foo * p_foo = new Foo(); - * rb_p(to_ruby(p_foo)); - * \endcode - */ - template - class To_Ruby; - - // Helper template function that let's users avoid having to specify the template type - its deduced - template - VALUE to_ruby(T&& x) - { - using Unqualified_T = remove_cv_recursive_t; - return To_Ruby().convert(std::forward(x)); - } - - // Helper template function that let's users avoid having to specify the template type - its deduced - template - VALUE to_ruby(T* x) - { - using Unqualified_T = remove_cv_recursive_t; - return To_Ruby().convert(x); - } - } // detail -} // Rice - -#endif // Rice__detail__to_ruby_defn__hpp_ diff --git a/rice/global_function.hpp b/rice/global_function.hpp index 0e6424d6..23b32f1e 100644 --- a/rice/global_function.hpp +++ b/rice/global_function.hpp @@ -1,8 +1,6 @@ #ifndef Rice__global_function__hpp_ #define Rice__global_function__hpp_ -#include "Arg.hpp" - namespace Rice { //! Define an global function @@ -20,6 +18,4 @@ namespace Rice void define_global_function(char const * name, Function_T&& func, Arg_Ts const& ...args); } // Rice -#include "global_function.ipp" - #endif // Rice__global_function__hpp_ diff --git a/rice/global_function.ipp b/rice/global_function.ipp index fb76f594..f54470f8 100644 --- a/rice/global_function.ipp +++ b/rice/global_function.ipp @@ -1,4 +1,3 @@ -#include "cpp_api/Module.hpp" template void Rice::define_global_function(char const * name, Function_T&& func, Arg_Ts const& ...args) diff --git a/rice/rice.hpp b/rice/rice.hpp index 633fabd5..d2d2b4dd 100644 --- a/rice/rice.hpp +++ b/rice/rice.hpp @@ -12,58 +12,102 @@ #include "detail/TupleIterator.hpp" // Code for C++ to call Ruby -#include "Exception_defn.hpp" +#include "Exception.hpp" #include "detail/Jump_Tag.hpp" #include "detail/RubyFunction.hpp" +#include "detail/RubyFunction.ipp" + +// Type Conversion declarations +#include "detail/Type.hpp" +#include "detail/to_ruby.hpp" +#include "detail/from_ruby.hpp" + +// C++ API declarations +#include "Identifier.hpp" +#include "Identifier.ipp" +#include "cpp_api/Object.hpp" +#include "cpp_api/Builtin_Object.hpp" +#include "cpp_api/String.hpp" +#include "cpp_api/Array.hpp" +#include "cpp_api/Hash.hpp" +#include "cpp_api/Symbol.hpp" -// Code for Ruby to call C++ -#include "detail/ExceptionHandler.hpp" #include "Arg.hpp" +#include "Arg.ipp" #include "Return.hpp" -#include "detail/from_ruby.hpp" -#include "detail/to_ruby.hpp" -#include "detail/Native.hpp" -#include "detail/Type.hpp" +#include "Return.ipp" +#include "detail/to_ruby.ipp" +#include "detail/from_ruby.ipp" + +// Registries #include "detail/TypeRegistry.hpp" +#include "detail/Type.ipp" +#include "detail/TypeRegistry.ipp" #include "detail/InstanceRegistry.hpp" +#include "detail/InstanceRegistry.ipp" + +#include "detail/ExceptionHandler.hpp" #include "detail/HandlerRegistry.hpp" +#include "detail/HandlerRegistry.ipp" +#include "HandlerRegistration.hpp" + +#include "detail/ExceptionHandler.ipp" + +#include "detail/Native.hpp" #include "detail/NativeRegistry.hpp" +#include "detail/NativeRegistry.ipp" #include "detail/Registries.hpp" -#include "detail/Type.ipp" +#include "detail/Registries.ipp" + +// Code for Ruby to call C++ +#include "Exception.ipp" #include "detail/cpp_protect.hpp" #include "detail/Wrapper.hpp" +#include "detail/Wrapper.ipp" #include "detail/MethodInfo.hpp" -#include "Identifier.hpp" -#include "Exception.ipp" +#include "detail/MethodInfo.ipp" #include "detail/Native.ipp" #include "detail/NativeAttributeGet.hpp" +#include "detail/NativeAttributeGet.ipp" #include "detail/NativeAttributeSet.hpp" +#include "detail/NativeAttributeSet.ipp" #include "detail/NativeFunction.hpp" +#include "detail/NativeFunction.ipp" #include "detail/NativeIterator.hpp" -#include "HandlerRegistration.hpp" +#include "detail/NativeIterator.ipp" + +// C++ API definitions +#include "cpp_api/Object.ipp" +#include "cpp_api/Builtin_Object.ipp" +#include "cpp_api/String.ipp" +#include "cpp_api/Array.ipp" +#include "cpp_api/Hash.ipp" +#include "cpp_api/Symbol.ipp" -// C++ classes for using the Ruby API -#include "cpp_api/Object.hpp" -#include "cpp_api/Builtin_Object.hpp" -#include "cpp_api/String.hpp" -#include "cpp_api/Array.hpp" -#include "cpp_api/Hash.hpp" -#include "cpp_api/Symbol.hpp" #include "cpp_api/Module.hpp" +#include "cpp_api/Module.ipp" #include "cpp_api/Class.hpp" +#include "cpp_api/Class.ipp" #include "cpp_api/Struct.hpp" +#include "cpp_api/Struct.ipp" + #include "Address_Registration_Guard.hpp" +#include "Address_Registration_Guard.ipp" #include "global_function.hpp" +#include "global_function.ipp" // Code involved in creating custom DataTypes (ie, Ruby classes that wrap C++ classes) #include "ruby_mark.hpp" #include "detail/default_allocation_func.hpp" #include "Director.hpp" #include "Data_Type.hpp" +#include "Data_Type.ipp" #include "detail/default_allocation_func.ipp" #include "Constructor.hpp" #include "Data_Object.hpp" +#include "Data_Object.ipp" #include "Enum.hpp" +#include "Enum.ipp" #include "MemoryView.hpp" // Dependent on Module, Class, Array and String diff --git a/rice/ruby_mark.hpp b/rice/ruby_mark.hpp index 31e8c485..53caa888 100644 --- a/rice/ruby_mark.hpp +++ b/rice/ruby_mark.hpp @@ -1,5 +1,5 @@ -#ifndef ruby_mark__hpp -#define ruby_mark__hpp +#ifndef Rice__ruby_mark__hpp_ +#define Rice__ruby_mark__hpp_ //! Default function to call to mark a data object. /*! This function can be specialized for a particular type to override @@ -12,4 +12,5 @@ namespace Rice { } } -#endif // ruby_mark__hpp +#endif // Rice__ruby_mark__hpp_ + diff --git a/rice/traits/method_traits.hpp b/rice/traits/method_traits.hpp index 44287032..d1b2dfd6 100644 --- a/rice/traits/method_traits.hpp +++ b/rice/traits/method_traits.hpp @@ -2,7 +2,6 @@ #define Rice__detail__method_traits__hpp_ #include -#include "function_traits.hpp" namespace Rice::detail { diff --git a/test/CMakeSettings.json b/test/CMakeSettings.json new file mode 100644 index 00000000..9204f06e --- /dev/null +++ b/test/CMakeSettings.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + } + ] +} \ No newline at end of file