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 ¶meter : 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 ¶meter : 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 ¶meters) 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