/*************************************************************************** * rubyinterpreter.cpp * This file is part of the KDE project * copyright (C)2005 by Cyrille Berger (cberger@cberger.net) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. ***************************************************************************/ #include "rubyextension.h" #include "rubyinterpreter.h" #define HAVE_STRLCAT_PROTO 1 #define HAVE_STRLCPY_PROTO 1 #include "config.h" #ifndef HAVE_RUBY_1_9 #include #else // HAVE_RUBY_1_9 #include #define STR2CSTR(x) StringValuePtr(x) #endif // HAVE_RUBY_1_9 #include #include #include "api/list.h" #include "rubyconfig.h" namespace Kross { namespace Ruby { class RubyExtensionPrivate { friend class RubyExtension; /// The \a Kross::Api::Object this RubyExtension wraps. Kross::Api::Object::Ptr m_object; /// static VALUE s_krossObject; static VALUE s_krossException; }; VALUE RubyExtensionPrivate::s_krossObject = 0; VALUE RubyExtensionPrivate::s_krossException = 0; VALUE RubyExtension::method_missing(int argc, VALUE *argv, VALUE self) { #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug("method_missing(argc, argv, self)"); #endif if(argc < 1) { return 0; } #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug("Converting self to Kross::Api::Object"); #endif Kross::Api::Object::Ptr object = toObject( self ); return RubyExtension::call_method(object, argc, argv); } VALUE RubyExtension::call_method( Kross::Api::Object::Ptr object, int argc, VALUE *argv) { TQString funcname = rb_id2name(SYM2ID(argv[0])); TQValueList argsList; #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug(TQString("Building arguments list for function: %1 there are %2 arguments.").arg(funcname).arg(argc-1)); #endif for(int i = 1; i < argc; i++) { Kross::Api::Object::Ptr obj = toObject(argv[i]); if(obj) argsList.append(obj); } Kross::Api::Object::Ptr result; try { // We need a double try/catch because, the cleaning is only done at the end of the catch, so if we had only one try/catch, kross would crash after the call to rb_exc_raise try { // We can't let a C++ exceptions propagate in the C mechanism Kross::Api::Callable* callable = dynamic_cast(object.data()); if(callable && callable->hasChild(funcname)) { #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug( TQString("Kross::Ruby::RubyExtension::method_missing name='%1' is a child object of '%2'.").arg(funcname).arg(object->getName()) ); #endif result = callable->getChild(funcname)->call(TQString(), new Api::List(argsList)); } else { #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug( TQString("Kross::Ruby::RubyExtension::method_missing try to call function with name '%1' in object '%2'.").arg(funcname).arg(object->getName()) ); #endif result = object->call(funcname, new Api::List(argsList)); } } catch(Kross::Api::Exception::Ptr exception) { #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug("c++ exception catched, raise a ruby error"); #endif throw convertFromException(exception); } catch(...) { throw convertFromException(new Kross::Api::Exception( "Unknow error" )); // TODO: fix //i18n } } catch(VALUE v) { rb_exc_raise(v ); } return toVALUE(result); } void RubyExtension::delete_object(void* object) { krossdebug("delete_object"); RubyExtension* obj = static_cast(object); if(obj) delete obj; } void RubyExtension::delete_exception(void* object) { Kross::Api::Exception* exc = static_cast(object); exc->_TDEShared_unref(); } RubyExtension::RubyExtension(Kross::Api::Object::Ptr object) : d(new RubyExtensionPrivate()) { d->m_object = object; } RubyExtension::~RubyExtension() { krossdebug("Delete RubyExtension"); delete d; } typedef TQMap mStrObj; int RubyExtension::convertHash_i(VALUE key, VALUE value, VALUE vmap) { TQMap* map; Data_Get_Struct(vmap, mStrObj, map); if (key != Qundef) { Kross::Api::Object::Ptr o = RubyExtension::toObject( value ); if(o) map->replace(STR2CSTR(key), o); } return ST_CONTINUE; } bool RubyExtension::isOfExceptionType(VALUE value) { VALUE result = rb_funcall(value, rb_intern("kind_of?"), 1, RubyExtensionPrivate::s_krossException ); return (TYPE(result) == T_TRUE); } bool RubyExtension::isOfObjectType(VALUE value) { VALUE result = rb_funcall(value, rb_intern("kind_of?"), 1, RubyExtensionPrivate::s_krossObject ); return (TYPE(result) == T_TRUE); } Kross::Api::Exception::Ptr RubyExtension::convertToException(VALUE value) { if( isOfExceptionType(value) ) { Kross::Api::Exception* exception; Data_Get_Struct(value, Kross::Api::Exception, exception); return exception; } return 0; } VALUE RubyExtension::convertFromException(Kross::Api::Exception::Ptr exc) { if(RubyExtensionPrivate::s_krossException == 0) { RubyExtensionPrivate::s_krossException = rb_define_class_under(RubyInterpreter::krossModule(), "KrossException", rb_eRuntimeError); } exc->_TDEShared_ref(); return Data_Wrap_Struct(RubyExtensionPrivate::s_krossException, 0, RubyExtension::delete_exception, exc.data() ); } Kross::Api::Object::Ptr RubyExtension::toObject(VALUE value) { #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug(TQString("RubyExtension::toObject of type %1").arg(TYPE(value))); #endif switch( TYPE( value ) ) { case T_DATA: { #ifdef KROSS_RUBY_EXTENSION_DEBUG krossdebug("Object is a Kross Object"); #endif if( isOfObjectType(value) ) { RubyExtension* objectExtension; Data_Get_Struct(value, RubyExtension, objectExtension); Kross::Api::Object::Ptr object = objectExtension->d->m_object; return object; } else { krosswarning("Cannot yet convert standard ruby type to kross object"); return 0; } } case T_FLOAT: return new Kross::Api::Variant(NUM2DBL(value)); case T_STRING: return new Kross::Api::Variant(TQString(STR2CSTR(value))); case T_ARRAY: { TQValueList l; #ifdef HAVE_RUBY_1_9 for(int i = 0; i < RARRAY_LEN(value); i++) #else // HAVE_RUBY_1_9 for(int i = 0; i < RARRAY(value)->len; i++) #endif // HAVE_RUBY_1_9 { Kross::Api::Object::Ptr o = toObject( rb_ary_entry( value , i ) ); if(o) l.append(o); } return new Kross::Api::List(l); } case T_FIXNUM: return new Kross::Api::Variant((TQ_LLONG)FIX2INT(value)); case T_HASH: { TQMap map; VALUE vmap = Data_Wrap_Struct(rb_cObject, 0,0, &map); rb_hash_foreach(value, (int (*)(...))convertHash_i, vmap); return new Kross::Api::Dict(map); } case T_BIGNUM: { return new Kross::Api::Variant((TQ_LLONG)NUM2LONG(value)); } case T_TRUE: { return new Kross::Api::Variant(true); } case T_FALSE: { return new Kross::Api::Variant(false); } case T_SYMBOL: { return new Kross::Api::Variant(TQString(rb_id2name(SYM2ID(value)))); } case T_MATCH: case T_OBJECT: case T_FILE: case T_STRUCT: case T_REGEXP: case T_MODULE: case T_ICLASS: case T_CLASS: krosswarning(TQString("This ruby type '%1' cannot be converted to a Kross::Api::Object").arg(TYPE(value))); default: case T_NIL: return 0; } } VALUE RubyExtension::toVALUE(const TQString& s) { return s.isNull() ? rb_str_new2("") : rb_str_new2(s.latin1()); } VALUE RubyExtension::toVALUE(TQStringList list) { VALUE l = rb_ary_new(); for(TQStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) rb_ary_push(l, toVALUE(*it)); return l; } VALUE RubyExtension::toVALUE(TQMap map) { VALUE h = rb_hash_new(); for(TQMap::Iterator it = map.begin(); it != map.end(); ++it) rb_hash_aset(h, toVALUE(it.key()), toVALUE(it.data()) ); return h; } VALUE RubyExtension::toVALUE(TQValueList list) { VALUE l = rb_ary_new(); for(TQValueList::Iterator it = list.begin(); it != list.end(); ++it) rb_ary_push(l, toVALUE(*it)); return l; } VALUE RubyExtension::toVALUE(const TQVariant& variant) { switch(variant.type()) { case TQVariant::Invalid: return Qnil; case TQVariant::Bool: return (variant.toBool()) ? Qtrue : Qfalse; case TQVariant::Int: return INT2FIX(variant.toInt()); case TQVariant::UInt: return UINT2NUM(variant.toUInt()); case TQVariant::Double: return rb_float_new(variant.toDouble()); case TQVariant::Date: case TQVariant::Time: case TQVariant::DateTime: case TQVariant::ByteArray: case TQVariant::BitArray: case TQVariant::CString: case TQVariant::String: return toVALUE(variant.toString()); case TQVariant::StringList: return toVALUE(variant.toStringList()); case TQVariant::Map: return toVALUE(variant.toMap()); case TQVariant::List: return toVALUE(variant.toList()); // To handle following both cases is a bit difficult // cause Python doesn't spend an easy possibility // for such large numbers (TODO maybe BigInt?). So, // we risk overflows here, but well... case TQVariant::LongLong: { return INT2NUM((long)variant.toLongLong()); } case TQVariant::ULongLong: return UINT2NUM((unsigned long)variant.toULongLong()); default: { krosswarning( TQString("Kross::Ruby::RubyExtension::toVALUE(TQVariant) Not possible to convert the TQVariant type '%1' to a VALUE.").arg(variant.typeName()) ); return Qundef; } } } VALUE RubyExtension::toVALUE(Kross::Api::Object::Ptr object) { if(! object.data()) { return 0; } if(object->getClassName() == "Kross::Api::Variant") { TQVariant v = static_cast( object.data() )->getValue(); return toVALUE(v); } if(object->getClassName() == "Kross::Api::List") { Kross::Api::List* list = static_cast( object.data() ); return toVALUE((Kross::Api::List::Ptr)list); } if(object->getClassName() == "Kross::Api::Dict") { Kross::Api::Dict* dict = static_cast( object.data() ); return toVALUE((Kross::Api::Dict::Ptr)dict); } if(RubyExtensionPrivate::s_krossObject == 0) { RubyExtensionPrivate::s_krossObject = rb_define_class_under(RubyInterpreter::krossModule(), "Object", rb_cObject ); rb_define_method(RubyExtensionPrivate::s_krossObject, "method_missing", (VALUE (*)(...))RubyExtension::method_missing, -1); } return Data_Wrap_Struct(RubyExtensionPrivate::s_krossObject, 0, RubyExtension::delete_object, new RubyExtension(object) ); } VALUE RubyExtension::toVALUE(Kross::Api::List::Ptr list) { VALUE l = rb_ary_new(); uint count = list ? list->count() : 0; for(uint i = 0; i < count; i++) rb_ary_push(l, toVALUE(list->item(i))); return l; } } }