lib

rubyextension.cpp
1/***************************************************************************
2 * rubyinterpreter.cpp
3 * This file is part of the KDE project
4 * copyright (C)2005 by Cyrille Berger (cberger@cberger.net)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this program; see the file COPYING. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 ***************************************************************************/
19#include "rubyextension.h"
20#include "rubyinterpreter.h"
21
22#define HAVE_STRLCAT_PROTO 1
23#define HAVE_STRLCPY_PROTO 1
24#include "config.h"
25
26#ifndef HAVE_RUBY_1_9
27#include <st.h>
28#else // HAVE_RUBY_1_9
29#include <ruby/st.h>
30#define STR2CSTR(x) StringValuePtr(x)
31#endif // HAVE_RUBY_1_9
32
33#include <tqmap.h>
34#include <tqstring.h>
35
36#include "api/list.h"
37
38#include "rubyconfig.h"
39
40namespace Kross {
41
42namespace Ruby {
43
44
45class RubyExtensionPrivate {
46 friend class RubyExtension;
50 static VALUE s_krossObject;
51 static VALUE s_krossException;
52};
53
54VALUE RubyExtensionPrivate::s_krossObject = 0;
55VALUE RubyExtensionPrivate::s_krossException = 0;
56
57VALUE RubyExtension::method_missing(int argc, VALUE *argv, VALUE self)
58{
59#ifdef KROSS_RUBY_EXTENSION_DEBUG
60 krossdebug("method_missing(argc, argv, self)");
61#endif
62 if(argc < 1)
63 {
64 return 0;
65 }
66#ifdef KROSS_RUBY_EXTENSION_DEBUG
67 krossdebug("Converting self to Kross::Api::Object");
68#endif
69
70 Kross::Api::Object::Ptr object = toObject( self );
71 return RubyExtension::call_method(object, argc, argv);
72}
73
74VALUE RubyExtension::call_method( Kross::Api::Object::Ptr object, int argc, VALUE *argv)
75{
76 TQString funcname = rb_id2name(SYM2ID(argv[0]));
77 TQValueList<Api::Object::Ptr> argsList;
78#ifdef KROSS_RUBY_EXTENSION_DEBUG
79 krossdebug(TQString("Building arguments list for function: %1 there are %2 arguments.").arg(funcname).arg(argc-1));
80#endif
81 for(int i = 1; i < argc; i++)
82 {
83 Kross::Api::Object::Ptr obj = toObject(argv[i]);
84 if(obj) argsList.append(obj);
85 }
87 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
88 try { // We can't let a C++ exceptions propagate in the C mechanism
89 Kross::Api::Callable* callable = dynamic_cast<Kross::Api::Callable*>(object.data());
90 if(callable && callable->hasChild(funcname)) {
91#ifdef KROSS_RUBY_EXTENSION_DEBUG
92 krossdebug( TQString("Kross::Ruby::RubyExtension::method_missing name='%1' is a child object of '%2'.").arg(funcname).arg(object->getName()) );
93#endif
94 result = callable->getChild(funcname)->call(TQString(), new Api::List(argsList));
95 }
96 else {
97#ifdef KROSS_RUBY_EXTENSION_DEBUG
98 krossdebug( TQString("Kross::Ruby::RubyExtension::method_missing try to call function with name '%1' in object '%2'.").arg(funcname).arg(object->getName()) );
99#endif
100 result = object->call(funcname, new Api::List(argsList));
101 }
102 } catch(Kross::Api::Exception::Ptr exception)
103 {
104#ifdef KROSS_RUBY_EXTENSION_DEBUG
105 krossdebug("c++ exception catched, raise a ruby error");
106#endif
107 throw convertFromException(exception);
108 } catch(...)
109 {
110 throw convertFromException(new Kross::Api::Exception( "Unknow error" )); // TODO: fix //i18n
111 }
112 } catch(VALUE v) {
113 rb_exc_raise(v );
114 }
115 return toVALUE(result);
116}
117
118void RubyExtension::delete_object(void* object)
119{
120 krossdebug("delete_object");
121 RubyExtension* obj = static_cast<RubyExtension*>(object);
122 if(obj)
123 delete obj;
124}
125
126void RubyExtension::delete_exception(void* object)
127{
128 Kross::Api::Exception* exc = static_cast<Kross::Api::Exception*>(object);
129 exc->_TDEShared_unref();
130}
131
132
133RubyExtension::RubyExtension(Kross::Api::Object::Ptr object) : d(new RubyExtensionPrivate())
134{
135 d->m_object = object;
136}
137
138
140{
141 krossdebug("Delete RubyExtension");
142 delete d;
143}
144
145typedef TQMap<TQString, Kross::Api::Object::Ptr> mStrObj;
146
147int RubyExtension::convertHash_i(VALUE key, VALUE value, VALUE vmap)
148{
149 TQMap<TQString, Kross::Api::Object::Ptr>* map;
150 Data_Get_Struct(vmap, mStrObj, map);
151 if (key != Qundef)
152 {
153 Kross::Api::Object::Ptr o = RubyExtension::toObject( value );
154 if(o) map->replace(STR2CSTR(key), o);
155 }
156 return ST_CONTINUE;
157}
158
159bool RubyExtension::isOfExceptionType(VALUE value)
160{
161 VALUE result = rb_funcall(value, rb_intern("kind_of?"), 1, RubyExtensionPrivate::s_krossException );
162 return (TYPE(result) == T_TRUE);
163}
164
165bool RubyExtension::isOfObjectType(VALUE value)
166{
167 VALUE result = rb_funcall(value, rb_intern("kind_of?"), 1, RubyExtensionPrivate::s_krossObject );
168 return (TYPE(result) == T_TRUE);
169}
170
171
172Kross::Api::Exception::Ptr RubyExtension::convertToException(VALUE value)
173{
174 if( isOfExceptionType(value) )
175 {
176 Kross::Api::Exception* exception;
177 Data_Get_Struct(value, Kross::Api::Exception, exception);
178 return exception;
179 }
180 return 0;
181}
182
183VALUE RubyExtension::convertFromException(Kross::Api::Exception::Ptr exc)
184{
185 if(RubyExtensionPrivate::s_krossException == 0)
186 {
187 RubyExtensionPrivate::s_krossException = rb_define_class_under(RubyInterpreter::krossModule(), "KrossException", rb_eRuntimeError);
188 }
189 exc->_TDEShared_ref();
190 return Data_Wrap_Struct(RubyExtensionPrivate::s_krossException, 0, RubyExtension::delete_exception, exc.data() );
191}
192
193
194Kross::Api::Object::Ptr RubyExtension::toObject(VALUE value)
195{
196#ifdef KROSS_RUBY_EXTENSION_DEBUG
197 krossdebug(TQString("RubyExtension::toObject of type %1").arg(TYPE(value)));
198#endif
199 switch( TYPE( value ) )
200 {
201 case T_DATA:
202 {
203#ifdef KROSS_RUBY_EXTENSION_DEBUG
204 krossdebug("Object is a Kross Object");
205#endif
206 if( isOfObjectType(value) )
207 {
208 RubyExtension* objectExtension;
209 Data_Get_Struct(value, RubyExtension, objectExtension);
210 Kross::Api::Object::Ptr object = objectExtension->d->m_object;
211 return object;
212 } else {
213 krosswarning("Cannot yet convert standard ruby type to kross object");
214 return 0;
215 }
216 }
217 case T_FLOAT:
218 return new Kross::Api::Variant(NUM2DBL(value));
219 case T_STRING:
220 return new Kross::Api::Variant(TQString(STR2CSTR(value)));
221 case T_ARRAY:
222 {
223 TQValueList<Kross::Api::Object::Ptr> l;
224#ifdef HAVE_RUBY_1_9
225 for(int i = 0; i < RARRAY_LEN(value); i++)
226#else // HAVE_RUBY_1_9
227 for(int i = 0; i < RARRAY(value)->len; i++)
228#endif // HAVE_RUBY_1_9
229 {
230 Kross::Api::Object::Ptr o = toObject( rb_ary_entry( value , i ) );
231 if(o) l.append(o);
232 }
233 return new Kross::Api::List(l);
234 }
235 case T_FIXNUM:
236 return new Kross::Api::Variant((TQ_LLONG)FIX2INT(value));
237 case T_HASH:
238 {
239 TQMap<TQString, Kross::Api::Object::Ptr> map;
240 VALUE vmap = Data_Wrap_Struct(rb_cObject, 0,0, &map);
241 rb_hash_foreach(value, (int (*)(...))convertHash_i, vmap);
242 return new Kross::Api::Dict(map);
243 }
244 case T_BIGNUM:
245 {
246 return new Kross::Api::Variant((TQ_LLONG)NUM2LONG(value));
247 }
248 case T_TRUE:
249 {
250 return new Kross::Api::Variant(true);
251 }
252 case T_FALSE:
253 {
254 return new Kross::Api::Variant(false);
255 }
256 case T_SYMBOL:
257 {
258 return new Kross::Api::Variant(TQString(rb_id2name(SYM2ID(value))));
259 }
260 case T_MATCH:
261 case T_OBJECT:
262 case T_FILE:
263 case T_STRUCT:
264 case T_REGEXP:
265 case T_MODULE:
266 case T_ICLASS:
267 case T_CLASS:
268 krosswarning(TQString("This ruby type '%1' cannot be converted to a Kross::Api::Object").arg(TYPE(value)));
269 default:
270 case T_NIL:
271 return 0;
272 }
273}
274
275VALUE RubyExtension::toVALUE(const TQString& s)
276{
277 return s.isNull() ? rb_str_new2("") : rb_str_new2(s.latin1());
278}
279
280VALUE RubyExtension::toVALUE(TQStringList list)
281{
282 VALUE l = rb_ary_new();
283 for(TQStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it)
284 rb_ary_push(l, toVALUE(*it));
285 return l;
286}
287
288
289VALUE RubyExtension::toVALUE(TQStringVariantMap map)
290{
291 VALUE h = rb_hash_new();
292 for(TQStringVariantMap::Iterator it = map.begin(); it != map.end(); ++it)
293 rb_hash_aset(h, toVALUE(it.key()), toVALUE(it.data()) );
294 return h;
295
296}
297
298VALUE RubyExtension::toVALUE(TQValueList<TQVariant> list)
299{
300 VALUE l = rb_ary_new();
301 for(TQValueList<TQVariant>::Iterator it = list.begin(); it != list.end(); ++it)
302 rb_ary_push(l, toVALUE(*it));
303 return l;
304}
305
306
307VALUE RubyExtension::toVALUE(const TQVariant& variant)
308{
309
310 switch(variant.type()) {
311 case TQVariant::Invalid:
312 return Qnil;
313 case TQVariant::Bool:
314 return (variant.toBool()) ? Qtrue : Qfalse;
315 case TQVariant::Int:
316 return INT2FIX(variant.toInt());
317 case TQVariant::UInt:
318 return UINT2NUM(variant.toUInt());
319 case TQVariant::Double:
320 return rb_float_new(variant.toDouble());
321 case TQVariant::Date:
322 case TQVariant::Time:
323 case TQVariant::DateTime:
324 case TQVariant::ByteArray:
325 case TQVariant::BitArray:
326 case TQVariant::CString:
327 case TQVariant::String:
328 return toVALUE(variant.toString());
329 case TQVariant::StringList:
330 return toVALUE(variant.toStringList());
331 case TQVariant::Map:
332 return toVALUE(variant.toMap());
333 case TQVariant::List:
334 return toVALUE(variant.toList());
335
336 // To handle following both cases is a bit difficult
337 // cause Python doesn't spend an easy possibility
338 // for such large numbers (TODO maybe BigInt?). So,
339 // we risk overflows here, but well...
340 case TQVariant::LongLong: {
341 return INT2NUM((long)variant.toLongLong());
342 }
343 case TQVariant::ULongLong:
344 return UINT2NUM((unsigned long)variant.toULongLong());
345 default: {
346 krosswarning( TQString("Kross::Ruby::RubyExtension::toVALUE(TQVariant) Not possible to convert the TQVariant type '%1' to a VALUE.").arg(variant.typeName()) );
347 return Qundef;
348 }
349 }
350}
351
352VALUE RubyExtension::toVALUE(Kross::Api::Object::Ptr object)
353{
354 if(! object.data()) {
355 return 0;
356 }
357 if(object->getClassName() == "Kross::Api::Variant") {
358 TQVariant v = static_cast<Kross::Api::Variant*>( object.data() )->getValue();
359 return toVALUE(v);
360 }
361
362 if(object->getClassName() == "Kross::Api::List") {
363 Kross::Api::List* list = static_cast<Kross::Api::List*>( object.data() );
364 return toVALUE((Kross::Api::List::Ptr)list);
365 }
366
367 if(object->getClassName() == "Kross::Api::Dict") {
368 Kross::Api::Dict* dict = static_cast<Kross::Api::Dict*>( object.data() );
369 return toVALUE((Kross::Api::Dict::Ptr)dict);
370 }
371
372 if(RubyExtensionPrivate::s_krossObject == 0)
373 {
374 RubyExtensionPrivate::s_krossObject = rb_define_class_under(RubyInterpreter::krossModule(), "Object", rb_cObject );
375 rb_define_method(RubyExtensionPrivate::s_krossObject, "method_missing", (VALUE (*)(...))RubyExtension::method_missing, -1);
376 }
377 return Data_Wrap_Struct(RubyExtensionPrivate::s_krossObject, 0, RubyExtension::delete_object, new RubyExtension(object) );
378}
379
380VALUE RubyExtension::toVALUE(Kross::Api::List::Ptr list)
381{
382 VALUE l = rb_ary_new();
383 uint count = list ? list->count() : 0;
384 for(uint i = 0; i < count; i++)
385 rb_ary_push(l, toVALUE(list->item(i)));
386 return l;
387
388}
389
390}
391
392}
bool hasChild(const TQString &name) const
Returns if the defined child is avaible.
Definition callable.cpp:47
Object::Ptr getChild(const TQString &name) const
Return the defined child or NULL if there is no such object with that name avaible.
Definition callable.cpp:52
TDESharedPtr< Exception > Ptr
Shared pointer to implement reference-counting.
Definition exception.h:45
TDESharedPtr< List > Ptr
Shared pointer to implement reference-counting.
Definition list.h:46
TDESharedPtr< Object > Ptr
Shared pointer to implement reference-counting.
Definition object.h:56
RubyExtension(Kross::Api::Object::Ptr object)
Constructor.
The Ruby plugin for the Kross scripting framework.
Definition rubyconfig.h:33
The Kross scripting bridge to embed scripting functionality into an application.
Definition callable.h:33
KOFFICE_EXPORT void krossdebug(const TQString &s)
Debugging function.
KOFFICE_EXPORT void krosswarning(const TQString &s)
Warning function.