diff --git a/src/qmljsc/CMakeLists.txt b/src/qmljsc/CMakeLists.txt index 2da5a06..62f30b9 100644 --- a/src/qmljsc/CMakeLists.txt +++ b/src/qmljsc/CMakeLists.txt @@ -1,35 +1,36 @@ find_package(Qt5 COMPONENTS Core Qml) set(libqmljsc_srcs compiler.cpp compilerpipeline.cpp compilerpass.cpp + purejavascriptgenerator.cpp ir/objecttree.cpp ir/module.cpp ir/file.cpp ir/typesystem.cpp ir/builtintypes.cpp compilerpasses/parserpass.cpp compilerpasses/prettygeneratorpass.cpp moduleloading/moduleloading.cpp moduleloading/abstractmoduleloader.cpp moduleloading/javascriptmoduleloader.cpp moduleloading/qtqmlmoduleloader.cpp utils/error.cpp utils/shortsymbolname.cpp ) add_library(libqmljsc SHARED ${libqmljsc_srcs}) qt5_use_modules(libqmljsc Core Qml) include_directories(${Qt5Qml_PRIVATE_INCLUDE_DIRS}) set(qmljsc_srcs main.cpp ) add_executable(qmljsc ${qmljsc_srcs}) target_link_libraries(qmljsc libqmljsc) diff --git a/src/qmljsc/purejavascriptgenerator.cpp b/src/qmljsc/purejavascriptgenerator.cpp new file mode 100644 index 0000000..203ae0d --- /dev/null +++ b/src/qmljsc/purejavascriptgenerator.cpp @@ -0,0 +1,153 @@ +/* + * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. + * + * Copyright (C) 2015 Jan Marker + * + * 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 "purejavascriptgenerator.h" + +PureJavaScriptGenerator::PureJavaScriptGenerator() + : m_expressionStack() + , m_generatedCode(new QString()) +{ +} + +QString PureJavaScriptGenerator::getGeneratedCode() { + return *m_generatedCode.string(); +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::BinaryExpression *) { + +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::Block *) { + m_generatedCode << '{'; + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::BreakStatement *breakStatement) { + m_generatedCode << "break"; + if (breakStatement->label.length() > 0) { + m_generatedCode << ' '; + m_generatedCode << breakStatement->label.toString(); + } + + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::IdentifierExpression *identifierExpression) { + m_expressionStack << identifierExpression->name.toString(); + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::NumericLiteral *numericLiteral) { + m_expressionStack.push(QString("%1").arg(numericLiteral->value)); + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::StringLiteral *stringLiteral) { + m_expressionStack.push(stringLiteral->value.toString()); + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::PostDecrementExpression *) { + m_expressionStack.push("--"); + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::PostIncrementExpression *) { + m_expressionStack.push("++"); + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::PreDecrementExpression *) { + m_expressionStack.push("--"); + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::PreIncrementExpression *) { + m_expressionStack.push("++"); + return true; +} + +bool PureJavaScriptGenerator::visit(QQmlJS::AST::VariableDeclaration *variableDeclaration) { + const QString identifier = variableDeclaration->name.toString(); + if (variableDeclaration->readOnly) { + m_generatedCode << "const"; + } else { + m_generatedCode << "var"; + } + m_generatedCode << ' ' << identifier; + if (variableDeclaration->expression) { + m_generatedCode << '='; + } + return true; +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::Block *) { + m_generatedCode << '}'; +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::IdentifierExpression *) { + generateIfLastElementOnStack(); +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::NumericLiteral *) { + generateIfLastElementOnStack(); +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::PostDecrementExpression *) { + updateStackWithPostOperation(); + generateIfLastElementOnStack(); +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::PostIncrementExpression *) { + updateStackWithPostOperation(); + generateIfLastElementOnStack(); +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::PreDecrementExpression *) { + updateStackWithPreOperation(); + generateIfLastElementOnStack(); +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::PreIncrementExpression *) { + updateStackWithPreOperation(); + generateIfLastElementOnStack(); +} + +void PureJavaScriptGenerator::endVisit(QQmlJS::AST::StringLiteral *) { + generateIfLastElementOnStack(); +} + +void PureJavaScriptGenerator::generateIfLastElementOnStack() { + if (m_expressionStack.size() == 1) { + m_generatedCode << m_expressionStack.pop(); + } +} + +void PureJavaScriptGenerator::updateStackWithPostOperation() { + const QString expression = m_expressionStack.pop(); + const QString operation = m_expressionStack.pop(); + m_expressionStack.push(QString("%1%2").arg(expression).arg(operation)); +} + +void PureJavaScriptGenerator::updateStackWithPreOperation() { + const QString expression = m_expressionStack.pop(); + const QString operation = m_expressionStack.pop(); + m_expressionStack.push(QString("%1%2").arg(operation).arg(expression)); +} diff --git a/src/qmljsc/purejavascriptgenerator.h b/src/qmljsc/purejavascriptgenerator.h new file mode 100644 index 0000000..036d3e4 --- /dev/null +++ b/src/qmljsc/purejavascriptgenerator.h @@ -0,0 +1,68 @@ +/* + * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. + * + * Copyright (C) 2015 Jan Marker + * + * 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 QMLWEB_PUREJAVASCRIPTGENERATOR_H +#define QMLWEB_PUREJAVASCRIPTGENERATOR_H + +#include + +#include + +class PureJavaScriptGenerator : public QQmlJS::AST::Visitor { + +public: + PureJavaScriptGenerator(); + + QString getGeneratedCode(); + + virtual bool visit(QQmlJS::AST::BinaryExpression *) override; + virtual bool visit(QQmlJS::AST::Block *) override; + virtual bool visit(QQmlJS::AST::BreakStatement *) override; + virtual bool visit(QQmlJS::AST::IdentifierExpression *) override; + virtual bool visit(QQmlJS::AST::NumericLiteral *) override; + virtual bool visit(QQmlJS::AST::StringLiteral *) override; + virtual bool visit(QQmlJS::AST::PostDecrementExpression *) override; + virtual bool visit(QQmlJS::AST::PostIncrementExpression *) override; + virtual bool visit(QQmlJS::AST::PreDecrementExpression *) override; + virtual bool visit(QQmlJS::AST::PreIncrementExpression *) override; + virtual bool visit(QQmlJS::AST::VariableDeclaration *) override; + + virtual void endVisit(QQmlJS::AST::Block *) override; + virtual void endVisit(QQmlJS::AST::IdentifierExpression *) override; + virtual void endVisit(QQmlJS::AST::NumericLiteral *) override; + virtual void endVisit(QQmlJS::AST::PostDecrementExpression *) override; + virtual void endVisit(QQmlJS::AST::PostIncrementExpression *) override; + virtual void endVisit(QQmlJS::AST::PreDecrementExpression *) override; + virtual void endVisit(QQmlJS::AST::PreIncrementExpression *) override; + virtual void endVisit(QQmlJS::AST::StringLiteral *) override; + +private: + void generateIfLastElementOnStack(); + void updateStackWithPostOperation(); + void updateStackWithPreOperation(); + + QStack m_expressionStack; + QTextStream m_generatedCode; + + friend class TestPureJavaScriptGenerator; +}; + + +#endif //QMLWEB_PUREJAVASCRIPTGENERATOR_H diff --git a/tests/auto/qmljsc/CMakeLists.txt b/tests/auto/qmljsc/CMakeLists.txt index 444f113..51fe12d 100644 --- a/tests/auto/qmljsc/CMakeLists.txt +++ b/tests/auto/qmljsc/CMakeLists.txt @@ -1,26 +1,27 @@ find_package(Qt5 CONFIG REQUIRED Core Test Qml) function(new_test) set(options ) set(oneValueArgs TEST) set(multiValueArgs RESOURCES) cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) include_directories(${Qt5Qml_PRIVATE_INCLUDE_DIRS}) add_test(NAME ${ARGS_TEST} COMMAND ${ARGS_TEST}) generate_qrc_file(FILENAME "${ARGS_TEST}_resources.qrc" RESOURCES ${ARGS_RESOURCES} FILEPATH_VAR qrcfilepath) qt5_add_resources(ResourceSources ${qrcfilepath}) add_executable(${ARGS_TEST} ${ARGS_TEST}.cpp ${ResourceSources}) qt5_use_modules(${ARGS_TEST} Core Qml Test) target_link_libraries(${ARGS_TEST} libqmljsc) endfunction() new_test(TEST testcompiler) new_test(TEST testparserpass RESOURCES ../data/minimal/minimal.qml) new_test(TEST testprettygeneratorpass RESOURCES ../data/minimal/minimal.qml.js) new_test(TEST testcompilerpipeline RESOURCES ../data/minimal/minimal.qml) new_test(TEST testmodules RESOURCES ../data/imports/MinimalModule.0.1.js ../data/imports/TestModule.0.1.js) new_test(TEST testir) +new_test(TEST testpurejavascriptgenerator) diff --git a/tests/auto/qmljsc/testpurejavascriptgenerator.cpp b/tests/auto/qmljsc/testpurejavascriptgenerator.cpp new file mode 100644 index 0000000..467aa97 --- /dev/null +++ b/tests/auto/qmljsc/testpurejavascriptgenerator.cpp @@ -0,0 +1,177 @@ +/* + * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. + * + * Copyright (C) 2015 Jan Marker + * + * 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 "../../../src/qmljsc/purejavascriptgenerator.h" + +#define TEST_SOMEVISIT_GENERATES_FROM(testSituation, expectedResult, visitType, className, ...) \ + void test_ ## visitType ## _ ## className ## _generatesCorrectCode_ ## testSituation() { \ + QQmlJS::AST::className classInstance(__VA_ARGS__); \ + m_generator->visitType(&classInstance); \ + QCOMPARE(static_cast(m_generator)->getGeneratedCode(), QStringLiteral(expectedResult)); \ + } + +#define TEST_VISIT_GENERATES(testSituation, expectedResult, className, ...) \ + TEST_SOMEVISIT_GENERATES_FROM(testSituation, expectedResult, visit, className, __VA_ARGS__) + +#define TEST_ENDVISIT_GENERATES(testSituation, expectedResult, className, ...) \ + TEST_SOMEVISIT_GENERATES_FROM(testSituation, expectedResult, endVisit, className, __VA_ARGS__) + +#define TEST_VISIT_RETURNS(testSituation, expectedReturnResult, className, ...) \ + void test_visit ## _ ## className ## _returns_ ## expectedReturnResult ## _ ## testSituation() { \ + QQmlJS::AST::className classInstance(__VA_ARGS__); \ + QCOMPARE(m_generator->visit(&classInstance), expectedReturnResult); \ + } + +#define TEST_VISIT_IS_DEFAULT_IMPLEMENTATION(className, ...) \ + TEST_VISIT_RETURNS(defaultImplementation, true, className, __VA_ARGS__) \ + TEST_VISIT_GENERATES(defaultImplementation, "", className, __VA_ARGS__) + +#define TEST_VISIT_PUTS_ON_STACK(testScenario, expectedTopOfStack, className, ...) \ + void test_visit_## className ## _putsOnStack_ ## testScenario() { \ + QQmlJS::AST::className classInstance(__VA_ARGS__); \ + m_generator->visit(&classInstance); \ + int stackElementCount = asPureJSGen(m_generator)->m_expressionStack.count(); \ + QCOMPARE(stackElementCount, 1); \ + QString stringOnStack = asPureJSGen(m_generator)->m_expressionStack.top(); \ + QCOMPARE(stringOnStack, QStringLiteral(expectedTopOfStack)); \ + } + +#define TEST_ENDVISIT_FROM_STACK(testScenario, action, expectedResult, expectedStackSize, stackContent, className, ...) \ + void test_endVisit_ ## className ## _ ## action ## _ ## testScenario() { \ + QQmlJS::AST::className classInstance(__VA_ARGS__); \ + asPureJSGen(m_generator)->m_expressionStack.append(QVectorstackContent); \ + m_generator->endVisit(&classInstance); \ + int stackElementCount = asPureJSGen(m_generator)->m_expressionStack.count(); \ + QCOMPARE(stackElementCount, expectedStackSize); \ + QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral(expectedResult)); \ + } + +#define TEST_ENDVISIT_GENERATES_FROM_STACK(testScenario, expectedResult, stackContent, className, ...) \ + TEST_ENDVISIT_FROM_STACK(testScenario, generatesFromStack, expectedResult, 0, stackContent, className, __VA_ARGS__) + +#define TEST_ENDVISIT_ONLY_UPDATES_STACK(testScenario, expectedTopOfStack, stackContent, className, ...) \ + void test_endVisit_ ## className ## _updatesStack_ ## testScenario() { \ + QQmlJS::AST::className classInstance(__VA_ARGS__); \ + asPureJSGen(m_generator)->m_expressionStack.append(QVectorstackContent); \ + m_generator->endVisit(&classInstance); \ + QString stringOnStack = asPureJSGen(m_generator)->m_expressionStack.top(); \ + QCOMPARE(stringOnStack, QStringLiteral(expectedTopOfStack)); \ + QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("")); \ + } + +#define TEST_ENDVISIT_DOES_NOTHING(testScenario, className, ...) \ + void test_endVisit_ ## className ## _doesNothing_ ## testScenario() { \ + QQmlJS::AST::className classInstance(__VA_ARGS__); \ + m_generator->endVisit(&classInstance); \ + QCOMPARE(asPureJSGen(m_generator)->m_expressionStack.size(), 0); \ + QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("")); \ + } + +class TestPureJavaScriptGenerator + : public QObject +{ + Q_OBJECT + +private: + PureJavaScriptGenerator* asPureJSGen(QQmlJS::AST::Visitor* generator) { + return static_cast(generator); + } + QQmlJS::AST::Visitor *m_generator; + + const QString m_someLabel = QString("ALabel"); + const QStringRef m_someLabelStringRef = QStringRef(&m_someLabel); + const QString m_someString = QString("some string"); + const QStringRef m_someStringStringRef = QStringRef(&m_someString); + const QString m_someIdentifier = QString("i"); + const QStringRef m_someIdentifierStringRef = QStringRef(&m_someIdentifier); + QQmlJS::AST::TrueLiteral m_someExpression; + +private slots: + void init() { + m_generator = new PureJavaScriptGenerator(); + }; + + void cleanup() { + delete m_generator; + } + + void testGeneratedCodeIsInitiallyEmpty() { + QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("")); + } + + TEST_VISIT_RETURNS(WithoutStatements, true, Block, nullptr) + TEST_VISIT_GENERATES(WithoutStatements, "{", Block, nullptr) + TEST_VISIT_RETURNS(WithLabel, true, BreakStatement, m_someLabelStringRef) + TEST_VISIT_GENERATES(WithLabel, "break ALabel", BreakStatement, m_someLabelStringRef) + TEST_VISIT_RETURNS(WithoutLabel, true, BreakStatement, nullptr) + TEST_VISIT_GENERATES(WithoutLabel, "break", BreakStatement, nullptr) + TEST_VISIT_RETURNS(DefaultScenario, true, IdentifierExpression, m_someIdentifierStringRef) + TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "i", IdentifierExpression, m_someIdentifierStringRef) + TEST_VISIT_RETURNS(DefaultScenario, true, NumericLiteral, 3.14) + TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "3.14", NumericLiteral, 3.14) + TEST_VISIT_RETURNS(DefaultScenario, true, PostDecrementExpression, nullptr) + TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "--", PostDecrementExpression, nullptr) + TEST_VISIT_RETURNS(DefaultScenario, true, PostIncrementExpression, nullptr) + TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "++", PostIncrementExpression, nullptr) + TEST_VISIT_RETURNS(DefaultScenario, true, PreDecrementExpression, nullptr) + TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "--", PreDecrementExpression, nullptr) + TEST_VISIT_RETURNS(DefaultScenario, true, PreIncrementExpression, nullptr) + TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "++", PreIncrementExpression, nullptr) + TEST_VISIT_RETURNS(SomeString, true, StringLiteral, m_someStringStringRef) + TEST_VISIT_PUTS_ON_STACK(SomeString, "some string", StringLiteral, m_someStringStringRef) + TEST_VISIT_RETURNS(AssignmentScenario, true, VariableDeclaration, m_someIdentifierStringRef, nullptr) + TEST_VISIT_GENERATES(AssignmentScenario, "var i=", VariableDeclaration, m_someIdentifierStringRef, &m_someExpression) + TEST_VISIT_GENERATES(NoAssignmentScenario, "var i", VariableDeclaration, m_someIdentifierStringRef, nullptr) + void test_visit_VariableDeclaration_generatesCorrectCode_ConstAssignment() { + // Prepare + QQmlJS::AST::VariableDeclaration variableDeclaration(m_someIdentifierStringRef, &m_someExpression); + variableDeclaration.readOnly = true; + + // Do + m_generator->visit(&variableDeclaration); + + // Verify + QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("const i=")); + } + + TEST_ENDVISIT_GENERATES(WithoutStatements, "}", Block, nullptr) + TEST_ENDVISIT_GENERATES_FROM_STACK(DefaultScenario, "abc", ({"abc"}), IdentifierExpression, nullptr) + TEST_ENDVISIT_ONLY_UPDATES_STACK(DefaultScenario, "abc", ({"def", "abc"}), IdentifierExpression, nullptr) + TEST_ENDVISIT_GENERATES_FROM_STACK(OneElement, "2.7", ({"2.7"}), NumericLiteral, 3.14) + TEST_ENDVISIT_ONLY_UPDATES_STACK(TwoElements, "2.2", ({"2.1", "2.2"}), NumericLiteral, 3.14) + TEST_ENDVISIT_GENERATES_FROM_STACK(OneLiteral, "2.7--", ({"--", "2.7"}), PostDecrementExpression, nullptr) + TEST_ENDVISIT_ONLY_UPDATES_STACK(TwoLiterals, "2.7--", ({"0.0", "--", "2.7"}), PostDecrementExpression, nullptr) + TEST_ENDVISIT_GENERATES_FROM_STACK(OneLiteral, "2.7++", ({"++", "2.7"}), PostIncrementExpression, nullptr) + TEST_ENDVISIT_ONLY_UPDATES_STACK(TwoLiterals, "2.7++", ({"0.0", "++", "2.7"}), PostIncrementExpression, nullptr) + TEST_ENDVISIT_GENERATES_FROM_STACK(OneLiteral, "--2.7", ({"--", "2.7"}), PreDecrementExpression, nullptr) + TEST_ENDVISIT_ONLY_UPDATES_STACK(TwoLiterals, "--2.7", ({"0.0", "--", "2.7"}), PreDecrementExpression, nullptr) + TEST_ENDVISIT_GENERATES_FROM_STACK(OneLiteral, "++2.7", ({"++", "2.7"}), PreIncrementExpression, nullptr) + TEST_ENDVISIT_ONLY_UPDATES_STACK(TwoLiterals, "++2.7", ({"0.0", "++", "2.7"}), PreIncrementExpression, nullptr) + TEST_ENDVISIT_GENERATES_FROM_STACK(OneLiteral, "another string", ({"another string"}), StringLiteral, nullptr) + TEST_ENDVISIT_ONLY_UPDATES_STACK(TwoLiterals, "string", ({"another", "string"}), StringLiteral, nullptr) + TEST_ENDVISIT_DOES_NOTHING(DefaultScenario, VariableDeclaration, m_someIdentifierStringRef, nullptr) +}; + +QTEST_MAIN(TestPureJavaScriptGenerator) +#include "testpurejavascriptgenerator.moc"