File indexing completed on 2024-05-12 05:51:07
0001 /* 0002 SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "tokens.h" 0008 #include <QString> 0009 0010 namespace gdbmi 0011 { 0012 0013 /** 0014 * @brief eatSpace 0015 * @param message 0016 * @param position 0017 * @return new position 0018 */ 0019 int advanceBlanks(const QByteArray &buffer, int position) 0020 { 0021 const int size = buffer.size(); 0022 while (position < size) { 0023 const char head = buffer.at(position); 0024 if ((head != ' ') && (head != '\t')) { 0025 break; 0026 } 0027 ++position; 0028 } 0029 return position; 0030 } 0031 0032 int advanceNewlines(const QByteArray &buffer, int position) 0033 { 0034 if (position < 0) { 0035 return position; 0036 } 0037 0038 const int size = buffer.size(); 0039 while (position < size) { 0040 const char head = buffer.at(position); 0041 if ((head != ' ') && (head != '\t') && (head != '\n') && (head != '\r')) { 0042 break; 0043 } 0044 ++position; 0045 } 0046 return position; 0047 } 0048 0049 /** 0050 * index of char or newline 0051 */ 0052 static int findInLine(const QByteArray &buffer, char item, int position) 0053 { 0054 if (position < 0) { 0055 return -1; 0056 } 0057 const int size = buffer.size(); 0058 while (position < size) { 0059 const char head = buffer.at(position); 0060 if ((head == item) || (head == '\r') || (head == '\n')) { 0061 break; 0062 } 0063 ++position; 0064 } 0065 0066 if (position < size) { 0067 return position; 0068 } 0069 return -1; 0070 } 0071 0072 int indexOfNewline(const QByteArray &buffer, const int offset) 0073 { 0074 // \r\n 0075 const int r_idx = buffer.indexOf('\r', offset); 0076 if ((r_idx >= 0) && ((r_idx + 1) < buffer.size()) && (buffer.at(r_idx + 1) == '\n')) { 0077 return r_idx; 0078 } 0079 0080 const int n_idx = buffer.indexOf('\n', offset); 0081 if (n_idx >= 0) { 0082 // \n 0083 return n_idx; 0084 } else { 0085 // \r 0086 return r_idx; 0087 } 0088 } 0089 0090 QString unescapeString(const QByteArray &escapedString, QJsonParseError *error) 0091 { 0092 // hack: use json to unescape c-string 0093 // maybe change to std::quoted 0094 const auto doc = QJsonDocument::fromJson(QByteArrayLiteral("[\"") + escapedString + QByteArrayLiteral("\"]"), error); 0095 if ((error != nullptr) && (error->error != QJsonParseError::NoError)) { 0096 return QString(); 0097 } 0098 return doc.array()[0].toString(); 0099 } 0100 0101 QString quotedString(const QString &message) 0102 { 0103 static const QRegularExpression rx(QStringLiteral(R"--(((?<!\\)\"))--")); 0104 return QString(message).replace(rx, QStringLiteral(R"--(\")--")); 0105 } 0106 0107 Item<int> tryPrompt(const QByteArray &buffer, const int start) 0108 { 0109 const int size = buffer.size() - start; 0110 0111 const bool is_prompt = (size >= 5) && (buffer.at(start) == '(') && (buffer.at(start + 1) == 'g') && (buffer.at(start + 2) == 'd') 0112 && (buffer.at(start + 3) == 'b') && (buffer.at(start + 4) == ')'); 0113 0114 if (!is_prompt) { 0115 return make_error_item<int>(start, QStringLiteral("unexpected prompt format")); 0116 } 0117 0118 return make_empty_item<int>(start + 5); 0119 } 0120 0121 Item<QString> tryString(const QByteArray &buffer, int start) 0122 { 0123 int position = advanceBlanks(buffer, start); 0124 0125 const int size = buffer.size(); 0126 0127 if (position >= size) { 0128 return make_empty_item<QString>(start); 0129 } 0130 0131 char head = buffer.at(position); 0132 if (head != '"') { 0133 return make_error_item<QString>(start, QStringLiteral("unexpected string delimiter")); 0134 } 0135 0136 const int strStart = ++position; 0137 0138 bool mustUnescape = false; 0139 bool escaped = false; 0140 0141 while (position < size) { 0142 head = buffer.at(position); 0143 if ((head == '"') && !escaped) 0144 break; 0145 escaped = !escaped && (head == '\\'); 0146 mustUnescape = mustUnescape || escaped; 0147 ++position; 0148 } 0149 0150 if (position >= size) { 0151 return make_error_item<QString>(start, QStringLiteral("unexpected end of buffer")); 0152 } 0153 0154 const auto &roi = buffer.mid(strStart, position - start - 1); 0155 if (!mustUnescape) { 0156 return make_item(position + 1, QString::fromLocal8Bit(roi)); 0157 } 0158 // use json to unescape c-string 0159 QJsonParseError error; 0160 const QString finalString = unescapeString(roi, &error); 0161 if (error.error != QJsonParseError::NoError) { 0162 return make_error_item<QString>(start, error.errorString()); 0163 } 0164 0165 return make_item(position + 1, std::move(finalString)); 0166 } 0167 0168 /** 0169 * @brief findToken 0170 * @param message 0171 * @param start 0172 * @return [position, value] 0173 */ 0174 Item<int> tryToken(const QByteArray &buffer, const int start) 0175 { 0176 int position = start; 0177 const int size = buffer.size(); 0178 while (position < size) { 0179 const char head = buffer.at(position); 0180 if ((head < '0') || (head > '9')) { 0181 break; 0182 } 0183 ++position; 0184 } 0185 0186 if (position > start) { 0187 return make_item(position, buffer.mid(start, position - start).toInt()); 0188 } 0189 0190 return make_empty_item<int>(position); 0191 } 0192 0193 Item<QString> tryClassName(const QByteArray &buffer, const int start) 0194 { 0195 int position = advanceBlanks(buffer, start); 0196 if (position >= buffer.size()) { 0197 return make_error_item<QString>(start, QStringLiteral("unexpected end on line")); 0198 } 0199 int end = findInLine(buffer, ',', position); 0200 if (end < 0) { 0201 end = indexOfNewline(buffer, position); 0202 if (end < 0) { 0203 return make_item(buffer.size(), QString::fromLocal8Bit(buffer.mid(position)).trimmed()); 0204 } else { 0205 return make_item(end, QString::fromLocal8Bit(buffer.mid(position, end - position)).trimmed()); 0206 } 0207 } else { 0208 return make_item(end, QString::fromLocal8Bit(buffer.mid(position, end - position)).trimmed()); 0209 } 0210 } 0211 0212 Item<QString> tryVariable(const QByteArray &message, const int start, const char sep) 0213 { 0214 int position = advanceBlanks(message, start); 0215 if (position >= message.size()) { 0216 return make_error_item<QString>(start, QStringLiteral("unexpected end of variable")); 0217 } 0218 int end = findInLine(message, sep, position); 0219 if (end < 0) { 0220 return make_error_item<QString>(start, QStringLiteral("result name separator '=' not found")); 0221 } 0222 0223 return make_item(end + 1, QString::fromLocal8Bit(message.mid(position, end - position)).trimmed()); 0224 } 0225 0226 Item<StreamOutput> tryStreamOutput(const char prefix, const QByteArray &buffer, int start) 0227 { 0228 /* 0229 * console-stream-output → "~" c-string nl 0230 * target-stream-output → "@" c-string nl 0231 * log-stream-output → "&" c-string nl 0232 */ 0233 Item<StreamOutput> out; 0234 0235 int position = start + 1; 0236 0237 QString message; 0238 const auto strTok = tryString(buffer, position); 0239 if (!strTok.isEmpty()) { 0240 message = strTok.value.value(); 0241 out.position = advanceNewlines(buffer, strTok.position); 0242 } else { 0243 int idx = indexOfNewline(buffer, position); 0244 if (idx < 0) { 0245 message = QString::fromLocal8Bit(buffer.mid(position)); 0246 out.position = buffer.size(); 0247 } else { 0248 message = QString::fromLocal8Bit(buffer.mid(position, idx - position)); 0249 out.position = advanceNewlines(buffer, idx); 0250 } 0251 } 0252 0253 StreamOutput payload; 0254 0255 payload.message = message; 0256 if (prefix == '~') { 0257 payload.channel = StreamOutput::Console; 0258 } else if (prefix == '@') { 0259 payload.channel = StreamOutput::Output; 0260 } else if (prefix == '&') { 0261 payload.channel = StreamOutput::Log; 0262 } else { 0263 make_error_item<StreamOutput>(start, QStringLiteral("unknown streamoutput prefix")); 0264 } 0265 out.value = std::move(payload); 0266 0267 return out; 0268 } 0269 0270 Item<Result> tryResult(const QByteArray &message, const int start) 0271 { 0272 /* 0273 * result → variable "=" value 0274 */ 0275 int position = advanceBlanks(message, start); 0276 if (position >= message.size()) { 0277 return make_empty_item<Result>(position); 0278 } 0279 0280 // variable 0281 const auto tokVariable = tryVariable(message, position); 0282 if (tokVariable.isEmpty()) { 0283 return relay_item<Result>(tokVariable); 0284 } 0285 0286 Result out; 0287 out.name = tokVariable.value.value(); 0288 0289 position = advanceBlanks(message, tokVariable.position); 0290 if (position >= message.size()) { 0291 return make_error_item<Result>(start, QStringLiteral("unexpected end of result")); 0292 } 0293 0294 const auto tokValue = tryValue(message, position); 0295 if (tokValue.isEmpty()) { 0296 return relay_item<Result>(tokValue, start); 0297 } 0298 0299 out.value = tokValue.value.value(); 0300 0301 return make_item(tokValue.position, std::move(out)); 0302 } 0303 0304 Item<QJsonObject> tryResults(const QByteArray &message, const int start) 0305 { 0306 QJsonObject out; 0307 const int size = message.size(); 0308 0309 int position = start; 0310 do { 0311 if (position > start) { 0312 // skip ',' 0313 ++position; 0314 } 0315 const auto tokResult = tryResult(message, position); 0316 if (tokResult.isEmpty()) { 0317 return relay_item<QJsonObject>(tokResult); 0318 } 0319 out[tokResult.value->name] = tokResult.value->value; 0320 position = advanceBlanks(message, tokResult.position); 0321 } while ((position < size) && (message.at(position) == ',')); 0322 0323 return make_item(position, std::move(out)); 0324 } 0325 0326 Item<QJsonArray> tryResultList(const QByteArray &message, const int start) 0327 { 0328 QJsonArray out; 0329 const int size = message.size(); 0330 0331 int position = start; 0332 do { 0333 if (position > start) { 0334 // skip ',' 0335 ++position; 0336 } 0337 const auto tokResult = tryResult(message, position); 0338 if (tokResult.isEmpty()) { 0339 return relay_item<QJsonArray>(tokResult); 0340 } 0341 out.push_back(QJsonObject{{tokResult.value->name, tokResult.value->value}}); 0342 position = advanceBlanks(message, tokResult.position); 0343 } while ((position < size) && (message.at(position) == ',')); 0344 0345 return make_item(position, std::move(out)); 0346 } 0347 0348 Item<QJsonArray> tryValueList(const QByteArray &message, const int start) 0349 { 0350 QJsonArray out; 0351 0352 int position = start; 0353 do { 0354 if (position > start) { 0355 // skip ',' 0356 ++position; 0357 } 0358 const auto tok = tryValue(message, position); 0359 if (tok.isEmpty()) { 0360 return relay_item<QJsonArray>(tok); 0361 } 0362 out << tok.value.value(); 0363 position = advanceBlanks(message, tok.position); 0364 } while ((position < message.size()) && (message.at(position) == ',')); 0365 0366 return make_item(position, std::move(out)); 0367 } 0368 0369 Item<QJsonValue> tryValue(const QByteArray &message, const int start) 0370 { 0371 /* 0372 * value → const | tuple | list 0373 */ 0374 int position = advanceBlanks(message, start); 0375 if (position >= message.size()) { 0376 return make_error_item<QJsonValue>(start, QStringLiteral("unexpected end of value")); 0377 } 0378 0379 QJsonValue out; 0380 0381 const char head = message.at(position); 0382 switch (head) { 0383 case '"': { 0384 const auto tokString = tryString(message, position); 0385 if (tokString.isEmpty()) { 0386 return relay_item<QJsonValue>(tokString); 0387 } 0388 out = tokString.value.value(); 0389 position = tokString.position; 0390 } break; 0391 case '{': 0392 // tuple 0393 { 0394 const auto tokTuple = tryTuple(message, position); 0395 if (tokTuple.isEmpty()) { 0396 return relay_item<QJsonValue>(tokTuple); 0397 } 0398 out = tokTuple.value.value(); 0399 position = tokTuple.position; 0400 } 0401 break; 0402 case '[': 0403 // list 0404 { 0405 const auto tokList = tryList(message, position); 0406 if (tokList.isEmpty()) { 0407 return relay_item<QJsonValue>(tokList); 0408 } 0409 out = tokList.value.value(); 0410 position = tokList.position; 0411 } 0412 break; 0413 default: 0414 return make_error_item<QJsonValue>(start, QStringLiteral("unexpected character")); 0415 } 0416 0417 return make_item(position, std::move(out)); 0418 } 0419 0420 Item<QJsonObject> tryTuple(const QByteArray &message, const int start) 0421 { 0422 /* 0423 * tuple → "{}" | "{" result ( "," result )* "}" 0424 */ 0425 // advance { or [ 0426 int position = advanceBlanks(message, start + 1); 0427 if (position >= message.size()) { 0428 return make_error_item<QJsonObject>(start, QStringLiteral("unexpected end of tuple")); 0429 } 0430 0431 QJsonObject out; 0432 0433 // empty tuple ? 0434 if (message.at(position) != '}') { 0435 const auto tokResults = tryResults(message, position); 0436 if (!tokResults.isEmpty()) { 0437 position = advanceBlanks(message, tokResults.position); 0438 } 0439 if ((position >= message.size()) || (message.at(position) != '}')) { 0440 return make_error_item<QJsonObject>(start, QStringLiteral("unexpected end of tuple")); 0441 } 0442 if (tokResults.value) { 0443 out = tokResults.value.value(); 0444 } 0445 } 0446 return make_item(position + 1, std::move(out)); 0447 } 0448 0449 Item<QJsonArray> tryTupleList(const QByteArray &message, const int start) 0450 { 0451 /* 0452 * list → "[]" | "[" result ( "," result )* "]" 0453 */ 0454 // advance { or [ 0455 int position = advanceBlanks(message, start + 1); 0456 if (position >= message.size()) { 0457 return make_error_item<QJsonArray>(start, QStringLiteral("unexpected end of list")); 0458 } 0459 0460 QJsonArray out; 0461 0462 // empty tuple ? 0463 if (message.at(position) != ']') { 0464 const auto tokResults = tryResultList(message, position); 0465 if (!tokResults.isEmpty()) { 0466 position = advanceBlanks(message, tokResults.position); 0467 } 0468 if ((position >= message.size()) || (message.at(position) != ']')) { 0469 return make_error_item<QJsonArray>(start, QStringLiteral("unexpected end of list")); 0470 } 0471 if (tokResults.value) { 0472 out = tokResults.value.value(); 0473 } 0474 } 0475 return make_item(position + 1, std::move(out)); 0476 } 0477 0478 Item<QJsonValue> tryList(const QByteArray &message, const int start) 0479 { 0480 /* 0481 * list → "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]" 0482 */ 0483 int position = advanceBlanks(message, start); 0484 if (position >= message.size()) { 0485 return make_error_item<QJsonValue>(start, QStringLiteral("unexpected end of list")); 0486 } 0487 0488 // try values 0489 const auto tokTuple = tryTupleList(message, position); 0490 if (!tokTuple.isEmpty()) 0491 return make_item(tokTuple.position, QJsonValue(tokTuple.value.value())); 0492 0493 // skip [ 0494 const auto tokValues = tryValueList(message, position + 1); 0495 if (!tokValues.isEmpty()) { 0496 position = advanceBlanks(message, tokValues.position); 0497 } 0498 0499 if ((position >= message.size()) || (message.at(position) != ']')) { 0500 return make_error_item<QJsonValue>(start, QStringLiteral("unexpected end of list")); 0501 } 0502 0503 QJsonValue out; 0504 if (tokValues.value) { 0505 out = tokValues.value.value(); 0506 } 0507 return make_item(position + 1, std::move(out)); 0508 } 0509 0510 Item<Record> tryRecord(const char prefix, const QByteArray &message, const int start, int token) 0511 { 0512 /* 0513 * exec-async-output → [ token ] "*" async-output nl 0514 * status-async-output → [ token ] "+" async-output nl 0515 * notify-async-output → [ token ] "=" async-output nl 0516 * async-output → async-class ( "," result )* 0517 * 0518 * result-record → [ token ] "^" result-class ( "," result )* nl 0519 */ 0520 0521 const auto className = tryClassName(message, start + 1); 0522 if (className.hasError()) { 0523 return relay_item<Record>(className); 0524 } else if (className.isEmpty()) { 0525 return make_error_item<Record>(className.position, QStringLiteral("class name not found")); 0526 } 0527 0528 Record payload; 0529 payload.resultClass = className.value.value(); 0530 0531 if (token >= 0) { 0532 payload.token = token; 0533 } 0534 if (prefix == '*') { 0535 payload.category = Record::Exec; 0536 } else if (prefix == '+') { 0537 payload.category = Record::Status; 0538 } else if (prefix == '=') { 0539 payload.category = Record::Notify; 0540 } else if (prefix == '^') { 0541 payload.category = Record::Result; 0542 } 0543 0544 // parse results 0545 int position = advanceBlanks(message, className.position); 0546 if ((position >= message.size()) || (message.at(position) != ',')) { 0547 return make_item(position, std::move(payload)); 0548 } 0549 0550 const auto tokResults = tryResults(message, ++position); 0551 if (tokResults.isEmpty()) { 0552 return relay_item<Record>(tokResults); 0553 } 0554 0555 payload.value = tokResults.value.value(); 0556 0557 position = advanceNewlines(message, tokResults.position); 0558 0559 return make_item(position, std::move(payload)); 0560 } 0561 0562 } // namespace gdbmi