Kross
kjsscript.cpp
Go to the documentation of this file.
00001 /*************************************************************************** 00002 * kjsscript.cpp 00003 * This file is part of the KDE project 00004 * copyright (C)2004-2006 by Sebastian Sauer (mail@dipe.org) 00005 * 00006 * This program is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2 of the License, or (at your option) any later version. 00010 * This program is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 * Library General Public License for more details. 00014 * You should have received a copy of the GNU Library General Public License 00015 * along with this program; see the file COPYING. If not, write to 00016 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 * Boston, MA 02110-1301, USA. 00018 ***************************************************************************/ 00019 00020 #include "kjsscript.h" 00021 #include "../core/action.h" 00022 #include "../core/manager.h" 00023 #include "../core/interpreter.h" 00024 00025 // for Kjs 00026 #include <kjs/interpreter.h> 00027 #include <kjs/ustring.h> 00028 #include <kjs/object.h> 00029 #include <kjs/PropertyNameArray.h> 00030 //#include <kjs/array_instance.h> 00031 #include <kjs/function_object.h> 00032 00033 // for KjsEmbed 00034 #include <kjsembed/kjsembed.h> 00035 #include <kjsembed/qobject_binding.h> 00036 #include <kjsembed/variant_binding.h> 00037 #include <kjsembed/slotproxy.h> 00038 00039 #include <QMetaObject> 00040 #include <QMetaMethod> 00041 #include <QPointer> 00042 #include <QTextCodec> 00043 00044 using namespace Kross; 00045 00046 namespace Kross { 00047 00049 static ErrorInterface extractError(const KJS::Completion& completion, KJS::ExecState* exec) 00050 { 00051 QString type; 00052 switch( completion.complType() ) { 00053 case KJS::Normal: type = "Normal"; break; 00054 case KJS::Break: type = "Break"; break; 00055 case KJS::Continue: type = "Continue"; break; 00056 case KJS::ReturnValue: type = "ReturnValue"; break; 00057 case KJS::Throw: { 00058 type = "Throw"; 00059 } break; 00060 case KJS::Interrupted: type = "Interrupted"; break; 00061 default: type = "Unknown"; break; 00062 } 00063 00064 KJS::JSValue* value = completion.value(); 00065 int lineno = -1; 00066 if( value && value->type() == KJS::ObjectType ) { 00067 KJS::JSValue* linevalue = value->getObject()->get(exec, "line"); 00068 if( linevalue && linevalue->type() == KJS::NumberType ) 00069 lineno = linevalue->toInt32(exec); 00070 } 00071 const QString message = QString("%1%2: %3").arg( type ).arg((lineno >= 0) ? QString(" line %1").arg(lineno) : "").arg(value ? value->toString(exec).qstring() : "NULL"); 00072 00073 ErrorInterface err; 00074 err.setError(message, QString(), lineno); 00075 return err; 00076 } 00077 00079 class KjsScriptPrivate 00080 { 00081 public: 00085 KJSEmbed::Engine* m_engine; 00086 00090 QList< QPair<KJS::JSObject*, QPointer<QObject> > > m_publishedObjects; 00091 00097 QList< QObject* > m_autoconnect; 00098 00102 QStringList m_defaultFunctionNames; 00103 00110 void addFunctions(ChildrenInterface* children) 00111 { 00112 QHashIterator< QString, ChildrenInterface::Options > it( children->objectOptions() ); 00113 while(it.hasNext()) { 00114 it.next(); 00115 if( it.value() & ChildrenInterface::AutoConnectSignals ) { 00116 QObject* sender = children->object( it.key() ); 00117 if( sender ) { 00118 krossdebug( QString("KjsScript::addFunctions sender name=%1 className=%2").arg(sender->objectName()).arg(sender->metaObject()->className()) ); 00119 m_autoconnect.append( sender ); 00120 } 00121 } 00122 } 00123 } 00124 00126 bool publishObject(KJS::ExecState* exec, const QString &name, QObject* object) 00127 { 00128 Q_UNUSED(exec); 00129 00130 KJS::JSObject* obj = m_engine->addObject(object, name.isEmpty() ? object->objectName() : name); 00131 if( ! obj ) { 00132 krosswarning( QString("Failed to publish the QObject name=\"%1\" objectName=\"%2\"").arg(name).arg(object ? object->objectName() : "NULL") ); 00133 return false; 00134 } 00135 m_publishedObjects << QPair<KJS::JSObject*, QPointer<QObject> >(obj, object); 00136 00137 /* 00138 bool restricted = interpreter()->interpreterInfo()->optionValue("restricted", true).toBool(); 00139 if( restricted ) { 00140 KJSEmbed::QObjectBinding* objImp = KJSEmbed::extractBindingImp<KJSEmbed::QObjectBinding>(exec, obj); 00141 objImp->setAccess( 00142 KJSEmbed::QObjectBinding::ScriptableSlots | 00143 KJSEmbed::QObjectBinding::NonScriptableSlots | 00144 KJSEmbed::QObjectBinding::PublicSlots | 00145 KJSEmbed::QObjectBinding::ScriptableSignals | 00146 KJSEmbed::QObjectBinding::NonScriptableSignals | 00147 KJSEmbed::QObjectBinding::PublicSignals | 00148 KJSEmbed::QObjectBinding::ScriptableProperties | 00149 KJSEmbed::QObjectBinding::NonScriptableProperties | 00150 KJSEmbed::QObjectBinding::GetParentObject | 00151 KJSEmbed::QObjectBinding::ChildObjects 00152 ); 00153 } 00154 */ 00155 return true; 00156 } 00157 00158 }; 00159 00160 } 00161 00162 KjsScript::KjsScript(Interpreter* interpreter, Action* action) 00163 : Script(interpreter, action) 00164 , d(new KjsScriptPrivate()) 00165 { 00166 krossdebug( QString("KjsScript::KjsScript") ); 00167 d->m_engine = 0; 00168 00169 d->addFunctions( &Manager::self() ); 00170 d->addFunctions( action ); 00171 } 00172 00173 KjsScript::~KjsScript() 00174 { 00175 krossdebug( QString("KjsScript::~KjsScript") ); 00176 finalize(); 00177 delete d; 00178 } 00179 00180 bool KjsScript::initialize() 00181 { 00182 if( d->m_engine ) 00183 finalize(); // finalize before initialize 00184 clearError(); // clear previous errors. 00185 00186 krossdebug( QString("KjsScript::initialize") ); 00187 00188 d->m_engine = new KJSEmbed::Engine(); 00189 00190 KJS::Interpreter* kjsinterpreter = d->m_engine->interpreter(); 00191 kjsinterpreter->setShouldPrintExceptions(true); 00192 KJS::ExecState* exec = kjsinterpreter->globalExec(); 00193 00194 // publish our own action and the manager 00195 d->publishObject(exec, "self", action()); 00196 d->publishObject(exec, "Kross", &Manager::self()); 00197 00198 d->m_defaultFunctionNames = functionNames(); 00199 d->m_defaultFunctionNames << "Kross"; 00200 00201 { // publish the global objects. 00202 QHash< QString, QObject* > objects = Manager::self().objects(); 00203 QHash< QString, QObject* >::Iterator it(objects.begin()), end(objects.end()); 00204 for(; it != end; ++it) 00205 d->publishObject(exec, it.key(), it.value()); 00206 } 00207 00208 { // publish the local objects. 00209 QHash< QString, QObject* > objects = action()->objects(); 00210 QHash< QString, QObject* >::Iterator it(objects.begin()), end(objects.end()); 00211 for(; it != end; ++it) 00212 d->publishObject(exec, it.key(), it.value()); 00213 } 00214 00215 /* 00216 { // some debugging 00217 krossdebug( QString("Global object") ); 00218 KJS::JSObject* obj = kjsinterpreter->globalObject(); 00219 KJS::ExecState* exec = kjsinterpreter->globalExec(); 00220 KJS::PropertyNameArray props; 00221 obj->getPropertyNames(exec, props); 00222 for(KJS::PropertyNameArrayIterator it = props.begin(); it != props.end(); ++it) 00223 krossdebug( QString(" property name=%1").arg( it->ascii() ) ); 00224 } 00225 */ 00226 00227 return true; 00228 } 00229 00230 void KjsScript::finalize() 00231 { 00232 KJS::Interpreter* kjsinterpreter = d->m_engine->interpreter(); 00233 KJS::ExecState* exec = kjsinterpreter->globalExec(); 00234 Q_UNUSED(exec); 00235 00236 QList< QPair<KJS::JSObject*, QPointer<QObject> > >::Iterator it( d->m_publishedObjects.begin() ); 00237 QList< QPair<KJS::JSObject*, QPointer<QObject> > >::Iterator end( d->m_publishedObjects.end() ); 00238 for(; it != end; ++it) { 00239 QObject* obj = (*it).second; 00240 if( ! obj ) 00241 continue; 00242 /* 00243 KJSEmbed::QObjectBinding *imp = KJSEmbed::extractBindingImp<KJSEmbed::QObjectBinding>(exec, kjsobj); 00244 Q_ASSERT(imp); 00245 QObject* obj = imp->object<QObject>(); 00246 Q_ASSERT(obj); 00247 */ 00248 00249 // try to remove all pending slotproxy's the dirty way... please note, that we can't 00250 // do it using findChildren since the slotproxy's are handcraftet QObject's and don't 00251 // implement all of the QObject functionality. Also it seems KjsEmbed does some wired 00252 // things with the slotproxy's what prevents us from doing it another more nicer way. 00253 foreach( QObject* child, obj->children() ) 00254 if( KJSEmbed::SlotProxy* proxy = dynamic_cast< KJSEmbed::SlotProxy* >(child) ) 00255 delete proxy; 00256 00257 /* the kjsobj-instance will be or got already deleted by KJS and we don't need to care 00258 KJS::JSObject* kjsobj = (*it).first; 00259 krossdebug(QString("KjsScript::finalize published object=%1").arg( kjsobj->className().ascii() )); 00260 delete kjsobj; 00261 */ 00262 } 00263 d->m_publishedObjects.clear(); 00264 00265 d->m_autoconnect.clear(); 00266 d->m_defaultFunctionNames.clear(); 00267 00268 delete d->m_engine; 00269 d->m_engine = 0; 00270 } 00271 00272 void KjsScript::execute() 00273 { 00274 if(! initialize()) { 00275 krosswarning( QString("KjsScript::execute aborted cause initialize failed.") ); 00276 return; 00277 } 00278 00279 QByteArray code = action()->code(); 00280 if(code.startsWith("#!")) // krazy:exclude=strings 00281 code.remove(0, code.indexOf('\n')); // remove optional shebang-line 00282 00283 QTextCodec *codec = QTextCodec::codecForLocale(); 00284 KJS::UString c = codec ? KJS::UString(codec->toUnicode(code)) : KJS::UString(code.data(), code.size()); 00285 //krossdebug( QString("KjsScript::execute code=\n%1").arg(c.qstring()) ); 00286 KJSEmbed::Engine::ExitStatus exitstatus = d->m_engine->execute(c); 00287 00288 KJS::Completion completion = d->m_engine->completion(); 00289 KJS::Interpreter* kjsinterpreter = d->m_engine->interpreter(); 00290 KJS::ExecState* exec = kjsinterpreter->globalExec(); 00291 00292 if(exitstatus != KJSEmbed::Engine::Success) { 00293 ErrorInterface error = extractError(completion, exec); 00294 setError(&error); 00295 return; 00296 } 00297 00298 KJS::JSObject* kjsglobal = kjsinterpreter->globalObject(); 00299 if( exec->hadException() ) { 00300 ErrorInterface error = extractError(d->m_engine->completion(), exec); 00301 krossdebug(QString("KjsScript::execute() failed: %1").arg(error.errorMessage())); 00302 setError(&error); 00303 //exec->clearException(); 00304 return; 00305 } 00306 00307 foreach(QObject* object, d->m_autoconnect) { 00308 const QMetaObject* metaobject = object->metaObject(); 00309 const int count = metaobject->methodCount(); 00310 for(int i = 0; i < count; ++i) { 00311 QMetaMethod metamethod = metaobject->method(i); 00312 if( metamethod.methodType() == QMetaMethod::Signal ) { 00313 const QString signature = metamethod.signature(); 00314 const QByteArray name = signature.left(signature.indexOf('(')).toLatin1(); 00315 krossdebug( QString("KjsScript::execute function=%1").arg(name.data()) ); 00316 00317 KJS::Identifier id = KJS::Identifier( KJS::UString(name.data()) ); 00318 KJS::JSValue *functionvalue = kjsglobal->get(exec, id); 00319 if( ! functionvalue->isObject() ) 00320 continue; 00321 KJS::JSObject *function = functionvalue->toObject(exec); 00322 Q_ASSERT( ! exec->hadException() ); 00323 if( exec->hadException() ) 00324 continue; 00325 if ( function && function->implementsCall() ) { 00326 krossdebug( QString("KjsScript::execute connect function=%1 with signal=%2").arg(name.data()).arg(signature) ); 00327 00328 QByteArray sendersignal = QString("2%1").arg(signature).toLatin1(); 00329 QByteArray receiverslot = QString("1%1").arg(signature).toLatin1(); 00330 KJSEmbed::SlotProxy* receiver = new KJSEmbed::SlotProxy(kjsglobal, exec->dynamicInterpreter(), object, signature.toLatin1()); 00331 00332 if( connect(object, sendersignal, receiver, receiverslot) ) { 00333 krossdebug( QString("KjsScript::execute connected function=%1 with object=%2 signal=%3").arg(name.data()).arg(object->objectName()).arg(signature) ); 00334 } 00335 else { 00336 krosswarning( QString("KjsScript::execute failed to connect object=%1 signal=%2").arg(object->objectName()).arg(signature) ); 00337 } 00338 00339 } 00340 } 00341 } 00342 00343 } 00344 } 00345 00346 QStringList KjsScript::functionNames() 00347 { 00348 KJS::Interpreter* kjsinterpreter = d->m_engine->interpreter(); 00349 KJS::ExecState* exec = kjsinterpreter->globalExec(); 00350 KJS::JSObject* kjsglobal = kjsinterpreter->globalObject(); 00351 if( exec->hadException() ) { 00352 return QStringList(); 00353 } 00354 00355 KJS::PropertyNameArray props; 00356 kjsglobal->getPropertyNames(exec, props); 00357 00358 QStringList list; 00359 for(KJS::PropertyNameArrayIterator it = props.begin(); it != props.end(); ++it) { 00360 const char* name = it->ascii(); 00361 KJS::Identifier id = KJS::Identifier(name); 00362 KJS::JSValue *value = kjsglobal->get(exec, id); 00363 if( ! value || ! value->isObject() ) 00364 continue; 00365 KJS::JSObject *obj = value->toObject(exec); 00366 if( ! obj || ! obj->implementsCall() || ! obj->implementsConstruct() || ! obj->classInfo() ) 00367 continue; 00368 if( d->m_defaultFunctionNames.contains(name) ) 00369 continue; 00370 list << name; 00371 } 00372 00373 Q_ASSERT( ! exec->hadException() ); 00374 return list; 00375 } 00376 00377 QVariant KjsScript::callFunction(const QString& name, const QVariantList& args) 00378 { 00379 //if( hadError() ) return QVariant(); // check if we had a prev error and abort if that's the case 00380 00381 KJS::Interpreter* kjsinterpreter = d->m_engine->interpreter(); 00382 KJS::ExecState* exec = kjsinterpreter->globalExec(); 00383 KJS::JSObject* kjsglobal = kjsinterpreter->globalObject(); 00384 if( exec->hadException() ) { 00385 ErrorInterface error = extractError(d->m_engine->completion(), exec); 00386 //setError(&error); 00387 krossdebug(QString("KjsScript::callFunction(\"%1\") Prev error: %2").arg(name).arg(error.errorMessage())); 00388 return QVariant(); 00389 } 00390 00391 KJS::Identifier id = KJS::Identifier( KJS::UString(name.toLatin1().data()) ); 00392 KJS::JSValue *functionvalue = kjsglobal->get(exec, id); 00393 Q_ASSERT( ! exec->hadException() ); 00394 00395 KJS::JSObject *function = functionvalue->toObject(exec); 00396 if ( ! function || ! function->implementsCall() ) { 00397 krossdebug(QString("KjsScript::callFunction(\"%1\") No such function").arg(name)); 00398 setError(QString("No such function \"%1\"").arg(name)); 00399 return QVariant(); 00400 } 00401 00402 KJS::List kjsargs; 00403 foreach(const QVariant &variant, args) { 00404 if( qVariantCanConvert< QWidget* >(variant) ) { 00405 if( QWidget* widget = qvariant_cast< QWidget* >(variant) ) { 00406 kjsargs.append( KJSEmbed::createQObject(exec, widget, KJSEmbed::ObjectBinding::QObjOwned) ); 00407 Q_ASSERT( ! exec->hadException() ); 00408 continue; 00409 } 00410 } 00411 if( qVariantCanConvert< QObject* >(variant) ) { 00412 if( QObject* obj = qvariant_cast< QObject* >(variant) ) { 00413 kjsargs.append( KJSEmbed::createQObject(exec, obj, KJSEmbed::ObjectBinding::QObjOwned) ); 00414 Q_ASSERT( ! exec->hadException() ); 00415 continue; 00416 } 00417 } 00418 KJS::JSValue* jsvalue = KJSEmbed::convertToValue(exec, variant); 00419 Q_ASSERT( ! exec->hadException() ); 00420 kjsargs.append( jsvalue ); 00421 } 00422 00423 KJS::JSValue *retValue = function->call(exec, kjsglobal, kjsargs); 00424 if( exec->hadException() ) { 00425 ErrorInterface error = extractError(d->m_engine->completion(), exec); 00426 //exec->clearException(); 00427 krossdebug(QString("KjsScript::callFunction(\"%1\") Call failed: %2").arg(name).arg(error.errorMessage())); 00428 setError(&error); 00429 return QVariant(); 00430 } 00431 00432 QVariant result = retValue ? KJSEmbed::convertToVariant(exec, retValue) : QVariant(); 00433 Q_ASSERT( ! exec->hadException() ); 00434 return result; 00435 } 00436 00437 QVariant KjsScript::evaluate(const QByteArray& code) 00438 { 00439 QTextCodec *codec = QTextCodec::codecForLocale(); 00440 KJS::UString c = codec ? KJS::UString(codec->toUnicode(code)) : KJS::UString(code.data(), code.size()); 00441 00442 KJSEmbed::Engine::ExitStatus exitstatus = d->m_engine->execute(c); 00443 00444 KJS::Completion completion = d->m_engine->completion(); 00445 KJS::Interpreter* kjsinterpreter = d->m_engine->interpreter(); 00446 KJS::ExecState* exec = kjsinterpreter->globalExec(); 00447 00448 if(exitstatus != KJSEmbed::Engine::Success) { 00449 ErrorInterface error = extractError(completion, exec); 00450 setError(&error); 00451 return QVariant(); 00452 } 00453 00454 KJS::JSValue *retValue = completion.value(); 00455 QVariant result = retValue ? KJSEmbed::convertToVariant(exec, retValue) : QVariant(); 00456 Q_ASSERT( ! exec->hadException() ); 00457 return result; 00458 }
KDE 4.6 API Reference