Improve documentation.
[profile/ivi/qtdeclarative.git] / src / qml / qml / qqmlrewrite.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qqmlrewrite_p.h"
43
44 #include <private/qqmlglobal_p.h>
45
46 #include <QtCore/qdebug.h>
47
48 QT_BEGIN_NAMESPACE
49
50 DEFINE_BOOL_CONFIG_OPTION(rewriteDump, QML_REWRITE_DUMP)
51
52 namespace QQmlRewrite {
53
54 static void rewriteStringLiteral(AST::StringLiteral *ast, const QString *code, int startPosition, TextWriter *writer)
55 {
56     const unsigned position = ast->firstSourceLocation().begin() - startPosition + 1;
57     const unsigned length = ast->literalToken.length - 2;
58     const QStringRef spell = code->midRef(position, length);
59     const int end = spell.size();
60     int index = 0;
61
62     while (index < end) {
63         const QChar ch = spell.at(index++);
64
65         if (index < end && ch == QLatin1Char('\\')) {
66             int pos = index;
67
68             // skip a possibly empty sequence of \r characters
69             while (pos < end && spell.at(pos) == QLatin1Char('\r'))
70                 ++pos;
71
72             if (pos < end && spell.at(pos) == QLatin1Char('\n')) {
73                 // This is a `\' followed by a newline terminator.
74                 // In this case there's nothing to replace. We keep the code
75                 // as it is and we resume the searching.
76                 index = pos + 1; // refresh the index
77             }
78         } else if (ch == QLatin1Char('\r') || ch == QLatin1Char('\n')) {
79             const QString sep = ch == QLatin1Char('\r') ? QLatin1String("\\r") : QLatin1String("\\n");
80             const int pos = index - 1;
81             QString s = sep;
82
83             while (index < end && spell.at(index) == ch) {
84                 s += sep;
85                 ++index;
86             }
87
88             writer->replace(position + pos, index - pos, s);
89         }
90     }
91 }
92
93 SharedBindingTester::SharedBindingTester()
94 : _sharable(false), _safe(false)
95 {
96 }
97
98 void SharedBindingTester::parse(const QString &code)
99 {
100     _sharable = _safe = false;
101
102     Engine engine;
103     Lexer lexer(&engine);
104     Parser parser(&engine);
105     lexer.setCode(code, 0);
106     parser.parseStatement();
107     if (!parser.statement()) 
108         return;
109
110     return parse(parser.statement());
111 }
112
113 void SharedBindingTester::parse(AST::Node *node)
114 {
115     _sharable = true;
116     _safe = true;
117
118     AST::Node::acceptChild(node, this);
119 }
120
121 bool SharedBindingTester::visit(AST::FunctionDeclaration *)
122 {
123     _sharable = false;
124     return false;
125 }
126
127 bool SharedBindingTester::visit(AST::FunctionExpression *)
128 {
129     _sharable = false;
130     return false;
131 }
132
133 bool SharedBindingTester::visit(AST::CallExpression *e)
134 {
135     static const QString mathString = QStringLiteral("Math");
136
137     if (AST::IdentifierExpression *ie = AST::cast<AST::IdentifierExpression *>(e->base)) {
138         if (ie->name == mathString)
139             return true;
140     }
141
142     _safe = false;
143     return true;
144 }
145
146 bool SharedBindingTester::visit(AST::IdentifierExpression *e)
147 {
148     static const QString evalString = QStringLiteral("eval");
149     if (e->name == evalString)
150         _sharable = false;
151
152     return false; // IdentifierExpression is a leaf node anyway
153 }
154
155 bool SharedBindingTester::visit(AST::PostDecrementExpression *)
156 {
157     _safe = false;
158     return true;
159 }
160
161 bool SharedBindingTester::visit(AST::PostIncrementExpression *)
162 {
163     _safe = false;
164     return true;
165 }
166
167 bool SharedBindingTester::visit(AST::PreDecrementExpression *)
168 {
169     _safe = false;
170     return true;
171 }
172
173 bool SharedBindingTester::visit(AST::PreIncrementExpression *)
174 {
175     _safe = false;
176     return true;
177 }
178
179 bool SharedBindingTester::visit(AST::BinaryExpression *e)
180 {
181     if (e->op == QSOperator::InplaceAnd ||
182         e->op == QSOperator::Assign ||
183         e->op == QSOperator::InplaceSub ||
184         e->op == QSOperator::InplaceDiv ||
185         e->op == QSOperator::InplaceAdd ||
186         e->op == QSOperator::InplaceLeftShift ||
187         e->op == QSOperator::InplaceMod ||
188         e->op == QSOperator::InplaceMul ||
189         e->op == QSOperator::InplaceOr ||
190         e->op == QSOperator::InplaceRightShift ||
191         e->op == QSOperator::InplaceURightShift ||
192         e->op == QSOperator::InplaceXor)
193         _safe = false;
194
195     return true;
196 }
197
198 QString RewriteBinding::operator()(const QString &code, bool *ok, bool *sharable, bool *safe)
199 {
200     Engine engine;
201     Lexer lexer(&engine);
202     Parser parser(&engine);
203     lexer.setCode(code, 0);
204     parser.parseStatement();
205     if (!parser.statement()) {
206         if (ok) *ok = false;
207         return QString();
208     } else {
209         if (ok) *ok = true;
210         if (sharable || safe) {
211             SharedBindingTester tester;
212             tester.parse(parser.statement());
213             if (sharable) *sharable = tester.isSharable();
214             if (safe) *safe = tester.isSafe();
215         }
216     }
217     return rewrite(code, 0, parser.statement());
218 }
219
220 QString RewriteBinding::operator()(QQmlJS::AST::Node *node, const QString &code, bool *sharable, bool *safe)
221 {
222     if (!node)
223         return code;
224
225     if (sharable || safe) {
226         SharedBindingTester tester;
227         tester.parse(node);
228         if (sharable) *sharable = tester.isSharable();
229         if (safe) *safe = tester.isSafe();
230     }
231
232     QQmlJS::AST::ExpressionNode *expression = node->expressionCast();
233     QQmlJS::AST::Statement *statement = node->statementCast();
234     if(!expression && !statement)
235         return code;
236
237     TextWriter w;
238     _writer = &w;
239     _position = expression ? expression->firstSourceLocation().begin() : statement->firstSourceLocation().begin();
240     _inLoop = 0;
241     _code = &code;
242
243     accept(node);
244
245     unsigned startOfStatement = 0;
246     unsigned endOfStatement = (expression ? expression->lastSourceLocation().end() : statement->lastSourceLocation().end()) - _position;
247
248     QString startString = QLatin1String("(function ") + _name + QLatin1String("() { ");
249     if (expression)
250         startString += QLatin1String("return ");
251     _writer->replace(startOfStatement, 0, startString);
252     _writer->replace(endOfStatement, 0, QLatin1String(" })"));
253
254     if (rewriteDump()) {
255         qWarning() << "=============================================================";
256         qWarning() << "Rewrote:";
257         qWarning() << qPrintable(code);
258     }
259
260     QString codeCopy = code;
261     w.write(&codeCopy);
262
263     if (rewriteDump()) {
264         qWarning() << "To:";
265         qWarning() << qPrintable(codeCopy);
266         qWarning() << "=============================================================";
267     }
268
269     return codeCopy;
270 }
271
272 void RewriteBinding::accept(AST::Node *node)
273 {
274     AST::Node::acceptChild(node, this);
275 }
276
277 QString RewriteBinding::rewrite(QString code, unsigned position,
278                                 AST::Statement *node)
279 {
280     TextWriter w;
281     _writer = &w;
282     _position = position;
283     _inLoop = 0;
284     _code = &code;
285
286     accept(node);
287
288     unsigned startOfStatement = node->firstSourceLocation().begin() - _position;
289     unsigned endOfStatement = node->lastSourceLocation().end() - _position;
290
291     _writer->replace(startOfStatement, 0, QLatin1String("(function ") + _name + QLatin1String("() { "));
292     _writer->replace(endOfStatement, 0, QLatin1String(" })"));
293
294     if (rewriteDump()) {
295         qWarning() << "=============================================================";
296         qWarning() << "Rewrote:";
297         qWarning() << qPrintable(code);
298     }
299
300     w.write(&code);
301
302     if (rewriteDump()) {
303         qWarning() << "To:";
304         qWarning() << qPrintable(code);
305         qWarning() << "=============================================================";
306     }
307
308     return code;
309 }
310
311 bool RewriteBinding::visit(AST::Block *ast)
312 {
313     for (AST::StatementList *it = ast->statements; it; it = it->next) {
314         if (! it->next) {
315             // we need to rewrite only the last statement of a block.
316             accept(it->statement);
317         }
318     }
319
320     return false;
321 }
322
323 bool RewriteBinding::visit(AST::ExpressionStatement *ast)
324 {
325     if (! _inLoop) {
326         unsigned startOfExpressionStatement = ast->firstSourceLocation().begin() - _position;
327         _writer->replace(startOfExpressionStatement, 0, QLatin1String("return "));
328     }
329
330     return false;
331 }
332
333 bool RewriteBinding::visit(AST::StringLiteral *ast)
334 {
335     rewriteStringLiteral(ast, _code, _position, _writer);
336     return false;
337 }
338
339 bool RewriteBinding::visit(AST::DoWhileStatement *)
340 {
341     ++_inLoop;
342     return true;
343 }
344
345 void RewriteBinding::endVisit(AST::DoWhileStatement *)
346 {
347     --_inLoop;
348 }
349
350 bool RewriteBinding::visit(AST::WhileStatement *)
351 {
352     ++_inLoop;
353     return true;
354 }
355
356 void RewriteBinding::endVisit(AST::WhileStatement *)
357 {
358     --_inLoop;
359 }
360
361 bool RewriteBinding::visit(AST::ForStatement *)
362 {
363     ++_inLoop;
364     return true;
365 }
366
367 void RewriteBinding::endVisit(AST::ForStatement *)
368 {
369     --_inLoop;
370 }
371
372 bool RewriteBinding::visit(AST::LocalForStatement *)
373 {
374     ++_inLoop;
375     return true;
376 }
377
378 void RewriteBinding::endVisit(AST::LocalForStatement *)
379 {
380     --_inLoop;
381 }
382
383 bool RewriteBinding::visit(AST::ForEachStatement *)
384 {
385     ++_inLoop;
386     return true;
387 }
388
389 void RewriteBinding::endVisit(AST::ForEachStatement *)
390 {
391     --_inLoop;
392 }
393
394 bool RewriteBinding::visit(AST::LocalForEachStatement *)
395 {
396     ++_inLoop;
397     return true;
398 }
399
400 void RewriteBinding::endVisit(AST::LocalForEachStatement *)
401 {
402     --_inLoop;
403 }
404
405 bool RewriteBinding::visit(AST::FunctionExpression *)
406 {
407     return false;
408 }
409
410 bool RewriteBinding::visit(AST::FunctionDeclaration *)
411 {
412     return false;
413 }
414
415 bool RewriteBinding::visit(AST::CaseBlock *ast)
416 {
417     // Process the initial sequence of the case clauses.
418     for (AST::CaseClauses *it = ast->clauses; it; it = it->next) {
419         // Return the value of the last statement in the block, if this is the last `case clause'
420         // of the switch statement.
421         bool returnTheValueOfLastStatement = (it->next == 0) && (ast->defaultClause == 0) && (ast->moreClauses == 0);
422
423         if (AST::CaseClause *clause = it->clause) {
424             accept(clause->expression);
425             rewriteCaseStatements(clause->statements, returnTheValueOfLastStatement);
426         }
427     }
428
429     // Process the default case clause
430     if (ast->defaultClause) {
431         // Return the value of the last statement in the block, if this is the last `case clause'
432         // of the switch statement.
433         bool rewriteTheLastStatement = (ast->moreClauses == 0);
434
435         rewriteCaseStatements(ast->defaultClause->statements, rewriteTheLastStatement);
436     }
437
438     // Process trailing `case clauses'
439     for (AST::CaseClauses *it = ast->moreClauses; it; it = it->next) {
440         // Return the value of the last statement in the block, if this is the last `case clause'
441         // of the switch statement.
442         bool returnTheValueOfLastStatement = (it->next == 0);
443
444         if (AST::CaseClause *clause = it->clause) {
445             accept(clause->expression);
446             rewriteCaseStatements(clause->statements, returnTheValueOfLastStatement);
447         }
448     }
449
450     return false;
451 }
452
453 void RewriteBinding::rewriteCaseStatements(AST::StatementList *statements, bool rewriteTheLastStatement)
454 {
455     for (AST::StatementList *it = statements; it; it = it->next) {
456         if (it->next && AST::cast<AST::BreakStatement *>(it->next->statement) != 0) {
457             // The value of the first statement followed by a `break'.
458             accept(it->statement);
459             break;
460         } else if (!it->next) {
461             if (rewriteTheLastStatement)
462                 accept(it->statement);
463             else if (AST::Block *block = AST::cast<AST::Block *>(it->statement))
464                 rewriteCaseStatements(block->statements, rewriteTheLastStatement);
465         }
466     }
467 }
468
469
470 /*
471     RewriteSignalHandler performs two different types of rewrites, depending on what information
472     is available.
473
474     When the target object is known, the rewriter can be provided a list of parameter names (and an
475     optional preconstructed parameter string), which allows us to:
476         1. Check whether the parameters are used
477         2. Rewrite with the parameters included in the rewrite
478     When this information is not available, we do a more generic rewrite, and rely on the expression
479     to perform a second rewrite with the parameter information (using createParameterString)
480     once the target object is known.
481 */
482 RewriteSignalHandler::RewriteSignalHandler()
483     : _writer(0)
484     , _code(0)
485     , _position(0)
486     , _parameterAccess(UnknownAccess)
487     , _parameterCountForJS(0)
488 {
489 }
490
491 void RewriteSignalHandler::accept(AST::Node *node)
492 {
493     AST::Node::acceptChild(node, this);
494 }
495
496 bool RewriteSignalHandler::visit(AST::StringLiteral *ast)
497 {
498     rewriteStringLiteral(ast, _code, _position, _writer);
499     return false;
500 }
501
502 //if we never make use of the signal parameters in our expression,
503 //there is no need to provide them
504 bool RewriteSignalHandler::visit(AST::IdentifierExpression *e)
505 {
506     //optimization: don't need to compare strings if a parameter has already been marked as used.
507     if (_parameterAccess == ParametersAccessed)
508         return false;
509
510     static const QString argumentsString = QStringLiteral("arguments");
511     if (_parameterNames.contains(e->name) || e->name == argumentsString)
512         _parameterAccess = ParametersAccessed;
513     return false;
514 }
515
516 static QString unnamed_error_string(QLatin1String(QT_TR_NOOP("Signal uses unnamed parameter followed by named parameter.")));
517 static QString global_error_string(QLatin1String(QT_TR_NOOP("Signal parameter \"%1\" hides global variable.")));
518
519 #define EXIT_ON_ERROR(error) \
520 { \
521     _error = error; \
522     return QString(); \
523 }
524
525 //create a parameter string which can be inserted into a generic rewrite
526 QString RewriteSignalHandler::createParameterString(const QList<QByteArray> &parameterNameList,
527                                                     const QStringHash<bool> &illegalNames)
528 {
529     QList<QHashedString> hashedParameterNameList;
530     for (int i = 0; i < parameterNameList.count(); ++i)
531         hashedParameterNameList.append(QString::fromUtf8(parameterNameList.at(i).constData()));
532
533     return createParameterString(hashedParameterNameList, illegalNames);
534 }
535
536 QString RewriteSignalHandler::createParameterString(const QList<QHashedString> &parameterNameList,
537                                                     const QStringHash<bool> &illegalNames)
538 {
539     QString parameters;
540     bool unnamedParam = false;
541     for (int i = 0; i < parameterNameList.count(); ++i) {
542         const QHashedString &param = parameterNameList.at(i);
543         if (param.isEmpty())
544             unnamedParam = true;
545         else if (unnamedParam)
546             EXIT_ON_ERROR(unnamed_error_string)
547         else if (illegalNames.contains(param))
548             EXIT_ON_ERROR(global_error_string.arg(param))
549         ++_parameterCountForJS;
550         parameters += param;
551         if (i < parameterNameList.count()-1)
552             parameters += QStringLiteral(",");
553     }
554     if (parameters.endsWith(QLatin1Char(',')))
555         parameters.resize(parameters.length() - 1);
556     return parameters;
557 }
558
559 /*
560     If \a parameterString is provided, use \a parameterNameList to test whether the
561     parameters are used in the body of the function
562       * if unused, the rewrite will not include parameters, else
563       * if used, the rewrite will use \a parameterString
564     If \a parameterString is not provided, it is constructed from \a parameterNameList
565     as needed.
566 */
567 QString RewriteSignalHandler::operator()(QQmlJS::AST::Node *node, const QString &code, const QString &name,
568                                          const QString &parameterString,
569                                          const QList<QByteArray> &parameterNameList,
570                                          const QStringHash<bool> &illegalNames)
571 {
572     if (rewriteDump()) {
573         qWarning() << "=============================================================";
574         qWarning() << "Rewrote:";
575         qWarning() << qPrintable(code);
576     }
577
578     bool hasParameterString = !parameterString.isEmpty();
579
580     QQmlJS::AST::ExpressionNode *expression = node->expressionCast();
581     QQmlJS::AST::Statement *statement = node->statementCast();
582     if (!expression && !statement)
583         return code;
584
585     if (!parameterNameList.isEmpty()) {
586         for (int i = 0; i < parameterNameList.count(); ++i) {
587             QHashedString param(QString::fromUtf8(parameterNameList.at(i).constData()));
588             _parameterNames.insert(param, i);
589             if (!hasParameterString)
590                 _parameterNameList.append(param);
591         }
592
593         //this is set to Unaccessed here, and will be set to Accessed
594         //if we detect that a parameter has been used
595         _parameterAccess = ParametersUnaccessed;
596     }
597
598     TextWriter w;
599     _writer = &w;
600     _code = &code;
601
602     _position = expression ? expression->firstSourceLocation().begin() : statement->firstSourceLocation().begin();
603     accept(node);
604
605     QString rewritten = code;
606     w.write(&rewritten);
607
608     QString parameters = (_parameterAccess == ParametersUnaccessed) ? QString()
609                                                                     : hasParameterString ? parameterString
610                                                                                          : createParameterString(_parameterNameList, illegalNames);
611     rewritten = QStringLiteral("(function ") + name + QStringLiteral("(") + parameters + QStringLiteral(") { ") + rewritten + QStringLiteral(" })");
612
613     if (rewriteDump()) {
614         qWarning() << "To:";
615         qWarning() << qPrintable(rewritten);
616         qWarning() << "=============================================================";
617     }
618
619     return rewritten;
620 }
621
622 QString RewriteSignalHandler::operator()(const QString &code, const QString &name, bool *ok,
623                                          const QList<QByteArray> &parameterNameList,
624                                          const QStringHash<bool> &illegalNames)
625 {
626     Engine engine;
627     Lexer lexer(&engine);
628     Parser parser(&engine);
629     lexer.setCode(code, 0);
630     parser.parseStatement();
631     if (!parser.statement()) {
632         if (ok) *ok = false;
633         return QString();
634     }
635     if (ok) *ok = true;
636     return operator()(parser.statement(), code, name, QString(), parameterNameList, illegalNames);
637 }
638
639 } // namespace QQmlRewrite
640
641 QT_END_NAMESPACE