Docs - add missing images and code, clean up sections
[profile/ivi/qtdeclarative.git] / src / qml / qml / qqmlrewrite.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
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 bool SharedBindingTester::isSharable(const QString &code)
94 {
95     Engine engine;
96     Lexer lexer(&engine);
97     Parser parser(&engine);
98     lexer.setCode(code, 0);
99     parser.parseStatement();
100     if (!parser.statement()) 
101         return false;
102
103     return isSharable(parser.statement());
104 }
105
106 bool SharedBindingTester::isSharable(AST::Node *node)
107 {
108     _sharable = true;
109     AST::Node::acceptChild(node, this);
110     return _sharable;
111 }
112
113 QString RewriteBinding::operator()(const QString &code, bool *ok, bool *sharable)
114 {
115     Engine engine;
116     Lexer lexer(&engine);
117     Parser parser(&engine);
118     lexer.setCode(code, 0);
119     parser.parseStatement();
120     if (!parser.statement()) {
121         if (ok) *ok = false;
122         return QString();
123     } else {
124         if (ok) *ok = true;
125         if (sharable) {
126             SharedBindingTester tester;
127             *sharable = tester.isSharable(parser.statement());
128         }
129     }
130     return rewrite(code, 0, parser.statement());
131 }
132
133 QString RewriteBinding::operator()(QQmlJS::AST::Node *node, const QString &code, bool *sharable)
134 {
135     if (!node)
136         return code;
137
138     if (sharable) {
139         SharedBindingTester tester;
140         *sharable = tester.isSharable(node);
141     }
142
143     QQmlJS::AST::ExpressionNode *expression = node->expressionCast();
144     QQmlJS::AST::Statement *statement = node->statementCast();
145     if(!expression && !statement)
146         return code;
147
148     TextWriter w;
149     _writer = &w;
150     _position = expression ? expression->firstSourceLocation().begin() : statement->firstSourceLocation().begin();
151     _inLoop = 0;
152     _code = &code;
153
154     accept(node);
155
156     unsigned startOfStatement = 0;
157     unsigned endOfStatement = (expression ? expression->lastSourceLocation().end() : statement->lastSourceLocation().end()) - _position;
158
159     QString startString = QLatin1String("(function ") + _name + QLatin1String("() { ");
160     if (expression)
161         startString += QLatin1String("return ");
162     _writer->replace(startOfStatement, 0, startString);
163     _writer->replace(endOfStatement, 0, QLatin1String(" })"));
164
165     if (rewriteDump()) {
166         qWarning() << "=============================================================";
167         qWarning() << "Rewrote:";
168         qWarning() << qPrintable(code);
169     }
170
171     QString codeCopy = code;
172     w.write(&codeCopy);
173
174     if (rewriteDump()) {
175         qWarning() << "To:";
176         qWarning() << qPrintable(codeCopy);
177         qWarning() << "=============================================================";
178     }
179
180     return codeCopy;
181 }
182
183 void RewriteBinding::accept(AST::Node *node)
184 {
185     AST::Node::acceptChild(node, this);
186 }
187
188 QString RewriteBinding::rewrite(QString code, unsigned position,
189                                 AST::Statement *node)
190 {
191     TextWriter w;
192     _writer = &w;
193     _position = position;
194     _inLoop = 0;
195     _code = &code;
196
197     accept(node);
198
199     unsigned startOfStatement = node->firstSourceLocation().begin() - _position;
200     unsigned endOfStatement = node->lastSourceLocation().end() - _position;
201
202     _writer->replace(startOfStatement, 0, QLatin1String("(function ") + _name + QLatin1String("() { "));
203     _writer->replace(endOfStatement, 0, QLatin1String(" })"));
204
205     if (rewriteDump()) {
206         qWarning() << "=============================================================";
207         qWarning() << "Rewrote:";
208         qWarning() << qPrintable(code);
209     }
210
211     w.write(&code);
212
213     if (rewriteDump()) {
214         qWarning() << "To:";
215         qWarning() << qPrintable(code);
216         qWarning() << "=============================================================";
217     }
218
219     return code;
220 }
221
222 bool RewriteBinding::visit(AST::Block *ast)
223 {
224     for (AST::StatementList *it = ast->statements; it; it = it->next) {
225         if (! it->next) {
226             // we need to rewrite only the last statement of a block.
227             accept(it->statement);
228         }
229     }
230
231     return false;
232 }
233
234 bool RewriteBinding::visit(AST::ExpressionStatement *ast)
235 {
236     if (! _inLoop) {
237         unsigned startOfExpressionStatement = ast->firstSourceLocation().begin() - _position;
238         _writer->replace(startOfExpressionStatement, 0, QLatin1String("return "));
239     }
240
241     return false;
242 }
243
244 bool RewriteBinding::visit(AST::StringLiteral *ast)
245 {
246     rewriteStringLiteral(ast, _code, _position, _writer);
247     return false;
248 }
249
250 bool RewriteBinding::visit(AST::DoWhileStatement *)
251 {
252     ++_inLoop;
253     return true;
254 }
255
256 void RewriteBinding::endVisit(AST::DoWhileStatement *)
257 {
258     --_inLoop;
259 }
260
261 bool RewriteBinding::visit(AST::WhileStatement *)
262 {
263     ++_inLoop;
264     return true;
265 }
266
267 void RewriteBinding::endVisit(AST::WhileStatement *)
268 {
269     --_inLoop;
270 }
271
272 bool RewriteBinding::visit(AST::ForStatement *)
273 {
274     ++_inLoop;
275     return true;
276 }
277
278 void RewriteBinding::endVisit(AST::ForStatement *)
279 {
280     --_inLoop;
281 }
282
283 bool RewriteBinding::visit(AST::LocalForStatement *)
284 {
285     ++_inLoop;
286     return true;
287 }
288
289 void RewriteBinding::endVisit(AST::LocalForStatement *)
290 {
291     --_inLoop;
292 }
293
294 bool RewriteBinding::visit(AST::ForEachStatement *)
295 {
296     ++_inLoop;
297     return true;
298 }
299
300 void RewriteBinding::endVisit(AST::ForEachStatement *)
301 {
302     --_inLoop;
303 }
304
305 bool RewriteBinding::visit(AST::LocalForEachStatement *)
306 {
307     ++_inLoop;
308     return true;
309 }
310
311 void RewriteBinding::endVisit(AST::LocalForEachStatement *)
312 {
313     --_inLoop;
314 }
315
316 bool RewriteBinding::visit(AST::FunctionExpression *)
317 {
318     return false;
319 }
320
321 bool RewriteBinding::visit(AST::FunctionDeclaration *)
322 {
323     return false;
324 }
325
326 bool RewriteBinding::visit(AST::CaseBlock *ast)
327 {
328     // Process the initial sequence of the case clauses.
329     for (AST::CaseClauses *it = ast->clauses; it; it = it->next) {
330         // Return the value of the last statement in the block, if this is the last `case clause'
331         // of the switch statement.
332         bool returnTheValueOfLastStatement = (it->next == 0) && (ast->defaultClause == 0) && (ast->moreClauses == 0);
333
334         if (AST::CaseClause *clause = it->clause) {
335             accept(clause->expression);
336             rewriteCaseStatements(clause->statements, returnTheValueOfLastStatement);
337         }
338     }
339
340     // Process the default case clause
341     if (ast->defaultClause) {
342         // Return the value of the last statement in the block, if this is the last `case clause'
343         // of the switch statement.
344         bool rewriteTheLastStatement = (ast->moreClauses == 0);
345
346         rewriteCaseStatements(ast->defaultClause->statements, rewriteTheLastStatement);
347     }
348
349     // Process trailing `case clauses'
350     for (AST::CaseClauses *it = ast->moreClauses; it; it = it->next) {
351         // Return the value of the last statement in the block, if this is the last `case clause'
352         // of the switch statement.
353         bool returnTheValueOfLastStatement = (it->next == 0);
354
355         if (AST::CaseClause *clause = it->clause) {
356             accept(clause->expression);
357             rewriteCaseStatements(clause->statements, returnTheValueOfLastStatement);
358         }
359     }
360
361     return false;
362 }
363
364 void RewriteBinding::rewriteCaseStatements(AST::StatementList *statements, bool rewriteTheLastStatement)
365 {
366     for (AST::StatementList *it = statements; it; it = it->next) {
367         if (it->next && AST::cast<AST::BreakStatement *>(it->next->statement) != 0) {
368             // The value of the first statement followed by a `break'.
369             accept(it->statement);
370             break;
371         } else if (!it->next) {
372             if (rewriteTheLastStatement)
373                 accept(it->statement);
374             else if (AST::Block *block = AST::cast<AST::Block *>(it->statement))
375                 rewriteCaseStatements(block->statements, rewriteTheLastStatement);
376         }
377     }
378 }
379
380
381 /*
382     RewriteSignalHandler performs two different types of rewrites, depending on what information
383     is available.
384
385     When the target object is known, the rewriter can be provided a list of parameter names (and an
386     optional preconstructed parameter string), which allows us to:
387         1. Check whether the parameters are used
388         2. Rewrite with the parameters included in the rewrite
389     When this information is not available, we do a more generic rewrite, and rely on the expression
390     to perform a second rewrite with the parameter information (using createParameterString)
391     once the target object is known.
392 */
393 RewriteSignalHandler::RewriteSignalHandler()
394     : _writer(0)
395     , _code(0)
396     , _position(0)
397     , _parameterAccess(UnknownAccess)
398     , _parameterCountForJS(0)
399 {
400 }
401
402 void RewriteSignalHandler::accept(AST::Node *node)
403 {
404     AST::Node::acceptChild(node, this);
405 }
406
407 bool RewriteSignalHandler::visit(AST::StringLiteral *ast)
408 {
409     rewriteStringLiteral(ast, _code, _position, _writer);
410     return false;
411 }
412
413 //if we never make use of the signal parameters in our expression,
414 //there is no need to provide them
415 bool RewriteSignalHandler::visit(AST::IdentifierExpression *e)
416 {
417     //optimization: don't need to compare strings if a parameter has already been marked as used.
418     if (_parameterAccess == ParametersAccessed)
419         return false;
420
421     static const QString argumentsString = QStringLiteral("arguments");
422     if (_parameterNames.contains(e->name) || e->name == argumentsString)
423         _parameterAccess = ParametersAccessed;
424     return false;
425 }
426
427 static QString unnamed_error_string(QLatin1String(QT_TR_NOOP("Signal uses unnamed parameter followed by named parameter.")));
428 static QString global_error_string(QLatin1String(QT_TR_NOOP("Signal parameter \"%1\" hides global variable.")));
429
430 #define EXIT_ON_ERROR(error) \
431 { \
432     _error = error; \
433     return QString(); \
434 }
435
436 //create a parameter string which can be inserted into a generic rewrite
437 QString RewriteSignalHandler::createParameterString(const QList<QByteArray> &parameterNameList,
438                                                     const QStringHash<bool> &illegalNames)
439 {
440     QList<QHashedString> hashedParameterNameList;
441     for (int i = 0; i < parameterNameList.count(); ++i)
442         hashedParameterNameList.append(QString::fromUtf8(parameterNameList.at(i).constData()));
443
444     return createParameterString(hashedParameterNameList, illegalNames);
445 }
446
447 QString RewriteSignalHandler::createParameterString(const QList<QHashedString> &parameterNameList,
448                                                     const QStringHash<bool> &illegalNames)
449 {
450     QString parameters;
451     bool unnamedParam = false;
452     for (int i = 0; i < parameterNameList.count(); ++i) {
453         const QHashedString &param = parameterNameList.at(i);
454         if (param.isEmpty())
455             unnamedParam = true;
456         else if (unnamedParam)
457             EXIT_ON_ERROR(unnamed_error_string)
458         else if (illegalNames.contains(param))
459             EXIT_ON_ERROR(global_error_string.arg(param))
460         ++_parameterCountForJS;
461         parameters += param;
462         if (i < parameterNameList.count()-1)
463             parameters += QStringLiteral(",");
464     }
465     if (parameters.endsWith(QLatin1Char(',')))
466         parameters.resize(parameters.length() - 1);
467     return parameters;
468 }
469
470 /*
471     If \a parameterString is provided, use \a parameterNameList to test whether the
472     parameters are used in the body of the function
473       * if unused, the rewrite will not include parameters, else
474       * if used, the rewrite will use \a parameterString
475     If \a parameterString is not provided, it is constructed from \a parameterNameList
476     as needed.
477 */
478 QString RewriteSignalHandler::operator()(QQmlJS::AST::Node *node, const QString &code, const QString &name,
479                                          const QString &parameterString,
480                                          const QList<QByteArray> &parameterNameList,
481                                          const QStringHash<bool> &illegalNames)
482 {
483     if (rewriteDump()) {
484         qWarning() << "=============================================================";
485         qWarning() << "Rewrote:";
486         qWarning() << qPrintable(code);
487     }
488
489     bool hasParameterString = !parameterString.isEmpty();
490
491     QQmlJS::AST::ExpressionNode *expression = node->expressionCast();
492     QQmlJS::AST::Statement *statement = node->statementCast();
493     if (!expression && !statement)
494         return code;
495
496     if (!parameterNameList.isEmpty()) {
497         for (int i = 0; i < parameterNameList.count(); ++i) {
498             QHashedString param(QString::fromUtf8(parameterNameList.at(i).constData()));
499             _parameterNames.insert(param, i);
500             if (!hasParameterString)
501                 _parameterNameList.append(param);
502         }
503
504         //this is set to Unaccessed here, and will be set to Accessed
505         //if we detect that a parameter has been used
506         _parameterAccess = ParametersUnaccessed;
507     }
508
509     TextWriter w;
510     _writer = &w;
511     _code = &code;
512
513     _position = expression ? expression->firstSourceLocation().begin() : statement->firstSourceLocation().begin();
514     accept(node);
515
516     QString rewritten = code;
517     w.write(&rewritten);
518
519     QString parameters = (_parameterAccess == ParametersUnaccessed) ? QString()
520                                                                     : hasParameterString ? parameterString
521                                                                                          : createParameterString(_parameterNameList, illegalNames);
522     rewritten = QStringLiteral("(function ") + name + QStringLiteral("(") + parameters + QStringLiteral(") { ") + rewritten + QStringLiteral(" })");
523
524     if (rewriteDump()) {
525         qWarning() << "To:";
526         qWarning() << qPrintable(rewritten);
527         qWarning() << "=============================================================";
528     }
529
530     return rewritten;
531 }
532
533 QString RewriteSignalHandler::operator()(const QString &code, const QString &name, bool *ok,
534                                          const QList<QByteArray> &parameterNameList,
535                                          const QStringHash<bool> &illegalNames)
536 {
537     Engine engine;
538     Lexer lexer(&engine);
539     Parser parser(&engine);
540     lexer.setCode(code, 0);
541     parser.parseStatement();
542     if (!parser.statement()) {
543         if (ok) *ok = false;
544         return QString();
545     }
546     if (ok) *ok = true;
547     return operator()(parser.statement(), code, name, QString(), parameterNameList, illegalNames);
548 }
549
550 } // namespace QQmlRewrite
551
552 QT_END_NAMESPACE