Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Enums #220

Merged
merged 1 commit into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions rice/Enum.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -32,34 +32,44 @@ namespace Rice
// First we need a constructor
klass.define_constructor(Constructor<Enum_T>());

// Instance methods
klass.define_method("to_s", [](Enum_T& self)
// Instance methods. The self parameter is confusing because it is really a Data_Object<Enum_T>.
// However, if we make that the type then the From_Ruby code will consider it a
// Data_Type<Data_Object<Enum_T>>>. But in define class above it was actually bound as
// Data_Type<Enum_T>. Thus the static_casts in the methods below.
klass.define_method("to_s", [](Enum_T& notSelf)
{
// We have to return string because we don't know if std::string support has
// been included by the user
return String(valuesToNames_[self]);
Data_Object<Enum_T> self = static_cast<Data_Object<Enum_T>>(notSelf);
return String(valuesToNames_[*self]);
})
.define_method("to_i", [](Enum_T& self) -> Underlying_T
.define_method("to_int", [](Enum_T& notSelf) -> Underlying_T
{
return (Underlying_T)self;
Data_Object<Enum_T> self = static_cast<Data_Object<Enum_T>>(notSelf);
return static_cast<Underlying_T>(*self);
})
.define_method("inspect", [](Enum_T& self)
.define_method("inspect", [](Enum_T& notSelf)
{
Data_Object<Enum_T> self = static_cast<Data_Object<Enum_T>>(notSelf);

std::stringstream result;
VALUE rubyKlass = Enum<Enum_T>::klass().value();
result << "#<" << detail::protect(rb_class2name, rubyKlass)
<< "::" << Enum<Enum_T>::valuesToNames_[self] << ">";
<< "::" << Enum<Enum_T>::valuesToNames_[*self] << ">";

// We have to return string because we don't know if std::string support has
// been included by the user
return String(result.str());
})
.define_method("hash", [](Enum_T& self) -> Underlying_T
.define_method("hash", [](Enum_T& notSelf) -> Underlying_T
{
return (Underlying_T)self;
Data_Object<Enum_T> self = static_cast<Data_Object<Enum_T>>(notSelf);
return (Underlying_T)*self;
})
.define_method("eql?", [](Enum_T& self, Enum_T& other)
.define_method("eql?", [](Enum_T& notSelf, Enum_T& notOther)
{
Data_Object<Enum_T> self = static_cast<Data_Object<Enum_T>>(notSelf);
Data_Object<Enum_T> other = static_cast<Data_Object<Enum_T>>(notOther);
return self == other;
});

Expand Down
66 changes: 64 additions & 2 deletions rice/detail/from_ruby.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdexcept>
#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
Expand Down Expand Up @@ -163,6 +164,23 @@ namespace Rice::detail
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;
}
Expand All @@ -176,6 +194,7 @@ namespace Rice::detail
}
else
{
// rb_num2long_inline will call to_int for RUBY_T_DATA objects
return (int)protect(rb_num2long_inline, value);
}
}
Expand Down Expand Up @@ -1106,7 +1125,7 @@ namespace Rice::detail
}
}
}

template<>
class From_Ruby<char>
{
Expand Down Expand Up @@ -1262,7 +1281,7 @@ namespace Rice::detail
}
};

// =========== unsinged char ============
// =========== unsigned char ============
template<>
class From_Ruby<unsigned char>
{
Expand Down Expand Up @@ -1305,6 +1324,49 @@ namespace Rice::detail
Arg* arg_ = nullptr;
};

template<>
class From_Ruby<unsigned char*>
{
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;
}
}

unsigned char* convert(VALUE value)
{
if (value == Qnil)
{
return nullptr;
}
else
{
detail::protect(rb_check_type, value, (int)T_STRING);
return reinterpret_cast<unsigned char*>(RSTRING_PTR(value));
}
}

private:
Arg* arg_ = nullptr;
};

// =========== signed char ============
template<>
class From_Ruby<signed char>
Expand Down
112 changes: 78 additions & 34 deletions test/test_Enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,15 @@ using namespace Rice;

TESTSUITE(Enum);

SETUP(Enum)
{
embed_ruby();
}

namespace
{
enum Color { RED, BLACK, GREEN };

Enum<Color> define_color_enum()
{
static Enum<Color> colors = define_enum<Color>("Color")
.define_value("RED", RED)
.define_value("BLACK", BLACK)
.define_value("GREEN", GREEN);
return colors;
}

enum class Season { Spring, Summer, Fall, Winter };

// This is needed to make unittest compile (it uses ostream to report errors)
Expand All @@ -30,22 +26,26 @@ namespace
os << static_cast<std::underlying_type_t<Season>>(season);
return os;
}
}

Enum<Season> define_season_enum()
{
static Enum<Season> seasons = define_enum<Season>("Season")
.define_value("Spring", Season::Spring)
.define_value("Summer", Season::Summer)
.define_value("Fall", Season::Fall)
.define_value("Winter", Season::Winter);

return seasons;
}
Enum<Color> define_color_enum()
{
static Enum<Color> colors = define_enum<Color>("Color")
.define_value("RED", RED)
.define_value("BLACK", BLACK)
.define_value("GREEN", GREEN);
return colors;
}

SETUP(Enum)
Enum<Season> define_season_enum()
{
embed_ruby();
static Enum<Season> seasons = define_enum<Season>("Season")
.define_value("Spring", Season::Spring)
.define_value("Summer", Season::Summer)
.define_value("Fall", Season::Fall)
.define_value("Winter", Season::Winter);

return seasons;
}

TESTCASE(copy_construct)
Expand Down Expand Up @@ -157,14 +157,14 @@ TESTCASE(to_s)
ASSERT_EQUAL(String("GREEN"), String(m.module_eval("Color::GREEN.to_s")));
}

TESTCASE(to_i)
TESTCASE(to_int)
{
Module m = define_module("Testing");

Enum<Color> colorEnum = define_color_enum();
ASSERT_EQUAL(detail::to_ruby(int(RED)), m.module_eval("Color::RED.to_i").value());
ASSERT_EQUAL(detail::to_ruby(int(BLACK)), m.module_eval("Color::BLACK.to_i").value());
ASSERT_EQUAL(detail::to_ruby(int(GREEN)), m.module_eval("Color::GREEN.to_i").value());
ASSERT_EQUAL(detail::to_ruby(int(RED)), m.module_eval("Color::RED.to_int").value());
ASSERT_EQUAL(detail::to_ruby(int(BLACK)), m.module_eval("Color::BLACK.to_int").value());
ASSERT_EQUAL(detail::to_ruby(int(GREEN)), m.module_eval("Color::GREEN.to_int").value());
}

TESTCASE(inspect)
Expand Down Expand Up @@ -283,9 +283,9 @@ TESTCASE(nested_enums)

Module m = define_module("Testing");

ASSERT_EQUAL(detail::to_ruby(int(0)), m.module_eval("Inner::Props::VALUE1.to_i").value());
ASSERT_EQUAL(detail::to_ruby(int(1)), m.module_eval("Inner::Props::VALUE2.to_i").value());
ASSERT_EQUAL(detail::to_ruby(int(2)), m.module_eval("Inner::Props::VALUE3.to_i").value());
ASSERT_EQUAL(detail::to_ruby(int(0)), m.module_eval("Inner::Props::VALUE1.to_int").value());
ASSERT_EQUAL(detail::to_ruby(int(1)), m.module_eval("Inner::Props::VALUE2.to_int").value());
ASSERT_EQUAL(detail::to_ruby(int(2)), m.module_eval("Inner::Props::VALUE3.to_int").value());
}

namespace
Expand All @@ -295,19 +295,23 @@ namespace
return RED;
}

bool isMyFavoriteColor(Color aColor)
bool isMyFavoriteColor(Color color)
{
return aColor == RED;
return color == RED;
}

bool myFavoriteColorAsInt(int color)
{
return color == RED;
}
}

TESTCASE(using_enums)
{
Enum<Color> colorEnum = define_color_enum();
colorEnum.define_singleton_function("my_favorite_color", &myFavoriteColor)
.define_singleton_function("is_my_favorite_color", &isMyFavoriteColor)
.define_singleton_function("is_my_favorite_color", &isMyFavoriteColor)
.define_method("is_my_favorite_color", &isMyFavoriteColor);
.define_singleton_function("is_my_favorite_color", &isMyFavoriteColor)
.define_method("is_my_favorite_color", &isMyFavoriteColor);

Module m = define_module("Testing");

Expand All @@ -327,6 +331,46 @@ TESTCASE(using_enums)
ASSERT_EQUAL(Qfalse, result.value());
}

TESTCASE(enum_to_int)
{
Enum<Color> colorEnum = define_color_enum();

Module m = define_module("Testing");
m.define_module_function("my_favorite_color_as_int", &myFavoriteColorAsInt);

std::string code = R"(my_favorite_color_as_int(Color::RED))";
Object result = m.module_eval(code);
ASSERT_EQUAL(Qtrue, result.value());

code = R"(my_favorite_color_as_int(Color::GREEN))";
result = m.module_eval(code);
ASSERT_EQUAL(Qfalse, result.value());
}

namespace
{
bool isMyFavoriteSeasonAsInt(int season)
{
return ((Season)season == Season::Summer);
}
}

TESTCASE(enum_class_to_int)
{
define_season_enum();

Module m = define_module("Testing");
m.define_module_function("is_my_favorite_season_as_int", &isMyFavoriteSeasonAsInt);

std::string code = R"(is_my_favorite_season_as_int(Season::Spring))";
Object result = m.module_eval(code);
ASSERT_EQUAL(Qfalse, result.value());

code = R"(is_my_favorite_season_as_int(Season::Summer))";
result = m.module_eval(code);
ASSERT_EQUAL(Qtrue, result.value());
}

namespace
{
Color defaultColor(Color aColor = BLACK)
Expand Down Expand Up @@ -377,4 +421,4 @@ TESTCASE(not_defined)
define_global_function("undefined_return", &undefinedReturn),
ASSERT_EQUAL(message, ex.what())
);
}
}