diff --git a/src/qmljsc/module.cpp b/src/qmljsc/module.cpp index b209ed7..5310c47 100644 --- a/src/qmljsc/module.cpp +++ b/src/qmljsc/module.cpp @@ -1,207 +1,242 @@ /* * * Copyright (C) 2015 Anton Kreuzkamp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ // Own #include "module.h" #include "compiler.h" #include "error.h" // Qt #include #include +#include +#include // Qt private #include #include #include using namespace QmlJSc; using namespace QQmlJS::AST; -QHash Module::loadedModules; +QHash ModuleLoader::s_loadedModules; Module::Module(ModuleImport import, QObject *parent) - : QObject(parent) - , m_parseData({0}) - , m_import(import) + : m_import(import) , m_status(Loading) { } -void Module::doLoad() +Module *ModuleLoader::loadModule(QmlJSc::ModuleImport import, QObject *parent) +{ + if (Module *module = s_loadedModules.value(import)) { + return module; + } + + Module *module = new Module(import, parent); + s_loadedModules.insert(import, module); + ModuleLoader *loader = new ModuleLoader(module); // will delete itself when done + + QThreadPool::globalInstance()->start(loader); + + return module; +} + + +ModuleLoader::ModuleLoader(Module *module) + : QRunnable() + , Visitor() + , m_module(module) + , m_functionDepth(0) +{ +} + +ModuleLoader::~ModuleLoader() +{ +} + +void ModuleLoader::run() +{ + try { + doLoad(); + } catch (const Error &e) { + m_module->m_status = Module::ErrorState; + m_module->m_waitCondition.wakeAll(); + } +} + +void ModuleLoader::doLoad() { // === Load Module File === QFile moduleFile; - const QString moduleFileName = QStringLiteral("%1.%2.%3.js").arg(m_import.name) - .arg(m_import.versionMajor) - .arg(m_import.versionMinor); + const QString moduleFileName = QStringLiteral("%1.%2.%3.js").arg(m_module->m_import.name) + .arg(m_module->m_import.versionMajor) + .arg(m_module->m_import.versionMinor); // For now we only support local files. const QStringList &includePaths = compiler->includePaths(); foreach (QString includePath, includePaths) { QDir includeDir(includePath); if (includeDir.exists(moduleFileName)) { moduleFile.setFileName(includeDir.absoluteFilePath(moduleFileName)); break; } } if (!moduleFile.exists()) { - error(QStringLiteral("Could not find file %1 in path.").arg(moduleFileName)); - return; + throw Error(Error::ModuleImportError, QStringLiteral("Could not find file %1 in path.").arg(moduleFileName)); } // Read file moduleFile.open(QFile::ReadOnly); QTextStream modueFileStream(&moduleFile); QString moduleSource = modueFileStream.readAll(); // === Parse file === // parsing happens in three steps: Calling the QQmlJS-parser to parse the // file and return an AST, then using the visit functions of this class to // collect the data we need and put it into m_parseData and third calling // finalizeParse() to evaluate the parse data and transform it to actual // type information. QQmlJS::Engine* engine = new QQmlJS::Engine(); QQmlJS::Lexer* lexer = new QQmlJS::Lexer(engine); lexer->setCode(moduleSource, 1, true); QQmlJS::Parser* parser = new QQmlJS::Parser(engine); bool successfullyParsed = parser->parseProgram(); if (!successfullyParsed) { Error *err = new Error(Error::ParseError, parser->errorMessage()); err->setColumn(parser->errorColumnNumber()); err->setLine(parser->errorLineNumber()); - error(QStringLiteral("Error while processing module %1 %2.%3") - .arg(m_import.name).arg(m_import.versionMajor).arg(m_import.versionMinor), err); - return; + throw Error(Error::ModuleImportError, + QStringLiteral("Error while processing module %1 %2.%3") + .arg(m_module->m_import.name) + .arg(m_module->m_import.versionMajor) + .arg(m_module->m_import.versionMinor), + err); } parser->rootNode()->accept(this); finalizeParse(); - m_status = Successful; - m_waitCondition.wakeAll(); // Wake up threads, that wait to access this module. + m_module->m_status = Module::Successful; + m_module->m_waitCondition.wakeAll(); // Wake up threads, that wait to access this module. } -bool Module::visit(QQmlJS::AST::FunctionDeclaration* func) +bool ModuleLoader::visit(QQmlJS::AST::FunctionDeclaration* func) { - m_parseData.functionDepth++; - if (m_parseData.functionDepth == 2) - m_parseData.currentFunction = func->name; + m_functionDepth++; + if (m_functionDepth == 2) + m_currentFunction = func->name; return true; } -void Module::endVisit(QQmlJS::AST::FunctionDeclaration* func) +void ModuleLoader::endVisit(QQmlJS::AST::FunctionDeclaration* func) { - m_parseData.functionDepth--; - if (m_parseData.functionDepth == 1) - m_parseData.currentFunction.clear(); + m_functionDepth--; + if (m_functionDepth == 1) + m_currentFunction.clear(); } -bool Module::visit(QQmlJS::AST::FunctionExpression* func) +bool ModuleLoader::visit(QQmlJS::AST::FunctionExpression* func) { - m_parseData.functionDepth++; + m_functionDepth++; return true; } -void Module::endVisit(QQmlJS::AST::FunctionExpression* func) +void ModuleLoader::endVisit(QQmlJS::AST::FunctionExpression* func) { - m_parseData.functionDepth--; + m_functionDepth--; } -bool Module::visit(QQmlJS::AST::ReturnStatement *returnStatement) +bool ModuleLoader::visit(QQmlJS::AST::ReturnStatement *returnStatement) { - if (m_parseData.functionDepth != 1) + if (m_functionDepth != 1) return false; ObjectLiteral *returnLiteral = cast(returnStatement->expression); if (!returnLiteral) return false; PropertyAssignmentList *assignment = returnLiteral->properties; while(assignment) { PropertyNameAndValue *nameAndValue = cast(assignment->assignment); if (!nameAndValue) { qWarning() << "Ignoring invalid type specification."; continue; } IdentifierExpression *functionId = cast(nameAndValue->value); if (!functionId) { qWarning() << "Can't recognize function identifier. Please use a simple identifier as value on type object."; continue; } - m_parseData.typesToFunctionsMap.insert(nameAndValue->name->asString(), functionId->name); + m_typesToFunctionsMap.insert(nameAndValue->name->asString(), functionId->name); assignment = assignment->next; } return true; } -void Module::finalizeParse() +void ModuleLoader::finalizeParse() { - for (auto i = m_parseData.typesToFunctionsMap.constBegin(); i != m_parseData.typesToFunctionsMap.constEnd(); i++) { + for (auto i = m_typesToFunctionsMap.constBegin(); i != m_typesToFunctionsMap.constEnd(); i++) { // the typesToFunctionsMap contains the most basic information, namely // which types exist. The key in this hash is the name of the type, the // value is the name of the function that defines it. Type *type = new Type(); type->name = i.key(); - m_types.insert(i.key(), type); + m_module->m_types.insert(i.key(), type); } - - m_parseData = {0}; // reset parse data } Module::Status Module::status() { return m_status; } const QString &Module::name() { return m_import.name; } +Type *Module::type(QString name) +{ + waitForLoaded(); + return m_types.value(name); +} void Module::waitForLoaded() { if (m_status != Loading) return; m_loadMutex.lock(); m_waitCondition.wait(&m_loadMutex); m_loadMutex.unlock(); } - -void Module::error(QString message, Error *reason) -{ - m_status = ErrorState; - m_waitCondition.wakeAll(); - emit importError({ Error::ModuleImportError, message, reason }); -} - diff --git a/src/qmljsc/module.h b/src/qmljsc/module.h index dcd9dfe..8676581 100644 --- a/src/qmljsc/module.h +++ b/src/qmljsc/module.h @@ -1,102 +1,104 @@ /* * * Copyright (C) 2015 Anton Kreuzkamp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef MODULELOADER_H #define MODULELOADER_H -// #include +//Qt +#include + +// private Qt #include #include #include "symboltable.h" namespace QmlJSc { struct Type { QString name; }; /** * This class provides API representing a Qml.js module and allows to learn * about the modules API. * * Therefore it parses the javascript source of the module and analyses it for * class definitions, properties, methods, etc. */ -class Module : public QObject, public QQmlJS::AST::Visitor +class Module { - Q_OBJECT - public: enum Status { Loading, Successful, ErrorState }; - static QHash loadedModules; - explicit Module(ModuleImport import, QObject *parent = 0); Status status(); void waitForLoaded(); - inline Type *type(QString name) - { - waitForLoaded(); - return m_types.value(name); - } + Type *type(QString name); const QString &name(); -public slots: - void doLoad(); - -signals: - void importError(QmlJSc::Error error); +private: + QHash m_types; + ModuleImport m_import; + Status m_status; + QWaitCondition m_waitCondition; + QMutex m_loadMutex; + friend class ModuleLoader; +}; +class ModuleLoader : public QRunnable, public QQmlJS::AST::Visitor +{ +public: + static Module *loadModule(ModuleImport import, QObject *parent = 0); + void run() override; private: + ModuleLoader(Module *module); + virtual ~ModuleLoader(); + + void doLoad(); bool visit(QQmlJS::AST::FunctionExpression*) override; void endVisit(QQmlJS::AST::FunctionExpression*) override; bool visit(QQmlJS::AST::FunctionDeclaration*) override; void endVisit(QQmlJS::AST::FunctionDeclaration*) override; - bool visit(QQmlJS::AST::ReturnStatement *returnStatement); - void error(QString message, Error *reason = 0); + bool visit(QQmlJS::AST::ReturnStatement *returnStatement) override; + void finalizeParse(); - struct { - int functionDepth; - QStringRef currentFunction; - QHash> functionProperties; - QHash typesToFunctionsMap; - } m_parseData; + static QHash s_loadedModules; - QHash m_types; - ModuleImport m_import; - Status m_status; - QWaitCondition m_waitCondition; - QMutex m_loadMutex; + Module *m_module; + int m_functionDepth; + QStringRef m_currentFunction; + QHash> m_functionProperties; + QHash m_typesToFunctionsMap; }; } #endif // MODULELOADER_H diff --git a/src/qmljsc/symboltable.cpp b/src/qmljsc/symboltable.cpp index a6a0f57..86c7f2e 100644 --- a/src/qmljsc/symboltable.cpp +++ b/src/qmljsc/symboltable.cpp @@ -1,183 +1,178 @@ /* * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. * * Copyright (C) 2015 Anton Kreuzkamp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "symboltable.h" +#include "compiler.h" +#include "compiler.h" #include "module.h" using namespace QmlJSc; ShortSymbolName::ShortSymbolName(char first) : QString(first) {} ShortSymbolName::ShortSymbolName(QString first) : QString(first) {} ShortSymbolName &ShortSymbolName::operator++() { Q_ASSERT(!isEmpty()); iterator i = end(); i--; char borrow = 1; while (i != begin()) { if ((*i) == '9') { *i = 'A'; borrow = 0; break; } else if ((*i) == 'Z') { *i = 'a'; borrow = 0; break; } else if ((*i) == 'z') { // We need to add a borrow of 1 to the previous digit *i = '0'; i--; borrow = 1; continue; } else { *i = i->toLatin1() + 1; borrow = 0; break; } } if (borrow == 1) { if (*i <= 'Z') { // the first letter is a capital one, so it should remain so. if (*i == 'Z') { // We need to prepend a new digit *i = '0'; prepend('A'); } else { *i = i->toLatin1() + 1; } } else { // the first letter is a small one, so it should remain so. if (*i == 'z') { // We need to prepend a new digit *i = '0'; prepend('a'); } else { *i = i->toLatin1() + 1; } } } return *this; } SymbolTable::SymbolTable(QObject* parent) : QObject(parent) , m_prefix('A' - 1) // we want the prefix to be 'A' after the first preincrement { } SymbolTable::~SymbolTable() { } void SymbolTable::loadModule(ModuleImport import) { if (m_modules.contains(import)) return; - if (Module *module = Module::loadedModules.value(import)) { - m_modules.insert(import, { module, ++m_prefix }); - return; - } - - Module *module = new Module(import); - Module::loadedModules.insert(import, module); - m_modules.insert(import, { module, ++m_prefix }); - - connect(module, SIGNAL(importError(QmlJSc::Error)), this, SIGNAL(importError(QmlJSc::Error))); - - QMetaObject::invokeMethod(module, "doLoad"); + m_modules.insert(import, { + ModuleLoader::loadModule(import, compiler), + ++m_prefix + }); } Type *SymbolTable::type(const QString &typeName) { const ModuleData *data = moduleForType(typeName); if (data && data->module) return data->module->type(typeName); return 0; } QString SymbolTable::fullyQualifiedName(const QString &typeName) { return QStringLiteral("%1.%2").arg(moduleForType(typeName)->localPrefix, typeName); } const SymbolTable::ModuleData *SymbolTable::moduleForType(const QString &typeName) { Type *foundType = 0; const ModuleData *moduleData = 0; for (auto i = m_modules.constBegin(); i != m_modules.constEnd(); i++) { if (Type * type = i->module->type(typeName)) { if (foundType) { - emit symbolLookupError({ Error::SymbolLookupError, - QString("Ambitious type name. Type %1 was defined by module %2 and %3.") - .arg(typeName, moduleData->module->name(), i->module->name()) - }); + throw Error( + Error::SymbolLookupError, + QString("Ambitious type name. Type %1 was defined by module %2 and %3.") + .arg(typeName, moduleData->module->name(), i->module->name()) + ); return 0; } else { foundType = type; moduleData = &(*i); } } } return moduleData; } // QString SymbolTable::findType(ModuleImport module, QString typeName) // { // if (!m_modules.contains(module)) // return QString(); // // Module *data = m_modules.value(module); // if (data->status() == Module::Loading) { // Wait for data to be available. // data->waitForLoaded(); // // if (data->status() == Module::ErrorState) // If an error occurred, the module won't exist anymore // return QString(); // } // // // return data->typeToFQIHash.value(typeName); // } // // QString SymbolTable::findType(ModuleImports modules, QString typeName) // { // if (!modules.contains({"QtQml", 2, 0})) { // modules.append({"QtQml", 2, 0}); // } // foreach (ModuleImport module, modules) { // QString result = findType(module, typeName); // if (!result.isEmpty()) { // return result; // } // } // return QString(); // } void SymbolTable::doLoadModule(ModuleImport import) { } diff --git a/src/qmljsc/symboltable.h b/src/qmljsc/symboltable.h index 1f32a91..9b8afc2 100644 --- a/src/qmljsc/symboltable.h +++ b/src/qmljsc/symboltable.h @@ -1,112 +1,108 @@ /* * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. * * Copyright (C) 2015 Anton Kreuzkamp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef SYMBOLTABLE_H #define SYMBOLTABLE_H #include "error.h" #include #include #include #include #include namespace QmlJSc { struct ModuleImport { QString name; int versionMajor; int versionMinor; inline bool operator==(const ModuleImport& other) const { return name == other.name && versionMajor == other.versionMajor && versionMinor == other.versionMinor; } }; class Module; struct Type; inline uint qHash(const ModuleImport &key, uint seed) { return qHash(key.name, seed) ^ key.versionMajor ^ key.versionMinor; } typedef QList ModuleImports; /** * This class represents a minified symbol name like "a", "b", "aa", "A8",... * * Initialize it with the first character you want to use as symbol name MINUS * ONE, as you normally will use the preincrement operator before accessing it. * If you won't, feel free to initialize it with the first symbol name directly, * of course. * * To get the next valid symbol name, use the preincrement operator. * * This is a subclass of QString, so you can just use it as a string. */ class ShortSymbolName : public QString { public: ShortSymbolName(char first); ShortSymbolName(QString first); ShortSymbolName &operator++(); }; class SymbolTable : public QObject { Q_OBJECT struct ModuleData { Module *module; QString localPrefix; }; public: explicit SymbolTable(QObject *parent = 0); virtual ~SymbolTable(); void loadModule(ModuleImport import); Type* type(const QString &typeName); QString fullyQualifiedName(const QString &typeName); -signals: - void importError(QmlJSc::Error error); - void symbolLookupError(QmlJSc::Error error); - private slots: void doLoadModule(ModuleImport import); private: const ModuleData *moduleForType(const QString &typeName); QHash m_modules; ShortSymbolName m_prefix; }; } Q_DECLARE_METATYPE(QmlJSc::ModuleImport); #endif // SYMBOLTABLE_H diff --git a/tests/auto/qmljsc/testmodules.cpp b/tests/auto/qmljsc/testmodules.cpp index 132164f..c33e944 100644 --- a/tests/auto/qmljsc/testmodules.cpp +++ b/tests/auto/qmljsc/testmodules.cpp @@ -1,156 +1,149 @@ /* * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. * * Copyright (C) 2015 Anton Kreuzkamp * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include "../../../src/qmljsc/compiler.h" #include "../../../src/qmljsc/symboltable.h" class TestSymbolTable : public QObject { Q_OBJECT private slots: void loadModule(); void testShortSymbolName(); }; using namespace QmlJSc; void TestSymbolTable::loadModule() { Compiler c; SymbolTable symbolTable; const ModuleImport testModuleImport = {"TestModule", 0, 1}; - QSignalSpy spy(&symbolTable, SIGNAL(importError(QmlJSc::Error))); - compiler->addIncludePath(":/test/"); symbolTable.loadModule(testModuleImport); - if (spy.count()) { - QList arguments = spy.takeFirst(); - qDebug() << arguments.at(0).value().what(); - } - QCOMPARE(spy.count(), 0); QVERIFY(symbolTable.type("Pastry")); QCOMPARE(symbolTable.fullyQualifiedName("Pastry"), QStringLiteral("A.Pastry")); QVERIFY(symbolTable.type("Cake")); QCOMPARE(symbolTable.fullyQualifiedName("Cake"), QStringLiteral("A.Cake")); QVERIFY(symbolTable.type("Pizza")); QCOMPARE(symbolTable.fullyQualifiedName("Pizza"), QStringLiteral("A.Pizza")); QVERIFY(!symbolTable.type("Printer")); } void TestSymbolTable::testShortSymbolName() { ShortSymbolName nA('A'); ShortSymbolName nB('B'); ShortSymbolName nZ('Z'); ShortSymbolName na('a'); ShortSymbolName nb('b'); ShortSymbolName nz('z'); ShortSymbolName nA0("A0"); ShortSymbolName nA1("A1"); ShortSymbolName nA9("A9"); ShortSymbolName nAA("AA"); ShortSymbolName nAB("AB"); ShortSymbolName nAZ("AZ"); ShortSymbolName nAa("Aa"); ShortSymbolName nAb("Ab"); ShortSymbolName nAz("Az"); ShortSymbolName na0("a0"); ShortSymbolName na1("a1"); ShortSymbolName na9("a9"); ShortSymbolName naA("aA"); ShortSymbolName naB("aB"); ShortSymbolName naZ("aZ"); ShortSymbolName naa("aa"); ShortSymbolName nab("ab"); ShortSymbolName naz("az"); ShortSymbolName nZ9("Z9"); ShortSymbolName nZZ("ZZ"); ShortSymbolName nZz("Zz"); ShortSymbolName nz9("z9"); ShortSymbolName nzZ("zZ"); ShortSymbolName nzz("zz"); ShortSymbolName nA5dh("A5dh"); ShortSymbolName nA5dz("A5dz"); ShortSymbolName nA5zz("A5zz"); ShortSymbolName nAzzz("Azzz"); ShortSymbolName nZzzz("Zzzz"); ShortSymbolName nzzzz("zzzz"); QCOMPARE(static_cast(++nA), QStringLiteral("B")); QCOMPARE(static_cast(++nB), QStringLiteral("C")); QCOMPARE(static_cast(++nZ), QStringLiteral("A0")); QCOMPARE(static_cast(++na), QStringLiteral("b")); QCOMPARE(static_cast(++nb), QStringLiteral("c")); QCOMPARE(static_cast(++nz), QStringLiteral("a0")); QCOMPARE(static_cast(++nA0), QStringLiteral("A1")); QCOMPARE(static_cast(++nA1), QStringLiteral("A2")); QCOMPARE(static_cast(++nA9), QStringLiteral("AA")); QCOMPARE(static_cast(++nAA), QStringLiteral("AB")); QCOMPARE(static_cast(++nAB), QStringLiteral("AC")); QCOMPARE(static_cast(++nAZ), QStringLiteral("Aa")); QCOMPARE(static_cast(++nAa), QStringLiteral("Ab")); QCOMPARE(static_cast(++nAb), QStringLiteral("Ac")); QCOMPARE(static_cast(++nAz), QStringLiteral("B0")); QCOMPARE(static_cast(++na0), QStringLiteral("a1")); QCOMPARE(static_cast(++na1), QStringLiteral("a2")); QCOMPARE(static_cast(++na9), QStringLiteral("aA")); QCOMPARE(static_cast(++naA), QStringLiteral("aB")); QCOMPARE(static_cast(++naB), QStringLiteral("aC")); QCOMPARE(static_cast(++naZ), QStringLiteral("aa")); QCOMPARE(static_cast(++naa), QStringLiteral("ab")); QCOMPARE(static_cast(++nab), QStringLiteral("ac")); QCOMPARE(static_cast(++naz), QStringLiteral("b0")); QCOMPARE(static_cast(++nZ9), QStringLiteral("ZA")); QCOMPARE(static_cast(++nZZ), QStringLiteral("Za")); QCOMPARE(static_cast(++nZz), QStringLiteral("A00")); QCOMPARE(static_cast(++nz9), QStringLiteral("zA")); QCOMPARE(static_cast(++nzZ), QStringLiteral("za")); QCOMPARE(static_cast(++nzz), QStringLiteral("a00")); QCOMPARE(static_cast(++nA5dh), QStringLiteral("A5di")); QCOMPARE(static_cast(++nA5dz), QStringLiteral("A5e0")); QCOMPARE(static_cast(++nA5zz), QStringLiteral("A600")); QCOMPARE(static_cast(++nAzzz), QStringLiteral("B000")); QCOMPARE(static_cast(++nZzzz), QStringLiteral("A0000")); QCOMPARE(static_cast(++nzzzz), QStringLiteral("a0000")); } QTEST_MAIN(TestSymbolTable) #include "testmodules.moc"