mirror of https://github.com/KLayout/klayout.git
297 lines
9.4 KiB
C++
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
|