/** * @file * Declares the API imported from Ruby. */ #pragma once #include <cstdint> #include <string> #include <vector> #include <set> #include <functional> #include <memory> #include <initializer_list> #include <leatherman/dynamic_library/dynamic_library.hpp> #ifndef _WIN32 #include <sys/types.h> #endif namespace leatherman { namespace ruby { /* * Parts of the MRI (Matz's Ruby Interpreter; a.k.a. CRuby) we use is documented here: * https://github.com/ruby/ruby/blob/trunk/README.EXT * * Otherwise, the canonical documentation is unfortunately the MRI source code itself. * A useful index of the various MRI versions can be found here: * http://rxr.whitequark.org/mri/source * */ /** * Represents a MRI VALUE (a Ruby object). * VALUEs can be constants denoting things like true, false, or nil. * They can also be encoded numerical values (Integer, for example). * They can also be pointers to a heap-allocated Ruby object (class, module, etc). * The Ruby garbage collector scans the main thread's stack for VALUEs to mark during garbage collection. * Therefore, you may encounter "volatile" VALUES. These are marked simply to ensure the compiler * does not do any optimizations that may prevent the garbage collector from finding them. * This is likely not needed, but it isn't hurting us to do. */ typedef uintptr_t VALUE; /** * See MRI documentation. */ typedef intptr_t SIGNED_VALUE; /** * See MRI documentation. */ using LONG_LONG = int64_t; using ULONG_LONG = uint64_t; /** * See MRI documentation. */ typedef uintptr_t ID; /** * See MRI documentation. This is a complex struct type; we only use it as an opaque object. */ typedef void * rb_encoding_p; #ifdef _WIN32 typedef int rb_pid_t; #else typedef pid_t rb_pid_t; #endif /** * Macro to cast function pointers to a Ruby method. */ #define RUBY_METHOD_FUNC(x) reinterpret_cast<VALUE(*)(...)>(x) /** * Exception thrown when ruby library could not be loaded. */ struct library_not_loaded_exception : std::runtime_error { /** * Constructs a library_not_loaded_exception. * @param message The exception message. */ explicit library_not_loaded_exception(std::string const& message); }; /** * Exception thrown when Ruby to C type conversions fail. */ struct invalid_conversion : std::runtime_error { /** * Constructs an invalid_conversion exception. * @param message The exception message. */ explicit invalid_conversion(std::string const& message); }; /** * Contains utility functions and the pointers to the Ruby API. */ struct api { /** * Destructs the Ruby API. */ ~api(); /** * Prevents the API from being copied. */ api(api const&) = delete; /** * Prevents the API from being copied. * @returns Returns this API. */ api& operator=(api const&) = delete; /** * Prevents the API from being moved. */ api(api&&) = delete; /** * Prevents the API from being moved. * @return Returns this API. */ api& operator=(api&&) = delete; /** * Gets the Ruby API instance. * Throws a library_not_loaded_exception if the API instance can't be created. * @return Returns the Ruby API instance. */ static api& instance(); /** * Called to initialize the API. * This should be done at the same stack frame where code is loaded into the Ruby VM. */ void initialize(); /** * Gets whether or not the API has been initialized. * @return Returns true if the API has been initialized or false if it has not been initialized. */ bool initialized() const; /** * Called to uninitialize the API. * Called during destruction, but can also be called earlier to cleanup Ruby, avoiding potential * ordering conflicts between unloading the libfacter DLL and libruby DLL. */ void uninitialize(); /** * Gets whether or not exception stack traces are included when formatting exception messages. * @return Returns true if stack traces will be included in exception messages or false if they will not be. */ bool include_stack_trace() const; /** * Sets whether or not exception stack traces are included when formatting exception messages. * @param value True if stack traces will be included in exception messages or false if they will not be. */ void include_stack_trace(bool value); /** * See MRI documentation. */ ID (* const rb_intern)(char const*); /** * See MRI documentation. */ VALUE (* const rb_const_get)(VALUE, ID); /** * See MRI documentation. */ void (* const rb_const_set)(VALUE, ID, VALUE); /** * See MRI documentation. */ VALUE (* const rb_const_remove)(VALUE, ID); /** * See MRI documentation. */ int (* const rb_const_defined)(VALUE, ID); /** * See MRI documentation. */ VALUE (* const rb_define_module)(char const*); /** * See MRI documentation. */ VALUE (* const rb_define_module_under)(VALUE, char const*); /** * See MRI documentation. */ VALUE (* const rb_define_class_under)(VALUE, char const*, VALUE super); /** * See MRI documentation. */ void (* const rb_define_method)(VALUE, char const*, VALUE(*)(...), int); /** * See MRI documentation. */ void (* const rb_define_singleton_method)(VALUE, char const*, VALUE(*)(...), int); /** * See MRI documentation. */ VALUE (* const rb_class_new_instance)(int, VALUE*, VALUE); /** * See MRI documentation. */ VALUE (* const rb_gv_get)(char const*); /** * See MRI documentation. */ VALUE (* const rb_gv_set)(char const*, VALUE); /** * See MRI documentation. */ VALUE (* const rb_eval_string)(char const*); /** * See MRI documentation. */ VALUE (* const rb_funcall)(VALUE, ID, int, ...); /** * See MRI documentation. */ VALUE (* const rb_funcallv)(VALUE, ID, int, VALUE const*); /** * See MRI documentation. */ VALUE (* const rb_proc_new)(VALUE (*)(...), VALUE); /** * See MRI documentation. */ VALUE (* const rb_block_call)(VALUE, ID, int, VALUE*, VALUE(*)(...), VALUE); /** * See MRI documentation. */ VALUE (* const rb_funcall_passing_block)(VALUE, ID, int, VALUE const *); /** * See MRI documentation. */ ULONG_LONG (* const rb_num2ull)(VALUE); /** * See MRI documentation. */ LONG_LONG (* const rb_num2ll)(VALUE); /** * See MRI documentation. */ double (* const rb_num2dbl)(VALUE); /** * See MRI documentation. */ char const* (* const rb_string_value_ptr)(volatile VALUE*); /** * See MRI documentation. */ VALUE (* const rb_rescue2)(VALUE(*)(...), VALUE, VALUE(*)(...), VALUE, ...); /** * See MRI documentation. */ VALUE (* const rb_protect)(VALUE (*)(VALUE), VALUE, int*); /** * See MRI documentation. */ void (* const rb_jump_tag)(int); /** * See MRI documentation. */ VALUE (* const rb_int2inum)(SIGNED_VALUE); /** * See MRI documentation. */ VALUE (* const rb_ll2inum)(LONG_LONG); /** * See MRI documentation. */ VALUE (* const rb_enc_str_new)(char const*, long, rb_encoding_p); /** * See MRI documentation. */ rb_encoding_p (* const rb_utf8_encoding)(void); /** * See MRI documentation. */ VALUE (* const rb_str_encode)(VALUE, VALUE, int, VALUE); /** * See MRI documentation. */ VALUE (* const rb_load)(VALUE, int); /** * See MRI documentation. */ void (* const rb_raise)(VALUE, char const* fmt, ...); /** * See MRI documentation. */ VALUE (* const rb_block_proc)(); /** * See MRI documentation. */ int (* const rb_block_given_p)(); /** * See MRI documentation. */ void (* const rb_gc_register_address)(VALUE*); /** * See MRI documentation. */ void (* const rb_gc_unregister_address)(VALUE*); /** * See MRI documentation. */ void (* const rb_hash_foreach)(VALUE, int (*)(...), VALUE); /** * See MRI documentation. */ void (* const rb_define_attr)(VALUE, char const*, int, int); /** * See MRI documentation. */ VALUE (* const rb_ivar_set)(VALUE, ID, VALUE); /** * See MRI documentation. */ VALUE (* const rb_ivar_get)(VALUE, ID); /** * See MRI documentation. */ VALUE (* const rb_float_new_in_heap)(double); /** * See MRI documentation. */ VALUE (* const rb_ary_new_capa)(long); /** * See MRI documentation. */ VALUE (* const rb_ary_push)(VALUE, VALUE); /** * See MRI documentation. */ VALUE (* const rb_ary_entry)(VALUE, long); /** * See MRI documentation. */ VALUE (* const rb_hash_new)(); /** * See MRI documentation. */ VALUE (* const rb_hash_aset)(VALUE, VALUE, VALUE); /** * See MRI documentation. */ VALUE (* const rb_hash_lookup)(VALUE, VALUE); /** * See MRI documentation. */ VALUE (* const rb_hash_lookup2)(VALUE, VALUE, VALUE); /** * See MRI documentation. */ VALUE (* const rb_sym_to_s)(VALUE); /** * See MRI documentation. */ ID (* const rb_to_id)(VALUE); /** * See MRI documentation. */ char const* (* const rb_id2name)(ID); /** * See MRI documentation. */ void (* const rb_define_alloc_func)(VALUE, VALUE (*)(VALUE)); /** * See MRI documentation. */ typedef void (*RUBY_DATA_FUNC)(void*); /** * See MRI documentation. */ VALUE (* const rb_data_object_alloc)(VALUE, void*, RUBY_DATA_FUNC, RUBY_DATA_FUNC); /** * See MRI documentation. */ void (* const rb_gc_mark)(VALUE); /** * See MRI documentation. */ VALUE (* const rb_yield_values)(int n, ...); /** * See MRI documentation. */ VALUE (* const rb_require)(char const*); /** * Intern MRI method. We're being naughty */ VALUE(* const rb_last_status_set)(int, rb_pid_t); /** * See MRI documentation. */ VALUE* const rb_cObject; /** * See MRI documentation. */ VALUE* const rb_cArray; /** * See MRI documentation. */ VALUE* const rb_cHash; /** * See MRI documentation. */ VALUE* const rb_cString; /** * See MRI documentation. */ VALUE* const rb_cSymbol; /** * See MRI documentation. */ VALUE* const rb_cFloat; /** * See MRI documentation. */ VALUE* const rb_cInteger; /** * See MRI documentation. */ VALUE* const rb_eException; /** * See MRI documentation. */ VALUE* const rb_eArgError; /** * See MRI documentation. */ VALUE* const rb_eTypeError; /** * See MRI documentation. */ VALUE* const rb_eStandardError; /** * See MRI documentation. */ VALUE* const rb_eRuntimeError; /** * See MRI documentation. */ VALUE* const rb_eLoadError; /** * Gets the load path being used by Ruby. * @return Returns the load path being used by Ruby. */ std::vector<std::string> get_load_path() const; /** * Converts a Ruby number into a size_t integer, with checking for overflow. * Throws an invalid_conversion if overflow is detected. * @param v The Ruby value to convert. * @return Returns the Ruby value as a size_t integer. */ size_t num2size_t(VALUE v) const; /** * Converts a Ruby value into a C++ string. * @param v The Ruby value to convert. * @return Returns the Ruby value as a string. */ std::string to_string(VALUE v) const; /** * Converts the given string to a Ruby symbol. * @param s The string to convert to a symbol. * @return Returns the symbol. */ VALUE to_symbol(std::string const& s) const; /** * Converts a C string to a Ruby UTF-8 encoded string value. * @param s The C string. * @param len The number of bytes in the C string. * @return Returns the string as a UTF-8 encoded Ruby value. */ VALUE utf8_value(char const* s, size_t len) const; /** * Converts a C string to a Ruby UTF-8 encoded string value. * @param s The C string. * @return Returns the string as a UTF-8 encoded Ruby value. */ VALUE utf8_value(char const* s) const; /** * Converts a C++ string to a Ruby UTF-8 encoded string value. * @param s The C++ string. * @return Returns the string as a UTF-8 encoded Ruby value. */ VALUE utf8_value(std::string const& s) const; /** * A utility function for wrapping a callback with a rescue clause. * @param callback The callback to call in the context of the rescue clause. * @param rescue The rescue function to call if there is an exception. * @return Returns the VALUE returned from either the callback or the rescue function. */ VALUE rescue(std::function<VALUE()> callback, std::function<VALUE(VALUE)> rescue) const; /** * A utility function for wrapping a callback with protection. * @param tag The returned jump tag. An exception occurred if the jump tag is non-zero. * @param callback The callback to call in the context of protection. * @return Returns the VALUE returned from the callback if successful or nil otherwise. */ VALUE protect(int& tag, std::function<VALUE()> callback) const; /** * Enumerates an array. * @param array The array to enumerate. * @param callback The callback to call for every element in the array. */ void array_for_each(VALUE array, std::function<bool(VALUE)> callback) const; /** * Enumerates a hash. * @param hash The hash to enumerate. * @param callback The callback to call for every element in the hash. */ void hash_for_each(VALUE hash, std::function<bool(VALUE, VALUE)> callback) const; /** * Converts the given exception into a string. * @param ex The exception to get the string representation of. * @param message The optional message to use instead of the exception's message. * @return Returns the string representation of the exception. */ std::string exception_to_string(VALUE ex, std::string const& message = std::string()) const; /** * Determines if the given value is an instance of the given class (or superclass). * @param value The value to check. * @param klass The class to check. * @return Returns true if the value is an instance of the given class (or a superclass) or false if it is not. */ bool is_a(VALUE value, VALUE klass) const; /** * Determines if the given value is nil. * @param value The value to check. * @return Returns true if the given value is nil or false if it is not. */ bool is_nil(VALUE value) const; /** * Determines if the given value is true. * @param value The value to check. * @return Returns true if the given value is true or false if it is not. */ bool is_true(VALUE value) const; /** * Determines if the given value is false. * @param value The value to check. * @return Returns true if the given value is false or false if it is not. */ bool is_false(VALUE value) const; /** * Determines if the given value is a hash. * @param value The value to check. * @return Returns true if the given value is a hash or false if it is not. */ bool is_hash(VALUE value) const; /** * Determines if the given value is an array. * @param value The value to check. * @return Returns true if the given value is an array or false if it is not. */ bool is_array(VALUE value) const; /** * Determines if the given value is a string. * @param value The value to check. * @return Returns true if the given value is a string or false if it is not. */ bool is_string(VALUE value) const; /** * Determines if the given value is a symbol. * @param value The value to check. * @return Returns true if the given value is a symbol or false if it is not. */ bool is_symbol(VALUE value) const; /** * Determines if the given value is an Integer. * @param value The value to check. * @return Returns true if the given value is an integer (Integer) or false if it is not. */ bool is_integer(VALUE value) const; /** * Determines if the given value is a float. * @param value The value to check. * @return Returns true if the given value is a float or false if it is not. */ bool is_float(VALUE value) const; /** * Gets the VALUE for nil. * @return Returns the VALUE for nil. */ VALUE nil_value() const; /** * Gets the VALUE for true. * @return Returns the VALUE for true. */ VALUE true_value() const; /** * Gets the VALUE for false. * @return Returns the VALUE for false. */ VALUE false_value() const; /** * Get the length of a ruby array. * Throws an invalid_conversion if teh array length can not be represented by a long. * @return Returns the length of the array. */ long array_len(VALUE array) const; /** * Looks up a constant based on the given names. The individual entries correspond to * namespaces, as in {"A", "B", "C"} => A::B::C. * @param names The names to lookup. * @return Returns the value or raises a NameError. */ VALUE lookup(std::initializer_list<std::string> const& names) const; /** * Determines if two values are equal. * @param first The first value to compare. * @param second The second value to compare. * @return Returns true if eql? returns true for the first and second values. */ bool equals(VALUE first, VALUE second) const; /** * Determines if the first value has case equality (===) with the second value. * @param first The first value to compare. * @param second The second value to compare. * @return Returns true if === returns true for the first and second values. */ bool case_equals(VALUE first, VALUE second) const; /** * Evalutes a ASCII string as ruby code. * Any exception raised will be propagated as a C++ runtime_error * @param code the ruby code to execute */ VALUE eval(const std::string& code); /** * Gets the underlying native instance from a Ruby data object. * The Ruby object must have been allocated with rb_data_object_alloc. * @tparam T The underlying native type. * @param obj The Ruby data object to get the native instance for. * @return Returns a pointer to the underlying native type. */ template <typename T> T* to_native(VALUE obj) const { return reinterpret_cast<T*>(reinterpret_cast<RData*>(obj)->data); } /** * Registers a data object for cleanup when the API is destructed. * The object must have been created with rb_data_object_alloc. * @param obj The data object to register. */ void register_data_object(VALUE obj) const { _data_objects.insert(obj); } /** * Unregisters a data object. * @param obj The data object to unregister. */ void unregister_data_object(VALUE obj) const { _data_objects.erase(obj); } /** * Specifies the location of the preferred Ruby library. * Defaults to empty, must be specified before api::instance() is called. */ static std::string ruby_lib_location; private: explicit api(leatherman::dynamic_library::dynamic_library library); // Imported Ruby functions that should not be called externally int (* const ruby_setup)(); void (* const ruby_init)(); void* (* const ruby_options)(int, char**); int (* const ruby_cleanup)(volatile int); static leatherman::dynamic_library::dynamic_library create(); static leatherman::dynamic_library::dynamic_library find_library(); static leatherman::dynamic_library::dynamic_library find_loaded_library(); static VALUE callback_thunk(VALUE parameter); static VALUE rescue_thunk(VALUE parameter, VALUE exception); static VALUE protect_thunk(VALUE parameter); static int hash_for_each_thunk(VALUE key, VALUE value, VALUE arg); static std::set<VALUE> _data_objects; // Represents object data // This definition comes from Ruby (unfortunately) struct RData { VALUE flags; const VALUE klass; void (*dmark)(void*); void (*dfree)(void*); void *data; #ifdef __GNUC__ } __attribute__((aligned(sizeof(VALUE)))); #else }; #endif leatherman::dynamic_library::dynamic_library _library; VALUE _nil = 0u; VALUE _true = 0u; VALUE _false = 0u; bool _initialized = false; bool _include_stack_trace = false; }; }} // namespace leatherman::ruby