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

source code of qttools/src/qdoc/qdoc/parameters.cpp