Merge pull request #1004 from melex750/dev-cbot

Escape codes for strings in CBOT
1008-fix
krzys_h 2017-10-22 18:20:50 +02:00 committed by GitHub
commit 9448f6712f
14 changed files with 471 additions and 29 deletions

View File

@ -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 ""

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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 "Неизвестная функция"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,:()[]{}-+*/=;><!~^|&%.";
static char sep1[] = " \r\n\t,:()[]{}-+*/=;><!~^|&%.\"\'?";
static char sep2[] = " \r\n\t"; // only separators
static char sep3[] = ",:()[]{}-+*/=;<>!~^|&%."; // 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++);

View File

@ -236,4 +236,49 @@ float GetNumFloat(const std::string& str)
return static_cast<float>(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

View File

@ -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<typename T> class CBotLinkedList
{
public:

View File

@ -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");

View File

@ -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);

View File

@ -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