| 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 "cppcodemarker.h" |
| 5 | |
| 6 | #include "access.h" |
| 7 | #include "enumnode.h" |
| 8 | #include "functionnode.h" |
| 9 | #include "genustypes.h" |
| 10 | #include "namespacenode.h" |
| 11 | #include "propertynode.h" |
| 12 | #include "qmlpropertynode.h" |
| 13 | #include "text.h" |
| 14 | #include "tree.h" |
| 15 | #include "typedefnode.h" |
| 16 | #include "variablenode.h" |
| 17 | |
| 18 | #include <QtCore/qdebug.h> |
| 19 | #include <QtCore/qregularexpression.h> |
| 20 | |
| 21 | QT_BEGIN_NAMESPACE |
| 22 | |
| 23 | using namespace Qt::StringLiterals; |
| 24 | |
| 25 | /*! |
| 26 | Returns \c true. |
| 27 | */ |
| 28 | bool CppCodeMarker::recognizeCode(const QString & /* code */) |
| 29 | { |
| 30 | return true; |
| 31 | } |
| 32 | |
| 33 | /*! |
| 34 | Returns \c true if \a ext is any of a list of file extensions |
| 35 | for the C++ language. |
| 36 | */ |
| 37 | bool CppCodeMarker::recognizeExtension(const QString &extension) |
| 38 | { |
| 39 | QByteArray ext = extension.toLatin1(); |
| 40 | return ext == "c" || ext == "c++" || ext == "qdoc" || ext == "qtt" || ext == "qtx" |
| 41 | || ext == "cc" || ext == "cpp" || ext == "cxx" || ext == "ch" || ext == "h" |
| 42 | || ext == "h++" || ext == "hh" || ext == "hpp" || ext == "hxx" ; |
| 43 | } |
| 44 | |
| 45 | /*! |
| 46 | Returns \c true if \a lang is either "C" or "Cpp". |
| 47 | */ |
| 48 | bool CppCodeMarker::recognizeLanguage(const QString &lang) |
| 49 | { |
| 50 | return lang == QLatin1String("C" ) || lang == QLatin1String("Cpp" ); |
| 51 | } |
| 52 | |
| 53 | /*! |
| 54 | Returns the type of atom used to represent C++ code in the documentation. |
| 55 | */ |
| 56 | Atom::AtomType CppCodeMarker::atomType() const |
| 57 | { |
| 58 | return Atom::Code; |
| 59 | } |
| 60 | |
| 61 | QString CppCodeMarker::markedUpCode(const QString &code, const Node *relative, |
| 62 | const Location &location) |
| 63 | { |
| 64 | return addMarkUp(protectedCode: code, relative, location); |
| 65 | } |
| 66 | |
| 67 | QString CppCodeMarker::markedUpSynopsis(const Node *node, const Node * /* relative */, |
| 68 | Section::Style style) |
| 69 | { |
| 70 | const int MaxEnumValues = 6; |
| 71 | const FunctionNode *func; |
| 72 | const VariableNode *variable; |
| 73 | const EnumNode *enume; |
| 74 | QString synopsis; |
| 75 | QString name; |
| 76 | |
| 77 | name = taggedNode(node); |
| 78 | if (style != Section::Details) |
| 79 | name = linkTag(node, body: name); |
| 80 | name = "<@name>" + name + "</@name>" ; |
| 81 | |
| 82 | if (style == Section::Details) { |
| 83 | if (!node->isRelatedNonmember() && !node->isProxyNode() && !node->parent()->name().isEmpty() |
| 84 | && !node->parent()->isHeader() && !node->isProperty() && !node->isQmlNode()) { |
| 85 | name.prepend(s: taggedNode(node: node->parent()) + "::" ); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | switch (node->nodeType()) { |
| 90 | case NodeType::Namespace: |
| 91 | case NodeType::Class: |
| 92 | case NodeType::Struct: |
| 93 | case NodeType::Union: |
| 94 | synopsis = Node::nodeTypeString(t: node->nodeType()); |
| 95 | synopsis += QLatin1Char(' ') + name; |
| 96 | break; |
| 97 | case NodeType::Function: |
| 98 | func = (const FunctionNode *)node; |
| 99 | if (style == Section::Details) { |
| 100 | auto templateDecl = node->templateDecl(); |
| 101 | if (templateDecl) |
| 102 | synopsis = protect(string: (*templateDecl).to_qstring()) + QLatin1Char(' '); |
| 103 | } |
| 104 | if (style != Section::AllMembers && !func->returnType().isEmpty()) |
| 105 | synopsis += typified(string: func->returnTypeString(), trailingSpace: true); |
| 106 | synopsis += name; |
| 107 | if (!func->isMacroWithoutParams()) { |
| 108 | synopsis += QLatin1Char('('); |
| 109 | if (!func->parameters().isEmpty()) { |
| 110 | const Parameters ¶meters = func->parameters(); |
| 111 | for (int i = 0; i < parameters.count(); ++i) { |
| 112 | if (i > 0) |
| 113 | synopsis += ", " ; |
| 114 | QString name = parameters.at(i).name(); |
| 115 | QString type = parameters.at(i).type(); |
| 116 | QString value = parameters.at(i).defaultValue(); |
| 117 | bool trailingSpace = style != Section::AllMembers && !name.isEmpty(); |
| 118 | synopsis += typified(string: type, trailingSpace); |
| 119 | if (style != Section::AllMembers && !name.isEmpty()) |
| 120 | synopsis += "<@param>" + protect(string: name) + "</@param>" ; |
| 121 | if (style != Section::AllMembers && !value.isEmpty()) |
| 122 | synopsis += " = " + protect(string: value); |
| 123 | } |
| 124 | } |
| 125 | synopsis += QLatin1Char(')'); |
| 126 | } |
| 127 | if (func->isConst()) |
| 128 | synopsis += " const" ; |
| 129 | |
| 130 | if (style == Section::Summary || style == Section::Accessors) { |
| 131 | if (!func->isNonvirtual()) |
| 132 | synopsis.prepend(s: "virtual " ); |
| 133 | if (func->isFinal()) |
| 134 | synopsis.append(s: " final" ); |
| 135 | if (func->isOverride()) |
| 136 | synopsis.append(s: " override" ); |
| 137 | if (func->isPureVirtual()) |
| 138 | synopsis.append(s: " = 0" ); |
| 139 | if (func->isRef()) |
| 140 | synopsis.append(s: " &" ); |
| 141 | else if (func->isRefRef()) |
| 142 | synopsis.append(s: " &&" ); |
| 143 | } else if (style == Section::AllMembers) { |
| 144 | if (!func->returnType().isEmpty() && func->returnType() != "void" ) |
| 145 | synopsis += " : " + typified(string: func->returnTypeString()); |
| 146 | } else { |
| 147 | if (func->isRef()) |
| 148 | synopsis.append(s: " &" ); |
| 149 | else if (func->isRefRef()) |
| 150 | synopsis.append(s: " &&" ); |
| 151 | } |
| 152 | break; |
| 153 | case NodeType::Enum: |
| 154 | enume = static_cast<const EnumNode *>(node); |
| 155 | synopsis = "enum" ; |
| 156 | if (enume->isScoped()) |
| 157 | synopsis += " class" ; |
| 158 | if (!enume->isAnonymous()) |
| 159 | synopsis += " %1"_L1 .arg(args&: name); |
| 160 | else if (style != Section::Details) |
| 161 | synopsis = linkTag(node, body: synopsis); // Unnamed enum: Make `enum` a link to details |
| 162 | if (style == Section::Summary) { |
| 163 | synopsis += " { " ; |
| 164 | |
| 165 | QStringList documentedItems = enume->doc().enumItemNames(); |
| 166 | if (documentedItems.isEmpty()) { |
| 167 | const auto &enumItems = enume->items(); |
| 168 | for (const auto &item : enumItems) |
| 169 | documentedItems << item.name(); |
| 170 | } |
| 171 | const QStringList omitItems = enume->doc().omitEnumItemNames(); |
| 172 | for (const auto &item : omitItems) |
| 173 | documentedItems.removeAll(t: item); |
| 174 | |
| 175 | if (documentedItems.size() > MaxEnumValues) { |
| 176 | // Take the last element and keep it safe, then elide the surplus. |
| 177 | const QString last = documentedItems.last(); |
| 178 | documentedItems = documentedItems.mid(pos: 0, len: MaxEnumValues - 1); |
| 179 | documentedItems += "…" ; |
| 180 | documentedItems += last; |
| 181 | } |
| 182 | synopsis += documentedItems.join(sep: QLatin1String(", " )); |
| 183 | |
| 184 | if (!documentedItems.isEmpty()) |
| 185 | synopsis += QLatin1Char(' '); |
| 186 | synopsis += QLatin1Char('}'); |
| 187 | } |
| 188 | break; |
| 189 | case NodeType::TypeAlias: |
| 190 | if (style == Section::Details) { |
| 191 | auto templateDecl = node->templateDecl(); |
| 192 | if (templateDecl) |
| 193 | synopsis += protect(string: (*templateDecl).to_qstring()) + QLatin1Char(' '); |
| 194 | } |
| 195 | synopsis += name; |
| 196 | break; |
| 197 | case NodeType::Typedef: |
| 198 | if (static_cast<const TypedefNode *>(node)->associatedEnum()) |
| 199 | synopsis = "flags " ; |
| 200 | synopsis += name; |
| 201 | break; |
| 202 | case NodeType::Property: { |
| 203 | auto property = static_cast<const PropertyNode *>(node); |
| 204 | synopsis = name + " : " + typified(string: property->qualifiedDataType()); |
| 205 | break; |
| 206 | } |
| 207 | case NodeType::QmlProperty: { |
| 208 | auto property = static_cast<const QmlPropertyNode *>(node); |
| 209 | synopsis = name + " : " + typified(string: property->dataType()); |
| 210 | break; |
| 211 | } |
| 212 | case NodeType::Variable: |
| 213 | variable = static_cast<const VariableNode *>(node); |
| 214 | if (style == Section::AllMembers) { |
| 215 | synopsis = name + " : " + typified(string: variable->dataType()); |
| 216 | } else { |
| 217 | synopsis = typified(string: variable->leftType(), trailingSpace: true) + name + protect(string: variable->rightType()); |
| 218 | } |
| 219 | break; |
| 220 | default: |
| 221 | synopsis = std::move(name); |
| 222 | } |
| 223 | |
| 224 | QString = CodeMarker::extraSynopsis(node, style); |
| 225 | if (!extra.isEmpty()) { |
| 226 | extra.prepend(s: u"<@extra>"_s ); |
| 227 | extra.append(s: u"</@extra> "_s ); |
| 228 | } |
| 229 | |
| 230 | return extra + synopsis; |
| 231 | } |
| 232 | |
| 233 | /*! |
| 234 | */ |
| 235 | QString CppCodeMarker::markedUpQmlItem(const Node *node, bool summary) |
| 236 | { |
| 237 | QString name = taggedQmlNode(node); |
| 238 | QString synopsis; |
| 239 | |
| 240 | if (summary) { |
| 241 | name = linkTag(node, body: name); |
| 242 | } else if (node->isQmlProperty()) { |
| 243 | const auto *pn = static_cast<const QmlPropertyNode *>(node); |
| 244 | if (pn->isAttached()) |
| 245 | name.prepend(s: pn->element() + QLatin1Char('.')); |
| 246 | } |
| 247 | name = "<@name>" + name + "</@name>" ; |
| 248 | if (node->isQmlProperty()) { |
| 249 | const auto *pn = static_cast<const QmlPropertyNode *>(node); |
| 250 | synopsis = name + " : " + typified(string: pn->dataType()); |
| 251 | } else if (node->isFunction(g: Genus::QML)) { |
| 252 | const auto *func = static_cast<const FunctionNode *>(node); |
| 253 | if (!func->returnType().isEmpty()) |
| 254 | synopsis = typified(string: func->returnTypeString(), trailingSpace: true) + name; |
| 255 | else |
| 256 | synopsis = name; |
| 257 | synopsis += QLatin1Char('('); |
| 258 | if (!func->parameters().isEmpty()) { |
| 259 | const Parameters ¶meters = func->parameters(); |
| 260 | for (int i = 0; i < parameters.count(); ++i) { |
| 261 | if (i > 0) |
| 262 | synopsis += ", " ; |
| 263 | QString name = parameters.at(i).name(); |
| 264 | QString type = parameters.at(i).type(); |
| 265 | QString paramName; |
| 266 | if (!name.isEmpty()) { |
| 267 | synopsis += typified(string: type, trailingSpace: true); |
| 268 | paramName = std::move(name); |
| 269 | } else { |
| 270 | paramName = std::move(type); |
| 271 | } |
| 272 | synopsis += "<@param>" + protect(string: paramName) + "</@param>" ; |
| 273 | } |
| 274 | } |
| 275 | synopsis += QLatin1Char(')'); |
| 276 | } else { |
| 277 | synopsis = std::move(name); |
| 278 | } |
| 279 | |
| 280 | QString = CodeMarker::extraSynopsis(node, style: summary ? Section::Summary : Section::Details); |
| 281 | if (!extra.isEmpty()) { |
| 282 | extra.prepend(s: u" <@extra>"_s ); |
| 283 | extra.append(s: u"</@extra>"_s ); |
| 284 | } |
| 285 | |
| 286 | return synopsis + extra; |
| 287 | } |
| 288 | |
| 289 | QString CppCodeMarker::markedUpName(const Node *node) |
| 290 | { |
| 291 | QString name = linkTag(node, body: taggedNode(node)); |
| 292 | if (node->isFunction() && !node->isMacro()) |
| 293 | name += "()" ; |
| 294 | return name; |
| 295 | } |
| 296 | |
| 297 | QString CppCodeMarker::markedUpEnumValue(const QString &enumValue, const Node *relative) |
| 298 | { |
| 299 | const auto *node = relative->parent(); |
| 300 | |
| 301 | const NativeEnum *nativeEnum{nullptr}; |
| 302 | if (auto *ne_if = dynamic_cast<const NativeEnumInterface *>(relative)) |
| 303 | nativeEnum = ne_if->nativeEnum(); |
| 304 | |
| 305 | if (nativeEnum && nativeEnum->enumNode() |
| 306 | && !enumValue.startsWith(s: "%1."_L1 .arg(args: nativeEnum->prefix()))) |
| 307 | return "%1<@op>.</@op>%2"_L1 .arg(args: nativeEnum->prefix(), args: enumValue); |
| 308 | |
| 309 | if (!relative->isEnumType()) { |
| 310 | return enumValue; |
| 311 | } |
| 312 | |
| 313 | QStringList parts; |
| 314 | while (!node->isHeader() && node->parent()) { |
| 315 | parts.prepend(t: markedUpName(node)); |
| 316 | if (node->parent() == relative || node->parent()->name().isEmpty()) |
| 317 | break; |
| 318 | node = node->parent(); |
| 319 | } |
| 320 | if (static_cast<const EnumNode *>(relative)->isScoped()) |
| 321 | parts.append(t: relative->name()); |
| 322 | |
| 323 | parts.append(t: enumValue); |
| 324 | const auto &delim = (relative->genus() == Genus::QML) ? "."_L1 : "::"_L1 ; |
| 325 | return parts.join(sep: "<@op>%1</@op>"_L1 .arg(args: delim)); |
| 326 | } |
| 327 | |
| 328 | QString CppCodeMarker::addMarkUp(const QString &in, const Node * /* relative */, |
| 329 | const Location & /* location */) |
| 330 | { |
| 331 | static QSet<QString> types{ |
| 332 | QLatin1String("bool" ), QLatin1String("char" ), QLatin1String("double" ), |
| 333 | QLatin1String("float" ), QLatin1String("int" ), QLatin1String("long" ), |
| 334 | QLatin1String("short" ), QLatin1String("signed" ), QLatin1String("unsigned" ), |
| 335 | QLatin1String("uint" ), QLatin1String("ulong" ), QLatin1String("ushort" ), |
| 336 | QLatin1String("uchar" ), QLatin1String("void" ), QLatin1String("qlonglong" ), |
| 337 | QLatin1String("qulonglong" ), QLatin1String("qint" ), QLatin1String("qint8" ), |
| 338 | QLatin1String("qint16" ), QLatin1String("qint32" ), QLatin1String("qint64" ), |
| 339 | QLatin1String("quint" ), QLatin1String("quint8" ), QLatin1String("quint16" ), |
| 340 | QLatin1String("quint32" ), QLatin1String("quint64" ), QLatin1String("qreal" ), |
| 341 | QLatin1String("cond" ) |
| 342 | }; |
| 343 | |
| 344 | static QSet<QString> keywords{ |
| 345 | QLatin1String("and" ), QLatin1String("and_eq" ), QLatin1String("asm" ), QLatin1String("auto" ), |
| 346 | QLatin1String("bitand" ), QLatin1String("bitor" ), QLatin1String("break" ), |
| 347 | QLatin1String("case" ), QLatin1String("catch" ), QLatin1String("class" ), |
| 348 | QLatin1String("compl" ), QLatin1String("const" ), QLatin1String("const_cast" ), |
| 349 | QLatin1String("continue" ), QLatin1String("default" ), QLatin1String("delete" ), |
| 350 | QLatin1String("do" ), QLatin1String("dynamic_cast" ), QLatin1String("else" ), |
| 351 | QLatin1String("enum" ), QLatin1String("explicit" ), QLatin1String("export" ), |
| 352 | QLatin1String("extern" ), QLatin1String("false" ), QLatin1String("for" ), |
| 353 | QLatin1String("friend" ), QLatin1String("goto" ), QLatin1String("if" ), |
| 354 | QLatin1String("include" ), QLatin1String("inline" ), QLatin1String("monitor" ), |
| 355 | QLatin1String("mutable" ), QLatin1String("namespace" ), QLatin1String("new" ), |
| 356 | QLatin1String("not" ), QLatin1String("not_eq" ), QLatin1String("operator" ), |
| 357 | QLatin1String("or" ), QLatin1String("or_eq" ), QLatin1String("private" ), |
| 358 | QLatin1String("protected" ), QLatin1String("public" ), QLatin1String("register" ), |
| 359 | QLatin1String("reinterpret_cast" ), QLatin1String("return" ), QLatin1String("sizeof" ), |
| 360 | QLatin1String("static" ), QLatin1String("static_cast" ), QLatin1String("struct" ), |
| 361 | QLatin1String("switch" ), QLatin1String("template" ), QLatin1String("this" ), |
| 362 | QLatin1String("throw" ), QLatin1String("true" ), QLatin1String("try" ), |
| 363 | QLatin1String("typedef" ), QLatin1String("typeid" ), QLatin1String("typename" ), |
| 364 | QLatin1String("union" ), QLatin1String("using" ), QLatin1String("virtual" ), |
| 365 | QLatin1String("volatile" ), QLatin1String("wchar_t" ), QLatin1String("while" ), |
| 366 | QLatin1String("xor" ), QLatin1String("xor_eq" ), QLatin1String("synchronized" ), |
| 367 | // Qt specific |
| 368 | QLatin1String("signals" ), QLatin1String("slots" ), QLatin1String("emit" ) |
| 369 | }; |
| 370 | |
| 371 | QString code = in; |
| 372 | QString out; |
| 373 | QStringView text; |
| 374 | int braceDepth = 0; |
| 375 | int parenDepth = 0; |
| 376 | int i = 0; |
| 377 | int start = 0; |
| 378 | int finish = 0; |
| 379 | QChar ch; |
| 380 | static const QRegularExpression classRegExp(QRegularExpression::anchoredPattern(expression: "Qt?(?:[A-Z3]+[a-z][A-Za-z]*|t)" )); |
| 381 | static const QRegularExpression functionRegExp(QRegularExpression::anchoredPattern(expression: "q([A-Z][a-z]+)+" )); |
| 382 | static const QRegularExpression findFunctionRegExp(QStringLiteral("^\\s*\\(" )); |
| 383 | bool atEOF = false; |
| 384 | |
| 385 | auto readChar = [&]() { |
| 386 | if (i < code.size()) |
| 387 | ch = code[i++]; |
| 388 | else |
| 389 | atEOF = true; |
| 390 | }; |
| 391 | |
| 392 | readChar(); |
| 393 | while (!atEOF) { |
| 394 | QString tag; |
| 395 | bool target = false; |
| 396 | |
| 397 | if (ch.isLetter() || ch == '_') { |
| 398 | QString ident; |
| 399 | do { |
| 400 | ident += ch; |
| 401 | finish = i; |
| 402 | readChar(); |
| 403 | } while (!atEOF && (ch.isLetterOrNumber() || ch == '_')); |
| 404 | |
| 405 | if (classRegExp.match(subject: ident).hasMatch()) { |
| 406 | tag = QStringLiteral("type" ); |
| 407 | } else if (functionRegExp.match(subject: ident).hasMatch()) { |
| 408 | tag = QStringLiteral("func" ); |
| 409 | target = true; |
| 410 | } else if (types.contains(value: ident)) { |
| 411 | tag = QStringLiteral("type" ); |
| 412 | } else if (keywords.contains(value: ident)) { |
| 413 | tag = QStringLiteral("keyword" ); |
| 414 | } else if (braceDepth == 0 && parenDepth == 0) { |
| 415 | if (code.indexOf(re: findFunctionRegExp, from: i - 1) == i - 1) |
| 416 | tag = QStringLiteral("func" ); |
| 417 | target = true; |
| 418 | } |
| 419 | } else if (ch.isDigit()) { |
| 420 | do { |
| 421 | finish = i; |
| 422 | readChar(); |
| 423 | } while (!atEOF && (ch.isLetterOrNumber() || ch == '.' || ch == '\'')); |
| 424 | tag = QStringLiteral("number" ); |
| 425 | } else { |
| 426 | switch (ch.unicode()) { |
| 427 | case '+': |
| 428 | case '-': |
| 429 | case '!': |
| 430 | case '%': |
| 431 | case '^': |
| 432 | case '&': |
| 433 | case '*': |
| 434 | case ',': |
| 435 | case '.': |
| 436 | case '<': |
| 437 | case '=': |
| 438 | case '>': |
| 439 | case '?': |
| 440 | case '[': |
| 441 | case ']': |
| 442 | case '|': |
| 443 | case '~': |
| 444 | finish = i; |
| 445 | readChar(); |
| 446 | tag = QStringLiteral("op" ); |
| 447 | break; |
| 448 | case '"': |
| 449 | finish = i; |
| 450 | readChar(); |
| 451 | |
| 452 | while (!atEOF && ch != '"') { |
| 453 | if (ch == '\\') |
| 454 | readChar(); |
| 455 | readChar(); |
| 456 | } |
| 457 | finish = i; |
| 458 | readChar(); |
| 459 | tag = QStringLiteral("string" ); |
| 460 | break; |
| 461 | case '#': |
| 462 | finish = i; |
| 463 | readChar(); |
| 464 | while (!atEOF && ch != '\n') { |
| 465 | if (ch == '\\') |
| 466 | readChar(); |
| 467 | finish = i; |
| 468 | readChar(); |
| 469 | } |
| 470 | tag = QStringLiteral("preprocessor" ); |
| 471 | break; |
| 472 | case '\'': |
| 473 | finish = i; |
| 474 | readChar(); |
| 475 | |
| 476 | while (!atEOF && ch != '\'') { |
| 477 | if (ch == '\\') |
| 478 | readChar(); |
| 479 | readChar(); |
| 480 | } |
| 481 | finish = i; |
| 482 | readChar(); |
| 483 | tag = QStringLiteral("char" ); |
| 484 | break; |
| 485 | case '(': |
| 486 | finish = i; |
| 487 | readChar(); |
| 488 | ++parenDepth; |
| 489 | break; |
| 490 | case ')': |
| 491 | finish = i; |
| 492 | readChar(); |
| 493 | --parenDepth; |
| 494 | break; |
| 495 | case ':': |
| 496 | finish = i; |
| 497 | readChar(); |
| 498 | if (!atEOF && ch == ':') { |
| 499 | finish = i; |
| 500 | readChar(); |
| 501 | tag = QStringLiteral("op" ); |
| 502 | } |
| 503 | break; |
| 504 | case '/': |
| 505 | finish = i; |
| 506 | readChar(); |
| 507 | if (!atEOF && ch == '/') { |
| 508 | do { |
| 509 | finish = i; |
| 510 | readChar(); |
| 511 | } while (!atEOF && ch != '\n'); |
| 512 | tag = QStringLiteral("comment" ); |
| 513 | } else if (ch == '*') { |
| 514 | bool metAster = false; |
| 515 | bool metAsterSlash = false; |
| 516 | |
| 517 | finish = i; |
| 518 | readChar(); |
| 519 | |
| 520 | while (!metAsterSlash) { |
| 521 | if (atEOF) |
| 522 | break; |
| 523 | if (ch == '*') |
| 524 | metAster = true; |
| 525 | else if (metAster && ch == '/') |
| 526 | metAsterSlash = true; |
| 527 | else |
| 528 | metAster = false; |
| 529 | finish = i; |
| 530 | readChar(); |
| 531 | } |
| 532 | tag = QStringLiteral("comment" ); |
| 533 | } else { |
| 534 | tag = QStringLiteral("op" ); |
| 535 | } |
| 536 | break; |
| 537 | case '{': |
| 538 | finish = i; |
| 539 | readChar(); |
| 540 | braceDepth++; |
| 541 | break; |
| 542 | case '}': |
| 543 | finish = i; |
| 544 | readChar(); |
| 545 | braceDepth--; |
| 546 | break; |
| 547 | default: |
| 548 | finish = i; |
| 549 | readChar(); |
| 550 | } |
| 551 | } |
| 552 | |
| 553 | text = QStringView{code}.mid(pos: start, n: finish - start); |
| 554 | start = finish; |
| 555 | |
| 556 | if (!tag.isEmpty()) { |
| 557 | out += QStringLiteral("<@" ); |
| 558 | out += tag; |
| 559 | if (target) { |
| 560 | out += QStringLiteral(" target=\"" ); |
| 561 | out += text; |
| 562 | out += QStringLiteral("()\"" ); |
| 563 | } |
| 564 | out += QStringLiteral(">" ); |
| 565 | } |
| 566 | |
| 567 | appendProtectedString(output: &out, str: text); |
| 568 | |
| 569 | if (!tag.isEmpty()) { |
| 570 | out += QStringLiteral("</@" ); |
| 571 | out += tag; |
| 572 | out += QStringLiteral(">" ); |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | if (start < code.size()) { |
| 577 | appendProtectedString(output: &out, str: QStringView{code}.mid(pos: start)); |
| 578 | } |
| 579 | |
| 580 | return out; |
| 581 | } |
| 582 | |
| 583 | QT_END_NAMESPACE |
| 584 | |