1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "parameters.h"
30 
31 #include "codechunk.h"
32 #include "generator.h"
33 #include "tokenizer.h"
34 
35 QT_BEGIN_NAMESPACE
36 
37 QRegExp Parameters::varComment_("/\\*\\s*([a-zA-Z_0-9]+)\\s*\\*/");
38 
39 /*!
40   \class Parameter
41   \brief The Parameter class describes one function parameter.
42 
43   A parameter can be a function parameter or a macro parameter.
44   It has a name, a data type, and an optional default value.
45   These are all stored as strings so they can be compared with
46   a parameter in a function signature to find a match.
47  */
48 
49 /*!
50   \fn Parameter::Parameter(const QString &type, const QString &name, const QString &defaultValue)
51 
52   Constructs the parameter from the \a type, the optional \a name,
53   and the optional \a defaultValue.
54  */
55 
56 /*!
57   Reconstructs the text signature for the parameter and returns
58   it. If \a includeValue is true and there is a default value,
59   the default value is appended with '='.
60  */
signature(bool includeValue) const61 QString Parameter::signature(bool includeValue) const
62 {
63     QString p = type_;
64     if (!p.endsWith(QChar('*')) && !p.endsWith(QChar('&')) && !p.endsWith(QChar(' ')))
65         p += QLatin1Char(' ');
66     p += name_;
67     if (includeValue && !defaultValue_.isEmpty())
68         p += " = " + defaultValue_;
69     return p;
70 }
71 
72 /*!
73   \class Parameters
74 
75   \brief A class for parsing and managing a function parameter list
76 
77   The constructor is passed a string that is the text inside the
78   parentheses of a function declaration. The constructor parses
79   the parameter list into a vector of class Parameter.
80 
81   The Parameters object is then used in function searches to find
82   the correct function node given the function name and the signature
83   of its parameters.
84  */
85 
Parameters()86 Parameters::Parameters() : valid_(true), privateSignal_(false), tok_(0), tokenizer_(nullptr)
87 {
88     // nothing.
89 }
90 
Parameters(const QString & signature)91 Parameters::Parameters(const QString &signature)
92     : valid_(true), privateSignal_(false), tok_(0), tokenizer_(nullptr)
93 {
94     if (!signature.isEmpty()) {
95         if (!parse(signature)) {
96             parameters_.clear();
97             valid_ = false;
98         }
99     }
100 }
101 
102 /*!
103   Get the next token from the string being parsed and store
104   it in the token variable.
105  */
readToken()106 void Parameters::readToken()
107 {
108     tok_ = tokenizer_->getToken();
109 }
110 
111 /*!
112   Return the current lexeme from the string being parsed.
113  */
lexeme()114 QString Parameters::lexeme()
115 {
116     return tokenizer_->lexeme();
117 }
118 
119 /*!
120   Return the previous lexeme read from the string being parsed.
121  */
previousLexeme()122 QString Parameters::previousLexeme()
123 {
124     return tokenizer_->previousLexeme();
125 }
126 
127 /*!
128   If the current token is \a target, read the next token and
129   return \c true. Otherwise, return false without reading the
130   next token.
131  */
match(int target)132 bool Parameters::match(int target)
133 {
134     if (tok_ == target) {
135         readToken();
136         return true;
137     }
138     return false;
139 }
140 
141 /*!
142   Match a template clause in angle brackets, append it to the
143   \a type, and return \c true. If there is no template clause,
144   or if an error is detected, return \c false.
145  */
matchTemplateAngles(CodeChunk & type)146 void Parameters::matchTemplateAngles(CodeChunk &type)
147 {
148     if (tok_ == Tok_LeftAngle) {
149         int leftAngleDepth = 0;
150         int parenAndBraceDepth = 0;
151         do {
152             if (tok_ == Tok_LeftAngle) {
153                 leftAngleDepth++;
154             } else if (tok_ == Tok_RightAngle) {
155                 leftAngleDepth--;
156             } else if (tok_ == Tok_LeftParen || tok_ == Tok_LeftBrace) {
157                 ++parenAndBraceDepth;
158             } else if (tok_ == Tok_RightParen || tok_ == Tok_RightBrace) {
159                 if (--parenAndBraceDepth < 0)
160                     return;
161             }
162             type.append(lexeme());
163             readToken();
164         } while (leftAngleDepth > 0 && tok_ != Tok_Eoi);
165     }
166 }
167 
168 /*!
169   Uses the current tokenizer to parse the \a name and \a type
170   of the parameter.
171  */
matchTypeAndName(CodeChunk & type,QString & name,bool qProp)172 bool Parameters::matchTypeAndName(CodeChunk &type, QString &name, bool qProp)
173 {
174     /*
175       This code is really hard to follow... sorry. The loop is there to match
176       Alpha::Beta::Gamma::...::Omega.
177     */
178     for (;;) {
179         bool virgin = true;
180 
181         if (tok_ != Tok_Ident) {
182             /*
183               There is special processing for 'Foo::operator int()'
184               and such elsewhere. This is the only case where we
185               return something with a trailing gulbrandsen ('Foo::').
186             */
187             if (tok_ == Tok_operator)
188                 return true;
189 
190             /*
191               People may write 'const unsigned short' or
192               'short unsigned const' or any other permutation.
193             */
194             while (match(Tok_const) || match(Tok_volatile))
195                 type.append(previousLexeme());
196             QString pending;
197             while (tok_ == Tok_signed || tok_ == Tok_int || tok_ == Tok_unsigned
198                    || tok_ == Tok_short || tok_ == Tok_long || tok_ == Tok_int64) {
199                 if (tok_ == Tok_signed)
200                     pending = lexeme();
201                 else {
202                     if (tok_ == Tok_unsigned && !pending.isEmpty())
203                         type.append(pending);
204                     pending.clear();
205                     type.append(lexeme());
206                 }
207                 readToken();
208                 virgin = false;
209             }
210             if (!pending.isEmpty()) {
211                 type.append(pending);
212                 pending.clear();
213             }
214             while (match(Tok_const) || match(Tok_volatile))
215                 type.append(previousLexeme());
216 
217             if (match(Tok_Tilde))
218                 type.append(previousLexeme());
219         }
220 
221         if (virgin) {
222             if (match(Tok_Ident)) {
223                 /*
224                   This is a hack until we replace this "parser"
225                   with the real one used in Qt Creator.
226                   Is it still needed? mws 11/12/2018
227                  */
228                 if (lexeme() == "("
229                     && ((previousLexeme() == "QT_PREPEND_NAMESPACE")
230                         || (previousLexeme() == "NS"))) {
231                     readToken();
232                     readToken();
233                     type.append(previousLexeme());
234                     readToken();
235                 } else
236                     type.append(previousLexeme());
237             } else if (match(Tok_void) || match(Tok_int) || match(Tok_char) || match(Tok_double)
238                        || match(Tok_Ellipsis)) {
239                 type.append(previousLexeme());
240             } else {
241                 return false;
242             }
243         } else if (match(Tok_int) || match(Tok_char) || match(Tok_double)) {
244             type.append(previousLexeme());
245         }
246 
247         matchTemplateAngles(type);
248 
249         while (match(Tok_const) || match(Tok_volatile))
250             type.append(previousLexeme());
251 
252         if (match(Tok_Gulbrandsen))
253             type.append(previousLexeme());
254         else
255             break;
256     }
257 
258     while (match(Tok_Ampersand) || match(Tok_Aster) || match(Tok_const) || match(Tok_Caret)
259            || match(Tok_Ellipsis))
260         type.append(previousLexeme());
261 
262     if (match(Tok_LeftParenAster)) {
263         /*
264           A function pointer. This would be rather hard to handle without a
265           tokenizer hack, because a type can be followed with a left parenthesis
266           in some cases (e.g., 'operator int()'). The tokenizer recognizes '(*'
267           as a single token.
268         */
269         type.append(" "); // force a space after the type
270         type.append(previousLexeme());
271         type.appendHotspot();
272         if (match(Tok_Ident))
273             name = previousLexeme();
274         if (!match(Tok_RightParen))
275             return false;
276         type.append(previousLexeme());
277         if (!match(Tok_LeftParen))
278             return false;
279         type.append(previousLexeme());
280 
281         /* parse the parameters. Ignore the parameter name from the type */
282         while (tok_ != Tok_RightParen && tok_ != Tok_Eoi) {
283             QString dummy;
284             if (!matchTypeAndName(type, dummy))
285                 return false;
286             if (match(Tok_Comma))
287                 type.append(previousLexeme());
288         }
289         if (!match(Tok_RightParen))
290             return false;
291         type.append(previousLexeme());
292     } else {
293         /*
294           The common case: Look for an optional identifier, then for
295           some array brackets.
296         */
297         type.appendHotspot();
298 
299         if (match(Tok_Ident)) {
300             name = previousLexeme();
301         } else if (match(Tok_Comment)) {
302             /*
303               A neat hack: Commented-out parameter names are
304               recognized by qdoc. It's impossible to illustrate
305               here inside a C-style comment, because it requires
306               an asterslash. It's also impossible to illustrate
307               inside a C++-style comment, because the explanation
308               does not fit on one line.
309             */
310             if (varComment_.exactMatch(previousLexeme()))
311                 name = varComment_.cap(1);
312         } else if (match(Tok_LeftParen)) {
313             name = "(";
314             while (tok_ != Tok_RightParen && tok_ != Tok_Eoi) {
315                 name.append(lexeme());
316                 readToken();
317             }
318             name.append(")");
319             readToken();
320             if (match(Tok_LeftBracket)) {
321                 name.append("[");
322                 while (tok_ != Tok_RightBracket && tok_ != Tok_Eoi) {
323                     name.append(lexeme());
324                     readToken();
325                 }
326                 name.append("]");
327                 readToken();
328             }
329         } else if (qProp && (match(Tok_default) || match(Tok_final) || match(Tok_override))) {
330             // Hack to make 'default', 'final' and 'override'  work again in Q_PROPERTY
331             name = previousLexeme();
332         }
333 
334         if (tok_ == Tok_LeftBracket) {
335             int bracketDepth0 = tokenizer_->bracketDepth();
336             while ((tokenizer_->bracketDepth() >= bracketDepth0 && tok_ != Tok_Eoi)
337                    || tok_ == Tok_RightBracket) {
338                 type.append(lexeme());
339                 readToken();
340             }
341         }
342     }
343     return true;
344 }
345 
346 /*!
347   Parse the next function parameter, if there is one, and
348   append it to the internal parameter vector. Return true
349   if a parameter is parsed correctly. Otherwise return false.
350  */
matchParameter()351 bool Parameters::matchParameter()
352 {
353     if (match(Tok_QPrivateSignal)) {
354         privateSignal_ = true;
355         return true;
356     }
357 
358     CodeChunk chunk;
359     QString name;
360     if (!matchTypeAndName(chunk, name))
361         return false;
362     QString type = chunk.toString();
363     QString defaultValue;
364     match(Tok_Comment);
365     if (match(Tok_Equal)) {
366         chunk.clear();
367         int pdepth = tokenizer_->parenDepth();
368         while (tokenizer_->parenDepth() >= pdepth
369                && (tok_ != Tok_Comma || (tokenizer_->parenDepth() > pdepth)) && tok_ != Tok_Eoi) {
370             chunk.append(lexeme());
371             readToken();
372         }
373         defaultValue = chunk.toString();
374     }
375     append(type, name, defaultValue);
376     return true;
377 }
378 
379 /*!
380   This function uses a Tokenizer to parse the \a signature,
381   which is a comma-separated list of parameter declarations.
382   If an error is detected, the Parameters object is cleared
383   and \c false is returned. Otherwise \c true is returned.
384  */
parse(const QString & signature)385 bool Parameters::parse(const QString &signature)
386 {
387     Tokenizer *outerTokenizer = tokenizer_;
388     int outerTok = tok_;
389 
390     QByteArray latin1 = signature.toLatin1();
391     Tokenizer stringTokenizer(Location(), latin1);
392     stringTokenizer.setParsingFnOrMacro(true);
393     tokenizer_ = &stringTokenizer;
394 
395     readToken();
396     do {
397         if (!matchParameter()) {
398             parameters_.clear();
399             valid_ = false;
400             break;
401         }
402     } while (match(Tok_Comma));
403 
404     tokenizer_ = outerTokenizer;
405     tok_ = outerTok;
406     return valid_;
407 }
408 
409 /*!
410   Append a Parameter constructed from \a type, \a name, and \a value
411   to the parameter vector.
412  */
append(const QString & type,const QString & name,const QString & value)413 void Parameters::append(const QString &type, const QString &name, const QString &value)
414 {
415     parameters_.append(Parameter(type, name, value));
416 }
417 
418 /*!
419   Returns the list of reconstructed parameters. If \a includeValues
420   is true, the default values are included, if any are present.
421  */
signature(bool includeValues) const422 QString Parameters::signature(bool includeValues) const
423 {
424     QString result;
425     if (parameters_.size() > 0) {
426         for (int i = 0; i < parameters_.size(); i++) {
427             if (i > 0)
428                 result += ", ";
429             result += parameters_.at(i).signature(includeValues);
430         }
431     }
432     return result;
433 }
434 
435 /*!
436   Returns the signature of all the parameters with all the
437   spaces and commas removed. It is unintelligible, but that
438   is what the caller wants.
439 
440   If \a names is true, the parameter names are included. If
441   \a values is true, the default values are included.
442  */
rawSignature(bool names,bool values) const443 QString Parameters::rawSignature(bool names, bool values) const
444 {
445     QString raw;
446     const auto params = parameters_;
447     for (const auto &parameter : params) {
448         raw += parameter.type();
449         if (names)
450             raw += parameter.name();
451         if (values)
452             raw += parameter.defaultValue();
453     }
454     return raw;
455 }
456 
457 /*!
458   Parse the parameter \a signature by splitting the string,
459   and store the individual parameters in the parameter vector.
460 
461   This method of parsing is naive but sufficient for QML methods
462   and macros.
463  */
set(const QString & signature)464 void Parameters::set(const QString &signature)
465 {
466     clear();
467     if (!signature.isEmpty()) {
468         QStringList commaSplit = signature.split(',');
469         parameters_.resize(commaSplit.size());
470         int i = 0;
471         for (const auto &item : qAsConst(commaSplit)) {
472             QStringList blankSplit = item.split(' ', Qt::SkipEmptyParts);
473             QString pDefault;
474             int defaultIdx = blankSplit.indexOf(QStringLiteral("="));
475             if (defaultIdx != -1) {
476                 if (++defaultIdx < blankSplit.size())
477                     pDefault = blankSplit.mid(defaultIdx).join(' ');
478                 blankSplit = blankSplit.mid(0, defaultIdx - 1);
479             }
480             QString pName = blankSplit.takeLast();
481             QString pType = blankSplit.join(' ');
482             if (pType.isEmpty() && pName == QLatin1String("..."))
483                 qSwap(pType, pName);
484             else {
485                 int j = 0;
486                 while (j < pName.length() && !pName.at(j).isLetter())
487                     j++;
488                 if (j > 0) {
489                     pType += QChar(' ') + pName.left(j);
490                     pName = pName.mid(j);
491                 }
492             }
493             parameters_[i++].set(pType, pName, pDefault);
494         }
495     }
496 }
497 
498 /*!
499   Insert all the parameter names into names.
500  */
getNames() const501 QSet<QString> Parameters::getNames() const
502 {
503     QSet<QString> names;
504     const auto params = parameters_;
505     for (const auto &parameter : params) {
506         if (!parameter.name().isEmpty())
507             names.insert(parameter.name());
508     }
509     return names;
510 }
511 
512 /*!
513   Construct a list of the parameter types and return it.
514  */
generateTypeList() const515 QString Parameters::generateTypeList() const
516 {
517     QString out;
518     if (count() > 0) {
519         for (int i = 0; i < count(); ++i) {
520             if (i > 0)
521                 out += ", ";
522             out += parameters_.at(i).type();
523         }
524     }
525     return out;
526 }
527 
528 /*!
529   Construct a list of the parameter type/name pairs and
530   return it.
531 */
generateTypeAndNameList() const532 QString Parameters::generateTypeAndNameList() const
533 {
534     QString out;
535     if (count() > 0) {
536         for (int i = 0; i < count(); ++i) {
537             if (i != 0)
538                 out += ", ";
539             const Parameter &p = parameters_.at(i);
540             out += p.type();
541             if (out[out.size() - 1].isLetterOrNumber())
542                 out += QLatin1Char(' ');
543             out += p.name();
544         }
545     }
546     return out;
547 }
548 
549 /*!
550   Returns true if \a parameters contains the same parameter
551   signature as this.
552  */
match(const Parameters & parameters) const553 bool Parameters::match(const Parameters &parameters) const
554 {
555     if (count() != parameters.count())
556         return false;
557     if (count() == 0)
558         return true;
559     for (int i = 0; i < count(); i++) {
560         if (parameters.at(i).type() != parameters_.at(i).type())
561             return false;
562     }
563     return true;
564 }
565 
566 QT_END_NAMESPACE
567