Commit 03fa188b authored by Christian Kamm's avatar Christian Kamm
Browse files

Bind each QML document after parsing. Link them before use.


Reviewed-by: default avatarRoberto Raggi <roberto.raggi@nokia.com>
parent e7a330f9
......@@ -121,76 +121,27 @@ public:
}
};
class ProcessSourceElements: protected AST::Visitor
{
Interpreter::Engine *_interp;
public:
ProcessSourceElements(Interpreter::Engine *interp)
: _interp(interp),
typeOfExpression(interp)
{
}
void operator()(AST::Node *node)
{
if (node)
node->accept(this);
}
protected:
using AST::Visitor::visit;
virtual bool visit(FunctionDeclaration *ast)
{
if (ast->name)
_interp->globalObject()->setProperty(ast->name->asString(), new ASTFunctionValue(ast, _interp));
return false;
}
virtual bool visit(VariableDeclaration *ast)
{
if (ast->name) {
const Value *value = _interp->undefinedValue();
if (ast->expression)
value = typeOfExpression(ast->expression, _interp->globalObject());
_interp->globalObject()->setProperty(ast->name->asString(), value);
}
return false;
}
Check typeOfExpression;
};
} // end of anonymous namespace
Bind::Bind(Document::Ptr doc, Interpreter::Engine *interp)
Bind::Bind(Document *doc)
: _doc(doc),
_interp(interp),
_currentObjectValue(0),
_typeEnvironment(0),
_idEnvironment(0),
_functionEnvironment(0),
_rootObjectValue(0)
{
if (_doc && _doc->qmlProgram() != 0) {
UiProgram *program = _doc->qmlProgram();
_currentObjectValue = 0;
_typeEnvironment = _interp->newObject(/*prototype =*/ 0);
_idEnvironment = _interp->newObject(/*prototype =*/ 0);
_functionEnvironment = _interp->newObject(/*prototype =*/ 0);
_rootObjectValue = 0;
_qmlObjectDefinitions.clear();
_qmlObjectBindings.clear();
if (!_doc)
return;
accept(program);
if (_doc->qmlProgram()) {
_idEnvironment = _interp.newObject(/*prototype =*/ 0);
_functionEnvironment = _interp.newObject(/*prototype =*/ 0);
} else if (_doc->jsProgram()) {
_currentObjectValue = _interp.globalObject();
_rootObjectValue = _interp.globalObject();
}
accept(_doc->ast());
}
Bind::~Bind()
......@@ -214,77 +165,6 @@ ObjectValue *Bind::switchObjectValue(ObjectValue *newObjectValue)
return oldObjectValue;
}
ObjectValue *Bind::scopeChainAt(Document::Ptr currentDocument, const Snapshot &snapshot,
Interpreter::Engine *interp, AST::UiObjectMember *currentObject)
{
Bind *currentBind = 0;
QList<Bind *> binds;
QSet<QString> processed;
QStringList todo;
QMultiHash<QString, Document::Ptr> documentByPath;
foreach (Document::Ptr doc, snapshot)
documentByPath.insert(doc->path(), doc);
todo.append(currentDocument->path());
// Bind the reachable documents.
while (! todo.isEmpty()) {
const QString path = todo.takeFirst();
if (processed.contains(path))
continue;
processed.insert(path);
QStringList localImports;
foreach (Document::Ptr doc, documentByPath.values(path)) {
Bind *newBind = new Bind(doc, interp);
binds += newBind;
localImports += newBind->localImports();
if (doc == currentDocument)
currentBind = newBind;
}
localImports.removeDuplicates();
todo += localImports;
}
LinkImports linkImports;
Link link;
// link the import directives
linkImports(binds);
// link the scripts
QStringList includedScriptFiles;
foreach (Bind *bind, binds)
includedScriptFiles += bind->includedScripts();
includedScriptFiles.removeDuplicates();
ProcessSourceElements processSourceElements(interp);
foreach (const QString &scriptFile, includedScriptFiles) {
if (Document::Ptr scriptDoc = snapshot.document(scriptFile)) {
if (AST::Program *program = scriptDoc->jsProgram()) {
processSourceElements(program);
}
}
}
ObjectValue *scope = interp->globalObject();
if (currentBind)
scope = link(binds, currentBind, currentObject);
qDeleteAll(binds);
return scope;
}
QString Bind::toString(UiQualifiedId *qualifiedId, QChar delimiter)
{
QString result;
......@@ -350,7 +230,7 @@ ObjectValue *Bind::bindObject(UiQualifiedId *qualifiedTypeNameId, UiObjectInitia
}
// normal component instance
ASTObjectValue *objectValue = new ASTObjectValue(qualifiedTypeNameId, initializer, _interp);
ASTObjectValue *objectValue = new ASTObjectValue(qualifiedTypeNameId, initializer, &_interp);
parentObjectValue = switchObjectValue(objectValue);
if (parentObjectValue)
......@@ -368,64 +248,9 @@ void Bind::accept(Node *node)
Node::accept(node, this);
}
/*
import Qt 4.6
import Qt 4.6 as Xxx
(import com.nokia.qt is the same as the ones above)
import "content"
import "content" as Xxx
import "content" 4.6
import "content" 4.6 as Xxx
import "http://www.ovi.com/" as Ovi
*/
// ### TODO: Move to LinkImports
bool Bind::visit(UiImport *ast)
{
if (! (ast->importUri || ast->fileName))
return false; // nothing to do.
ObjectValue *namespaceObject = 0;
if (ast->asToken.isValid()) { // with namespace we insert an object in the type env. to hold the imported types
if (!ast->importId)
return false; // this should never happen, but better be safe than sorry
namespaceObject = _interp->newObject(/*prototype */ 0);
_typeEnvironment->setProperty(ast->importId->asString(), namespaceObject);
} else { // without namespace we insert all types directly into the type env.
namespaceObject = _typeEnvironment;
}
// look at files first
// else try the metaobject system
if (ast->importUri) {
const QString package = toString(ast->importUri, '/');
int majorVersion = -1; // ### TODO: Check these magic version numbers
int minorVersion = -1; // ### TODO: Check these magic version numbers
if (ast->versionToken.isValid()) {
const QString versionString = _doc->source().mid(ast->versionToken.offset, ast->versionToken.length);
const int dotIdx = versionString.indexOf(QLatin1Char('.'));
if (dotIdx == -1) {
// only major (which is probably invalid, but let's handle it anyway)
majorVersion = versionString.toInt();
minorVersion = 0; // ### TODO: Check with magic version numbers above
} else {
majorVersion = versionString.left(dotIdx).toInt();
minorVersion = versionString.mid(dotIdx + 1).toInt();
}
}
#ifndef NO_DECLARATIVE_BACKEND
foreach (QmlObjectValue *object, _interp->metaTypeSystem().staticTypesForImport(package, majorVersion, minorVersion)) {
namespaceObject->setProperty(object->qmlTypeName(), object);
}
#endif // NO_DECLARATIVE_BACKEND
} else if (ast->fileName) {
if (ast->fileName) {
QString path = _doc->path();
path += QLatin1Char('/');
path += ast->fileName->asString();
......@@ -488,7 +313,7 @@ bool Bind::visit(FunctionDeclaration *ast)
if (_currentObjectValue->property(ast->name->asString()))
return false;
ASTFunctionValue *function = new ASTFunctionValue(ast, _interp);
ASTFunctionValue *function = new ASTFunctionValue(ast, &_interp);
_currentObjectValue->setProperty(ast->name->asString(), function);
return false; // ### eventually want to visit function bodies
}
......@@ -31,11 +31,12 @@
#define QMLBIND_H
#include <qmljs/parser/qmljsastvisitor_p.h>
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsinterpreter.h>
#include <qmljs/qmljsdocument.h>
#include <QtCore/QHash>
#include <QtCore/QStringList>
#include <QtCore/QSharedPointer>
namespace QmlJS {
......@@ -44,21 +45,13 @@ class Link;
class QMLJS_EXPORT Bind: protected AST::Visitor
{
protected:
Bind(Document::Ptr doc, Interpreter::Engine *interp);
public:
Bind(Document *doc);
virtual ~Bind();
QStringList includedScripts() const;
QStringList localImports() const;
// ### TODO: This methods should go. Bind each document after parsing, link later.
static Interpreter::ObjectValue *scopeChainAt(Document::Ptr currentDocument,
const Snapshot &snapshot,
Interpreter::Engine *interp,
AST::UiObjectMember *currentObject);
protected:
using AST::Visitor::visit;
......@@ -83,11 +76,10 @@ protected:
Interpreter::ObjectValue *bindObject(AST::UiQualifiedId *qualifiedTypeNameId, AST::UiObjectInitializer *initializer);
private:
Document::Ptr _doc;
Interpreter::Engine *_interp;
Document *_doc;
Interpreter::Engine _interp;
Interpreter::ObjectValue *_currentObjectValue;
Interpreter::ObjectValue *_typeEnvironment;
Interpreter::ObjectValue *_idEnvironment;
Interpreter::ObjectValue *_functionEnvironment;
Interpreter::ObjectValue *_rootObjectValue;
......@@ -100,6 +92,7 @@ private:
friend class LinkImports;
friend class Link;
};
typedef QSharedPointer<Bind> BindPtr;
} // end of namespace Qml
......
......@@ -28,6 +28,7 @@
**************************************************************************/
#include "qmljsdocument.h"
#include "qmljsbind.h"
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/parser/qmljslexer_p.h>
#include <qmljs/parser/qmljsparser_p.h>
......@@ -143,6 +144,8 @@ bool Document::parseQml()
_ast = parser.ast();
_diagnosticMessages = parser.diagnosticMessages();
_bind = BindPtr(new Bind(this));
return _parsedCorrectly;
}
......@@ -164,6 +167,8 @@ bool Document::parseJavaScript()
_ast = cast<Program*>(parser.rootNode());
_diagnosticMessages = parser.diagnosticMessages();
_bind = BindPtr(new Bind(this));
return _parsedCorrectly;
}
......@@ -190,6 +195,11 @@ bool Document::parseExpression()
return _parsedCorrectly;
}
BindPtr Document::bind() const
{
return _bind;
}
Snapshot::Snapshot()
{
}
......
......@@ -40,6 +40,9 @@
namespace QmlJS {
class Bind;
typedef QSharedPointer<Bind> BindPtr;
class QMLJS_EXPORT Document
{
public:
......@@ -71,6 +74,8 @@ public:
bool isParsedCorrectly() const
{ return _parsedCorrectly; }
BindPtr bind() const;
int documentRevision() const;
void setDocumentRevision(int documentRevision);
......@@ -89,6 +94,7 @@ private:
QString _path;
QString _componentName;
QString _source;
BindPtr _bind;
};
class QMLJS_EXPORT Snapshot
......
......@@ -12,6 +12,124 @@ using namespace QmlJS;
using namespace QmlJS::Interpreter;
using namespace QmlJS::AST;
Link::Link(Document::Ptr currentDoc, const Snapshot &snapshot, Interpreter::Engine *interp)
: _snapshot(snapshot)
, _interp(interp)
{
_docs = reachableDocuments(currentDoc, snapshot);
linkImports();
}
Link::~Link()
{
// unset all prototypes and scopes
foreach (Document::Ptr doc, _docs) {
BindPtr bind = doc->bind();
if (doc->qmlProgram()) {
bind->_idEnvironment->setScope(0);
bind->_functionEnvironment->setScope(0);
foreach (ObjectValue *object, bind->_qmlObjectBindings) {
object->setPrototype(0);
object->setScope(0);
}
foreach (ObjectValue *object, bind->_qmlObjectDefinitions) {
object->setPrototype(0);
object->setScope(0);
}
} else if (doc->jsProgram()) {
bind->_rootObjectValue->setScope(0);
}
}
}
static ObjectValue *pushScope(ObjectValue *next, ObjectValue *onto)
{
onto->setScope(next);
return next;
}
ObjectValue *Link::scopeChainAt(Document::Ptr doc, UiObjectMember *currentObject)
{
BindPtr bind = doc->bind();
ObjectValue *scopeObject;
if (UiObjectDefinition *definition = cast<UiObjectDefinition *>(currentObject))
scopeObject = bind->_qmlObjectDefinitions.value(definition);
else if (UiObjectBinding *binding = cast<UiObjectBinding *>(currentObject))
scopeObject = bind->_qmlObjectBindings.value(binding);
else
return bind->_interp.globalObject();
if (!scopeObject)
return bind->_interp.globalObject();
// Build the scope chain.
ObjectValue *scopeStart = _typeEnvironments.value(doc.data());
ObjectValue *scope = scopeStart;
scope = pushScope(bind->_idEnvironment, scope);
scope = pushScope(bind->_functionEnvironment, scope);
foreach (const QString &scriptFile, doc->bind()->includedScripts()) {
if (Document::Ptr scriptDoc = _snapshot.document(scriptFile)) {
if (scriptDoc->jsProgram()) {
scope = pushScope(scriptDoc->bind()->_rootObjectValue, scope);
}
}
}
scope = pushScope(scopeObject, scope);
if (scopeObject != bind->_rootObjectValue)
scope = pushScope(bind->_rootObjectValue, scope);
scope = pushScope(bind->_interp.globalObject(), scope);
// May want to link to instantiating components from here.
return scopeStart;
}
void Link::linkImports()
{
foreach (Document::Ptr doc, _docs) {
BindPtr bind = doc->bind();
ObjectValue *typeEnv = _interp->newObject(/*prototype =*/0);
_typeEnvironments.insert(doc.data(), typeEnv);
// Populate the _typeEnvironment with imports.
populateImportedTypes(typeEnv, doc);
// Set the prototypes.
{
QHash<UiObjectDefinition *, ObjectValue *>::iterator it = bind->_qmlObjectDefinitions.begin();
QHash<UiObjectDefinition *, ObjectValue *>::iterator end = bind->_qmlObjectDefinitions.end();
for (; it != end; ++it) {
UiObjectDefinition *key = it.key();
ObjectValue *value = it.value();
if (!key->qualifiedTypeNameId)
continue;
value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId));
}
}
{
QHash<UiObjectBinding *, ObjectValue *>::iterator it = bind->_qmlObjectBindings.begin();
QHash<UiObjectBinding *, ObjectValue *>::iterator end = bind->_qmlObjectBindings.end();
for (; it != end; ++it) {
UiObjectBinding *key = it.key();
ObjectValue *value = it.value();
if (!key->qualifiedTypeNameId)
continue;
value->setPrototype(lookupType(typeEnv, key->qualifiedTypeNameId));
}
}
}
}
static QString componentName(const QString &fileName)
{
QString componentName = fileName;
......@@ -22,11 +140,8 @@ static QString componentName(const QString &fileName)
return componentName;
}
void LinkImports::linkImports(Bind *bind, const QList<Bind *> &binds)
void Link::populateImportedTypes(Interpreter::ObjectValue *typeEnv, Document::Ptr doc)
{
// ### TODO: remove all properties from _typeEnv
Document::Ptr doc = bind->_doc;
if (! (doc->qmlProgram() && doc->qmlProgram()->imports))
return;
......@@ -35,65 +150,127 @@ void LinkImports::linkImports(Bind *bind, const QList<Bind *> &binds)
// implicit imports:
// qml files in the same directory are available without explicit imports
foreach (Bind *otherBind, binds) {
if (otherBind == bind)
foreach (Document::Ptr otherDoc, _docs) {
if (otherDoc == doc)
continue;
Document::Ptr otherDoc = otherBind->_doc;
QFileInfo otherFileInfo(otherDoc->fileName());
const QString otherAbsolutePath = otherFileInfo.absolutePath();
if (otherAbsolutePath != absolutePath)
continue;
bind->_typeEnvironment->setProperty(componentName(otherFileInfo.fileName()),
otherBind->_rootObjectValue);
typeEnv->setProperty(componentName(otherFileInfo.fileName()),
otherDoc->bind()->_rootObjectValue);
}
// explicit imports, whether directories or files
for (UiImportList *it = doc->qmlProgram()->imports; it; it = it->next) {
if (! (it->import && it->import->fileName))
if (! it->import)
continue;
QString path = absolutePath;
path += QLatin1Char('/');
path += it->import->fileName->asString();
path = QDir::cleanPath(path);
if (it->import->fileName) {
importFile(typeEnv, doc, it->import, absolutePath);
} else if (it->import->importUri) {
importNonFile(typeEnv, doc, it->import);
}
}
}
ObjectValue *importNamespace = 0;
/*
import "content"
import "content" as Xxx
import "content" 4.6
import "content" 4.6 as Xxx
foreach (Bind *otherBind, binds) {
Document::Ptr otherDoc = otherBind->_doc;
QFileInfo otherFileInfo(otherDoc->fileName());
const QString otherAbsolutePath = otherFileInfo.absolutePath();
import "http://www.ovi.com/" as Ovi
*/
void Link::importFile(Interpreter::ObjectValue *typeEnv, Document::Ptr doc,
AST::UiImport *import, const QString &startPath)
{
if (!import->fileName)
return;
bool directoryImport = (path == otherAbsolutePath);
bool fileImport = (path == otherDoc->fileName());
if (!directoryImport && !fileImport)
continue;
QString path = startPath;
path += QLatin1Char('/');
path += import->fileName->asString();
path = QDir::cleanPath(path);
if (directoryImport && it->import->importId && !importNamespace) {
importNamespace = bind->_interp->newObject(/*prototype =*/0);
bind->_typeEnvironment->setProperty(it->import->importId->asString(), importNamespace);
}
ObjectValue *importNamespace = 0;
QString targetName;
if (fileImport && it->import->importId) {
targetName = it->import->importId->asString();
} else {
targetName = componentName(otherFileInfo.fileName());
}
foreach (Document::Ptr otherDoc, _docs) {
QFileInfo otherFileInfo(otherDoc->fileName());
const QString otherAbsolutePath = otherFileInfo.absolutePath();
bool directoryImport = (path == otherAbsolutePath);
bool fileImport = (path == otherDoc->fileName());
if (!directoryImport && !fileImport)
continue;
ObjectValue *importInto = bind->_typeEnvironment;
if (importNamespace)
importInto = importNamespace;
if (directoryImport && import->importId && !importNamespace) {
importNamespace = _interp->newObject(/*prototype =*/0);
typeEnv->setProperty(import->importId->asString(), importNamespace);
}
importInto->setProperty(targetName, otherBind->_rootObjectValue);
QString targetName;
if (fileImport && import->importId) {
targetName = import->importId->asString();
} else {
targetName = componentName(otherFileInfo.fileName());
}