klayout/src/rba/rbaConvert.cc

297 lines
9.4 KiB
C++

/*
KLayout Layout Viewer
Copyright (C) 2006-2017 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if defined(HAVE_RUBY)
#include "rba.h"
#include "rbaConvert.h"
#include "rbaInternal.h"
#include "gsiDecl.h"
namespace rba
{
static int push_map_i (VALUE key, VALUE value, VALUE arg)
{
std::vector<std::pair<VALUE, VALUE> > *v = (std::vector<std::pair<VALUE, VALUE> > *) arg;
v->push_back (std::make_pair (key, value));
return ST_CONTINUE;
}
template <>
tl::Variant ruby2c<tl::Variant> (VALUE rval)
{
if (FIXNUM_P (rval)) {
return tl::Variant (ruby2c<long> (rval));
} else if (rval == Qnil) {
return tl::Variant ();
} else if (rval == Qfalse) {
return tl::Variant (false);
} else if (rval == Qtrue) {
return tl::Variant (true);
} else if (TYPE (rval) == T_BIGNUM) {
return tl::Variant (ruby2c<long long> (rval));
} else if (TYPE (rval) == T_FLOAT) {
return tl::Variant (ruby2c<double> (rval));
} else if (TYPE (rval) == T_HASH) {
std::vector<std::pair<VALUE, VALUE> > kv;
kv.reserve (RHASH_SIZE (rval));
// Note: we use this scheme rather than directly inserting into the variant because
// the rb_hash_foreach is not exception-safe.
rb_hash_foreach (rval, (int (*)(...)) &push_map_i, (VALUE) &kv);
tl::Variant r;
r.set_array ();
for (std::vector<std::pair<VALUE, VALUE> >::const_iterator i = kv.begin (); i != kv.end (); ++i) {
r.insert (ruby2c<tl::Variant> (i->first), ruby2c<tl::Variant> (i->second));
}
return r;
} else if (TYPE (rval) == T_ARRAY) {
unsigned int len = RARRAY_LEN(rval);
VALUE *el = RARRAY_PTR(rval);
static std::vector<tl::Variant> empty;
tl::Variant r (empty.begin (), empty.end ());
r.get_list ().reserve (len);
while (len-- > 0) {
r.get_list ().push_back (ruby2c<tl::Variant> (*el++));
}
return r;
} else if (TYPE (rval) == T_DATA) {
// some types are supported through "complex" tl::Variant's
Proxy *p = 0;
Data_Get_Struct (rval, Proxy, p);
// employ the tl::Variant binding capabilities of the Expression binding to derive the
// variant value.
const gsi::ClassBase *cls = p->cls_decl ();
void *obj = p->obj ();
if (! obj) {
return tl::Variant ();
}
if (cls->is_managed ()) {
const tl::VariantUserClassBase *var_cls = cls->var_cls (p->const_ref ());
tl_assert (var_cls != 0);
gsi::Proxy *gsi_proxy = cls->gsi_object (obj)->find_client<gsi::Proxy> ();
if (!gsi_proxy) {
// establish a new proxy
gsi_proxy = new gsi::Proxy (cls);
gsi_proxy->set (obj, false, p->const_ref (), false);
}
tl::Variant out;
out.set_user_ref (gsi_proxy, var_cls, false);
return out;
} else {
// No reference management available: deep copy mode.
return tl::Variant (cls->clone (obj), cls->var_cls (false), true);
}
} else if (TYPE (rval) == T_STRING) {
return tl::Variant (ruby2c<const char *> (rval));
} else {
return tl::Variant (ruby2c<const char *> (rba_safe_obj_as_string (rval)));
}
}
VALUE object_to_ruby (void *obj, const gsi::ArgType &atype)
{
const gsi::ClassBase *cls = atype.cls()->subclass_decl (obj);
bool is_direct = !(atype.is_ptr () || atype.is_ref () || atype.is_cptr () || atype.is_cref ());
bool pass_obj = atype.pass_obj () || is_direct;
bool is_const = atype.is_cptr () || atype.is_cref ();
bool prefer_copy = atype.is_cref ();
bool can_destroy = prefer_copy || atype.is_ptr ();
return object_to_ruby (obj, cls, pass_obj, is_const, prefer_copy, can_destroy);
}
/**
* @brief Correct constness if a reference is const and a non-const reference is required
* HINT: this is a workaround for the fact that unlike C++, Ruby does not have const or non-const
* references. Since a reference is identical with the object it points to, there are only const or non-const
* objects. We deliver const objects first, but if a non-const version is requestes, the
* object turns into a non-const one. This may be confusing but provides a certain level
* of "constness", at least until there is another non-const reference for that object.
*/
static void correct_constness (Proxy *p, bool const_required)
{
if (p && p->const_ref () && ! const_required) {
// promote to non-const object
p->set_const_ref (false);
}
}
VALUE
object_to_ruby (void *obj, const gsi::ClassBase *cls, bool pass_obj, bool is_const, bool prefer_copy, bool can_destroy)
{
VALUE ret = Qnil;
if (! obj || ! cls) {
return ret;
}
const gsi::ClassBase *clsact = cls->subclass_decl (obj);
if (! clsact) {
return ret;
}
// Derive a Proxy reference if the object is already bound
Proxy *rba_data = 0;
if (! clsact->adapted_type_info () && clsact->is_managed ()) {
rba_data = clsact->gsi_object (obj)->find_client<Proxy> ();
if (rba_data) {
// Don't use objects that are T_ZOMBIE or otherwise unusable
if (BUILTIN_TYPE (rba_data->self ()) != T_DATA) {
rba_data->detach ();
rba_data = 0;
// must be the last and only Proxy for this object
tl_assert (clsact->gsi_object (obj)->find_client<Proxy> () == 0);
}
}
} else if (clsact->adapted_type_info ()) {
// create an adaptor from an adapted type
if (pass_obj) {
obj = clsact->create_from_adapted_consume (obj);
} else {
obj = clsact->create_from_adapted (obj);
}
// we wil own the new object
pass_obj = true;
}
if (! pass_obj && prefer_copy && ! clsact->adapted_type_info () && ! clsact->is_managed () && clsact->can_copy ()) {
// We copy objects passed by const reference if they are not managed.
// Such objects are often exposed internals. First we can't
// guarantee the const correctness of references. Second, we
// can't guarantee the lifetime of the container will exceed that
// of the exposed property. Hence copying is safer.
// create a instance and copy the value
ret = rb_obj_alloc (ruby_cls (clsact));
Proxy *p = 0;
Data_Get_Struct (ret, Proxy, p);
clsact->assign (p->obj (), obj);
} else if (rba_data && rba_data->self () != Qnil) {
// we have a that is located in C++ space but is supposed to get attached
// a Ruby object. If it already has, we simply return a reference to this.
ret = rba_data->self ();
#if HAVE_RUBY_VERSION_CODE >= 20200
// Mark the returned object - the original one may have been already
// scheduled for sweeping. This happens at least for Ruby 2.3 which
// has a two-phase GC (mark and sweep in separate steps). If by chance
// the marking has been done already, we must not return this object
// unmarked. This happens in GC_LAZY_SWEEP mode which became popular
// with Ruby 2.2.
rb_gc_mark_maybe (ret);
#endif
// correct constness if the object is not supposed to be const
correct_constness (rba_data, is_const);
} else {
// create an instance
// TODO: we will create a fresh object here, delete it again and link the
// reference to the existing object to the Ruby object. This is not quite
// efficient - we should avoid creating and deleting a dummy object first.
ret = rb_obj_alloc (ruby_cls (clsact));
Proxy *p = 0;
Data_Get_Struct (ret, Proxy, p);
p->set (obj, pass_obj, is_const /*const*/, can_destroy /*can_destroy*/, ret);
}
return ret;
}
template <>
VALUE c2ruby<tl::Variant> (const tl::Variant &c)
{
if (c.is_double ()) {
return c2ruby<double> (c.to_double ());
} else if (c.is_bool ()) {
return c2ruby<bool> (c.to_bool ());
} else if (c.is_a_string ()) {
return c2ruby<std::string> (c.to_string ());
} else if (c.is_long () || c.is_char ()) {
return c2ruby<long> (c.to_long ());
} else if (c.is_ulong ()) {
return c2ruby<unsigned long> (c.to_ulong ());
} else if (c.is_longlong ()) {
return c2ruby<long long> (c.to_longlong ());
} else if (c.is_ulonglong ()) {
return c2ruby<unsigned long long> (c.to_ulonglong ());
} else if (c.is_array ()) {
VALUE ret = rb_hash_new ();
for (tl::Variant::const_array_iterator i = c.begin_array (); i != c.end_array (); ++i) {
rb_hash_aset (ret, c2ruby<tl::Variant> (i->first), c2ruby<tl::Variant> (i->second));
}
return ret;
} else if (c.is_list ()) {
VALUE ret = rb_ary_new ();
for (tl::Variant::const_iterator i = c.begin (); i != c.end (); ++i) {
rb_ary_push (ret, c2ruby<tl::Variant> (*i));
}
return ret;
} else if (c.is_user ()) {
const gsi::ClassBase *cls = c.gsi_cls ();
if (cls) {
void *obj = const_cast<void *> (c.to_user ());
return object_to_ruby (obj, c.user_cls ()->gsi_cls (), false, false, true, false);
} else {
// not a known type -> return nil
return Qnil;
}
} else {
return Qnil;
}
}
}
#endif