/***************************************************************************
* 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 <st.h>
# include <tqmap.h>
# include <tqstring.h>
# 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 < Api : : Object : : Ptr > argsList ;
# ifdef KROSS_RUBY_EXTENSION_DEBUG
krossdebug ( TQString ( " Building arguments list for function: %1 there are %2 arguments. " ) . tqarg ( funcname ) . tqarg ( 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 < Kross : : Api : : Callable * > ( 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'. " ) . tqarg ( funcname ) . tqarg ( 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'. " ) . tqarg ( funcname ) . tqarg ( 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 < RubyExtension * > ( object ) ;
if ( obj )
delete obj ;
}
void RubyExtension : : delete_exception ( void * object )
{
Kross : : Api : : Exception * exc = static_cast < Kross : : Api : : Exception * > ( object ) ;
exc - > _KShared_unref ( ) ;
}
RubyExtension : : RubyExtension ( Kross : : Api : : Object : : Ptr object ) : d ( new RubyExtensionPrivate ( ) )
{
d - > m_object = object ;
}
RubyExtension : : ~ RubyExtension ( )
{
krossdebug ( " Delete RubyExtension " ) ;
delete d ;
}
typedef TQMap < TQString , Kross : : Api : : Object : : Ptr > mStrObj ;
int RubyExtension : : convertHash_i ( VALUE key , VALUE value , VALUE vmap )
{
TQMap < TQString , Kross : : Api : : Object : : Ptr > * 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 ( " KrossException " , rb_eRuntimeError ) ;
}
exc - > _KShared_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 " ) . tqarg ( 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 < Kross : : Api : : Object : : Ptr > l ;
for ( int i = 0 ; i < RARRAY ( value ) - > len ; i + + )
{
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 < TQString , Kross : : Api : : Object : : Ptr > 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 " ) . tqarg ( 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 < TQString , TQVariant > map )
{
VALUE h = rb_hash_new ( ) ;
for ( TQMap < TQString , TQVariant > : : Iterator it = map . begin ( ) ; it ! = map . end ( ) ; + + it )
rb_hash_aset ( h , toVALUE ( it . key ( ) ) , toVALUE ( it . data ( ) ) ) ;
return h ;
}
VALUE RubyExtension : : toVALUE ( TQValueList < TQVariant > list )
{
VALUE l = rb_ary_new ( ) ;
for ( TQValueList < TQVariant > : : 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. " ) . tqarg ( 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 < Kross : : Api : : Variant * > ( object . data ( ) ) - > getValue ( ) ;
return toVALUE ( v ) ;
}
if ( object - > getClassName ( ) = = " Kross::Api::List " ) {
Kross : : Api : : List * list = static_cast < Kross : : Api : : List * > ( object . data ( ) ) ;
return toVALUE ( ( Kross : : Api : : List : : Ptr ) list ) ;
}
if ( object - > getClassName ( ) = = " Kross::Api::Dict " ) {
Kross : : Api : : Dict * dict = static_cast < Kross : : Api : : Dict * > ( object . data ( ) ) ;
return toVALUE ( ( Kross : : Api : : Dict : : Ptr ) dict ) ;
}
if ( RubyExtensionPrivate : : s_krossObject = = 0 )
{
RubyExtensionPrivate : : s_krossObject = rb_define_class ( " KrossObject " , 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 ;
}
}
}