diff --git a/po/colobot.pot b/po/colobot.pot index 509a3c7a..419b88e9 100644 --- a/po/colobot.pot +++ b/po/colobot.pot @@ -1770,6 +1770,24 @@ msgstr "" msgid "This parameter needs a default value" msgstr "" +msgid "Missing end quote" +msgstr "" + +msgid "Unknown escape sequence" +msgstr "" + +msgid "Octal value out of range" +msgstr "" + +msgid "Missing hex digits after escape sequence" +msgstr "" + +msgid "Hex value out of range" +msgstr "" + +msgid "Invalid universal character name" +msgstr "" + msgid "Dividing by zero" msgstr "" diff --git a/po/de.po b/po/de.po index 5e2f4a54..1c839a09 100644 --- a/po/de.po +++ b/po/de.po @@ -680,6 +680,9 @@ msgstr "Anweisungen über das ausgewählte Objekt" msgid "Help balloons\\Explain the function of the buttons" msgstr "Hilfeblasen\\Hilfeblasen" +msgid "Hex value out of range" +msgstr "" + #, fuzzy msgid "Higher speed\\Doubles speed" msgstr "Geschwindigkeit 2.0x\\Spielgeschwindigkeit doppelt so schnell" @@ -759,6 +762,9 @@ msgstr "Anweisungen\\Anweisungen für die Mission oder Übung" msgid "Internal error - tell the developers" msgstr "Interner Fehler - Benachrichtige bitte die Entwickler" +msgid "Invalid universal character name" +msgstr "" + msgid "Invert\\Invert values on this axis" msgstr "Invertieren\\Die Werte dieser Achse invertieren" @@ -869,6 +875,12 @@ msgstr "Verkleinern" msgid "Mipmap level\\Mipmap level" msgstr "Mipmap-Level\\Mipmap-Level" +msgid "Missing end quote" +msgstr "" + +msgid "Missing hex digits after escape sequence" +msgstr "" + msgid "Mission name" msgstr "Name der Mission" @@ -1031,6 +1043,9 @@ msgstr "OK\\Programm kompilieren" msgid "Object too close" msgstr "Gegenstand zu nahe" +msgid "Octal value out of range" +msgstr "" + msgid "One step" msgstr "Ein Schritt" @@ -1632,6 +1647,9 @@ msgstr "Das Objekt existiert nicht" msgid "Unknown command" msgstr "Befehl unbekannt" +msgid "Unknown escape sequence" +msgstr "" + msgid "Unknown function" msgstr "Unbekannte Funktion" diff --git a/po/fr.po b/po/fr.po index 01ca9deb..a2877175 100644 --- a/po/fr.po +++ b/po/fr.po @@ -677,6 +677,9 @@ msgstr "Instructions sur la sélection" msgid "Help balloons\\Explain the function of the buttons" msgstr "Bulles d'aide\\Bulles explicatives" +msgid "Hex value out of range" +msgstr "" + #, fuzzy msgid "Higher speed\\Doubles speed" msgstr "Vitesse 2.0x\\Deux fois plus rapide" @@ -756,6 +759,9 @@ msgstr "Instructions mission\\Marche à suivre" msgid "Internal error - tell the developers" msgstr "Erreur interne - contacter les développeurs" +msgid "Invalid universal character name" +msgstr "" + msgid "Invert\\Invert values on this axis" msgstr "Inversion\\Inverse les valeurs sur cet axe" @@ -866,6 +872,12 @@ msgstr "Taille réduite" msgid "Mipmap level\\Mipmap level" msgstr "Niveau de MIP mapping\\Niveau de MIP mapping" +msgid "Missing end quote" +msgstr "" + +msgid "Missing hex digits after escape sequence" +msgstr "" + msgid "Mission name" msgstr "Nom de la mission" @@ -1028,6 +1040,9 @@ msgstr "D'accord\\Compiler le programme" msgid "Object too close" msgstr "Objet trop proche" +msgid "Octal value out of range" +msgstr "" + msgid "One step" msgstr "Un pas" @@ -1629,6 +1644,9 @@ msgstr "Objet n'existe pas" msgid "Unknown command" msgstr "Commande inconnue" +msgid "Unknown escape sequence" +msgstr "" + msgid "Unknown function" msgstr "Routine inconnue" diff --git a/po/pl.po b/po/pl.po index 07a6328a..bb288518 100644 --- a/po/pl.po +++ b/po/pl.po @@ -678,6 +678,9 @@ msgstr "Pomoc na temat zaznaczonego obiektu" msgid "Help balloons\\Explain the function of the buttons" msgstr "Dymki pomocy\\Wyjaśnia funkcje przycisków" +msgid "Hex value out of range" +msgstr "" + msgid "Higher speed\\Doubles speed" msgstr "Zwiększ prędkość\\Podwaja prędkość" @@ -756,6 +759,9 @@ msgstr "Rozkazy\\Pokazuje rozkazy dotyczące bieżącej misji" msgid "Internal error - tell the developers" msgstr "Błąd wewnętrzny - powiadom twórców gry" +msgid "Invalid universal character name" +msgstr "" + msgid "Invert\\Invert values on this axis" msgstr "Odwróć\\Odwróć wartości na tej osi" @@ -852,6 +858,12 @@ msgstr "Pomniejsz" msgid "Mipmap level\\Mipmap level" msgstr "Poziom mipmap\\Poziom mipmap" +msgid "Missing end quote" +msgstr "" + +msgid "Missing hex digits after escape sequence" +msgstr "" + msgid "Mission name" msgstr "Nazwa misji" @@ -1014,6 +1026,9 @@ msgstr "OK\\Zamyka edytor programu i powraca do gry" msgid "Object too close" msgstr "Obiekt za blisko" +msgid "Octal value out of range" +msgstr "" + msgid "One step" msgstr "Jeden krok" @@ -1614,6 +1629,9 @@ msgstr "Obiekt nieznany" msgid "Unknown command" msgstr "Nieznane polecenie" +msgid "Unknown escape sequence" +msgstr "" + msgid "Unknown function" msgstr "Funkcja nieznana" diff --git a/po/ru.po b/po/ru.po index fdcf909e..f2b25354 100644 --- a/po/ru.po +++ b/po/ru.po @@ -686,6 +686,9 @@ msgstr "Справка о выбранном объекте" msgid "Help balloons\\Explain the function of the buttons" msgstr "Подсказки\\Объяснение функций кнопок" +msgid "Hex value out of range" +msgstr "" + #, fuzzy msgid "Higher speed\\Doubles speed" msgstr "Скорость 2.0х\\В два раза быстрее" @@ -765,6 +768,9 @@ msgstr "Инструкции\\Показывает инструкции по т msgid "Internal error - tell the developers" msgstr "Внутренняя ошибка - сообщите разработчикам" +msgid "Invalid universal character name" +msgstr "" + msgid "Invert\\Invert values on this axis" msgstr "Инвертир.\\Инвертировать значения по этой оси" @@ -875,6 +881,12 @@ msgstr "Свернуть" msgid "Mipmap level\\Mipmap level" msgstr "Уровень уменьшающей фильтрации\\Уровень уменьшающей фильтрации" +msgid "Missing end quote" +msgstr "" + +msgid "Missing hex digits after escape sequence" +msgstr "" + msgid "Mission name" msgstr "Название миссии" @@ -1039,6 +1051,9 @@ msgstr "ОК\\Закрыть редактор программ и вернуть msgid "Object too close" msgstr "Объект слишком близок" +msgid "Octal value out of range" +msgstr "" + msgid "One step" msgstr "Один шаг" @@ -1645,6 +1660,9 @@ msgstr "Неизвестный объект" msgid "Unknown command" msgstr "Неизвестная команда" +msgid "Unknown escape sequence" +msgstr "" + msgid "Unknown function" msgstr "Неизвестная функция" diff --git a/src/CBot/CBotEnums.h b/src/CBot/CBotEnums.h index f98d4e96..fb701bc4 100644 --- a/src/CBot/CBotEnums.h +++ b/src/CBot/CBotEnums.h @@ -241,6 +241,12 @@ enum CBotError : int CBotErrNoClassName = 5046, //!< class name expected CBotErrNoReturn = 5047, //!< non-void function needs "return;" CBotErrDefaultValue = 5048, //!< this parameter needs a default value + CBotErrEndQuote = 5049, //!< missing end quote + CBotErrBadEscape = 5050, //!< unknown escape sequence + CBotErrOctalRange = 5051, //!< octal value out of range + CBotErrHexDigits = 5052, //!< missing hex digits after escape sequence + CBotErrHexRange = 5053, //!< hex value out of range + CBotErrUnicodeName = 5054, //!< invalid universal character name // Runtime errors CBotErrZeroDiv = 6000, //!< division by zero diff --git a/src/CBot/CBotInstr/CBotExprLitString.cpp b/src/CBot/CBotInstr/CBotExprLitString.cpp index ad85f7a4..58d5fe16 100644 --- a/src/CBot/CBotInstr/CBotExprLitString.cpp +++ b/src/CBot/CBotInstr/CBotExprLitString.cpp @@ -42,15 +42,136 @@ CBotInstr* CBotExprLitString::Compile(CBotToken* &p, CBotCStack* pStack) { CBotCStack* pStk = pStack->TokenStack(); - CBotExprLitString* inst = new CBotExprLitString(); + std::string s = p->GetString(); - inst->SetToken(p); - p = p->GetNext(); + auto it = s.cbegin(); + if (++it != s.cend()) + { + int pos = p->GetStart(); + std::string valstring = ""; + while (it != s.cend() && *it != '\"') + { + pStk->SetStartError(++pos); + if (*it != '\\') // not escape sequence ? + { + valstring += *(it++); + continue; + } - CBotVar* var = CBotVar::Create("", CBotTypString); - pStk->SetVar(var); + if (++it == s.cend()) break; - return pStack->Return(inst, pStk); + if (CharInList(*it, "01234567")) // octal + { + std::string octal = ""; + + for (int i = 0; i < 3; i++) + { + if (!CharInList(*it, "01234567")) break; + ++pos; + octal += *it; + if (++it == s.cend()) break; + } + + unsigned int val = std::stoi(octal, nullptr, 8); + if (val <= 255) + { + valstring.push_back(val); + continue; + } + pStk->SetError(CBotErrOctalRange, pos + 1); + } + else + { + ++pos; + unsigned char c = *(it++); + if (c == '\"' || c == '\'' || c == '\\') valstring += c; + else if (c == 'a') valstring += '\a'; // alert bell + else if (c == 'b') valstring += '\b'; // backspace + else if (c == 'f') valstring += '\f'; // form feed + else if (c == 'n') valstring += '\n'; // new line + else if (c == 'r') valstring += '\r'; // carriage return + else if (c == 't') valstring += '\t'; // horizontal tab + else if (c == 'v') valstring += '\v'; // vertical tab + else if (c == 'x' || c == 'u' || c == 'U') // hex or unicode + { + if (it != s.cend()) + { + std::string hex = ""; + bool isHexCode = (c == 'x'); + size_t maxlen = (c == 'u') ? 4 : 8; + + for (size_t i = 0; isHexCode || i < maxlen; i++) + { + if (!CharInList(*it, "0123456789ABCDEFabcdef")) break; + ++pos; + hex += *it; + if (++it == s.cend()) break; + } + + if (!hex.empty()) + { + unsigned int val = 0; + try + { + val = std::stoi(hex, nullptr, 16); + } + catch (const std::out_of_range& e) + { + pStk->SetError(CBotErrHexRange, pos + 1); + } + + if (pStk->IsOk()) + { + if (isHexCode) // hexadecimal + { + if (val <= 255) + { + valstring.push_back(val); + continue; + } + pStk->SetError(CBotErrHexRange, pos + 1); + } + else if (maxlen == hex.length()) // unicode character + { + if (val < 0xD800 || (0xDFFF < val && val < 0x110000)) + { + valstring += CodePointToUTF8(val); + continue; + } + pStk->SetError(CBotErrUnicodeName, pos + 1); + } + } + } + } + + pStk->SetError(CBotErrHexDigits, pos + 1); + } + else + pStk->SetError(CBotErrBadEscape, pos + 1); // unknown escape code + } + + if (!pStk->IsOk()) break; + } + + if (it == s.cend() || *it != '\"') + pStk->SetError(CBotErrEndQuote, p); + + if (pStk->IsOk()) + { + CBotExprLitString* inst = new CBotExprLitString(); + inst->m_valstring.swap(valstring); + inst->SetToken(p); + p = p->GetNext(); + + CBotVar* var = CBotVar::Create("", CBotTypString); + pStk->SetVar(var); + + return pStack->Return(inst, pStk); + } + } + + pStk->SetError(CBotErrEndQuote, p); + return pStack->Return(nullptr, pStk); } //////////////////////////////////////////////////////////////////////////////// @@ -62,10 +183,7 @@ bool CBotExprLitString::Execute(CBotStack* &pj) CBotVar* var = CBotVar::Create("", CBotTypString); - std::string chaine = m_token.GetString(); - chaine = chaine.substr(1, chaine.length()-2); // removes the quotes - - var->SetValString(chaine); // value of the number + var->SetValString(m_valstring); pile->SetVar(var); // put on the stack diff --git a/src/CBot/CBotInstr/CBotExprLitString.h b/src/CBot/CBotInstr/CBotExprLitString.h index 4336dd15..55c39650 100644 --- a/src/CBot/CBotInstr/CBotExprLitString.h +++ b/src/CBot/CBotInstr/CBotExprLitString.h @@ -58,6 +58,9 @@ public: protected: virtual const std::string GetDebugName() override { return "CBotExprLitString"; } virtual std::string GetDebugData() override; + +private: + std::string m_valstring = ""; }; } // namespace CBot diff --git a/src/CBot/CBotToken.cpp b/src/CBot/CBotToken.cpp index d9e33ffd..3c4bc7d9 100644 --- a/src/CBot/CBotToken.cpp +++ b/src/CBot/CBotToken.cpp @@ -241,23 +241,13 @@ void CBotToken::SetPos(int start, int end) } //////////////////////////////////////////////////////////////////////////////// -bool CharInList(const char c, const char* list) -{ - int i = 0; - while (true) - { - if (c == list[i++]) return true; - if (list[i] == 0) return false; - } -} - -static char sep1[] = " \r\n\t,:()[]{}-+*/=;>!~^|&%."; // operational separators +static char sep3[] = ",:()[]{}-+*/=;<>!~^|&%.?"; // operational separators static char num[] = "0123456789"; // point (single) is tested separately static char hexnum[] = "0123456789ABCDEFabcdef"; -static char nch[] = "\"\r\n\t"; // forbidden in chains +static char nch[] = "\r\n\t"; // forbidden in chains //////////////////////////////////////////////////////////////////////////////// CBotToken* CBotToken::NextToken(const char*& program, bool first) @@ -278,14 +268,13 @@ CBotToken* CBotToken::NextToken(const char*& program, bool first) // special case for strings if (token[0] == '\"' ) { - while (c != 0 && !CharInList(c, nch)) + while (c != 0 && c != '\"' && !CharInList(c, nch)) { if ( c == '\\' ) { - c = *(program++); // next character - if ( c == 'n' ) c = '\n'; - if ( c == 'r' ) c = '\r'; - if ( c == 't' ) c = '\t'; + token += c; + c = *(program++); + if (c == 0 || CharInList(c, nch)) break; } token += c; c = *(program++); diff --git a/src/CBot/CBotUtils.cpp b/src/CBot/CBotUtils.cpp index 4e289317..2bb4d8e8 100644 --- a/src/CBot/CBotUtils.cpp +++ b/src/CBot/CBotUtils.cpp @@ -236,4 +236,49 @@ float GetNumFloat(const std::string& str) return static_cast(num); } +bool CharInList(const char c, const char* list) +{ + int i = 0; + + while (list[i] != 0) + { + if (c == list[i++]) return true; + } + + return false; +} + +std::string CodePointToUTF8(unsigned int val) +{ + std::string s = ""; + + if (val < 0xD800 || (0xDFFF < val && val < 0x110000)) + { + if (val < 0x80) + { + s.push_back(val); + } + else if (val < 0x800) + { + s.push_back(0xC0 + (val >> 6)); + s.push_back(0x80 + (val & 0x3F)); + } + else if (val < 0x10000) + { + s.push_back(0xE0 + (val >> 12)); + s.push_back(0x80 + ((val >> 6) & 0x3F)); + s.push_back(0x80 + (val & 0x3F)); + } + else + { + s.push_back(0xF0 + (val >> 18)); + s.push_back(0x80 + ((val >> 12) & 0x3F)); + s.push_back(0x80 + ((val >> 6) & 0x3F)); + s.push_back(0x80 + (val & 0x3F)); + } + } + + return s; +} + } // namespace CBot diff --git a/src/CBot/CBotUtils.h b/src/CBot/CBotUtils.h index 418e10bf..6db107a9 100644 --- a/src/CBot/CBotUtils.h +++ b/src/CBot/CBotUtils.h @@ -96,6 +96,21 @@ long GetNumInt(const std::string& str); */ float GetNumFloat(const std::string& str); +/*! + * \brief Search a null-terminated string for a char value. + * \param c The char to find. + * \param list The string to search. + * \return true if the char is found. + */ +bool CharInList(const char c, const char* list); + +/*! + * \brief Converts a Unicode code point to UTF-8 encoded character. + * \param val Code point value. + * \return UTF-8 encoded string or empty string. + */ +std::string CodePointToUTF8(unsigned int val); + template class CBotLinkedList { public: diff --git a/src/common/restext.cpp b/src/common/restext.cpp index 3d16603a..ac526af8 100644 --- a/src/common/restext.cpp +++ b/src/common/restext.cpp @@ -731,6 +731,12 @@ void InitializeRestext() stringsCbot[CBot::CBotErrNoClassName] = TR("Class name expected"); stringsCbot[CBot::CBotErrNoReturn] = TR("Non-void function needs \"return;\""); stringsCbot[CBot::CBotErrDefaultValue] = TR("This parameter needs a default value"); + stringsCbot[CBot::CBotErrEndQuote] = TR("Missing end quote"); + stringsCbot[CBot::CBotErrBadEscape] = TR("Unknown escape sequence"); + stringsCbot[CBot::CBotErrOctalRange] = TR("Octal value out of range"); + stringsCbot[CBot::CBotErrHexDigits] = TR("Missing hex digits after escape sequence"); + stringsCbot[CBot::CBotErrHexRange] = TR("Hex value out of range"); + stringsCbot[CBot::CBotErrUnicodeName] = TR("Invalid universal character name"); stringsCbot[CBot::CBotErrZeroDiv] = TR("Dividing by zero"); stringsCbot[CBot::CBotErrNotInit] = TR("Variable not initialized"); diff --git a/src/script/script.cpp b/src/script/script.cpp index 8253caea..f9a70e3b 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -589,6 +589,56 @@ void CScript::UpdateList(Ui::CList* list) list->SetState(Ui::STATE_ENABLE); } +// Colorize a string literal with escape sequences also colored + +void HighlightString(Ui::CEdit* edit, const std::string& s, int start) +{ + edit->SetFormat(start, start + 1, Gfx::FONT_HIGHLIGHT_STRING); + + auto it = s.cbegin() + 1; + + ++start; + while (it != s.cend() && *it != '\"') + { + if (*(it++) != '\\') // not escape sequence + { + edit->SetFormat(start, start + 1, Gfx::FONT_HIGHLIGHT_STRING); + ++start; + continue; + } + + if (it == s.cend()) break; + + int end = start + 2; + + if (CBot::CharInList(*it, "01234567")) // octal escape sequence + { + for (int i = 0; ++it != s.cend() && i < 2; i++, end++) + { + if (!CBot::CharInList(*it, "01234567")) break; + } + } + else if (*it == 'x' || *it == 'u' || *it == 'U') // hex or unicode escape + { + bool isHexCode = (*it == 'x'); + int maxlen = (*it == 'u') ? 4 : 8; + + for (int i = 0; ++it != s.cend(); i++, end++) + { + if (!isHexCode && i >= maxlen) break; + if (!CBot::CharInList(*it, "0123456789ABCDEFabcdef")) break; + } + } + else // n, r, t, etc. + ++it; + + edit->SetFormat(start, end, Gfx::FONT_HIGHLIGHT_NONE); + start = end; + } + + if (it != s.cend()) + edit->SetFormat(start, start + 1, Gfx::FONT_HIGHLIGHT_STRING); +} // Colorize the text according to syntax. @@ -643,10 +693,16 @@ void CScript::ColorizeScript(Ui::CEdit* edit, int rangeStart, int rangeEnd) { color = Gfx::FONT_HIGHLIGHT_CONST; } - else if (type == CBot::TokenTypString || type == CBot::TokenTypNum) // string literals and numbers + else if (type == CBot::TokenTypNum) // numbers { color = Gfx::FONT_HIGHLIGHT_STRING; } + else if (type == CBot::TokenTypString) // string literals + { + HighlightString(edit, token, cursor1); + bt = bt->GetNext(); + continue; + } assert(cursor1 < cursor2); edit->SetFormat(cursor1, cursor2, color); diff --git a/test/unit/CBot/CBot_test.cpp b/test/unit/CBot/CBot_test.cpp index 9e264e1e..f94b72d3 100644 --- a/test/unit/CBot/CBot_test.cpp +++ b/test/unit/CBot/CBot_test.cpp @@ -1533,6 +1533,120 @@ TEST_F(CBotUT, String) " ASSERT(c == \"Colobot!\");\n" "}\n" ); + + ExecuteTest( + "extern void MissingEndQuote()\n" + "{\n" + " \"Colobot...\n" + "}\n", + CBotErrEndQuote + ); +} + +TEST_F(CBotUT, StringEscapeCodes) +{ + ExecuteTest( + "extern void HexEscapeCodes()\n" + "{\n" + " ASSERT(\" \\x07 \" == \" \\a \");\n" + " ASSERT(\" \\x08 \" == \" \\b \");\n" + " ASSERT(\" \\x09 \" == \" \\t \");\n" + " ASSERT(\" \\x0A \" == \" \\n \");\n" + " ASSERT(\" \\x0B \" == \" \\v \");\n" + " ASSERT(\" \\x0C \" == \" \\f \");\n" + " ASSERT(\" \\x0D \" == \" \\r \");\n" + " ASSERT(\" \\x22 \" == \" \\\" \");\n" + " ASSERT(\" \\x27 \" == \" \\\' \");\n" + " ASSERT(\" \\x5C \" == \" \\\\ \");\n" + " string test = \"\\x31 \\x32 \\x33\";\n" + " ASSERT(test == \"1 2 3\");\n" + "}\n" + "extern void OctalEscapeCodes()\n" + "{\n" + " ASSERT(\" \\000 \" == \" \\x00 \");\n" + " ASSERT(\" \\007 \" == \" \\x07 \");\n" + " ASSERT(\" \\010 \" == \" \\x08 \");\n" + " ASSERT(\" \\011 \" == \" \\x09 \");\n" + " ASSERT(\" \\012 \" == \" \\x0A \");\n" + " ASSERT(\" \\013 \" == \" \\x0B \");\n" + " ASSERT(\" \\014 \" == \" \\x0C \");\n" + " ASSERT(\" \\015 \" == \" \\x0D \");\n" + " ASSERT(\" \\042 \" == \" \\x22 \");\n" + " ASSERT(\" \\047 \" == \" \\x27 \");\n" + " ASSERT(\" \\134 \" == \" \\x5C \");\n" + " string test = \"\\101 \\102 \\103\";\n" + " ASSERT(test == \"A B C\");\n" + "}\n" + "extern void UnicodeEscapeCodesToUTF_8()\n" + "{\n" + " ASSERT(\" \\u0000 \" == \" \\0 \");\n" + " ASSERT(\" \\u0007 \" == \" \\a \");\n" + " ASSERT(\" \\u0008 \" == \" \\b \");\n" + " ASSERT(\" \\u0009 \" == \" \\t \");\n" + " ASSERT(\" \\u000A \" == \" \\n \");\n" + " ASSERT(\" \\u000B \" == \" \\v \");\n" + " ASSERT(\" \\u000C \" == \" \\f \");\n" + " ASSERT(\" \\u000D \" == \" \\r \");\n" + " ASSERT(\" \\u0022 \" == \" \\\" \");\n" + " ASSERT(\" \\u0027 \" == \" \\\' \");\n" + " ASSERT(\" \\u005C \" == \" \\\\ \");\n" + "\n" + " ASSERT(\"\\u00A9\" == \"\\xC2\\xA9\");\n" + " ASSERT(\"\\u00AE\" == \"\\xC2\\xAE\");\n" + " ASSERT(\"\\u262E\" == \"\\xE2\\x98\\xAE\");\n" + " ASSERT(\"\\u262F\" == \"\\xE2\\x98\\xAF\");\n" + " ASSERT(\"\\U0001F60E\" == \"\\xF0\\x9F\\x98\\x8E\");\n" + " ASSERT(\"\\U0001F61C\" == \"\\xF0\\x9F\\x98\\x9C\");\n" + " ASSERT(\"\\U0001F6E0\" == \"\\xF0\\x9F\\x9B\\xA0\");\n" + "}\n" + "extern void UnicodeMaxCharacterNameToUTF_8()\n" + "{\n" + " ASSERT(\"\\U0010FFFF\" == \"\\xF4\\x8F\\xBF\\xBF\");\n" + "}\n" + ); +} + +TEST_F(CBotUT, StringEscapeCodeErrors) +{ + ExecuteTest( + "extern void UnknownEscapeSequence()\n" + "{\n" + " \"Unknown: \\p \";\n" + "}\n", + CBotErrBadEscape + ); + + ExecuteTest( + "extern void MissingHexDigits()\n" + "{\n" + " \" \\x \";\n" + "}\n", + CBotErrHexDigits + ); + + ExecuteTest( + "extern void HexValueOutOfRange()\n" + "{\n" + " \" \\x100 \";\n" + "}\n", + CBotErrHexRange + ); + + ExecuteTest( + "extern void OctalValueOutOfRange()\n" + "{\n" + " \" \\400 \";\n" + "}\n", + CBotErrOctalRange + ); + + ExecuteTest( + "extern void BadUnicodeCharacterName()\n" + "{\n" + " \" \\U00110000 \";\n" + "}\n", + CBotErrUnicodeName + ); } // TODO: not implemented, see issue #694