File indexing completed on 2024-04-21 15:02:59

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"