File indexing completed on 2024-04-28 11:44:15
0001 /*************************************************************************** 0002 * script.cpp 0003 * This file is part of the KDE project 0004 * copyright (C)2007-2008 by Sebastian Sauer (mail@dipe.org) 0005 * 0006 * This program is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Library General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2 of the License, or (at your option) any later version. 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 * Library General Public License for more details. 0014 * You should have received a copy of the GNU Library General Public License 0015 * along with this program; see the file COPYING. If not, write to 0016 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 * Boston, MA 02110-1301, USA. 0018 ***************************************************************************/ 0019 0020 #include "script.h" 0021 #include "kross_qtscript_debug.h" 0022 0023 #include <QMetaObject> 0024 #include <QMetaMethod> 0025 #include <QScriptEngine> 0026 #include <QScriptValueIterator> 0027 0028 using namespace Kross; 0029 0030 namespace Kross 0031 { 0032 0033 /// \internal private d-pointer class. 0034 class EcmaScript::Private 0035 { 0036 public: 0037 EcmaScript *m_script; 0038 QScriptEngine *m_engine; 0039 QScriptValue m_kross; 0040 QScriptValue m_self; 0041 0042 explicit Private(EcmaScript *script) : m_script(script), m_engine(nullptr) {} 0043 ~Private() 0044 { 0045 delete m_engine; 0046 } 0047 0048 bool init() 0049 { 0050 if (m_script->action()->hadError()) { 0051 m_script->action()->clearError(); 0052 } 0053 0054 delete m_engine; 0055 m_engine = new QScriptEngine(); 0056 m_engine->installTranslatorFunctions(); 0057 0058 // load the Kross QScriptExtensionPlugin plugin that provides 0059 // us a bridge between Kross and QtScript. See here plugin.h 0060 m_engine->importExtension("kross"); 0061 if (m_engine->hasUncaughtException()) { 0062 handleException(); 0063 delete m_engine; 0064 m_engine = nullptr; 0065 return false; 0066 } 0067 0068 // the Kross QScriptExtensionPlugin exports the "Kross" property. 0069 QScriptValue global = m_engine->globalObject(); 0070 m_kross = global.property("Kross"); 0071 Q_ASSERT(m_kross.isQObject()); 0072 Q_ASSERT(! m_engine->hasUncaughtException()); 0073 0074 // Attach our Kross::Action instance to be able to access it in 0075 // scripts. Just like at the Kjs-backend we publish our own 0076 // action as "self". 0077 m_self = m_engine->newQObject(m_script->action()); 0078 global.setProperty("self", m_self, QScriptValue::ReadOnly | QScriptValue::Undeletable); 0079 0080 { 0081 // publish the global objects. 0082 QHash< QString, QObject * > objects = Manager::self().objects(); 0083 QHash< QString, QObject * >::Iterator it(objects.begin()), end(objects.end()); 0084 for (; it != end; ++it) { 0085 global.setProperty(it.key(), m_engine->newQObject(it.value())); 0086 } 0087 } 0088 0089 { 0090 // publish the local objects. 0091 QHash< QString, QObject * > objects = m_script->action()->objects(); 0092 QHash< QString, QObject * >::Iterator it(objects.begin()), end(objects.end()); 0093 for (; it != end; ++it) { 0094 copyEnumsToProperties(it.value()); 0095 global.setProperty(it.key(), m_engine->newQObject(it.value())); 0096 } 0097 } 0098 0099 return ! m_engine->hasUncaughtException(); 0100 } 0101 0102 void copyEnumsToProperties(QObject *object) 0103 { 0104 const QMetaObject *meta = object->metaObject(); 0105 for (int i = 0; i < meta->enumeratorCount(); ++i) { 0106 QMetaEnum metaenum = meta->enumerator(i); 0107 for (int j = 0; j < metaenum.keyCount(); ++j) { 0108 object->setProperty(metaenum.key(j), metaenum.value(j)); 0109 } 0110 } 0111 } 0112 0113 void handleException() 0114 { 0115 Q_ASSERT(m_engine); 0116 Q_ASSERT(m_engine->hasUncaughtException()); 0117 const QString err = m_engine->uncaughtException().toString(); 0118 const int linenr = m_engine->uncaughtExceptionLineNumber(); 0119 const QString trace = m_engine->uncaughtExceptionBacktrace().join("\n"); 0120 qCDebug(KROSS_QTSCRIPT_LOG) << QStringLiteral("%1, line:%2, backtrace:\n%3").arg(err).arg(linenr).arg(trace); 0121 m_script->action()->setError(err, trace, linenr); 0122 m_engine->clearExceptions(); 0123 } 0124 0125 void addObject(QObject *object, const QString &name = QString()) 0126 { 0127 Q_ASSERT(m_engine); 0128 Q_ASSERT(! m_engine->hasUncaughtException()); 0129 QScriptValue global = m_engine->globalObject(); 0130 QScriptValue value = m_engine->newQObject(object); 0131 global.setProperty(name.isEmpty() ? object->objectName() : name, value); 0132 } 0133 0134 void connectFunctions(ChildrenInterface *children) 0135 { 0136 Q_ASSERT(m_engine); 0137 Q_ASSERT(! m_engine->hasUncaughtException()); 0138 QString eval; 0139 QScriptValue global = m_engine->globalObject(); 0140 QHashIterator< QString, ChildrenInterface::Options > it(children->objectOptions()); 0141 while (it.hasNext()) { 0142 it.next(); 0143 if (it.value() & ChildrenInterface::AutoConnectSignals) { 0144 QObject *sender = children->object(it.key()); 0145 if (! sender) { 0146 continue; 0147 } 0148 QScriptValue obj = m_engine->globalObject().property(it.key()); 0149 if (! obj.isQObject()) { 0150 continue; 0151 } 0152 const QMetaObject *mo = sender->metaObject(); 0153 const int count = mo->methodCount(); 0154 for (int i = 0; i < count; ++i) { 0155 QMetaMethod mm = mo->method(i); 0156 const QString signature = mm.methodSignature(); 0157 const QString name = signature.left(signature.indexOf('(')); 0158 if (mm.methodType() == QMetaMethod::Signal) { 0159 QScriptValue func = global.property(name); 0160 if (! func.isFunction()) { 0161 //krossdebug( QString("EcmaScript::connectFunctions No function to connect with %1.%2").arg(it.key()).arg(name) ); 0162 continue; 0163 } 0164 qCDebug(KROSS_QTSCRIPT_LOG) << "EcmaScript::connectFunctions Connecting with " << 0165 it.key() << "." << name; 0166 eval += QString("try { %1.%2.connect(%3); } catch(e) { print(e); }\n").arg(it.key()).arg(name).arg(name); 0167 } 0168 } 0169 } 0170 } 0171 Q_ASSERT(! m_engine->hasUncaughtException()); 0172 if (! eval.isNull()) { 0173 m_engine->evaluate(eval); 0174 Q_ASSERT(! m_engine->hasUncaughtException()); 0175 } 0176 } 0177 0178 }; 0179 0180 } 0181 0182 EcmaScript::EcmaScript(Interpreter *interpreter, Action *action) : Script(interpreter, action), d(new Private(this)) 0183 { 0184 //krossdebug( QString("EcmaScript::EcmaScript") ); 0185 } 0186 0187 EcmaScript::~EcmaScript() 0188 { 0189 //krossdebug( QString("EcmaScript::~EcmaScript") ); 0190 delete d; 0191 } 0192 0193 void EcmaScript::execute() 0194 { 0195 if (! d->init()) { 0196 d->handleException(); 0197 return; 0198 } 0199 0200 QString scriptCode = action()->code(); 0201 if (scriptCode.startsWith(QLatin1String("#!"))) { // remove optional shebang-line 0202 scriptCode.remove(0, scriptCode.indexOf('\n')); 0203 } 0204 0205 const QString fileName = action()->file().isEmpty() ? action()->name() : action()->file(); 0206 0207 //krossdebug( QString("EcmaScript::execute fileName=%1 scriptCode=\n%2").arg(fileName).arg(scriptCode) ); 0208 0209 Q_ASSERT(d->m_engine); 0210 0211 if (d->m_engine->hasUncaughtException()) { 0212 d->m_engine->clearExceptions(); 0213 } 0214 0215 d->m_engine->evaluate(scriptCode, fileName); 0216 0217 if (d->m_engine->hasUncaughtException()) { 0218 d->handleException(); 0219 return; 0220 } 0221 0222 //d->connectFunctions( &Manager::self() ); 0223 d->connectFunctions(action()); 0224 } 0225 0226 QStringList EcmaScript::functionNames() 0227 { 0228 if (! d->m_engine && ! d->init()) { 0229 d->handleException(); 0230 return QStringList(); 0231 } 0232 QStringList names; 0233 QScriptValueIterator it(d->m_engine->globalObject()); 0234 while (it.hasNext()) { 0235 it.next(); 0236 if (it.value().isFunction()) { 0237 names << it.name(); 0238 } 0239 } 0240 return names; 0241 } 0242 0243 QVariant EcmaScript::callFunction(const QString &name, const QVariantList &args) 0244 { 0245 if (! d->m_engine && ! d->init()) { 0246 d->handleException(); 0247 return QVariant(); 0248 } 0249 0250 QScriptValue obj = d->m_engine->globalObject(); 0251 QScriptValue function = obj.property(name); 0252 if (! function.isFunction()) { 0253 QString err = QString("No such function '%1'").arg(name); 0254 qCWarning(KROSS_QTSCRIPT_LOG) << "EcmaScript::callFunction " << err; 0255 setError(err); 0256 return QVariant(); 0257 } 0258 0259 QScriptValueList arguments; 0260 foreach (const QVariant &v, args) { 0261 arguments << d->m_engine->toScriptValue(v); 0262 } 0263 QScriptValue result = function.call(obj, arguments); 0264 if (d->m_engine->hasUncaughtException()) { 0265 d->handleException(); 0266 return QVariant(); 0267 } 0268 return result.toVariant(); 0269 } 0270 0271 QVariant EcmaScript::evaluate(const QByteArray &code) 0272 { 0273 if (! d->m_engine && ! d->init()) { 0274 d->handleException(); 0275 return QVariant(); 0276 } 0277 0278 QScriptValue result = d->m_engine->evaluate(code); 0279 if (d->m_engine->hasUncaughtException()) { 0280 d->handleException(); 0281 return QVariant(); 0282 } 0283 return result.toVariant(); 0284 } 0285 0286 QObject *EcmaScript::engine() const 0287 { 0288 return d->m_engine; 0289 } 0290 0291 #include "moc_script.cpp"