diff --git a/src/qmljsc/moduleloader.cpp b/src/qmljsc/moduleloader.cpp index 5a41a0f..8626205 100644 --- a/src/qmljsc/moduleloader.cpp +++ b/src/qmljsc/moduleloader.cpp @@ -1,211 +1,246 @@ /* * * 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 "moduleloader.h" #include "ir/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 ModuleLoader::s_loadedModules; IR::Module *ModuleLoader::loadModule(IR::ImportDescription import) { Q_ASSERT(import.kind == IR::ImportDescription::Kind_ModuleImport); if (IR::Module *module = s_loadedModules.value(import)) { return module; } IR::Module *module = new IR::Module(import, compiler); s_loadedModules.insert(import, module); ModuleLoader *loader = new ModuleLoader(module); // will delete itself when done QThreadPool::globalInstance()->start(loader); return module; } ModuleLoader::ModuleLoader(IR::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 = IR::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_module->m_import.name) + m_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)); + if (includeDir.exists(m_moduleFileName)) { + moduleFile.setFileName(includeDir.absoluteFilePath(m_moduleFileName)); break; } } if (!moduleFile.exists()) { - throw Error(Error::ModuleImportError, QStringLiteral("Could not find file %1 in path.").arg(moduleFileName)); + throw Error(Error::ModuleImportError, QStringLiteral("Could not find file %1 in path.").arg(m_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()); 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); + QQmlJS::AST::Program *ast = cast(parser->rootNode()); + + ast->accept(this); + + QQmlJS::AST::SourceElements *funcBodyElements = cast(cast(cast(cast(cast(ast->elements->element)->statement)->expression)->base)->expression)->body->elements; + + QQmlJS::AST::CallExpression *call = cast(cast(cast(funcBodyElements->next->next->next->next->next->next->next->next->next->element)->statement)->expression); finalizeParse(); m_module->m_status = IR::Module::Successful; m_module->m_waitCondition.wakeAll(); // Wake up threads, that wait to access this module. } bool ModuleLoader::visit(QQmlJS::AST::FunctionDeclaration* func) { m_functionDepth++; if (m_functionDepth == 2) m_currentFunction = func->name; return true; } void ModuleLoader::endVisit(QQmlJS::AST::FunctionDeclaration* func) { m_functionDepth--; if (m_functionDepth == 1) m_currentFunction.clear(); } bool ModuleLoader::visit(QQmlJS::AST::FunctionExpression* func) { m_functionDepth++; return true; } void ModuleLoader::endVisit(QQmlJS::AST::FunctionExpression* func) { m_functionDepth--; } -bool ModuleLoader::visit(QQmlJS::AST::ReturnStatement *returnStatement) +bool ModuleLoader::visit(CallExpression *call) { - if (m_functionDepth != 1) - return false; + FieldMemberExpression *base = cast(call->base); + if (!base || base->name != QStringLiteral("registerModule")) { + return true; + } + + IdentifierExpression *probablyEngine = cast(base->base); + if (!probablyEngine || probablyEngine->name != QStringLiteral("__engine")) { + return true; + } + + // Ok, apparently, it's the __engine.registerModule expression, we're looking for. + // If now still some assumtion fails, throw an error. - ObjectLiteral *returnLiteral = cast(returnStatement->expression); - if (!returnLiteral) - return false; + if (!call->arguments) { + Error error(Error::ModuleImportError, "Malformed registerModule call: No argument provided."); + error.setFile(m_moduleFileName); + error.setLine(call->lparenToken.startLine); + error.setColumn(call->lparenToken.startColumn); + throw error; + } + + ObjectLiteral *moduleInfoLiteral = cast(call->arguments->expression); + if (!moduleInfoLiteral) { + Error error(Error::ModuleImportError, "Malformed registerModule call: Wrong argument type provided. Expected Object Literal."); + error.setFile(m_moduleFileName); + error.setLine(call->lparenToken.startLine); + error.setColumn(call->lparenToken.startColumn); + throw error; + } - PropertyAssignmentList *assignment = returnLiteral->properties; + PropertyAssignmentList *assignment = moduleInfoLiteral->properties; while(assignment) { PropertyNameAndValue *nameAndValue = cast(assignment->assignment); if (!nameAndValue) { - qWarning() << "Ignoring invalid type specification."; - continue; + Error error(Error::ModuleImportError, "Malformed registerModule call: Invalid type specification."); + error.setFile(m_moduleFileName); + error.setLine(assignment->assignment->firstSourceLocation().startLine); + error.setColumn(assignment->assignment->firstSourceLocation().startColumn); + throw error; } IdentifierExpression *functionId = cast(nameAndValue->value); if (!functionId) { - qWarning() << "Can't recognize function identifier. Please use a simple identifier as value on type object."; - continue; + Error error(Error::ModuleImportError, "Malformed registerModule call: Can't recognize function identifier. Please use a simple identifier as value on type object."); + error.setFile(m_moduleFileName); + error.setLine(nameAndValue->value->firstSourceLocation().startLine); + error.setColumn(nameAndValue->value->firstSourceLocation().startColumn); + throw error; } m_typesToFunctionsMap.insert(nameAndValue->name->asString(), functionId->name); assignment = assignment->next; } return true; } void ModuleLoader::finalizeParse() { 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. IR::Type *type = new IR::Type(); type->setName(i.key()); m_module->m_types.insert(i.key(), type); } } diff --git a/src/qmljsc/moduleloader.h b/src/qmljsc/moduleloader.h index a198e13..e90ef28 100644 --- a/src/qmljsc/moduleloader.h +++ b/src/qmljsc/moduleloader.h @@ -1,82 +1,83 @@ /* * * 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 //Qt #include // private Qt #include #include #include "ir/file.h" namespace QmlJSc { /** * This class parses the javascript source of a module and analyses it for * class definitions, properties, methods, etc. to create a Module object. * * It will always run asynchronously in its own thread. * * To load a module, use the static function ModuleLoader::loadModule(). */ class ModuleLoader : public QRunnable, public QQmlJS::AST::Visitor { public: /** * Initializes module loading. * Creates a ModuleLoader object and runs it through QThreadPool. * Beware, that it is an asynchronous function and returns a valid but * uncompleted Module as soon as loading *started* (not when finished). * * This function is to be called from the compile-unit thread. */ static IR::Module *loadModule(IR::ImportDescription import); private: ModuleLoader(IR::Module *module); virtual ~ModuleLoader(); void run() override; 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) override; + bool visit(QQmlJS::AST::CallExpression *call) override; void finalizeParse(); static QHash s_loadedModules; IR::Module *m_module; int m_functionDepth; QStringRef m_currentFunction; QHash> m_functionProperties; QHash m_typesToFunctionsMap; + QString m_moduleFileName; }; } // namespace QMLJSc #endif // MODULELOADER_H