diff --git a/src/qmljsc/CMakeLists.txt b/src/qmljsc/CMakeLists.txt --- a/src/qmljsc/CMakeLists.txt +++ b/src/qmljsc/CMakeLists.txt @@ -4,6 +4,7 @@ compiler.cpp compilerpipeline.cpp compilerpass.cpp + purejavascriptgenerator.cpp ir/objecttree.cpp ir/module.cpp diff --git a/src/qmljsc/purejavascriptgenerator.h b/src/qmljsc/purejavascriptgenerator.h new file mode 100644 --- /dev/null +++ b/src/qmljsc/purejavascriptgenerator.h @@ -0,0 +1,94 @@ +/* + * 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::BreakStatement *) override; + virtual bool visit(QQmlJS::AST::CaseClause *) override; + virtual bool visit(QQmlJS::AST::ContinueStatement *) override; + virtual bool visit(QQmlJS::AST::DefaultClause *) override; + virtual bool visit(QQmlJS::AST::FormalParameterList *) override; + virtual bool visit(QQmlJS::AST::IdentifierExpression *) override; + virtual bool visit(QQmlJS::AST::NumericLiteral *) override; + virtual bool visit(QQmlJS::AST::StringLiteral *) override; + + virtual void endVisit(QQmlJS::AST::BinaryExpression *) override; + virtual void endVisit(QQmlJS::AST::Block *) override; + virtual void endVisit(QQmlJS::AST::CaseBlock *) override; + virtual void endVisit(QQmlJS::AST::CaseClause *) override; + virtual void endVisit(QQmlJS::AST::CaseClauses *) override; + virtual void endVisit(QQmlJS::AST::DefaultClause *) override; + virtual void endVisit(QQmlJS::AST::EmptyStatement *) override; + virtual void endVisit(QQmlJS::AST::ExpressionStatement *) override; + virtual void endVisit(QQmlJS::AST::FunctionBody *) override; + virtual void endVisit(QQmlJS::AST::FunctionDeclaration *) override; + virtual void endVisit(QQmlJS::AST::IdentifierExpression *) override; + virtual void endVisit(QQmlJS::AST::IfStatement *) 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::ReturnStatement *) override; + virtual void endVisit(QQmlJS::AST::SourceElements *) override; + virtual void endVisit(QQmlJS::AST::StringLiteral *) override; + virtual void endVisit(QQmlJS::AST::StatementList *) override; + virtual void endVisit(QQmlJS::AST::SwitchStatement *) override; + virtual void endVisit(QQmlJS::AST::VariableDeclaration *) override; + virtual void endVisit(QQmlJS::AST::VariableDeclarationList *) override; + virtual void endVisit(QQmlJS::AST::VariableStatement *) override; + +private: + template void reduceListStack(ListType* list, const char* separator = ""); + void reduceJumpStatement(const char *keyword, QStringRef label); + + QStack m_outputStack; + + friend class TestPureJavaScriptGenerator; +}; + +template void PureJavaScriptGenerator::reduceListStack(ListType* current, const char* separator) { + // the list is only iterated to count the elements + // current is not processed in any way + QString code; + while (current) { + code.prepend(m_outputStack.pop()); + if (current->next) { + code.prepend(separator); + } + current = current->next; + } + m_outputStack << code; +} + +#endif //QMLWEB_PUREJAVASCRIPTGENERATOR_H diff --git a/src/qmljsc/purejavascriptgenerator.cpp b/src/qmljsc/purejavascriptgenerator.cpp new file mode 100644 --- /dev/null +++ b/src/qmljsc/purejavascriptgenerator.cpp @@ -0,0 +1,279 @@ +/* + * 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" +#include "utils/error.h" + +using namespace QQmlJS; + +PureJavaScriptGenerator::PureJavaScriptGenerator() + : m_outputStack() +{ +} + +QString PureJavaScriptGenerator::getGeneratedCode() { + if (m_outputStack.size() > 1) { + const QString errorDescription = QString("stack was not reduced correctly, top element is %1").arg(m_outputStack.top()); + QmlJSc::Error stackError(QmlJSc::Error::InternalError, errorDescription); + throw stackError; + } else if (m_outputStack.size() == 0) { + return ""; + } + return m_outputStack.pop(); +} + +bool PureJavaScriptGenerator::visit(AST::BinaryExpression *binaryExpression) { + switch(binaryExpression->op) { + case QSOperator::Assign: m_outputStack << "="; break; + case QSOperator::InplaceAdd: m_outputStack << "+="; break; + case QSOperator::InplaceSub: m_outputStack << "-="; break; + case QSOperator::InplaceMul: m_outputStack << "*="; break; + case QSOperator::InplaceDiv: m_outputStack << "/="; break; + case QSOperator::InplaceMod: m_outputStack << "%="; break; + case QSOperator::InplaceLeftShift: m_outputStack << "<<="; break; + case QSOperator::InplaceRightShift: m_outputStack << ">>="; break; + case QSOperator::InplaceURightShift: m_outputStack << ">>>="; break; + case QSOperator::InplaceAnd: m_outputStack << "&="; break; + case QSOperator::InplaceXor: m_outputStack << "^="; break; + case QSOperator::InplaceOr: m_outputStack << "|="; break; + case QSOperator::Add: m_outputStack << "+"; break; + case QSOperator::Sub: m_outputStack << "-"; break; + case QSOperator::Mul: m_outputStack << "*"; break; + case QSOperator::Div: m_outputStack << "/"; break; + case QSOperator::Mod: m_outputStack << "%"; break; + case QSOperator::LShift: m_outputStack << "<<"; break; + case QSOperator::RShift: m_outputStack << ">>"; break; + case QSOperator::URShift: m_outputStack << ">>>"; break; + case QSOperator::BitAnd: m_outputStack << "&"; break; + case QSOperator::BitXor: m_outputStack << "^"; break; + case QSOperator::BitOr: m_outputStack << "|"; break; + case QSOperator::Equal: m_outputStack << "=="; break; + case QSOperator::NotEqual: m_outputStack << "!="; break; + case QSOperator::StrictEqual: m_outputStack << "==="; break; + case QSOperator::StrictNotEqual: m_outputStack << "!=="; break; + case QSOperator::Gt: m_outputStack << ">"; break; + case QSOperator::Ge: m_outputStack << ">="; break; + case QSOperator::Lt: m_outputStack << "<"; break; + case QSOperator::Le: m_outputStack << "<="; break; + case QSOperator::And: m_outputStack << "&&"; break; + case QSOperator::Or: m_outputStack << "||"; break; + case QSOperator::In: m_outputStack << " in "; break; + case QSOperator::InstanceOf: m_outputStack << " instanceof "; break; + default: Q_ASSERT_X(binaryExpression->op, __FILE__, "The operator is not handled yet"); + } + return true; +} + +bool PureJavaScriptGenerator::visit(AST::BreakStatement *breakStatement) { + reduceJumpStatement("break", breakStatement->label); + return true; +} + +bool PureJavaScriptGenerator::visit(AST::CaseClause *) { + m_outputStack << "case"; + return true; +} + +bool PureJavaScriptGenerator::visit(AST::ContinueStatement *continueStatement) { + reduceJumpStatement("continue", continueStatement->label); + return true; +} + +bool PureJavaScriptGenerator::visit(AST::DefaultClause *) { + m_outputStack << "default"; + return true; +} + +bool PureJavaScriptGenerator::visit(AST::FormalParameterList *currentParameter) { + QString parameterCode; + while (currentParameter) { + parameterCode += currentParameter->name.toString(); + if (currentParameter->next) { + parameterCode += ','; + } + currentParameter = currentParameter->next; + } + m_outputStack << parameterCode; + return true; +} + +bool PureJavaScriptGenerator::visit(AST::IdentifierExpression *identifierExpression) { + m_outputStack << identifierExpression->name.toString(); + return true; +} + +bool PureJavaScriptGenerator::visit(AST::NumericLiteral *numericLiteral) { + m_outputStack.push(QString::number(numericLiteral->value)); + return true; +} + +bool PureJavaScriptGenerator::visit(AST::StringLiteral *stringLiteral) { + m_outputStack << '"' + stringLiteral->value.toString() + '"'; + return true; +} + +void PureJavaScriptGenerator::endVisit(AST::BinaryExpression *) { + const QString rightOperand = m_outputStack.pop(); + const QString leftOperand = m_outputStack.pop(); + const QString operation = m_outputStack.pop(); + QString expression = QString("%1%2%3").arg(leftOperand).arg(operation).arg(rightOperand); + m_outputStack.push(expression); +} + +void PureJavaScriptGenerator::endVisit(AST::Block *) { + const QString blockCode = m_outputStack.pop(); + m_outputStack << '{' + blockCode + '}'; +} + +void PureJavaScriptGenerator::endVisit(AST::CaseBlock *caseBlock) { + QString clausesRight; + if (caseBlock->moreClauses) { + clausesRight = m_outputStack.pop(); + } + QString defaultClause; + if (caseBlock->defaultClause) { + defaultClause = m_outputStack.pop(); + } + const QString clausesLeft = m_outputStack.pop(); + m_outputStack << '{' + clausesLeft + defaultClause + clausesRight + '}'; +} + +void PureJavaScriptGenerator::endVisit(AST::CaseClause *) { + const QString statement = m_outputStack.pop(); + const QString expression = m_outputStack.pop(); + const QString caseKeyword = m_outputStack.pop(); + m_outputStack << caseKeyword + ' ' + expression + ':' + statement; +} + +void PureJavaScriptGenerator::endVisit(AST::CaseClauses *caseClauses) { + reduceListStack(caseClauses); +} + +void PureJavaScriptGenerator::endVisit(AST::DefaultClause *) { + const QString statement = m_outputStack.pop(); + const QString defaultKeyword = m_outputStack.pop(); + m_outputStack << defaultKeyword + ':' + statement; +} + +void PureJavaScriptGenerator::endVisit(AST::EmptyStatement *) { + m_outputStack << ";"; +} + +void PureJavaScriptGenerator::endVisit(AST::ExpressionStatement *) { + m_outputStack << m_outputStack.pop() + ';'; +} + +void PureJavaScriptGenerator::endVisit(AST::FunctionBody *) { + const QString body = m_outputStack.pop(); + m_outputStack << '{' + body + '}'; +} + +void PureJavaScriptGenerator::endVisit(AST::FunctionDeclaration *functionDeclaration) { + const QString body = (functionDeclaration->body) ? m_outputStack.pop() : "{}"; + const QString parameters = (functionDeclaration->formals) ? m_outputStack.pop() : ""; + const QString name = functionDeclaration->name.toString(); + m_outputStack << "function " + name + '(' + parameters + ')' + body; +} + +void PureJavaScriptGenerator::endVisit(AST::IdentifierExpression *) { +} + +void PureJavaScriptGenerator::endVisit(AST::IfStatement *ifExpression) { + const QString elseStatements = (ifExpression->ko) ? m_outputStack.pop() : ""; + const QString ifStatements = m_outputStack.pop(); + const QString expression = m_outputStack.pop(); + + QString code("if(" + expression + ')' + ifStatements); + if (ifExpression->ko) { + code += "else " + elseStatements; + } + m_outputStack << code; +} + +void PureJavaScriptGenerator::endVisit(AST::NumericLiteral *) { +} + +void PureJavaScriptGenerator::endVisit(AST::PostDecrementExpression *) { + const QString expression = m_outputStack.pop(); + m_outputStack << expression + "--"; +} + +void PureJavaScriptGenerator::endVisit(AST::PostIncrementExpression *) { + const QString expression = m_outputStack.pop(); + m_outputStack << expression + "++"; +} + +void PureJavaScriptGenerator::endVisit(AST::PreDecrementExpression *) { + const QString expression = m_outputStack.pop(); + m_outputStack << "--" + expression; +} + +void PureJavaScriptGenerator::endVisit(AST::PreIncrementExpression *) { + const QString expression = m_outputStack.pop(); + m_outputStack << "++" + expression; +} + +void PureJavaScriptGenerator::endVisit(AST::ReturnStatement *returnStatement) { + const QString expression = (returnStatement->expression) ? ' '+m_outputStack.pop() : ""; + m_outputStack << "return" + expression + ';'; +} + +void PureJavaScriptGenerator::endVisit(AST::SourceElements *sourceElements) { + reduceListStack(sourceElements); +} + +void PureJavaScriptGenerator::endVisit(AST::StatementList *statementList) { + reduceListStack(statementList); +} + +void PureJavaScriptGenerator::endVisit(AST::StringLiteral *) { +} + +void PureJavaScriptGenerator::endVisit(AST::SwitchStatement *) { + const QString clauseBlock = m_outputStack.pop(); + const QString expression = m_outputStack.pop(); + m_outputStack << "switch(" + expression + ')' + clauseBlock; +} + +void PureJavaScriptGenerator::endVisit(AST::VariableDeclaration *declaration) { + const QString expression = (declaration->expression) ? "="+m_outputStack.pop() : ""; + const QString variableName = declaration->name.toString(); + m_outputStack << variableName + expression; +} + +void PureJavaScriptGenerator::endVisit(AST::VariableDeclarationList *declarationList) { + reduceListStack(declarationList, ","); + const QString declarationListCode = m_outputStack.pop(); + const QString declarationType = (declarationList->declaration->readOnly) ? "const" : "var"; + m_outputStack << declarationType + ' ' + declarationListCode; +} + +void PureJavaScriptGenerator::endVisit(AST::VariableStatement *) { + m_outputStack << m_outputStack.pop() + ';'; +} + +void PureJavaScriptGenerator::reduceJumpStatement(const char *keyword, QStringRef label) { + QString labelStatementCode(keyword); + if (label.length() > 0) { + labelStatementCode.append(' '); + labelStatementCode.append(label.toString()); + } + m_outputStack << labelStatementCode + ';'; +} \ No newline at end of file diff --git a/src/qmljsc/utils/error.h b/src/qmljsc/utils/error.h --- a/src/qmljsc/utils/error.h +++ b/src/qmljsc/utils/error.h @@ -39,7 +39,8 @@ ReadFileError, ParseError, ModuleImportError, - SymbolLookupError + SymbolLookupError, + InternalError }; Error() diff --git a/tests/auto/data/javascript/binaryoperations.compiled.js b/tests/auto/data/javascript/binaryoperations.compiled.js new file mode 100644 --- /dev/null +++ b/tests/auto/data/javascript/binaryoperations.compiled.js @@ -0,0 +1 @@ +var x;var y;y=5;x=y;x+=y;x-=y;x*=y;x/=y;x%=y;x<<=y;x>>=y;x>>>=y;x&=y;x^=y;x|=y;x+y;x-y;x*y;x/y;x%y;x<>y;x>>>y;x&y;x^y;x|y;x==y;x!=y;x===y;x!==y;x>y;x>=y;x>=y; +x>>>=y; +x&=y; +x^=y; +x|=y; + +/* Arithmetic operators */ +x+y; +x-y; +x*y; +x/y; +x%y; + +/* Bitwise operators */ +x<>y; +x>>>y; +x&y; +x^y; +x|y; + +/* Comparison operators */ +x==y; +x!=y; +x===y; +x!==y; +x>y; +x>=y; +x + * + * 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/utils/error.h" +#include "../../../src/qmljsc/purejavascriptgenerator.h" + +#define TEST_VISIT_PUTS_ON_STACK(className, testSituation, expectedStackContent, instance) \ + void test_visit_ ## className ## _ ## testSituation ## _returnsTrue() { \ + QCOMPARE(m_generator->visit(&instance), true); \ + } \ + void test_ ## visit ## _ ## className ## _ ## testSituation ## _putsOnStack() { \ + m_generator->visit(&instance); \ + QCOMPARE(asPureJSGen(m_generator)->m_outputStack.top(), QStringLiteral(expectedStackContent)); \ + QCOMPARE(asPureJSGen(m_generator)->m_outputStack.size(), 1); \ + } + +#define TEST_VISIT_BINARYOP_PUTS_ON_STACK(operatorEnumName, character) \ + void test_visit_BinaryExpression_ ## operatorEnumName ## _returnsTrue() { \ + QQmlJS::AST::BinaryExpression classInstance(nullptr, QSOperator::operatorEnumName, nullptr); \ + QCOMPARE(m_generator->visit(&classInstance), true); \ + } \ + void test_visit_BinaryExpression_ ## operatorEnumName ## _putsOnStack() { \ + QQmlJS::AST::BinaryExpression instance(nullptr, QSOperator::operatorEnumName, nullptr); \ + QStack expectedStack;\ + expectedStack.append(character);\ + m_generator->visit(&instance); \ + QCOMPARE(asPureJSGen(m_generator)->m_outputStack, expectedStack); \ + } + +#define TEST_VISIT_DEFAULT_IMPL_(className, instance) \ + void test_visit ## _ ## className ## _defaultImplementation_returns_true() { \ + QCOMPARE(m_generator->visit(&instance), true); \ + } \ + void test_ ## visit ## _ ## className ## _ ## testSituation ## _putsNothingOnStack() { \ + m_generator->visit(&instance); \ + QCOMPARE(asPureJSGen(m_generator)->m_outputStack.count(), 0); \ + } + +#define TEST_ENDVISIT_REDUCES(className, scenarioName, expectedTopOfStack, stackContent, instance) \ + void test_endVisit_ ## className ## scenarioName ## _reducesStack() { \ + asPureJSGen(m_generator)->m_outputStack.append(QVectorstackContent); \ + m_generator->endVisit(&instance); \ + QCOMPARE(asPureJSGen(m_generator)->m_outputStack.top(), QStringLiteral(expectedTopOfStack)); \ + QCOMPARE(asPureJSGen(m_generator)->m_outputStack.count(), 1); \ + } + + +using namespace QQmlJS; + +class TestPureJavaScriptGenerator + : public QObject +{ + Q_OBJECT + +public: + TestPureJavaScriptGenerator() + : m_someLabel("ALabel") + , m_someLabelStringRef(&m_someLabel) + , m_someString("some string") + , m_someStringStringRef(&m_someString) + , m_someIdentifier("i") + , m_someIdentifierStringRef(&m_someIdentifier) + , m_anotherIdentifier("e") + , m_anotherIdentifierStringRef(&m_anotherIdentifier) + /* Expressions */ + , m_identifierExpression(m_someIdentifierStringRef) + , m_numericalExpressionPi(3.14) + , m_stringLiteral(m_someStringStringRef) + , m_trueExpression() + , m_falseExpression() + , m_equalsBinaryExpression(nullptr, QSOperator::Equal, nullptr) + , m_postDecrementExpression(&m_numericalExpressionPi) + , m_postIncrementExpression(&m_numericalExpressionPi) + , m_preDecrementExpression(&m_numericalExpressionPi) + , m_preIncrementExpression(&m_numericalExpressionPi) + , m_block(nullptr) + /* Variable Declarations */ + , m_constDeclaration1(m_someIdentifierStringRef, nullptr) + , m_constDeclaration2(m_anotherIdentifierStringRef, nullptr) + , m_variableDeclarationWithAssignment(m_someIdentifierStringRef, &m_trueExpression) + , m_variableDeclarationWithoutAssignment(m_someIdentifierStringRef, nullptr) + , m_twoConstDeclarations(&m_constDeclaration1) + , m_twoConstDeclarationsPart2(&m_twoConstDeclarations, &m_constDeclaration2) + , m_twoVarDeclarations(&m_variableDeclarationWithAssignment) + , m_twoVarDeclarationsPart2(&m_twoVarDeclarations, &m_variableDeclarationWithoutAssignment) + /* Statements */ + , m_breakStatementWithLabel(m_someLabelStringRef) + , m_breakStatementWithoutLabel(nullptr) + , m_continueStatementWithLabel(m_someLabelStringRef) + , m_continueStatementWithoutLabel(nullptr) + , m_emptyStatement() + , m_statement1() + , m_statement2() + , m_statement3() + , m_expressionStatement(nullptr) + , m_ifStatementWithoutElse(&m_trueExpression, &m_statement1) + , m_ifStatementWithElse(&m_trueExpression, &m_statement1, &m_statement2) + , m_returnStatementWithoutValue(nullptr) + , m_returnStatementWithValue(&m_trueExpression) + , m_variableStatement(&m_twoVarDeclarations) + , m_threeStatementsList(&m_statement1) + , m_statementListPart2(&m_threeStatementsList, &m_statement2) + , m_statementListPart3(&m_statementListPart2, &m_statement3) + , m_sourceElement1(nullptr) + , m_sourceElement2(nullptr) + , m_sourceElement3(nullptr) + , m_threeSourceElementsList(&m_sourceElement1) + , m_sourceElementsListPart2(&m_threeSourceElementsList, &m_sourceElement2) + , m_sourceElementsListPart3(&m_sourceElementsListPart2, &m_sourceElement3) + /* Function declaration */ + , m_twoParameters(m_someIdentifierStringRef) + , m_parameterListPart2(&m_twoParameters, m_anotherIdentifierStringRef) + , m_functionBody(nullptr) + , m_functionDeclarationWithoutParameters(m_someIdentifierStringRef, nullptr, &m_functionBody) + , m_functionDeclarationWithParameters(m_someIdentifierStringRef, &m_twoParameters , &m_functionBody) + , m_functionDeclarationWithoutBody(m_someIdentifierStringRef, &m_twoParameters, nullptr) + /* Switch */ + , m_caseClause1(&m_trueExpression, &m_threeStatementsList) + , m_caseClause2(&m_trueExpression, &m_threeStatementsList) + , m_twoCaseClauses(&m_caseClause1) + , m_twoCaseClausesPart2(&m_twoCaseClauses, &m_caseClause2) + , m_defaultClause(&m_threeStatementsList) + , m_caseBlock(&m_twoCaseClauses, &m_defaultClause, &m_twoCaseClauses) + , m_caseBlockOnlyCases(&m_twoCaseClauses) + , m_caseBlockCasesAndDefault(&m_twoCaseClauses, &m_defaultClause) + , m_caseBlockCasesDefaultCases(&m_twoCaseClauses, &m_defaultClause, &m_twoCaseClauses) + , m_caseClause(&m_trueExpression, &m_threeStatementsList) + , m_switchStatement(&m_trueExpression, &m_caseBlock) + { + m_statementListPart3.finish(); + m_sourceElementsListPart3.finish(); + m_parameterListPart2.finish(); + m_twoConstDeclarationsPart2.finish(true); + m_twoVarDeclarationsPart2.finish(false); + m_twoCaseClausesPart2.finish(); + } + +private: + PureJavaScriptGenerator* asPureJSGen(QQmlJS::AST::Visitor* generator) { + return static_cast(generator); + } + QQmlJS::AST::Visitor *m_generator; + + const QString m_someLabel; + const QStringRef m_someLabelStringRef; + const QString m_someString; + const QStringRef m_someStringStringRef; + const QString m_someIdentifier; + const QStringRef m_someIdentifierStringRef; + const QString m_anotherIdentifier; + const QStringRef m_anotherIdentifierStringRef; + + /* Expressions */ + AST::IdentifierExpression m_identifierExpression; + AST::NumericLiteral m_numericalExpressionPi; + AST::StringLiteral m_stringLiteral; + AST::TrueLiteral m_trueExpression; + AST::FalseLiteral m_falseExpression; + + AST::BinaryExpression m_equalsBinaryExpression; + + AST::PostDecrementExpression m_postDecrementExpression; + AST::PostIncrementExpression m_postIncrementExpression; + AST::PreDecrementExpression m_preDecrementExpression; + AST::PreIncrementExpression m_preIncrementExpression; + + /* Variable Declarations */ + AST::VariableDeclaration m_constDeclaration1; + AST::VariableDeclaration m_constDeclaration2; + AST::VariableDeclaration m_variableDeclarationWithAssignment; + AST::VariableDeclaration m_variableDeclarationWithoutAssignment; + AST::VariableDeclarationList m_twoConstDeclarations; + AST::VariableDeclarationList m_twoConstDeclarationsPart2; + AST::VariableDeclarationList m_twoVarDeclarations; + AST::VariableDeclarationList m_twoVarDeclarationsPart2; + + /* Statements */ + AST::BreakStatement m_breakStatementWithLabel; + AST::BreakStatement m_breakStatementWithoutLabel; + AST::ContinueStatement m_continueStatementWithLabel; + AST::ContinueStatement m_continueStatementWithoutLabel; + AST::EmptyStatement m_emptyStatement; + AST::EmptyStatement m_statement1; + AST::EmptyStatement m_statement2; + AST::EmptyStatement m_statement3; + AST::ExpressionStatement m_expressionStatement; + AST::IfStatement m_ifStatementWithoutElse; + AST::IfStatement m_ifStatementWithElse; + AST::ReturnStatement m_returnStatementWithoutValue; + AST::ReturnStatement m_returnStatementWithValue; + AST::VariableStatement m_variableStatement; + AST::Block m_block; + AST::StatementList m_threeStatementsList; + AST::StatementList m_statementListPart2; + AST::StatementList m_statementListPart3; + AST::StatementSourceElement m_sourceElement1; + AST::StatementSourceElement m_sourceElement2; + AST::StatementSourceElement m_sourceElement3; + AST::SourceElements m_threeSourceElementsList; + AST::SourceElements m_sourceElementsListPart2; + AST::SourceElements m_sourceElementsListPart3; + + /* Function declarations */ + AST::FormalParameterList m_twoParameters; + AST::FormalParameterList m_parameterListPart2; + AST::FunctionBody m_functionBody; + AST::FunctionDeclaration m_functionDeclarationWithoutParameters; + AST::FunctionDeclaration m_functionDeclarationWithParameters; + AST::FunctionDeclaration m_functionDeclarationWithoutBody; + + /* Switch */ + AST::CaseClause m_caseClause1; + AST::CaseClause m_caseClause2; + AST::CaseClauses m_twoCaseClauses; + AST::CaseClauses m_twoCaseClausesPart2; + AST::DefaultClause m_defaultClause; + AST::CaseBlock m_caseBlock; + AST::CaseBlock m_caseBlockOnlyCases; + AST::CaseBlock m_caseBlockCasesAndDefault; + AST::CaseBlock m_caseBlockCasesDefaultCases; + AST::CaseClause m_caseClause; + AST::SwitchStatement m_switchStatement; + +private slots: + void init() { + m_generator = new PureJavaScriptGenerator(); + }; + + void cleanup() { + delete m_generator; + } + + void testGeneratedCodeIsInitiallyEmpty() { + QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("")); + } + + void test_getGeneratedCode_getsTopOfStack() { + // Prepare + asPureJSGen(m_generator)->m_outputStack << "1"; + + // Verify + QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("1")); + } + + void test_getGeneratedCode_throwsError_OnStackSizeGreaterThanOne() { + // Prepare + asPureJSGen(m_generator)->m_outputStack << "1" << "2"; + + // Verify + QVERIFY_EXCEPTION_THROWN(asPureJSGen(m_generator)->getGeneratedCode(), QmlJSc::Error); + } + + TEST_VISIT_DEFAULT_IMPL_(Block , m_block) + TEST_VISIT_PUTS_ON_STACK(BreakStatement , WithLabel , "break ALabel;" , m_breakStatementWithLabel) + TEST_VISIT_PUTS_ON_STACK(BreakStatement , WithoutLabel , "break;" , m_breakStatementWithoutLabel) + TEST_VISIT_DEFAULT_IMPL_(CaseBlock , m_caseBlock) + TEST_VISIT_PUTS_ON_STACK(CaseClause , AnyCase , "case" , m_caseClause1) + TEST_VISIT_DEFAULT_IMPL_(CaseClauses , m_twoCaseClauses) + TEST_VISIT_PUTS_ON_STACK(DefaultClause , Default , "default" , m_defaultClause) + TEST_VISIT_PUTS_ON_STACK(ContinueStatement , WithLabel , "continue ALabel;", m_continueStatementWithLabel) + TEST_VISIT_PUTS_ON_STACK(ContinueStatement , WithoutLabel , "continue;" , m_continueStatementWithoutLabel) + TEST_VISIT_DEFAULT_IMPL_(EmptyStatement , m_emptyStatement) + TEST_VISIT_DEFAULT_IMPL_(ExpressionStatement , m_expressionStatement) + TEST_VISIT_PUTS_ON_STACK(FormalParameterList , OneParameter , "e" , m_parameterListPart2) + TEST_VISIT_PUTS_ON_STACK(FormalParameterList , TwoParameters , "i,e" , m_twoParameters) + TEST_VISIT_DEFAULT_IMPL_(FunctionBody , m_functionBody) + TEST_VISIT_DEFAULT_IMPL_(FunctionDeclaration , m_functionDeclarationWithParameters) + TEST_VISIT_PUTS_ON_STACK(IdentifierExpression , AnyCase , "i" , m_identifierExpression) + TEST_VISIT_DEFAULT_IMPL_(IfStatement , m_ifStatementWithoutElse) + TEST_VISIT_PUTS_ON_STACK(NumericLiteral , Pi , "3.14" , m_numericalExpressionPi) + TEST_VISIT_DEFAULT_IMPL_(PostDecrementExpression , m_postDecrementExpression) + TEST_VISIT_DEFAULT_IMPL_(PostIncrementExpression , m_postIncrementExpression) + TEST_VISIT_DEFAULT_IMPL_(PreDecrementExpression , m_preDecrementExpression) + TEST_VISIT_DEFAULT_IMPL_(PreIncrementExpression , m_preIncrementExpression) + TEST_VISIT_DEFAULT_IMPL_(ReturnStatement , m_returnStatementWithoutValue) + TEST_VISIT_DEFAULT_IMPL_(SourceElements , m_threeSourceElementsList) + TEST_VISIT_DEFAULT_IMPL_(StatementList , m_threeStatementsList) + TEST_VISIT_PUTS_ON_STACK(StringLiteral , AnyCase , "\"some string\"" , m_stringLiteral) + TEST_VISIT_DEFAULT_IMPL_(SwitchStatement , m_switchStatement) + TEST_VISIT_DEFAULT_IMPL_(VariableDeclaration , m_variableDeclarationWithoutAssignment) + TEST_VISIT_DEFAULT_IMPL_(VariableDeclarationList , m_twoConstDeclarations) + TEST_VISIT_DEFAULT_IMPL_(VariableStatement , m_variableStatement) + + TEST_ENDVISIT_REDUCES(BinaryExpression , TwoOperands , "2==4" , ({"==", "2", "4"}) , m_equalsBinaryExpression) + TEST_ENDVISIT_REDUCES(Block , AnyCase , "{content}" , ({"content"}) , m_block) + TEST_ENDVISIT_REDUCES(CaseBlock , OnlyCases , "{cases;}" , ({"cases;"}) , m_caseBlockOnlyCases) + TEST_ENDVISIT_REDUCES(CaseBlock , CasesAndDefault , "{cases;default;}", ({"cases;", "default;"}) , m_caseBlockCasesAndDefault) + TEST_ENDVISIT_REDUCES(CaseBlock , CasesDefaultCases , "{cases;default;cases;}", ({"cases;", "default;", "cases;"}), m_caseBlockCasesDefaultCases) + TEST_ENDVISIT_REDUCES(CaseClause , CaseWithStatement , "case exp:stm;" , ({"case", "exp", "stm;"}) , m_caseClause) + TEST_ENDVISIT_REDUCES(CaseClauses , TwoClauses , "case e:s;case e2:s2;", ({"case e:s;", "case e2:s2;"}), m_twoCaseClauses) + TEST_ENDVISIT_REDUCES(DefaultClause , AnyCase , "default:stm" , ({"default", "stm"}) , m_defaultClause) + TEST_ENDVISIT_REDUCES(EmptyStatement , DefaultScenario , ";" , ({}) , m_emptyStatement) + TEST_ENDVISIT_REDUCES(ExpressionStatement , AnyCase , "expression;" , ({"expression"}) , m_expressionStatement) + TEST_ENDVISIT_REDUCES(FormalParameterList , AnyCase , "i" , ({"i"}) , m_twoParameters) // does nothing + TEST_ENDVISIT_REDUCES(FunctionBody , ClosesCorrectly , "{func}" , ({"func"}) , m_functionBody) + TEST_ENDVISIT_REDUCES(FunctionDeclaration , BodyNoParameters , "function i(){body}" , ({"{body}"}) , m_functionDeclarationWithoutParameters) + TEST_ENDVISIT_REDUCES(FunctionDeclaration , BodyParameters , "function i(para){body}", ({"para", "{body}"}) , m_functionDeclarationWithParameters) + TEST_ENDVISIT_REDUCES(FunctionDeclaration , WithoutBody , "function i(para){}" , ({"para"}) , m_functionDeclarationWithoutBody) + TEST_ENDVISIT_REDUCES(IdentifierExpression , AnyCase , "abc" , ({"abc"}) , m_identifierExpression) + TEST_ENDVISIT_REDUCES(IfStatement , OnlyIf , "if(exp)stm;" , ({"exp", "stm;"}) , m_ifStatementWithoutElse) + TEST_ENDVISIT_REDUCES(IfStatement , IfElse , "if(exp)s;else s;", ({"exp", "s;", "s;"}) , m_ifStatementWithElse) + TEST_ENDVISIT_REDUCES(NumericLiteral , AnyCase , "2.7" , ({"2.7"}) , m_numericalExpressionPi) + TEST_ENDVISIT_REDUCES(PostDecrementExpression , AnyCase , "2.7--" , ({"2.7"}) , m_postDecrementExpression) + TEST_ENDVISIT_REDUCES(PostIncrementExpression , AnyCase , "2.7++" , ({"2.7"}) , m_postIncrementExpression) + TEST_ENDVISIT_REDUCES(PreDecrementExpression , AnyCase , "--2.7" , ({"2.7"}) , m_preDecrementExpression) + TEST_ENDVISIT_REDUCES(PreIncrementExpression , AnyCase , "++2.7" , ({"2.7"}) , m_preIncrementExpression) + TEST_ENDVISIT_REDUCES(ReturnStatement , WithoutReturnValue, "return;" , ({}) , m_returnStatementWithoutValue) + TEST_ENDVISIT_REDUCES(ReturnStatement , WithReturnValue , "return true;" , ({"true"}) , m_returnStatementWithValue) + TEST_ENDVISIT_REDUCES(SourceElements , ThreeSrcElements , "sEl1sEl2sEl3" , ({"sEl1", "sEl2", "sEl3"}) , m_threeSourceElementsList) + TEST_ENDVISIT_REDUCES(SourceElements , ThreeStatements , "st1st2st3" , ({"st1", "st2", "st3"}) , m_threeStatementsList) + TEST_ENDVISIT_REDUCES(StringLiteral , AnyCase , "another string" , ({"another string"}) , m_stringLiteral) + TEST_ENDVISIT_REDUCES(SwitchStatement , AnyCase , "switch(e){blk}" , ({"e", "{blk}"}) , m_switchStatement) + TEST_ENDVISIT_REDUCES(VariableDeclaration , WithAssignment , "i=5" , ({"5"}) , m_variableDeclarationWithAssignment) + TEST_ENDVISIT_REDUCES(VariableDeclaration , WithoutAssignment , "i" , ({}) , m_variableDeclarationWithoutAssignment) + TEST_ENDVISIT_REDUCES(VariableDeclarationList , TwoDeclarations , "var i,e=5" , ({"i", "e=5"}) , m_twoVarDeclarations) + TEST_ENDVISIT_REDUCES(VariableDeclarationList , OneDeclaration , "var e=5" , ({"e=5"}) , m_twoVarDeclarationsPart2) + TEST_ENDVISIT_REDUCES(VariableDeclarationList , ConstDeclaration , "const e=5" , ({"e=5"}) , m_twoConstDeclarationsPart2) + TEST_ENDVISIT_REDUCES(VariableStatement , AnyCase , "x;" , ({"x"}) , m_variableStatement) + + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Assign, "=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceAdd, "+=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceSub, "-=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceMul, "*=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceDiv, "/=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceMod, "%=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceLeftShift, "<<=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceRightShift, ">>=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceURightShift, ">>>=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceAnd, "&=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceXor, "^=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InplaceOr, "|=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Add, "+") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Sub, "-") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Mul, "*") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Div, "/") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Mod, "%") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(LShift, "<<") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(RShift, ">>") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(URShift, ">>>") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(BitAnd, "&") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(BitXor, "^") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(BitOr, "|") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Equal, "==") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(NotEqual, "!=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(StrictEqual, "===") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(StrictNotEqual, "!==") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Gt, ">") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Ge, ">=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Lt, "<") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Le, "<=") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(And, "&&") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(Or, "||") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(In, " in ") + TEST_VISIT_BINARYOP_PUTS_ON_STACK(InstanceOf, " instanceof ") +}; + +QTEST_MAIN(TestPureJavaScriptGenerator) +#include "testpurejavascriptgenerator.moc" diff --git a/tests/auto/qmljsc/testpurejavascriptgenerator_integration.cpp b/tests/auto/qmljsc/testpurejavascriptgenerator_integration.cpp new file mode 100644 --- /dev/null +++ b/tests/auto/qmljsc/testpurejavascriptgenerator_integration.cpp @@ -0,0 +1,111 @@ +/* + * 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 "../../../src/qmljsc/purejavascriptgenerator.h" + +#include +#include +#include + +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QQmlJS::AST::Node*) + +class TestPureJavaScriptGeneratorIntegration + : public QObject +{ + Q_OBJECT + +private: + QQmlJS::AST::Node* astForFile(QString fileName) { + QString input = fileContent(fileName); + + QQmlJS::Engine* engine = new QQmlJS::Engine(); + QQmlJS::Lexer* lexer = new QQmlJS::Lexer(engine); + + lexer->setCode(input, 1, true); + + QQmlJS::Parser* parser = new QQmlJS::Parser(engine); + + if (!parser->parseProgram()) { + qDebug() << parser->errorMessage(); + } + + return parser->rootNode(); + } + + QString fileContent(QString fileName) { + QFile inputFile(fileName); + Q_ASSERT_X(inputFile.open(QFile::ReadOnly), __FILE__, "File could not be opened. Is it added as resource in the CMakeLists.txt?"); + return QString(inputFile.readAll()); + } + + void addRowForFileWithCompiled(QString sourceFileName, QString compiledFileName) { + const QString folder(":/test/%1.js"); + QTest::newRow(sourceFileName.toLocal8Bit()) << astForFile(folder.arg(sourceFileName)) << fileContent(folder.arg(compiledFileName)); + + } + + void addRowForFile(QString fileName) { + addRowForFileWithCompiled(fileName, fileName); + } + + void addRowForFileWithCompiled(QString fileName) { + addRowForFileWithCompiled(fileName, QString("%1.compiled").arg(fileName)); + } + +private slots: + + void test_compileJavaScriptFile_data() { + QTest::addColumn("ast"); + QTest::addColumn("expectedOutput"); + + addRowForFileWithCompiled("declarations"); + addRowForFile("expressions"); + addRowForFileWithCompiled("functions"); + addRowForFileWithCompiled("binaryoperations"); + addRowForFileWithCompiled("otherstatements"); + addRowForFileWithCompiled("ifstatements"); + addRowForFileWithCompiled("switchstatements"); + } + + void test_compileJavaScriptFile() { + // Prepare + QFETCH(QQmlJS::AST::Node*, ast); + QFETCH(QString, expectedOutput); + + PureJavaScriptGenerator generator; + + // Do + ast->accept(&generator); + + // Verify + QCOMPARE(generator.getGeneratedCode(), expectedOutput); + } + + + +}; + +QTEST_MAIN(TestPureJavaScriptGeneratorIntegration) +#include "testpurejavascriptgenerator_integration.moc"