diff options
Diffstat (limited to 'src/declarative/qml/script/qmlbasicscript.cpp')
-rw-r--r-- | src/declarative/qml/script/qmlbasicscript.cpp | 923 |
1 files changed, 923 insertions, 0 deletions
diff --git a/src/declarative/qml/script/qmlbasicscript.cpp b/src/declarative/qml/script/qmlbasicscript.cpp new file mode 100644 index 0000000..4f40016 --- /dev/null +++ b/src/declarative/qml/script/qmlbasicscript.cpp @@ -0,0 +1,923 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the $MODULE$ of the Qt Toolkit. +** +** $TROLLTECH_DUAL_LICENSE$ +** +****************************************************************************/ + +#include "qmlbasicscript.h" +#include "qmlbasicscript_p.h" +#include "lexer.h" +#include <QColor> +#include <QDebug> +#include <private/qmlengine_p.h> +#include <private/qmlcontext_p.h> +#include <QStack> +#include <qfxperf.h> +#include <private/qmlrefcount_p.h> + + +QT_BEGIN_NAMESPACE +DEFINE_BOOL_CONFIG_OPTION(scriptWarnings, QML_SCRIPT_WARNINGS); + +class QmlBasicScriptPrivate +{ +public: + enum Flags { OwnData = 0x00000001 }; + + int size; + int stateSize; + int instructionCount; + int exprLen; + + ScriptInstruction *instructions() const { return (ScriptInstruction *)((char *)this + sizeof(QmlBasicScriptPrivate)); } + + const char *expr() const + { + return (const char *)(instructions() + instructionCount); + } + + const char *data() const + { + return (const char *)(instructions() + instructionCount) + exprLen + 1; + } + + static unsigned int alignRound(int s) + { + if(s % 4) + s += 4 - (s % 4); + return s; + } +}; + +QDebug operator<<(QDebug lhs, const QmlBasicScriptNodeCache &rhs) +{ + switch(rhs.type) { + case QmlBasicScriptNodeCache::Invalid: + lhs << "Invalid"; + break; + case QmlBasicScriptNodeCache::Core: + lhs << "Core" << rhs.object << rhs.core; + break; + case QmlBasicScriptNodeCache::Attached: + lhs << "Attached" << rhs.object << rhs.attached; + break; + case QmlBasicScriptNodeCache::Signal: + lhs << "Signal" << rhs.object << rhs.core; + break; + case QmlBasicScriptNodeCache::SignalProperty: + lhs << "SignalProperty" << rhs.object << rhs.core; + break; + case QmlBasicScriptNodeCache::Explicit: + lhs << "Explicit" << rhs.object; + break; + case QmlBasicScriptNodeCache::Variant: + lhs << "Variant" << rhs.context; + break; + case QmlBasicScriptNodeCache::ScriptValue: + lhs << "ScriptValue" << rhs.context; + break; + } + + return lhs; +} + +void QmlBasicScriptNodeCache::clear() +{ + object = 0; + metaObject = 0; + type = Invalid; +} + +static QVariant toObjectOrVariant(const QVariant &v) +{ + switch(v.type()) { + case QVariant::String: + case QVariant::UInt: + case QVariant::Int: + case 135: + case QVariant::Double: + case QVariant::Color: + case QVariant::Bool: + default: + return v; + case QVariant::UserType: + { + QObject *o = QmlMetaType::toQObject(v); + if (o) + return qVariantFromValue(o); + else + return v; + } + break; + } +} + +static QVariant fetch_value(QObject *o, int idx, int type) +{ + switch(type) { + case QVariant::String: + { + QString val; + void *args[] = { &val, 0 }; + QMetaObject::metacall(o, QMetaObject::ReadProperty, idx, args); + return QVariant(val); + } + break; + case QVariant::UInt: + { + uint val; + void *args[] = { &val, 0 }; + QMetaObject::metacall(o, QMetaObject::ReadProperty, idx, args); + return QVariant(val); + } + break; + case QVariant::Int: + { + int val; + void *args[] = { &val, 0 }; + QMetaObject::metacall(o, QMetaObject::ReadProperty, idx, args); + return QVariant(val); + } + break; + case 135: + case QVariant::Double: + { + qreal val; + void *args[] = { &val, 0 }; + QMetaObject::metacall(o, QMetaObject::ReadProperty, idx, args); + return QVariant(val); + } + break; + case QVariant::Color: + { + QColor val; + void *args[] = { &val, 0 }; + QMetaObject::metacall(o, QMetaObject::ReadProperty, idx, args); + return QVariant(val); + } + break; + case QVariant::Bool: + { + bool val; + void *args[] = { &val, 0 }; + QMetaObject::metacall(o, QMetaObject::ReadProperty, idx, args); + return QVariant(val); + } + break; + default: + { + if(QmlMetaType::isObject(type)) { + // NOTE: This assumes a cast to QObject does not alter the + // object pointer + QObject *val = 0; + void *args[] = { &val, 0 }; + QMetaObject::metacall(o, QMetaObject::ReadProperty, idx, args); + return QVariant::fromValue(val); + } else { + QVariant var = o->metaObject()->property(idx).read(o); + if(QmlMetaType::isObject(var.userType())) { + QObject *obj = 0; + obj = *(QObject **)var.data(); + var = QVariant::fromValue(obj); + } + return var; + } + } + break; + }; +} + +QVariant QmlBasicScriptNodeCache::value(const char *name) const +{ + //QFxPerfTimer<QFxPerf::BasicScriptValue> pt; + switch(type) { + case Invalid: + break; + case Core: + return fetch_value(object, core, coreType); + break; + case Attached: + return qVariantFromValue(static_cast<QObject *>(attached)); + break; + case Signal: + // XXX + Q_ASSERT(!"Not implemented"); + break; + case SignalProperty: + break; + case Explicit: + return qVariantFromValue(object); + break; + case Variant: + return toObjectOrVariant(context->variantProperties[QLatin1String(name)]); + break; + case ScriptValue: + return qVariantFromValue(context->properties[QLatin1String(name)]); + break; + }; + return QVariant(); +} + +struct QmlBasicScriptCompiler +{ + QmlBasicScriptCompiler() + : script(0), stateSize(0), src(0), idx(0) {} + QmlBasicScript *script; + QList<LexerToken> tokens; + int stateSize; + const char *src; + int idx; + + bool compile(); + bool compileExpr(); + + bool parseFetch(); + bool parseName(); + bool parseConstant(); + void skipWhitespace(); + + QByteArray data; + QList<ScriptInstruction> bytecode; + + QByteArray string(int, int); + Token token() const; + bool atEnd() const; + void adv(); + int index() const; +}; + +/*! + \class QmlBasicScript + \brief The QmlBasicScript class provides a fast implementation of a limited subset of JavaScript bindings. + + QmlBasicScript instances are used to accelerate binding. Instead of using + the slower, fully fledged JavaScript engine, many simple bindings can be + evaluated using the QmlBasicScript engine. + + To see if the QmlBasicScript engine can handle a binding, call compile() + and check the return value, or isValid() afterwards. + + To evaluate the binding, the QmlBasicScript instance needs some memory in + which to cache state. This may be allocated by calling newScriptState() + and destroyed by calling deleteScriptState(). The state data is then passed + to the run() method when evaluating the binding. + + To further accelerate binding, QmlBasicScript can return a precompiled + version of itself that can be saved for future use. Call compileData() to + get an opaque pointer to the compiled state, and compileDataSize() for the + size of this data in bytes. This data can be saved and passed to future + instances of the QmlBasicScript constructor. The initial copy of compile + data is owned by the QmlBindScript instance on which compile() was called. +*/ + +/*! + Create a new QmlBasicScript instance. +*/ +QmlBasicScript::QmlBasicScript() +: flags(0), d(0), rc(0) +{ +} + +/*! + Create a new QmlBasicScript instance from saved \a data. + + \a data \b must be data previously acquired from calling compileData() on a + previously created QmlBasicScript instance. Any other data will almost + certainly cause the QmlBasicScript engine to crash. + + \a data must continue to be valid throughout the QmlBasicScript instance + life. It does not assume ownership of the memory. + + If \a owner is set, it is referenced on creation and dereferenced on + destruction of this instance. +*/ +QmlBasicScript::QmlBasicScript(const char *data, QmlRefCount *owner) +: flags(0), d((QmlBasicScriptPrivate *)data), rc(owner) +{ + if(rc) rc->addref(); +} + +/*! + Return the text of the script expression. + */ +QByteArray QmlBasicScript::expression() const +{ + if(!d) + return QByteArray(); + else + return QByteArray(d->expr()); +} + +/*! + Destroy the script instance. +*/ +QmlBasicScript::~QmlBasicScript() +{ + if(flags & QmlBasicScriptPrivate::OwnData) + free(d); + if(rc) rc->release(); + d = 0; + rc = 0; +} + +/*! + Clear this script. The object will then be in its initial state, as though + it were freshly constructed with default constructor. +*/ +void QmlBasicScript::clear() +{ + if(flags & QmlBasicScriptPrivate::OwnData) + free(d); + if(rc) rc->release(); + d = 0; + rc = 0; + flags = 0; +} + +/*! + Return the script state memory for this script instance. This memory should + only be destroyed by calling deleteScriptState(). + */ +void *QmlBasicScript::newScriptState() +{ + if(!d) { + return 0; + } else { + void *rv = ::malloc(d->stateSize * sizeof(QmlBasicScriptNodeCache)); + ::memset(rv, 0, d->stateSize * sizeof(QmlBasicScriptNodeCache)); + return rv; + } +} + +/*! + Delete the \a data previously allocated by newScriptState(). + */ +void QmlBasicScript::deleteScriptState(void *data) +{ + if(!data) return; + Q_ASSERT(d); + clearCache(data); + free(data); +} + +/*! + Dump the script instructions to stderr for debugging. + */ +void QmlBasicScript::dump() +{ + if(!d) + return; + + qWarning() << d->instructionCount << "instructions:"; + const char *data = d->data(); + for(int ii = 0; ii < d->instructionCount; ++ii) { + const ScriptInstruction &instr = d->instructions()[ii]; + + switch(instr.type) { + case ScriptInstruction::Load: + qWarning().nospace() << "LOAD\t\t" << instr.fetch.idx << "\t\t" + << QByteArray(data + instr.fetch.idx); + break; + case ScriptInstruction::Fetch: + qWarning().nospace() << "FETCH\t\t" << instr.fetch.idx << "\t\t" + << QByteArray(data + instr.fetch.idx); + break; + case ScriptInstruction::Add: + qWarning().nospace() << "ADD"; + break; + case ScriptInstruction::Subtract: + qWarning().nospace() << "SUBTRACT"; + break; + case ScriptInstruction::Multiply: + qWarning().nospace() << "MULTIPLY"; + break; + case ScriptInstruction::Equals: + qWarning().nospace() << "EQUALS"; + break; + case ScriptInstruction::Int: + qWarning().nospace() << "INT\t\t" << instr.integer.value; + break; + case ScriptInstruction::Bool: + qWarning().nospace() << "BOOL\t\t" << instr.boolean.value; + break; + default: + qWarning().nospace() << "UNKNOWN"; + break; + } + } +} + +/*! + Return true if this is a valid script binding, otherwise returns false. + */ +bool QmlBasicScript::isValid() const +{ + return d != 0; +} + +/*! + Compile \a src and return true if the compilation is successful, otherwise + returns false. + */ +bool QmlBasicScript::compile(const QByteArray &src) +{ + bool rv = compile(src.constData()); + return rv; +} + +/*! + \overload + + Compile \a src and return true if the compilation is successful, otherwise + returns false. + */ +bool QmlBasicScript::compile(const char *src) +{ + if(!src) return false; + + QmlBasicScriptCompiler bsc; + bsc.script = this; + bsc.tokens = tokenize(src); + bsc.src = src; + // dumpTokens(src, bsc.tokens); + + if(d) { + if(flags & QmlBasicScriptPrivate::OwnData) + free(d); + d = 0; + flags = 0; + } + + if(bsc.compile()) { + int len = ::strlen(src); + flags = QmlBasicScriptPrivate::OwnData; + int size = sizeof(QmlBasicScriptPrivate) + + bsc.bytecode.count() * sizeof(ScriptInstruction) + + QmlBasicScriptPrivate::alignRound(bsc.data.count() + len + 1); + d = (QmlBasicScriptPrivate *) malloc(size); + d->size = size; + d->stateSize = bsc.stateSize; + d->instructionCount = bsc.bytecode.count(); + d->exprLen = len; + ::memcpy((char *)d->expr(), src, len + 1); + for(int ii = 0; ii < d->instructionCount; ++ii) + d->instructions()[ii] = bsc.bytecode.at(ii); + ::memcpy((char *)d->data(), bsc.data.constData(), bsc.data.count()); + } + + return d != 0; +} + +void QmlBasicScriptCompiler::skipWhitespace() +{ + while(idx < tokens.count() && tokens.at(idx).token == WHITESPACE) + ++idx; +} + +bool QmlBasicScriptCompiler::compile() +{ + if(!compileExpr()) + return false; + + skipWhitespace(); + + if(atEnd()) + return true; + + int t = token(); + if(t != AND) + return false; + + adv(); + skipWhitespace(); + if(!compileExpr()) + return false; + + ScriptInstruction instr; + instr.type = ScriptInstruction::And; + bytecode.append(instr); + + skipWhitespace(); + + return atEnd(); +} + +bool QmlBasicScriptCompiler::compileExpr() +{ + /* + EXPRESSION := <NAME><OPERATOR>[<CONSTANT>|<NAME>] + */ + + if(!parseName()) + return false; + + skipWhitespace(); + + if(atEnd()) + return true; + + int t = token(); + switch(t) { + case PLUS: + case MINUS: + /* + case LANGLE: + case RANGLE: + */ + case STAR: + case EQUALS: + break; + default: + return true; + } + adv(); + + skipWhitespace(); + + if(!parseConstant() && + !parseName()) + return false; + + ScriptInstruction instr; + switch(t) { + case PLUS: + instr.type = ScriptInstruction::Add; + break; + case MINUS: + instr.type = ScriptInstruction::Subtract; + break; + case STAR: + instr.type = ScriptInstruction::Multiply; + break; + case EQUALS: + instr.type = ScriptInstruction::Equals; + break; + default: + break; + } + bytecode.append(instr); + + skipWhitespace(); + + return true; +} + +bool QmlBasicScriptCompiler::parseName() +{ + skipWhitespace(); + + bool named = false; + bool seenchar = false; + bool seendot = false; + int namestart = -1; + bool pushed = false; + while(!atEnd()) { + int t = token(); + if(t == CHARACTER) { + named = true; + seendot = false; + seenchar = true; + namestart = index(); + adv(); + } else if(t == DIGIT) { + if(!seenchar) break; + adv(); + } else if(t == DOT) { + seendot = true; + if(namestart == -1) + break; + + seenchar = false; + QByteArray name = string(namestart, index() - 1); + int nref = data.count(); + data.append(name); + data.append('\0'); + ScriptInstruction instr; + if(pushed) + instr.type = ScriptInstruction::Fetch; + else + instr.type = ScriptInstruction::Load; + pushed = true; + instr.fetch.idx = nref; + bytecode.append(instr); + ++stateSize; + namestart = -1; + adv(); + } else { + break; + } + } + + if(namestart != -1) { + QByteArray name = string(namestart, index() - 1); + int nref = data.count(); + data.append(name); + data.append('\0'); + ScriptInstruction instr; + if(pushed) + instr.type = ScriptInstruction::Fetch; + else + instr.type = ScriptInstruction::Load; + pushed = true; + instr.fetch.idx = nref; + bytecode.append(instr); + ++stateSize; + } + + if(seendot) + return false; + else + return named; +} + +bool QmlBasicScriptCompiler::parseConstant() +{ + switch(token()) { + case DIGIT: + { + ScriptInstruction instr; + instr.type = ScriptInstruction::Int; + instr.integer.value = string(index(), index()).toUInt(); + bytecode.append(instr); + adv(); + } + break; + case TOKEN_TRUE: + case TOKEN_FALSE: + { + ScriptInstruction instr; + instr.type = ScriptInstruction::Bool; + instr.boolean.value = (token() == TOKEN_TRUE); + bytecode.append(instr); + adv(); + } + break; + + default: + return false; + } + + return true; +} + +bool QmlBasicScriptCompiler::atEnd() const +{ + return idx >= tokens.count(); +} + +Token QmlBasicScriptCompiler::token() const +{ + return tokens.at(idx).token; +} + +void QmlBasicScriptCompiler::adv() +{ + ++idx; +} + +int QmlBasicScriptCompiler::index() const +{ + return idx; +} + +QByteArray QmlBasicScriptCompiler::string(int from, int to) +{ + QByteArray rv; + for(int ii = from; ii <= to; ++ii) { + const LexerToken &token = tokens.at(ii); + rv.append(QByteArray(src + token.start, token.end - token.start + 1)); + } + return rv; +} + +/*! + \internal +*/ +void QmlBasicScript::clearCache(void *voidCache) +{ + QmlBasicScriptNodeCache *dataCache = + reinterpret_cast<QmlBasicScriptNodeCache *>(voidCache); + + for(int ii = 0; ii < d->stateSize; ++ii) { + if(!dataCache[ii].isCore() && !dataCache[ii].isExplicit() && + dataCache[ii].object) { + QMetaObject::removeGuard(&dataCache[ii].object); + dataCache[ii].object = 0; + } + dataCache[ii].clear(); + } +} + +void QmlBasicScript::guard(QmlBasicScriptNodeCache &n) +{ + if(n.object) { + if(n.isExplicit()) { + } else if(n.isCore()) { + n.metaObject = + n.object->metaObject(); + } else { + QMetaObject::addGuard(&n.object); + } + } +} + +bool QmlBasicScript::valid(QmlBasicScriptNodeCache &n, QObject *obj) +{ + return n.object == obj && + (!n.isCore() || obj->metaObject() == n.metaObject); +} + + +/*! + \enum QmlBasicScript::CacheState + \value NoChange The query has not change. Any previous monitoring is still + valid. + \value Incremental The query has been incrementally changed. Any previous + monitoring is still valid, but needs to have the fresh properties added to + it. + \value Reset The entire query has been reset from the beginning. Any previous + monitoring is now invalid. +*/ + +/*! + Run the script in \a context and return the result. \a voidCache should + contain state memory previously acquired from newScript. + */ +QVariant QmlBasicScript::run(QmlContext *context, void *voidCache, CacheState *cached) +{ + if(!isValid()) + return QVariant(); + + QmlBasicScriptNodeCache *dataCache = + reinterpret_cast<QmlBasicScriptNodeCache *>(voidCache); + int dataCacheItem; + QStack<QVariant> stack; + + bool resetting = false; + bool hasReset = false; + + const char *data = d->data(); + + if(dataCache[0].type == QmlBasicScriptNodeCache::Invalid) { + resetting = true; + hasReset = true; + } + + CacheState state = NoChange; + + dataCacheItem = 0; + for(int idx = 0; idx < d->instructionCount; ++idx) { + const ScriptInstruction &instr = d->instructions()[idx]; + + switch(instr.type) { + case ScriptInstruction::Load: // either an object or a property + case ScriptInstruction::Fetch: // can only be a property + { + const char *id = data + instr.fetch.idx; + QmlBasicScriptNodeCache &n = dataCache[dataCacheItem]; + + if(instr.type == ScriptInstruction::Load) { + + if(n.type == QmlBasicScriptNodeCache::Invalid) { + context->engine()->d_func()->loadCache(n, QLatin1String(id), static_cast<QmlContextPrivate*>(context->d_ptr)); + state = Incremental; + } + + } else { // instr.type == ScriptInstruction::Fetch + + QVariant o = stack.pop(); + QObject *obj = qvariant_cast<QObject *>(o); + if(!obj) { + if(n.type == QmlBasicScriptNodeCache::Invalid) { + if(scriptWarnings()) + qWarning() << "QmlBasicScript: Unable to convert" << o; + *cached = state; + return QVariant(); + } else { + clearCache(dataCache); + *cached = Reset; + CacheState dummy; + return run(context, voidCache, &dummy); + } + } else if(n.type == QmlBasicScriptNodeCache::Invalid) { + context->engine()->d_func()->fetchCache(n, QLatin1String(id), obj); + guard(n); + state = Incremental; + } else if(!valid(n, obj)) { + clearCache(dataCache); + *cached = Reset; + CacheState dummy; + return run(context, voidCache, &dummy); + } + + } + + QVariant var = n.value(id); + stack.push(var); + ++dataCacheItem; + } + break; + case ScriptInstruction::Int: + stack.push(QVariant(instr.integer.value)); + break; + case ScriptInstruction::Bool: + stack.push(QVariant(instr.boolean.value)); + break; + case ScriptInstruction::Add: + { + QVariant rhs = stack.pop(); + QVariant lhs = stack.pop(); + + stack.push(rhs.toDouble() + lhs.toDouble()); + } + break; + case ScriptInstruction::Subtract: + { + QVariant rhs = stack.pop(); + QVariant lhs = stack.pop(); + + stack.push(lhs.toDouble() - rhs.toDouble()); + } + break; + case ScriptInstruction::Multiply: + { + QVariant rhs = stack.pop(); + QVariant lhs = stack.pop(); + + stack.push(rhs.toDouble() * lhs.toDouble()); + } + break; + case ScriptInstruction::Equals: + { + QVariant rhs = stack.pop(); + QVariant lhs = stack.pop(); + + stack.push(rhs == lhs); + } + break; + case ScriptInstruction::And: + { + QVariant rhs = stack.pop(); + QVariant lhs = stack.pop(); + + stack.push(rhs.toBool() && lhs.toBool()); + } + break; + default: + break; + } + } + + *cached = state; + + if(stack.isEmpty()) + return QVariant(); + else + return stack.top(); +} + +/*! + Return a pointer to the script's compile data, or null if there is no data. + */ +const char *QmlBasicScript::compileData() const +{ + return (const char *)d; +} + +/*! + Return the size of the script's compile data, or zero if there is no data. + The size will always be a multiple of 4. + */ +unsigned int QmlBasicScript::compileDataSize() const +{ + if(d) + return d->size; + else + return 0; +} + +bool QmlBasicScript::isSingleLoad() const +{ + if(!d) + return false; + + return d->instructionCount == 1 && + d->instructions()[0].type == ScriptInstruction::Load; +} + +QByteArray QmlBasicScript::singleLoadTarget() const +{ + if(!isSingleLoad()) + return QByteArray(); + + // We know there is one instruction and it is a load + return QByteArray(d->data() + d->instructions()[0].fetch.idx); +} + + +QT_END_NAMESPACE |