diff --git a/src/qmljsc/purejavascriptgenerator.cpp b/src/qmljsc/purejavascriptgenerator.cpp index 7bfaa1a..2053976 100644 --- a/src/qmljsc/purejavascriptgenerator.cpp +++ b/src/qmljsc/purejavascriptgenerator.cpp @@ -1,233 +1,228 @@ /* * 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; default: Q_ASSERT(binaryExpression->op); } return true; } bool PureJavaScriptGenerator::visit(AST::Block *) { m_outputStack << "{"; return true; } bool PureJavaScriptGenerator::visit(AST::BreakStatement *breakStatement) { QString breakStatementCode("break"); if (breakStatement->label.length() > 0) { breakStatementCode.append(' '); breakStatementCode.append(breakStatement->label.toString()); } m_outputStack << breakStatementCode; return true; } bool PureJavaScriptGenerator::visit(AST::FormalParameterList *parameter) { m_outputStack << parameter->name.toString(); return true; } bool PureJavaScriptGenerator::visit(AST::FunctionBody *) { m_outputStack << "{"; return true; } bool PureJavaScriptGenerator::visit(AST::FunctionDeclaration *functionDeclaration) { const QString functionName = functionDeclaration->name.toString(); m_outputStack << QString("function") + ' ' + functionName; - if (!functionDeclaration->formals) { - // if no parameters are set, formals will be zero and therefore not be visited - // but a parameter string is needed on the stack - m_outputStack << ""; - } 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("%1").arg(numericLiteral->value)); return true; } bool PureJavaScriptGenerator::visit(AST::StringLiteral *stringLiteral) { m_outputStack.push(stringLiteral->value.toString()); return true; } bool PureJavaScriptGenerator::visit(AST::PostDecrementExpression *) { m_outputStack.push("--"); return true; } bool PureJavaScriptGenerator::visit(AST::PostIncrementExpression *) { m_outputStack.push("++"); return true; } bool PureJavaScriptGenerator::visit(AST::PreDecrementExpression *) { m_outputStack.push("--"); return true; } bool PureJavaScriptGenerator::visit(AST::PreIncrementExpression *) { m_outputStack.push("++"); return true; } bool PureJavaScriptGenerator::visit(AST::VariableDeclaration *variableDeclaration) { QString variableDeclarationCode; const QString identifier = variableDeclaration->name.toString(); if (variableDeclaration->readOnly) { variableDeclarationCode += "const"; } else { variableDeclarationCode += "var"; } variableDeclarationCode += ' '; variableDeclarationCode += identifier; if (variableDeclaration->expression) { variableDeclarationCode += '='; } m_outputStack << variableDeclarationCode; 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(); const QString openingParenthesis = m_outputStack.pop(); m_outputStack << QString("%1%2}").arg(openingParenthesis, blockCode); } void PureJavaScriptGenerator::endVisit(AST::EmptyStatement *) { m_outputStack << ";"; } void PureJavaScriptGenerator::endVisit(AST::ExpressionStatement *) { m_outputStack << m_outputStack.pop() + ';'; } void PureJavaScriptGenerator::endVisit(AST::FormalParameterList *parameterList) { reduceListStack(parameterList, ","); } void PureJavaScriptGenerator::endVisit(AST::FunctionBody *) { const QString body = m_outputStack.pop(); const QString openingBracket = m_outputStack.pop(); m_outputStack << openingBracket + body + '}'; } -void PureJavaScriptGenerator::endVisit(AST::FunctionDeclaration *) { +void PureJavaScriptGenerator::endVisit(AST::FunctionDeclaration *functionDeclaration) { const QString body = m_outputStack.pop(); - const QString parameters = m_outputStack.pop(); + const QString parameters = (functionDeclaration->formals)?m_outputStack.pop():""; const QString typeAndName = m_outputStack.pop(); m_outputStack << typeAndName + '(' + parameters + ')' + body; } void PureJavaScriptGenerator::endVisit(AST::IdentifierExpression *) { } void PureJavaScriptGenerator::endVisit(AST::NumericLiteral *) { } void PureJavaScriptGenerator::endVisit(AST::PostDecrementExpression *) { updateStackWithPostOperation(); } void PureJavaScriptGenerator::endVisit(AST::PostIncrementExpression *) { updateStackWithPostOperation(); } void PureJavaScriptGenerator::endVisit(AST::PreDecrementExpression *) { updateStackWithPreOperation(); } void PureJavaScriptGenerator::endVisit(AST::PreIncrementExpression *) { updateStackWithPreOperation(); } 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::VariableDeclaration *) { - const QString expression = m_outputStack.pop(); +void PureJavaScriptGenerator::endVisit(AST::VariableDeclaration *declaration) { + const QString expression = (declaration->expression)?m_outputStack.pop():""; const QString variableName = m_outputStack.pop(); m_outputStack << variableName + expression; } void PureJavaScriptGenerator::endVisit(AST::VariableStatement *) { m_outputStack << m_outputStack.pop() + ';'; } void PureJavaScriptGenerator::updateStackWithPostOperation() { const QString expression = m_outputStack.pop(); const QString operation = m_outputStack.pop(); m_outputStack.push(QString("%1%2").arg(expression).arg(operation)); } void PureJavaScriptGenerator::updateStackWithPreOperation() { const QString expression = m_outputStack.pop(); const QString operation = m_outputStack.pop(); m_outputStack.push(QString("%1%2").arg(operation).arg(expression)); } \ No newline at end of file diff --git a/tests/auto/data/javascript/expressions.js b/tests/auto/data/javascript/expressions.js index 231cd8e..d843e04 100644 --- a/tests/auto/data/javascript/expressions.js +++ b/tests/auto/data/javascript/expressions.js @@ -1 +1 @@ -var i=5;var e=i++;e=++i;e=i--;e=--i; \ No newline at end of file +var x;var i=5;var e=i++;e=++i;e=i--;e=--i; \ No newline at end of file diff --git a/tests/auto/qmljsc/testpurejavascriptgenerator.cpp b/tests/auto/qmljsc/testpurejavascriptgenerator.cpp index a228575..c547931 100644 --- a/tests/auto/qmljsc/testpurejavascriptgenerator.cpp +++ b/tests/auto/qmljsc/testpurejavascriptgenerator.cpp @@ -1,253 +1,256 @@ /* * 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/utils/error.h" #include "../../../src/qmljsc/purejavascriptgenerator.h" #define TEST_SOMEVISIT_PUTS_MULTIPLE_ON_STACK(testSituation, expectedStackContent, visitType, className, ...) \ void test_ ## visitType ## _ ## className ## _putsOnStack_ ## testSituation() { \ QQmlJS::AST::className classInstance(__VA_ARGS__); \ QStack expectedStack;\ expectedStack.append(QVectorexpectedStackContent);\ m_generator->visit(&classInstance); \ int stackElementCount = asPureJSGen(m_generator)->m_outputStack.size(); \ QCOMPARE(stackElementCount, expectedStack.size()); \ QCOMPARE(asPureJSGen(m_generator)->m_outputStack, expectedStack); \ } #define TEST_SOMEVISIT_PUTS_ON_STACK(testSituation, expectedStackContent, visitType, className, ...) \ TEST_SOMEVISIT_PUTS_MULTIPLE_ON_STACK(testSituation, ({expectedStackContent}), visitType, className, __VA_ARGS__) #define TEST_VISIT_PUTS_ON_STACK_WITHOUT_RETURN(testSituation, expectedResult, className, ...) \ TEST_SOMEVISIT_PUTS_ON_STACK(testSituation, expectedResult, visit, className, __VA_ARGS__) #define TEST_ENDVISIT_PUTS_ON_STACK(testSituation, expectedResult, className, ...) \ TEST_SOMEVISIT_PUTS_ON_STACK(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_PUTS_ON_STACK(testSituation, expectedResult, className, ...) \ TEST_VISIT_PUTS_ON_STACK_WITHOUT_RETURN(testSituation, expectedResult, className, __VA_ARGS__) \ TEST_VISIT_RETURNS(testSituation, true, className, __VA_ARGS__) #define TEST_VISIT_PUTS_NOTHING_ON_STACK(testSituation, className, ...) \ void test_ ## visitType ## _ ## className ## _putsNothingOnStack_ ## testSituation() { \ QQmlJS::AST::className classInstance(__VA_ARGS__); \ m_generator->visit(&classInstance); \ int stackElementCount = asPureJSGen(m_generator)->m_outputStack.count(); \ QCOMPARE(stackElementCount, 0); \ } #define TEST_VISIT_IS_DEFAULT_IMPLEMENTATION(className, ...) \ TEST_VISIT_RETURNS(defaultImplementation, true, className, __VA_ARGS__) \ TEST_VISIT_PUTS_NOTHING_ON_STACK(defaultImplementation, className, __VA_ARGS__) #define TEST_ENDVISIT_REDUCES_STACK(testScenario, expectedTopOfStack, stackContent, className, ...) \ void test_endVisit_ ## className ## _reducesStack_ ## testScenario() { \ QQmlJS::AST::className classInstance(__VA_ARGS__); \ asPureJSGen(m_generator)->m_outputStack.append(QVectorstackContent); \ m_generator->endVisit(&classInstance); \ int stackElementCount = asPureJSGen(m_generator)->m_outputStack.count(); \ QCOMPARE(stackElementCount, 1); \ QString stringOnStack = asPureJSGen(m_generator)->m_outputStack.top(); \ QCOMPARE(stringOnStack, QStringLiteral(expectedTopOfStack)); \ } #define TEST_ENDVISIT_REDUCES_STACK_OBJ(testScenario, expectedTopOfStack, stackContent, className, classInstancePtr) \ void test_endVisit_ ## className ## _reducesStack_ ## testScenario() { \ asPureJSGen(m_generator)->m_outputStack.append(QVectorstackContent); \ m_generator->endVisit(classInstancePtr); \ int stackElementCount = asPureJSGen(m_generator)->m_outputStack.count(); \ QCOMPARE(stackElementCount, 1); \ QString stringOnStack = asPureJSGen(m_generator)->m_outputStack.top(); \ QCOMPARE(stringOnStack, QStringLiteral(expectedTopOfStack)); \ } #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_outputStack.size(), 0); \ QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("")); \ } 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) , m_trueExpression() , m_falseExpression() , m_statement1() , m_statement2() , m_statement3() , 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) , m_twoParameters(m_someIdentifierStringRef) , m_parameterListPart2(&m_twoParameters, m_anotherIdentifierStringRef) { m_statementListPart3.finish(); m_sourceElementsListPart3.finish(); m_parameterListPart2.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; QQmlJS::AST::TrueLiteral m_trueExpression; QQmlJS::AST::FalseLiteral m_falseExpression; QQmlJS::AST::EmptyStatement m_statement1; QQmlJS::AST::EmptyStatement m_statement2; QQmlJS::AST::EmptyStatement m_statement3; QQmlJS::AST::StatementList m_threeStatementsList; QQmlJS::AST::StatementList m_statementListPart2; QQmlJS::AST::StatementList m_statementListPart3; QQmlJS::AST::StatementSourceElement m_sourceElement1; QQmlJS::AST::StatementSourceElement m_sourceElement2; QQmlJS::AST::StatementSourceElement m_sourceElement3; QQmlJS::AST::SourceElements m_threeSourceElementsList; QQmlJS::AST::SourceElements m_sourceElementsListPart2; QQmlJS::AST::SourceElements m_sourceElementsListPart3; QQmlJS::AST::FormalParameterList m_twoParameters; QQmlJS::AST::FormalParameterList m_parameterListPart2; 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_PUTS_ON_STACK(EqualOperator, "=", BinaryExpression, &m_trueExpression, QSOperator::Assign, &m_falseExpression) TEST_VISIT_PUTS_ON_STACK(WithoutStatements, "{", Block, nullptr) TEST_VISIT_PUTS_ON_STACK(WithLabel, "break ALabel", BreakStatement, m_someLabelStringRef) TEST_VISIT_PUTS_ON_STACK(WithoutLabel, "break", BreakStatement, nullptr) //TODO TEST_VISIT_IS_DEFAULT_IMPLEMENTATION(EmptyStatement) TEST_VISIT_IS_DEFAULT_IMPLEMENTATION(ExpressionStatement, nullptr) TEST_VISIT_PUTS_ON_STACK(OneParameter, "i", FormalParameterList, m_someIdentifierStringRef) TEST_VISIT_PUTS_ON_STACK(NoSourceElements, "{", FunctionBody, nullptr) TEST_VISIT_PUTS_ON_STACK(WithParameters, "function i", FunctionDeclaration, m_someIdentifierStringRef, &m_twoParameters, nullptr) - TEST_SOMEVISIT_PUTS_MULTIPLE_ON_STACK(WithoutParameters, ({"function i", ""}), visit, FunctionDeclaration, m_someIdentifierStringRef, nullptr, nullptr) + TEST_VISIT_PUTS_ON_STACK(WithoutParameters, "function i", FunctionDeclaration, m_someIdentifierStringRef, nullptr, nullptr) TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "i", IdentifierExpression, m_someIdentifierStringRef) TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "3.14", NumericLiteral, 3.14) TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "--", PostDecrementExpression, nullptr) TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "++", PostIncrementExpression, nullptr) TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "--", PreDecrementExpression, nullptr) TEST_VISIT_PUTS_ON_STACK(DefaultScenario, "++", PreIncrementExpression, nullptr) TEST_VISIT_IS_DEFAULT_IMPLEMENTATION(SourceElements, nullptr) TEST_VISIT_IS_DEFAULT_IMPLEMENTATION(StatementList, nullptr) TEST_VISIT_PUTS_ON_STACK(SomeString, "some string", StringLiteral, m_someStringStringRef) TEST_VISIT_PUTS_ON_STACK(AssignmentScenario, "var i=", VariableDeclaration, m_someIdentifierStringRef, &m_trueExpression) TEST_VISIT_PUTS_ON_STACK(NoAssignmentScenario, "var i", VariableDeclaration, m_someIdentifierStringRef, nullptr) void test_visit_VariableDeclaration_generatesCorrectCode_ConstAssignment() { // Prepare QQmlJS::AST::VariableDeclaration variableDeclaration(m_someIdentifierStringRef, &m_trueExpression); variableDeclaration.readOnly = true; // Do m_generator->visit(&variableDeclaration); // Verify QCOMPARE(asPureJSGen(m_generator)->getGeneratedCode(), QStringLiteral("const i=")); } TEST_VISIT_IS_DEFAULT_IMPLEMENTATION(VariableStatement, nullptr) TEST_ENDVISIT_REDUCES_STACK(TwoOperands, "2==4", ({"==", "2", "4"}), BinaryExpression, nullptr, -1, nullptr) TEST_ENDVISIT_REDUCES_STACK(WithoutStatements, "{content}", ({"{", "content"}), Block, nullptr) TEST_ENDVISIT_REDUCES_STACK_OBJ(DefaultScenario, ";", ({}), EmptyStatement, &m_statement1) TEST_ENDVISIT_REDUCES_STACK(NoExpression, "expression;", ({"expression"}), ExpressionStatement, nullptr) TEST_ENDVISIT_REDUCES_STACK_OBJ(TwoParameters, "i,e", ({"i", "e"}), FormalParameterList, &m_twoParameters) TEST_ENDVISIT_REDUCES_STACK(FunctionBodyClosesCorrectly, "{func}", ({"{", "func"}), FunctionBody, nullptr) - TEST_ENDVISIT_REDUCES_STACK(DefaultScenario, "name(parameters){body}", ({"name", "parameters", "{body}"}), - FunctionDeclaration, m_someIdentifierStringRef, nullptr, nullptr) + TEST_ENDVISIT_REDUCES_STACK(WithoutParameters, "name(){body}", ({"name", "{body}"}), + FunctionDeclaration, m_someIdentifierStringRef, nullptr, nullptr) + TEST_ENDVISIT_REDUCES_STACK(WithParameters, "name(parameters){body}", ({"name", "parameters", "{body}"}), + FunctionDeclaration, m_someIdentifierStringRef, &m_twoParameters, nullptr) TEST_ENDVISIT_REDUCES_STACK(DefaultScenario, "abc", ({"abc"}), IdentifierExpression, nullptr) TEST_ENDVISIT_REDUCES_STACK(OneElement, "2.7", ({"2.7"}), NumericLiteral, 3.14) TEST_ENDVISIT_REDUCES_STACK(OneLiteral, "2.7--", ({"--", "2.7"}), PostDecrementExpression, nullptr) TEST_ENDVISIT_REDUCES_STACK(OneLiteral, "2.7++", ({"++", "2.7"}), PostIncrementExpression, nullptr) TEST_ENDVISIT_REDUCES_STACK(OneLiteral, "--2.7", ({"--", "2.7"}), PreDecrementExpression, nullptr) TEST_ENDVISIT_REDUCES_STACK(OneLiteral, "++2.7", ({"++", "2.7"}), PreIncrementExpression, nullptr) TEST_ENDVISIT_REDUCES_STACK_OBJ(ThreeSourceElements, "sEl1sEl2sEl3", ({"sEl1", "sEl2", "sEl3"}), SourceElements, &m_threeSourceElementsList) TEST_ENDVISIT_REDUCES_STACK_OBJ(ThreeStatements, "st1st2st3", ({"st1", "st2", "st3"}), StatementList, &m_threeStatementsList) TEST_ENDVISIT_REDUCES_STACK(OneLiteral, "another string", ({"another string"}), StringLiteral, nullptr) - TEST_ENDVISIT_REDUCES_STACK(DefaultScenario, "var x=5", ({"var x=", "5"}), VariableDeclaration, m_someIdentifierStringRef, nullptr) + TEST_ENDVISIT_REDUCES_STACK(Assignment, "var x=5", ({"var x=", "5"}), VariableDeclaration, m_someIdentifierStringRef, &m_trueExpression) + TEST_ENDVISIT_REDUCES_STACK(NoAssignment, "var x", ({"var x"}), VariableDeclaration, m_someIdentifierStringRef, nullptr) TEST_ENDVISIT_REDUCES_STACK(DefaultScenario, "var x;", ({"var x"}), VariableStatement, nullptr) }; QTEST_MAIN(TestPureJavaScriptGenerator) #include "testpurejavascriptgenerator.moc"