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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | QRegularExpression Parameters::(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 | */ |
36 | QString 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 | |
63 | Parameters::Parameters() : m_valid(true), m_privateSignal(false), m_tok(0), m_tokenizer(nullptr) |
64 | { |
65 | // nothing. |
66 | } |
67 | |
68 | Parameters::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 | */ |
83 | void Parameters::readToken() |
84 | { |
85 | m_tok = m_tokenizer->getToken(); |
86 | } |
87 | |
88 | /*! |
89 | Return the current lexeme from the string being parsed. |
90 | */ |
91 | QString Parameters::lexeme() |
92 | { |
93 | return m_tokenizer->lexeme(); |
94 | } |
95 | |
96 | /*! |
97 | Return the previous lexeme read from the string being parsed. |
98 | */ |
99 | QString 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 | */ |
109 | bool 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 | */ |
123 | void 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 | */ |
149 | bool 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 | */ |
326 | bool 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 | */ |
361 | bool 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 | */ |
389 | void 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 | */ |
398 | QString 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 | */ |
419 | QString Parameters::rawSignature(bool names, bool values) const |
420 | { |
421 | QString raw; |
422 | const auto params = m_parameters; |
423 | for (const auto ¶meter : 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 | */ |
440 | void 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 | */ |
477 | QSet<QString> Parameters::getNames() const |
478 | { |
479 | QSet<QString> names; |
480 | const auto params = m_parameters; |
481 | for (const auto ¶meter : 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 | */ |
491 | QString 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 | */ |
508 | QString 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 | */ |
529 | bool Parameters::match(const Parameters ¶meters) 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 | |
542 | QT_END_NAMESPACE |
543 | |