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