diff options
Diffstat (limited to 'src/qml/qml/qqmlscript.cpp')
-rw-r--r-- | src/qml/qml/qqmlscript.cpp | 1700 |
1 files changed, 1700 insertions, 0 deletions
diff --git a/src/qml/qml/qqmlscript.cpp b/src/qml/qml/qqmlscript.cpp new file mode 100644 index 0000000000..d1c2faa138 --- /dev/null +++ b/src/qml/qml/qqmlscript.cpp @@ -0,0 +1,1700 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlscript_p.h" + +#include "parser/qqmljsengine_p.h" +#include "parser/qqmljsparser_p.h" +#include "parser/qqmljslexer_p.h" +#include "parser/qqmljsmemorypool_p.h" +#include "parser/qqmljsastvisitor_p.h" +#include "parser/qqmljsast_p.h" +#include <private/qqmlrewrite_p.h> + +#include <QStack> +#include <QCoreApplication> +#include <QtDebug> + +QT_BEGIN_NAMESPACE + +using namespace QQmlJS; +using namespace QQmlScript; + +// +// Parser IR classes +// +QQmlScript::Object::Object() +: type(-1), idIndex(-1), metatype(0), synthCache(0), defaultProperty(0), parserStatusCast(-1), + componentCompileState(0), nextAliasingObject(0), nextIdObject(0) +{ +} + +QQmlScript::Object::~Object() +{ + if (synthCache) synthCache->release(); +} + +void Object::setBindingBit(int b) +{ + while (bindingBitmask.size() < 4 * (1 + b / 32)) + bindingBitmask.append(char(0)); + + quint32 *bits = (quint32 *)bindingBitmask.data(); + bits[b / 32] |= (1 << (b % 32)); +} + +const QMetaObject *Object::metaObject() const +{ + if (!metadata.isEmpty() && metatype) + return &extObject; + else + return metatype; +} + +QQmlScript::Property *Object::getDefaultProperty() +{ + if (!defaultProperty) { + defaultProperty = pool()->New<Property>(); + defaultProperty->parent = this; + } + return defaultProperty; +} + +void QQmlScript::Object::addValueProperty(Property *p) +{ + valueProperties.append(p); +} + +void QQmlScript::Object::addSignalProperty(Property *p) +{ + signalProperties.append(p); +} + +void QQmlScript::Object::addAttachedProperty(Property *p) +{ + attachedProperties.append(p); +} + +void QQmlScript::Object::addGroupedProperty(Property *p) +{ + groupedProperties.append(p); +} + +void QQmlScript::Object::addValueTypeProperty(Property *p) +{ + valueTypeProperties.append(p); +} + +void QQmlScript::Object::addScriptStringProperty(Property *p) +{ + scriptStringProperties.append(p); +} + +// This lookup is optimized for missing, and having to create a new property. +Property *QQmlScript::Object::getProperty(const QHashedStringRef &name, bool create) +{ + if (create) { + quint32 h = name.hash(); + if (propertiesHashField.testAndSet(h)) { + for (Property *p = properties.first(); p; p = properties.next(p)) { + if (p->name() == name) + return p; + } + } + + Property *property = pool()->New<Property>(); + property->parent = this; + property->_name = name; + property->isDefault = false; + properties.prepend(property); + return property; + } else { + for (Property *p = properties.first(); p; p = properties.next(p)) { + if (p->name() == name) + return p; + } + } + + return 0; +} + +Property *QQmlScript::Object::getProperty(const QStringRef &name, bool create) +{ + return getProperty(QHashedStringRef(name), create); +} + +Property *QQmlScript::Object::getProperty(const QString &name, bool create) +{ + for (Property *p = properties.first(); p; p = properties.next(p)) { + if (p->name() == name) + return p; + } + + if (create) { + Property *property = pool()->New<Property>(); + property->parent = this; + property->_name = QStringRef(pool()->NewString(name)); + propertiesHashField.testAndSet(property->_name.hash()); + property->isDefault = false; + properties.prepend(property); + return property; + } else { + return 0; + } +} + +QQmlScript::Object::DynamicProperty::DynamicProperty() +: isDefaultProperty(false), isReadOnly(false), type(Variant), defaultValue(0), nextProperty(0), + resolvedCustomTypeName(0) +{ +} + +QQmlScript::Object::DynamicSignal::DynamicSignal() +: nextSignal(0) +{ +} + +// Returns length in utf8 bytes +int QQmlScript::Object::DynamicSignal::parameterTypesLength() const +{ + int rv = 0; + for (int ii = 0; ii < parameterTypes.count(); ++ii) + rv += parameterTypes.at(ii).length(); + return rv; +} + +// Returns length in utf8 bytes +int QQmlScript::Object::DynamicSignal::parameterNamesLength() const +{ + int rv = 0; + for (int ii = 0; ii < parameterNames.count(); ++ii) + rv += parameterNames.at(ii).utf8length(); + return rv; +} + +QQmlScript::Object::DynamicSlot::DynamicSlot() +: nextSlot(0) +{ +} + +int QQmlScript::Object::DynamicSlot::parameterNamesLength() const +{ + int rv = 0; + for (int ii = 0; ii < parameterNames.count(); ++ii) + rv += parameterNames.at(ii).length(); + return rv; +} + +QQmlScript::Property::Property() +: parent(0), type(0), index(-1), value(0), isDefault(true), isDeferred(false), + isValueTypeSubProperty(false), isAlias(false), isReadOnlyDeclaration(false), + scriptStringScope(-1), nextMainProperty(0), nextProperty(0) +{ +} + +QQmlScript::Object *QQmlScript::Property::getValue(const LocationSpan &l) +{ + if (!value) { value = pool()->New<Object>(); value->location = l; } + return value; +} + +void QQmlScript::Property::addValue(Value *v) +{ + values.append(v); +} + +void QQmlScript::Property::addOnValue(Value *v) +{ + onValues.append(v); +} + +bool QQmlScript::Property::isEmpty() const +{ + return !value && values.isEmpty() && onValues.isEmpty(); +} + +QQmlScript::Value::Value() +: type(Unknown), object(0), bindingReference(0), nextValue(0) +{ +} + +QQmlScript::Variant::Variant() +: t(Invalid) +{ +} + +QQmlScript::Variant::Variant(const Variant &o) +: t(o.t), d(o.d), asWritten(o.asWritten) +{ +} + +QQmlScript::Variant::Variant(bool v) +: t(Boolean), b(v) +{ +} + +QQmlScript::Variant::Variant(double v, const QStringRef &asWritten) +: t(Number), d(v), asWritten(asWritten) +{ +} + +QQmlScript::Variant::Variant(QQmlJS::AST::StringLiteral *v) +: t(String), l(v) +{ +} + +QQmlScript::Variant::Variant(const QStringRef &asWritten, QQmlJS::AST::Node *n) +: t(Script), n(n), asWritten(asWritten) +{ +} + +QQmlScript::Variant &QQmlScript::Variant::operator=(const Variant &o) +{ + t = o.t; + d = o.d; + asWritten = o.asWritten; + return *this; +} + +QQmlScript::Variant::Type QQmlScript::Variant::type() const +{ + return t; +} + +bool QQmlScript::Variant::asBoolean() const +{ + return b; +} + +QString QQmlScript::Variant::asString() const +{ + if (t == String) { + return l->value.toString(); + } else { + return asWritten.toString(); + } +} + +double QQmlScript::Variant::asNumber() const +{ + return d; +} + +//reverse of Lexer::singleEscape() +QString escapedString(const QString &string) +{ + QString tmp = QLatin1String("\""); + for (int i = 0; i < string.length(); ++i) { + const QChar &c = string.at(i); + switch(c.unicode()) { + case 0x08: + tmp += QLatin1String("\\b"); + break; + case 0x09: + tmp += QLatin1String("\\t"); + break; + case 0x0A: + tmp += QLatin1String("\\n"); + break; + case 0x0B: + tmp += QLatin1String("\\v"); + break; + case 0x0C: + tmp += QLatin1String("\\f"); + break; + case 0x0D: + tmp += QLatin1String("\\r"); + break; + case 0x22: + tmp += QLatin1String("\\\""); + break; + case 0x27: + tmp += QLatin1String("\\\'"); + break; + case 0x5C: + tmp += QLatin1String("\\\\"); + break; + default: + tmp += c; + break; + } + } + tmp += QLatin1Char('\"'); + return tmp; +} + +QString QQmlScript::Variant::asScript() const +{ + switch(type()) { + default: + case Invalid: + return QString(); + case Boolean: + return b?QLatin1String("true"):QLatin1String("false"); + case Number: + if (asWritten.isEmpty()) + return QString::number(d); + else + return asWritten.toString(); + case String: + return escapedString(asString()); + case Script: + if (AST::IdentifierExpression *i = AST::cast<AST::IdentifierExpression *>(n)) { + return i->name.toString(); + } else + return asWritten.toString(); + } +} + +QQmlJS::AST::Node *QQmlScript::Variant::asAST() const +{ + if (type() == Script) + return n; + else + return 0; +} + +bool QQmlScript::Variant::isStringList() const +{ + if (isString()) + return true; + + if (type() != Script || !n) + return false; + + AST::ArrayLiteral *array = AST::cast<AST::ArrayLiteral *>(n); + if (!array) + return false; + + AST::ElementList *elements = array->elements; + + while (elements) { + + if (!AST::cast<AST::StringLiteral *>(elements->expression)) + return false; + + elements = elements->next; + } + + return true; +} + +QStringList QQmlScript::Variant::asStringList() const +{ + QStringList rv; + if (isString()) { + rv << asString(); + return rv; + } + + AST::ArrayLiteral *array = AST::cast<AST::ArrayLiteral *>(n); + if (!array) + return rv; + + AST::ElementList *elements = array->elements; + while (elements) { + + AST::StringLiteral *string = AST::cast<AST::StringLiteral *>(elements->expression); + if (!string) + return QStringList(); + rv.append(string->value.toString()); + + elements = elements->next; + } + + return rv; +} + +// +// Actual parser classes +// +void QQmlScript::Import::extractVersion(int *maj, int *min) const +{ + *maj = -1; *min = -1; + + if (!version.isEmpty()) { + int dot = version.indexOf(QLatin1Char('.')); + if (dot < 0) { + *maj = version.toInt(); + *min = 0; + } else { + *maj = version.left(dot).toInt(); + *min = version.mid(dot+1).toInt(); + } + } +} + +namespace { + +class ProcessAST: protected AST::Visitor +{ + struct State { + State() : object(0), property(0) {} + State(QQmlScript::Object *o) : object(o), property(0) {} + State(QQmlScript::Object *o, Property *p) : object(o), property(p) {} + + QQmlScript::Object *object; + Property *property; + }; + + struct StateStack : public QStack<State> + { + void pushObject(QQmlScript::Object *obj) + { + push(State(obj)); + } + + void pushProperty(const QString &name, const LocationSpan &location) + { + const State &state = top(); + if (state.property) { + State s(state.property->getValue(location), + state.property->getValue(location)->getProperty(name)); + s.property->location = location; + push(s); + } else { + State s(state.object, state.object->getProperty(name)); + + s.property->location = location; + push(s); + } + } + + void pushProperty(const QStringRef &name, const LocationSpan &location) + { + const State &state = top(); + if (state.property) { + State s(state.property->getValue(location), + state.property->getValue(location)->getProperty(name)); + s.property->location = location; + push(s); + } else { + State s(state.object, state.object->getProperty(name)); + + s.property->location = location; + push(s); + } + } + }; + +public: + ProcessAST(QQmlScript::Parser *parser); + virtual ~ProcessAST(); + + void operator()(const QString &code, AST::Node *node); + +protected: + + QQmlScript::Object *defineObjectBinding(AST::UiQualifiedId *propertyName, bool onAssignment, + const QString &objectType, + AST::SourceLocation typeLocation, + LocationSpan location, + AST::UiObjectInitializer *initializer = 0); + + QQmlScript::Variant getVariant(AST::Statement *stmt); + QQmlScript::Variant getVariant(AST::ExpressionNode *expr); + + LocationSpan location(AST::SourceLocation start, AST::SourceLocation end); + LocationSpan location(AST::UiQualifiedId *); + + using AST::Visitor::visit; + using AST::Visitor::endVisit; + + virtual bool visit(AST::UiProgram *node); + virtual bool visit(AST::UiImport *node); + virtual bool visit(AST::UiObjectDefinition *node); + virtual bool visit(AST::UiPublicMember *node); + virtual bool visit(AST::UiObjectBinding *node); + + virtual bool visit(AST::UiScriptBinding *node); + virtual bool visit(AST::UiArrayBinding *node); + virtual bool visit(AST::UiSourceElement *node); + + void accept(AST::Node *node); + + QString asString(AST::UiQualifiedId *node) const; + + const State state() const; + QQmlScript::Object *currentObject() const; + Property *currentProperty() const; + + QString qualifiedNameId() const; + + QString textAt(const AST::SourceLocation &loc) const + { return _contents->mid(loc.offset, loc.length); } + + QStringRef textRefAt(const AST::SourceLocation &loc) const + { return QStringRef(_contents, loc.offset, loc.length); } + + QString textAt(const AST::SourceLocation &first, + const AST::SourceLocation &last) const + { return _contents->mid(first.offset, last.offset + last.length - first.offset); } + + QStringRef textRefAt(const AST::SourceLocation &first, + const AST::SourceLocation &last) const + { return QStringRef(_contents, first.offset, last.offset + last.length - first.offset); } + + QString asString(AST::ExpressionNode *expr) + { + if (! expr) + return QString(); + + return textAt(expr->firstSourceLocation(), expr->lastSourceLocation()); + } + + QStringRef asStringRef(AST::ExpressionNode *expr) + { + if (! expr) + return QStringRef(); + + return textRefAt(expr->firstSourceLocation(), expr->lastSourceLocation()); + } + + QString asString(AST::Statement *stmt) + { + if (! stmt) + return QString(); + + QString s = textAt(stmt->firstSourceLocation(), stmt->lastSourceLocation()); + s += QLatin1Char('\n'); + return s; + } + + QStringRef asStringRef(AST::Statement *stmt) + { + if (! stmt) + return QStringRef(); + + return textRefAt(stmt->firstSourceLocation(), stmt->lastSourceLocation()); + } + +private: + QQmlScript::Parser *_parser; + StateStack _stateStack; + QStringList _scope; + const QString *_contents; +}; + +ProcessAST::ProcessAST(QQmlScript::Parser *parser) + : _parser(parser) +{ +} + +ProcessAST::~ProcessAST() +{ +} + +void ProcessAST::operator()(const QString &code, AST::Node *node) +{ + _contents = &code; + accept(node); +} + +void ProcessAST::accept(AST::Node *node) +{ + AST::Node::acceptChild(node, this); +} + +const ProcessAST::State ProcessAST::state() const +{ + if (_stateStack.isEmpty()) + return State(); + + return _stateStack.back(); +} + +QQmlScript::Object *ProcessAST::currentObject() const +{ + return state().object; +} + +Property *ProcessAST::currentProperty() const +{ + return state().property; +} + +QString ProcessAST::qualifiedNameId() const +{ + return _scope.join(QLatin1String("/")); +} + +QString ProcessAST::asString(AST::UiQualifiedId *node) const +{ + QString s; + + for (AST::UiQualifiedId *it = node; it; it = it->next) { + s.append(it->name.toString()); + + if (it->next) + s.append(QLatin1Char('.')); + } + + return s; +} + +QQmlScript::Object * +ProcessAST::defineObjectBinding(AST::UiQualifiedId *propertyName, + bool onAssignment, + const QString &objectType, + AST::SourceLocation typeLocation, + LocationSpan location, + AST::UiObjectInitializer *initializer) +{ + int lastTypeDot = objectType.lastIndexOf(QLatin1Char('.')); + + // With no preceding qualification, first char is at (-1 + 1) == 0 + bool isType = !objectType.isEmpty() && objectType.at(lastTypeDot+1).isUpper(); + + int propertyCount = 0; + for (AST::UiQualifiedId *name = propertyName; name; name = name->next){ + ++propertyCount; + _stateStack.pushProperty(name->name, + this->location(name)); + } + + if (!onAssignment && propertyCount && currentProperty() && !currentProperty()->values.isEmpty()) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Property value set multiple times")); + error.setLine(this->location(propertyName).start.line); + error.setColumn(this->location(propertyName).start.column); + _parser->_errors << error; + return 0; + } + + if (!isType) { + + // Is the identifier qualified by a namespace? + int namespaceLength = 0; + if (lastTypeDot > 0) { + const QString qualifier(objectType.left(lastTypeDot)); + + for (int ii = 0; ii < _parser->_imports.count(); ++ii) { + const QQmlScript::Import &import = _parser->_imports.at(ii); + if (import.qualifier == qualifier) { + // The qualifier is a namespace - expect a type here + namespaceLength = qualifier.length() + 1; + break; + } + } + } + + if (propertyCount || !currentObject() || namespaceLength) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Expected type name")); + error.setLine(typeLocation.startLine); + error.setColumn(typeLocation.startColumn + namespaceLength); + _parser->_errors << error; + return 0; + } + + LocationSpan loc = ProcessAST::location(typeLocation, typeLocation); + if (propertyName) + loc = ProcessAST::location(propertyName); + + _stateStack.pushProperty(objectType, loc); + accept(initializer); + _stateStack.pop(); + + return 0; + + } else { + // Class + + QString resolvableObjectType = objectType; + if (lastTypeDot >= 0) + resolvableObjectType.replace(QLatin1Char('.'),QLatin1Char('/')); + + QQmlScript::Object *obj = _parser->_pool.New<QQmlScript::Object>(); + + QQmlScript::TypeReference *typeRef = _parser->findOrCreateType(resolvableObjectType); + obj->type = typeRef->id; + + typeRef->refObjects.append(obj); + + // XXX this doesn't do anything (_scope never builds up) + _scope.append(resolvableObjectType); + obj->typeName = qualifiedNameId(); + _scope.removeLast(); + + obj->location = location; + + if (propertyCount) { + Property *prop = currentProperty(); + QQmlScript::Value *v = _parser->_pool.New<QQmlScript::Value>(); + v->object = obj; + v->location = obj->location; + if (onAssignment) + prop->addOnValue(v); + else + prop->addValue(v); + + while (propertyCount--) + _stateStack.pop(); + + } else { + + if (! _parser->tree()) { + _parser->setTree(obj); + } else { + const State state = _stateStack.top(); + QQmlScript::Value *v = _parser->_pool.New<QQmlScript::Value>(); + v->object = obj; + v->location = obj->location; + if (state.property) { + state.property->addValue(v); + } else { + Property *defaultProp = state.object->getDefaultProperty(); + if (defaultProp->location.start.line == -1) { + defaultProp->location = v->location; + defaultProp->location.end = defaultProp->location.start; + defaultProp->location.range.length = 0; + } + defaultProp->addValue(v); + } + } + } + + _stateStack.pushObject(obj); + accept(initializer); + _stateStack.pop(); + + return obj; + } +} + +LocationSpan ProcessAST::location(AST::UiQualifiedId *id) +{ + return location(id->identifierToken, id->identifierToken); +} + +LocationSpan ProcessAST::location(AST::SourceLocation start, AST::SourceLocation end) +{ + LocationSpan rv; + rv.start.line = start.startLine; + rv.start.column = start.startColumn; + rv.end.line = end.startLine; + rv.end.column = end.startColumn + end.length - 1; + rv.range.offset = start.offset; + rv.range.length = end.offset + end.length - start.offset; + return rv; +} + +// UiProgram: UiImportListOpt UiObjectMemberList ; +bool ProcessAST::visit(AST::UiProgram *node) +{ + accept(node->imports); + accept(node->members->member); + return false; +} + +// UiImport: T_IMPORT T_STRING_LITERAL ; +bool ProcessAST::visit(AST::UiImport *node) +{ + QString uri; + QQmlScript::Import import; + + if (!node->fileName.isNull()) { + uri = node->fileName.toString(); + + if (uri.endsWith(QLatin1String(".js"))) { + import.type = QQmlScript::Import::Script; + } else { + import.type = QQmlScript::Import::File; + } + } else { + import.type = QQmlScript::Import::Library; + uri = asString(node->importUri); + } + + AST::SourceLocation startLoc = node->importToken; + AST::SourceLocation endLoc = node->semicolonToken; + + // Qualifier + if (!node->importId.isNull()) { + import.qualifier = node->importId.toString(); + if (!import.qualifier.at(0).isUpper()) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Invalid import qualifier ID")); + error.setLine(node->importIdToken.startLine); + error.setColumn(node->importIdToken.startColumn); + _parser->_errors << error; + return false; + } + if (import.qualifier == QLatin1String("Qt")) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Reserved name \"Qt\" cannot be used as an qualifier")); + error.setLine(node->importIdToken.startLine); + error.setColumn(node->importIdToken.startColumn); + _parser->_errors << error; + return false; + } + + // Check for script qualifier clashes + bool isScript = import.type == QQmlScript::Import::Script; + for (int ii = 0; ii < _parser->_imports.count(); ++ii) { + const QQmlScript::Import &other = _parser->_imports.at(ii); + bool otherIsScript = other.type == QQmlScript::Import::Script; + + if ((isScript || otherIsScript) && import.qualifier == other.qualifier) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Script import qualifiers must be unique.")); + error.setLine(node->importIdToken.startLine); + error.setColumn(node->importIdToken.startColumn); + _parser->_errors << error; + return false; + } + } + + } else if (import.type == QQmlScript::Import::Script) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Script import requires a qualifier")); + error.setLine(node->fileNameToken.startLine); + error.setColumn(node->fileNameToken.startColumn); + _parser->_errors << error; + return false; + } + + if (node->versionToken.isValid()) { + import.version = textAt(node->versionToken); + } else if (import.type == QQmlScript::Import::Library) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Library import requires a version")); + error.setLine(node->importIdToken.startLine); + error.setColumn(node->importIdToken.startColumn); + _parser->_errors << error; + return false; + } + + + import.location = location(startLoc, endLoc); + import.uri = uri; + + _parser->_imports << import; + + return false; +} + +bool ProcessAST::visit(AST::UiPublicMember *node) +{ + static const struct TypeNameToType { + const char *name; + int nameLength; + Object::DynamicProperty::Type type; + const char *qtName; + int qtNameLength; + } propTypeNameToTypes[] = { + { "int", strlen("int"), Object::DynamicProperty::Int, "int", strlen("int") }, + { "bool", strlen("bool"), Object::DynamicProperty::Bool, "bool", strlen("bool") }, + { "double", strlen("double"), Object::DynamicProperty::Real, "double", strlen("double") }, + { "real", strlen("real"), Object::DynamicProperty::Real, "qreal", strlen("qreal") }, + { "string", strlen("string"), Object::DynamicProperty::String, "QString", strlen("QString") }, + { "url", strlen("url"), Object::DynamicProperty::Url, "QUrl", strlen("QUrl") }, + { "color", strlen("color"), Object::DynamicProperty::Color, "QColor", strlen("QColor") }, + // Internally QTime, QDate and QDateTime are all supported. + // To be more consistent with JavaScript we expose only + // QDateTime as it matches closely with the Date JS type. + // We also call it "date" to match. + // { "time", strlen("time"), Object::DynamicProperty::Time, "QTime", strlen("QTime") }, + // { "date", strlen("date"), Object::DynamicProperty::Date, "QDate", strlen("QDate") }, + { "date", strlen("date"), Object::DynamicProperty::DateTime, "QDateTime", strlen("QDateTime") }, + { "variant", strlen("variant"), Object::DynamicProperty::Variant, "QVariant", strlen("QVariant") }, + { "var", strlen("var"), Object::DynamicProperty::Var, "QVariant", strlen("QVariant") } + }; + static const int propTypeNameToTypesCount = sizeof(propTypeNameToTypes) / + sizeof(propTypeNameToTypes[0]); + + if(node->type == AST::UiPublicMember::Signal) { + Object::DynamicSignal *signal = _parser->_pool.New<Object::DynamicSignal>(); + signal->name = node->name; + + AST::UiParameterList *p = node->parameters; + int paramLength = 0; + while (p) { paramLength++; p = p->next; } + p = node->parameters; + + if (paramLength) { + signal->parameterTypes = _parser->_pool.NewRawList<QHashedCStringRef>(paramLength); + signal->parameterNames = _parser->_pool.NewRawList<QHashedStringRef>(paramLength); + } + + int index = 0; + while (p) { + const QStringRef &memberType = p->type; + + const TypeNameToType *type = 0; + for(int typeIndex = 0; typeIndex < propTypeNameToTypesCount; ++typeIndex) { + const TypeNameToType *t = propTypeNameToTypes + typeIndex; + if (t->nameLength == memberType.length() && + QHashedString::compare(memberType.constData(), t->name, t->nameLength)) { + type = t; + break; + } + } + + if (!type) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Expected parameter type")); + error.setLine(node->typeToken.startLine); + error.setColumn(node->typeToken.startColumn); + _parser->_errors << error; + return false; + } + + signal->parameterTypes[index] = QHashedCStringRef(type->qtName, type->qtNameLength); + signal->parameterNames[index] = QHashedStringRef(p->name); + p = p->next; + index++; + } + + signal->location = location(node->typeToken, node->semicolonToken); + _stateStack.top().object->dynamicSignals.append(signal); + } else { + const QStringRef &memberType = node->memberType; + const QStringRef &name = node->name; + + bool typeFound = false; + Object::DynamicProperty::Type type; + + if ((unsigned)memberType.length() == strlen("alias") && + QHashedString::compare(memberType.constData(), "alias", strlen("alias"))) { + type = Object::DynamicProperty::Alias; + typeFound = true; + } + + for(int ii = 0; !typeFound && ii < propTypeNameToTypesCount; ++ii) { + const TypeNameToType *t = propTypeNameToTypes + ii; + if (t->nameLength == memberType.length() && + QHashedString::compare(memberType.constData(), t->name, t->nameLength)) { + type = t->type; + typeFound = true; + } + } + + if (!typeFound && memberType.at(0).isUpper()) { + const QStringRef &typeModifier = node->typeModifier; + + if (typeModifier.isEmpty()) { + type = Object::DynamicProperty::Custom; + } else if((unsigned)typeModifier.length() == strlen("list") && + QHashedString::compare(typeModifier.constData(), "list", strlen("list"))) { + type = Object::DynamicProperty::CustomList; + } else { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Invalid property type modifier")); + error.setLine(node->typeModifierToken.startLine); + error.setColumn(node->typeModifierToken.startColumn); + _parser->_errors << error; + return false; + } + typeFound = true; + } else if (!node->typeModifier.isNull()) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Unexpected property type modifier")); + error.setLine(node->typeModifierToken.startLine); + error.setColumn(node->typeModifierToken.startColumn); + _parser->_errors << error; + return false; + } + + if(!typeFound) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Expected property type")); + error.setLine(node->typeToken.startLine); + error.setColumn(node->typeToken.startColumn); + _parser->_errors << error; + return false; + } + + Object::DynamicProperty *property = _parser->_pool.New<Object::DynamicProperty>(); + property->isDefaultProperty = node->isDefaultMember; + property->isReadOnly = node->isReadonlyMember; + property->type = type; + property->nameLocation.line = node->identifierToken.startLine; + property->nameLocation.column = node->identifierToken.startColumn; + if (type >= Object::DynamicProperty::Custom) { + QQmlScript::TypeReference *typeRef = + _parser->findOrCreateType(memberType.toString()); + typeRef->refObjects.append(_stateStack.top().object); + property->customType = memberType; + } + + property->name = QHashedStringRef(name); + property->location = location(node->firstSourceLocation(), + node->lastSourceLocation()); + + if (node->statement) { // default value + property->defaultValue = _parser->_pool.New<Property>(); + property->defaultValue->parent = _stateStack.top().object; + property->defaultValue->location = + location(node->statement->firstSourceLocation(), + node->statement->lastSourceLocation()); + QQmlScript::Value *value = _parser->_pool.New<QQmlScript::Value>(); + value->location = location(node->statement->firstSourceLocation(), + node->statement->lastSourceLocation()); + value->value = getVariant(node->statement); + property->defaultValue->values.append(value); + } + + _stateStack.top().object->dynamicProperties.append(property); + + // process QML-like initializers (e.g. property Object o: Object {}) + accept(node->binding); + } + + return false; +} + + +// UiObjectMember: UiQualifiedId UiObjectInitializer ; +bool ProcessAST::visit(AST::UiObjectDefinition *node) +{ + LocationSpan l = location(node->firstSourceLocation(), + node->lastSourceLocation()); + + const QString objectType = asString(node->qualifiedTypeNameId); + const AST::SourceLocation typeLocation = node->qualifiedTypeNameId->identifierToken; + + defineObjectBinding(/*propertyName = */ 0, false, objectType, + typeLocation, l, node->initializer); + + return false; +} + + +// UiObjectMember: UiQualifiedId T_COLON UiQualifiedId UiObjectInitializer ; +bool ProcessAST::visit(AST::UiObjectBinding *node) +{ + LocationSpan l = location(node->qualifiedTypeNameId->identifierToken, + node->initializer->rbraceToken); + + const QString objectType = asString(node->qualifiedTypeNameId); + const AST::SourceLocation typeLocation = node->qualifiedTypeNameId->identifierToken; + + defineObjectBinding(node->qualifiedId, node->hasOnToken, objectType, + typeLocation, l, node->initializer); + + return false; +} + +QQmlScript::Variant ProcessAST::getVariant(AST::Statement *stmt) +{ + if (stmt) { + if (AST::ExpressionStatement *exprStmt = AST::cast<AST::ExpressionStatement *>(stmt)) + return getVariant(exprStmt->expression); + + return QQmlScript::Variant(asStringRef(stmt), stmt); + } + + return QQmlScript::Variant(); +} + +QQmlScript::Variant ProcessAST::getVariant(AST::ExpressionNode *expr) +{ + if (AST::StringLiteral *lit = AST::cast<AST::StringLiteral *>(expr)) { + return QQmlScript::Variant(lit); + } else if (expr->kind == AST::Node::Kind_TrueLiteral) { + return QQmlScript::Variant(true); + } else if (expr->kind == AST::Node::Kind_FalseLiteral) { + return QQmlScript::Variant(false); + } else if (AST::NumericLiteral *lit = AST::cast<AST::NumericLiteral *>(expr)) { + return QQmlScript::Variant(lit->value, asStringRef(expr)); + } else { + + if (AST::UnaryMinusExpression *unaryMinus = AST::cast<AST::UnaryMinusExpression *>(expr)) { + if (AST::NumericLiteral *lit = AST::cast<AST::NumericLiteral *>(unaryMinus->expression)) { + return QQmlScript::Variant(-lit->value, asStringRef(expr)); + } + } + + return QQmlScript::Variant(asStringRef(expr), expr); + } +} + + +// UiObjectMember: UiQualifiedId T_COLON Statement ; +bool ProcessAST::visit(AST::UiScriptBinding *node) +{ + int propertyCount = 0; + AST::UiQualifiedId *propertyName = node->qualifiedId; + for (AST::UiQualifiedId *name = propertyName; name; name = name->next){ + ++propertyCount; + _stateStack.pushProperty(name->name, + location(name)); + } + + Property *prop = currentProperty(); + + if (!prop->values.isEmpty()) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Property value set multiple times")); + error.setLine(this->location(propertyName).start.line); + error.setColumn(this->location(propertyName).start.column); + _parser->_errors << error; + return 0; + } + + QQmlScript::Variant primitive; + + if (AST::ExpressionStatement *stmt = AST::cast<AST::ExpressionStatement *>(node->statement)) { + primitive = getVariant(stmt->expression); + } else { // do binding + primitive = QQmlScript::Variant(asStringRef(node->statement), node->statement); + } + + prop->location.range.length = prop->location.range.offset + prop->location.range.length - node->qualifiedId->identifierToken.offset; + prop->location.range.offset = node->qualifiedId->identifierToken.offset; + QQmlScript::Value *v = _parser->_pool.New<QQmlScript::Value>(); + v->value = primitive; + v->location = location(node->statement->firstSourceLocation(), + node->statement->lastSourceLocation()); + + prop->addValue(v); + + while (propertyCount--) + _stateStack.pop(); + + return false; +} + +// UiObjectMember: UiQualifiedId T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET ; +bool ProcessAST::visit(AST::UiArrayBinding *node) +{ + int propertyCount = 0; + AST::UiQualifiedId *propertyName = node->qualifiedId; + for (AST::UiQualifiedId *name = propertyName; name; name = name->next){ + ++propertyCount; + _stateStack.pushProperty(name->name, + location(name)); + } + + Property* prop = currentProperty(); + + if (!prop->values.isEmpty()) { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","Property value set multiple times")); + error.setLine(this->location(propertyName).start.line); + error.setColumn(this->location(propertyName).start.column); + _parser->_errors << error; + return false; + } + + accept(node->members); + + // For the DOM, store the position of the T_LBRACKET upto the T_RBRACKET as the range: + prop->listValueRange.offset = node->lbracketToken.offset; + prop->listValueRange.length = node->rbracketToken.offset + node->rbracketToken.length - node->lbracketToken.offset; + + while (propertyCount--) + _stateStack.pop(); + + return false; +} + +bool ProcessAST::visit(AST::UiSourceElement *node) +{ + QQmlScript::Object *obj = currentObject(); + + if (AST::FunctionDeclaration *funDecl = AST::cast<AST::FunctionDeclaration *>(node->sourceElement)) { + + Object::DynamicSlot *slot = _parser->_pool.New<Object::DynamicSlot>(); + slot->location = location(funDecl->identifierToken, funDecl->lastSourceLocation()); + + AST::FormalParameterList *f = funDecl->formals; + while (f) { + slot->parameterNames << f->name.toUtf8(); + f = f->next; + } + + AST::SourceLocation loc = funDecl->rparenToken; + loc.offset = loc.end(); + loc.startColumn += 1; + QString body = textAt(loc, funDecl->rbraceToken); + slot->name = funDecl->name; + slot->body = body; + obj->dynamicSlots.append(slot); + + } else { + QQmlError error; + error.setDescription(QCoreApplication::translate("QQmlParser","JavaScript declaration outside Script element")); + error.setLine(node->firstSourceLocation().startLine); + error.setColumn(node->firstSourceLocation().startColumn); + _parser->_errors << error; + } + return false; +} + +} // end of anonymous namespace + + +QQmlScript::Parser::Parser() +: root(0), data(0) +{ + +} + +QQmlScript::Parser::~Parser() +{ + clear(); +} + +namespace QQmlScript { +class ParserJsASTData +{ +public: + ParserJsASTData(const QString &filename) + : filename(filename) {} + + QString filename; + Engine engine; +}; +} + +bool QQmlScript::Parser::parse(const QByteArray &qmldata, const QUrl &url, + const QString &urlString) +{ + clear(); + + if (urlString.isEmpty()) { + _scriptFile = url.toString(); + } else { + // Q_ASSERT(urlString == url.toString()); + _scriptFile = urlString; + } + + QTextStream stream(qmldata, QIODevice::ReadOnly); +#ifndef QT_NO_TEXTCODEC + stream.setCodec("UTF-8"); +#endif + QString *code = _pool.NewString(stream.readAll()); + + data = new QQmlScript::ParserJsASTData(_scriptFile); + + Lexer lexer(&data->engine); + lexer.setCode(*code, /*line = */ 1); + + QQmlJS::Parser parser(&data->engine); + + if (! parser.parse() || !_errors.isEmpty()) { + + // Extract errors from the parser + foreach (const DiagnosticMessage &m, parser.diagnosticMessages()) { + + if (m.isWarning()) + continue; + + QQmlError error; + error.setUrl(url); + error.setDescription(m.message); + error.setLine(m.loc.startLine); + error.setColumn(m.loc.startColumn); + _errors << error; + + } + } + + if (_errors.isEmpty()) { + ProcessAST process(this); + process(*code, parser.ast()); + + // Set the url for process errors + for(int ii = 0; ii < _errors.count(); ++ii) + _errors[ii].setUrl(url); + } + + return _errors.isEmpty(); +} + +QList<QQmlScript::TypeReference*> QQmlScript::Parser::referencedTypes() const +{ + return _refTypes; +} + +QQmlScript::Object *QQmlScript::Parser::tree() const +{ + return root; +} + +QList<QQmlScript::Import> QQmlScript::Parser::imports() const +{ + return _imports; +} + +QList<QQmlError> QQmlScript::Parser::errors() const +{ + return _errors; +} + +static void replaceWithSpace(QString &str, int idx, int n) +{ + QChar *data = str.data() + idx; + const QChar space(QLatin1Char(' ')); + for (int ii = 0; ii < n; ++ii) + *data++ = space; +} + +static QQmlScript::LocationSpan +locationFromLexer(const QQmlJS::Lexer &lex, int startLine, int startColumn, int startOffset) +{ + QQmlScript::LocationSpan l; + + l.start.line = startLine; l.start.column = startColumn; + l.end.line = lex.tokenEndLine(); l.end.column = lex.tokenEndColumn(); + l.range.offset = startOffset; + l.range.length = lex.tokenOffset() + lex.tokenLength() - startOffset; + + return l; +} + +/* +Searches for ".pragma <value>" declarations within \a script. Currently supported pragmas +are: + library +*/ +QQmlScript::Object::ScriptBlock::Pragmas QQmlScript::Parser::extractPragmas(QString &script) +{ + QQmlScript::Object::ScriptBlock::Pragmas rv = QQmlScript::Object::ScriptBlock::None; + + const QString pragma(QLatin1String("pragma")); + const QString library(QLatin1String("library")); + + QQmlJS::Lexer l(0); + l.setCode(script, 0); + + int token = l.lex(); + + while (true) { + if (token != QQmlJSGrammar::T_DOT) + return rv; + + int startOffset = l.tokenOffset(); + int startLine = l.tokenStartLine(); + + token = l.lex(); + + if (token != QQmlJSGrammar::T_IDENTIFIER || + l.tokenStartLine() != startLine || + script.mid(l.tokenOffset(), l.tokenLength()) != pragma) + return rv; + + token = l.lex(); + + if (token != QQmlJSGrammar::T_IDENTIFIER || + l.tokenStartLine() != startLine) + return rv; + + QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength()); + int endOffset = l.tokenLength() + l.tokenOffset(); + + token = l.lex(); + if (l.tokenStartLine() == startLine) + return rv; + + if (pragmaValue == library) { + rv |= QQmlScript::Object::ScriptBlock::Shared; + replaceWithSpace(script, startOffset, endOffset - startOffset); + } else { + return rv; + } + } + return rv; +} + +#define CHECK_LINE if (l.tokenStartLine() != startLine) return rv; +#define CHECK_TOKEN(t) if (token != QQmlJSGrammar:: t) return rv; + +static const int uriTokens[] = { + QQmlJSGrammar::T_IDENTIFIER, + QQmlJSGrammar::T_PROPERTY, + QQmlJSGrammar::T_SIGNAL, + QQmlJSGrammar::T_READONLY, + QQmlJSGrammar::T_ON, + QQmlJSGrammar::T_BREAK, + QQmlJSGrammar::T_CASE, + QQmlJSGrammar::T_CATCH, + QQmlJSGrammar::T_CONTINUE, + QQmlJSGrammar::T_DEFAULT, + QQmlJSGrammar::T_DELETE, + QQmlJSGrammar::T_DO, + QQmlJSGrammar::T_ELSE, + QQmlJSGrammar::T_FALSE, + QQmlJSGrammar::T_FINALLY, + QQmlJSGrammar::T_FOR, + QQmlJSGrammar::T_FUNCTION, + QQmlJSGrammar::T_IF, + QQmlJSGrammar::T_IN, + QQmlJSGrammar::T_INSTANCEOF, + QQmlJSGrammar::T_NEW, + QQmlJSGrammar::T_NULL, + QQmlJSGrammar::T_RETURN, + QQmlJSGrammar::T_SWITCH, + QQmlJSGrammar::T_THIS, + QQmlJSGrammar::T_THROW, + QQmlJSGrammar::T_TRUE, + QQmlJSGrammar::T_TRY, + QQmlJSGrammar::T_TYPEOF, + QQmlJSGrammar::T_VAR, + QQmlJSGrammar::T_VOID, + QQmlJSGrammar::T_WHILE, + QQmlJSGrammar::T_CONST, + QQmlJSGrammar::T_DEBUGGER, + QQmlJSGrammar::T_RESERVED_WORD, + QQmlJSGrammar::T_WITH, + + QQmlJSGrammar::EOF_SYMBOL +}; +static inline bool isUriToken(int token) +{ + const int *current = uriTokens; + while (*current != QQmlJSGrammar::EOF_SYMBOL) { + if (*current == token) + return true; + ++current; + } + return false; +} + +QQmlScript::Parser::JavaScriptMetaData QQmlScript::Parser::extractMetaData(QString &script) +{ + JavaScriptMetaData rv; + + QQmlScript::Object::ScriptBlock::Pragmas &pragmas = rv.pragmas; + + const QString pragma(QLatin1String("pragma")); + const QString js(QLatin1String(".js")); + const QString library(QLatin1String("library")); + + QQmlJS::Lexer l(0); + l.setCode(script, 0); + + int token = l.lex(); + + while (true) { + if (token != QQmlJSGrammar::T_DOT) + return rv; + + int startOffset = l.tokenOffset(); + int startLine = l.tokenStartLine(); + int startColumn = l.tokenStartColumn(); + + token = l.lex(); + + CHECK_LINE; + + if (token == QQmlJSGrammar::T_IMPORT) { + + // .import <URI> <Version> as <Identifier> + // .import <file.js> as <Identifier> + + token = l.lex(); + + CHECK_LINE; + + if (token == QQmlJSGrammar::T_STRING_LITERAL) { + + QString file = l.tokenText(); + + if (!file.endsWith(js)) + return rv; + + token = l.lex(); + + CHECK_TOKEN(T_AS); + CHECK_LINE; + + token = l.lex(); + + CHECK_TOKEN(T_IDENTIFIER); + CHECK_LINE; + + int endOffset = l.tokenLength() + l.tokenOffset(); + + QString importId = script.mid(l.tokenOffset(), l.tokenLength()); + + if (!importId.at(0).isUpper()) + return rv; + + QQmlScript::LocationSpan location = + locationFromLexer(l, startLine, startColumn, startOffset); + + token = l.lex(); + if (l.tokenStartLine() == startLine) + return rv; + + replaceWithSpace(script, startOffset, endOffset - startOffset); + + Import import; + import.type = Import::Script; + import.uri = file; + import.qualifier = importId; + import.location = location; + + rv.imports << import; + } else { + // URI + QString uri; + QString version; + + while (true) { + if (!isUriToken(token)) + return rv; + + uri.append(l.tokenText()); + + token = l.lex(); + CHECK_LINE; + if (token != QQmlJSGrammar::T_DOT) + break; + + uri.append(QLatin1Char('.')); + + token = l.lex(); + CHECK_LINE; + } + + CHECK_TOKEN(T_NUMERIC_LITERAL); + version = script.mid(l.tokenOffset(), l.tokenLength()); + + token = l.lex(); + + CHECK_TOKEN(T_AS); + CHECK_LINE; + + token = l.lex(); + + CHECK_TOKEN(T_IDENTIFIER); + CHECK_LINE; + + int endOffset = l.tokenLength() + l.tokenOffset(); + + QString importId = script.mid(l.tokenOffset(), l.tokenLength()); + + if (!importId.at(0).isUpper()) + return rv; + + QQmlScript::LocationSpan location = + locationFromLexer(l, startLine, startColumn, startOffset); + + token = l.lex(); + if (l.tokenStartLine() == startLine) + return rv; + + replaceWithSpace(script, startOffset, endOffset - startOffset); + + Import import; + import.type = Import::Library; + import.uri = uri; + import.version = version; + import.qualifier = importId; + import.location = location; + + rv.imports << import; + } + + } else if (token == QQmlJSGrammar::T_IDENTIFIER && + script.mid(l.tokenOffset(), l.tokenLength()) == pragma) { + + token = l.lex(); + + CHECK_TOKEN(T_IDENTIFIER); + CHECK_LINE; + + QString pragmaValue = script.mid(l.tokenOffset(), l.tokenLength()); + int endOffset = l.tokenLength() + l.tokenOffset(); + + if (pragmaValue == library) { + pragmas |= QQmlScript::Object::ScriptBlock::Shared; + replaceWithSpace(script, startOffset, endOffset - startOffset); + } else { + return rv; + } + + token = l.lex(); + if (l.tokenStartLine() == startLine) + return rv; + + } else { + return rv; + } + } + return rv; +} + +void QQmlScript::Parser::clear() +{ + _imports.clear(); + qDeleteAll(_refTypes); + _refTypes.clear(); + _errors.clear(); + + if (data) { + delete data; + data = 0; + } + + _pool.clear(); +} + +QQmlScript::TypeReference *QQmlScript::Parser::findOrCreateType(const QString &name) +{ + TypeReference *type = 0; + int i = 0; + for (; i < _refTypes.size(); ++i) { + if (_refTypes.at(i)->name == name) { + type = _refTypes.at(i); + break; + } + } + if (!type) { + type = new TypeReference(i, name); + _refTypes.append(type); + } + + return type; +} + +void QQmlScript::Parser::setTree(QQmlScript::Object *tree) +{ + Q_ASSERT(! root); + + root = tree; +} + +QT_END_NAMESPACE |