diff --git a/po/colobot.pot b/po/colobot.pot index b91aba04..266d13b1 100644 --- a/po/colobot.pot +++ b/po/colobot.pot @@ -1795,6 +1795,9 @@ msgstr "" msgid "Invalid universal character name" msgstr "" +msgid "Empty character constant" +msgstr "" + msgid "Dividing by zero" msgstr "" diff --git a/po/cs.po b/po/cs.po index b5c41e3a..7f1b50d4 100644 --- a/po/cs.po +++ b/po/cs.po @@ -507,6 +507,9 @@ msgstr "Upravit vybraný program" msgid "Egg" msgstr "Vejce" +msgid "Empty character constant" +msgstr "" + msgid "End of block missing" msgstr "Chybí konec bloku" diff --git a/po/de.po b/po/de.po index 66d9918a..61f72697 100644 --- a/po/de.po +++ b/po/de.po @@ -508,6 +508,9 @@ msgstr "Gewähltes Programm bearbeiten" msgid "Egg" msgstr "Ei" +msgid "Empty character constant" +msgstr "" + msgid "End of block missing" msgstr "Es fehlt eine geschlossene geschweifte Klammer \"}\" (Ende des Blocks)" diff --git a/po/fr.po b/po/fr.po index c8d48217..f0229e72 100644 --- a/po/fr.po +++ b/po/fr.po @@ -510,6 +510,9 @@ msgstr "Éditer le programme sélectionné" msgid "Egg" msgstr "Oeuf" +msgid "Empty character constant" +msgstr "" + msgid "End of block missing" msgstr "Il manque la fin du bloc" diff --git a/po/pl.po b/po/pl.po index 073c7b58..5b9bab87 100644 --- a/po/pl.po +++ b/po/pl.po @@ -506,6 +506,9 @@ msgstr "Edytuj zaznaczony program" msgid "Egg" msgstr "Jajo" +msgid "Empty character constant" +msgstr "" + msgid "End of block missing" msgstr "Brak końca bloku" diff --git a/po/pt.po b/po/pt.po index f99a9cbf..53bdd07c 100644 --- a/po/pt.po +++ b/po/pt.po @@ -505,6 +505,9 @@ msgstr "Alterar o programa selecionado" msgid "Egg" msgstr "Ovo" +msgid "Empty character constant" +msgstr "" + msgid "End of block missing" msgstr "Fim do bloco ausente" diff --git a/po/ru.po b/po/ru.po index ca44c0c5..df68516f 100644 --- a/po/ru.po +++ b/po/ru.po @@ -514,6 +514,9 @@ msgstr "Изменить выбранную программу" msgid "Egg" msgstr "Яйцо" +msgid "Empty character constant" +msgstr "" + msgid "End of block missing" msgstr "Отсутствует конец блока" diff --git a/src/CBot/CBotEnums.h b/src/CBot/CBotEnums.h index 3dff66eb..35775295 100644 --- a/src/CBot/CBotEnums.h +++ b/src/CBot/CBotEnums.h @@ -182,7 +182,8 @@ enum TokenType TokenTypNum = 2, //!< number TokenTypString = 3, //!< string TokenTypVar = 4, //!< a variable name - TokenTypDef = 5 //!< value according DefineNum + TokenTypDef = 5, //!< value according DefineNum + TokenTypChar = 6, //!< character literal }; /** @@ -252,6 +253,7 @@ enum CBotError : int CBotErrHexDigits = 5052, //!< missing hex digits after escape sequence CBotErrHexRange = 5053, //!< hex value out of range CBotErrUnicodeName = 5054, //!< invalid universal character name + CBotErrCharEmpty = 5055, //!< empty character constant // Runtime errors CBotErrZeroDiv = 6000, //!< division by zero diff --git a/src/CBot/CBotInstr/CBotExprLitChar.cpp b/src/CBot/CBotInstr/CBotExprLitChar.cpp new file mode 100644 index 00000000..dc7de906 --- /dev/null +++ b/src/CBot/CBotInstr/CBotExprLitChar.cpp @@ -0,0 +1,151 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2018, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "CBot/CBotInstr/CBotExprLitChar.h" + +#include "CBot/CBotStack.h" +#include "CBot/CBotCStack.h" + +#include "CBot/CBotVar/CBotVar.h" + +namespace CBot +{ + +CBotExprLitChar::CBotExprLitChar() +{ +} + +CBotExprLitChar::~CBotExprLitChar() +{ +} + +CBotInstr* CBotExprLitChar::Compile(CBotToken* &p, CBotCStack* pStack) +{ + CBotCStack* pStk = pStack->TokenStack(); + + const auto& s = p->GetString(); + + auto it = s.cbegin(); + if (++it != s.cend()) + { + if (*it != '\'') // not empty quotes ? + { + uint32_t valchar = 0; + int pos = p->GetStart() + 1; + + if (*it != '\\') valchar = *(it++); // not escape sequence ? + else if (++it != s.cend()) + { + pStk->SetStartError(pos++); + unsigned char c = *(it++); + if (c == '\"' || c == '\'' || c == '\\') valchar = c; + else if (c == 'a') valchar = '\a'; // alert bell + else if (c == 'b') valchar = '\b'; // backspace + else if (c == 'f') valchar = '\f'; // form feed + else if (c == 'n') valchar = '\n'; // new line + else if (c == 'r') valchar = '\r'; // carriage return + else if (c == 't') valchar = '\t'; // horizontal tab + else if (c == 'v') valchar = '\v'; // vertical tab + else if (c == 'u' || c == 'U') // unicode escape + { + if (it != s.cend()) + { + std::string hex = ""; + size_t maxlen = (c == 'u') ? 4 : 8; + + for (size_t i = 0; i < maxlen; i++) + { + if (!CharInList(*it, "0123456789ABCDEFabcdef")) break; + ++pos; + hex += *it; + if (++it == s.cend()) break; + } + + if (maxlen == hex.length()) // unicode character + { + try + { + valchar = std::stoi(hex, nullptr, 16); + if (0x10FFFF < valchar || (0xD7FF < valchar && valchar < 0xE000)) + pStk->SetError(CBotErrUnicodeName, pos + 1); + } + catch (const std::out_of_range& e) + { + pStk->SetError(CBotErrHexRange, pos + 1); + } + } + else + pStk->SetError(CBotErrHexDigits, pos + 1); + } + else + pStk->SetError(CBotErrHexDigits, pos + 1); + } + else + pStk->SetError(CBotErrBadEscape, pos + 1); + } + + if (it == s.cend() || *it != '\'') + pStk->SetError(CBotErrEndQuote, p); + + if (pStk->IsOk()) + { + CBotExprLitChar* inst = new CBotExprLitChar(); + inst->m_valchar = valchar; + inst->SetToken(p); + p = p->GetNext(); + + CBotVar* var = CBotVar::Create("", CBotTypChar); + pStk->SetVar(var); + + return pStack->Return(inst, pStk); + } + } + pStk->SetError(CBotErrCharEmpty, p); + } + + pStk->SetError(CBotErrEndQuote, p); + return pStack->Return(nullptr, pStk); +} + +bool CBotExprLitChar::Execute(CBotStack* &pj) +{ + CBotStack* pile = pj->AddStack(this); + + if (pile->IfStep()) return false; + + CBotVar* var = CBotVar::Create("", CBotTypChar); + + var->SetValChar(m_valchar); + + pile->SetVar(var); + + return pj->Return(pile); +} + +void CBotExprLitChar::RestoreState(CBotStack* &pj, bool bMain) +{ + if (bMain) pj->RestoreStack(this); +} + +std::string CBotExprLitChar::GetDebugData() +{ + return m_token.GetString(); +} + +} // namespace CBot diff --git a/src/CBot/CBotInstr/CBotExprLitChar.h b/src/CBot/CBotInstr/CBotExprLitChar.h new file mode 100644 index 00000000..1f31d6d2 --- /dev/null +++ b/src/CBot/CBotInstr/CBotExprLitChar.h @@ -0,0 +1,60 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2018, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include "CBot/CBotInstr/CBotInstr.h" + +namespace CBot +{ + +/** + * \brief A character literal + * \verbatim 'a', '\n', '\t', '\uFFFD', '\U0000FFFD', etc. \endverbatim + */ +class CBotExprLitChar : public CBotInstr +{ +public: + CBotExprLitChar(); + ~CBotExprLitChar(); + + /*! + * \brief Compile a character literal + */ + static CBotInstr* Compile(CBotToken* &p, CBotCStack* pStack); + + /*! + * \brief Execute, returns the corresponding char. + */ + bool Execute(CBotStack* &pj) override; + + /*! + * \brief RestoreState + */ + void RestoreState(CBotStack* &pj, bool bMain) override; + +protected: + virtual const std::string GetDebugName() override { return "CBotExprLitChar"; } + virtual std::string GetDebugData() override; + +private: + uint32_t m_valchar = 0; +}; + +} // namespace CBot diff --git a/src/CBot/CBotInstr/CBotExprLitString.cpp b/src/CBot/CBotInstr/CBotExprLitString.cpp index 72cca72b..fc254162 100644 --- a/src/CBot/CBotInstr/CBotExprLitString.cpp +++ b/src/CBot/CBotInstr/CBotExprLitString.cpp @@ -42,7 +42,7 @@ CBotInstr* CBotExprLitString::Compile(CBotToken* &p, CBotCStack* pStack) { CBotCStack* pStk = pStack->TokenStack(); - std::string s = p->GetString(); + const auto& s = p->GetString(); auto it = s.cbegin(); if (++it != s.cend()) @@ -51,7 +51,7 @@ CBotInstr* CBotExprLitString::Compile(CBotToken* &p, CBotCStack* pStack) std::string valstring = ""; while (it != s.cend() && *it != '\"') { - pStk->SetStartError(++pos); + ++pos; if (*it != '\\') // not escape sequence ? { valstring += *(it++); @@ -59,6 +59,7 @@ CBotInstr* CBotExprLitString::Compile(CBotToken* &p, CBotCStack* pStack) } if (++it == s.cend()) break; + pStk->SetStartError(pos); if (CharInList(*it, "01234567")) // octal { diff --git a/src/CBot/CBotInstr/CBotParExpr.cpp b/src/CBot/CBotInstr/CBotParExpr.cpp index 9572ff5e..fa455c27 100644 --- a/src/CBot/CBotInstr/CBotParExpr.cpp +++ b/src/CBot/CBotInstr/CBotParExpr.cpp @@ -21,6 +21,7 @@ #include "CBot/CBotInstr/CBotExpression.h" #include "CBot/CBotInstr/CBotExprLitBool.h" +#include "CBot/CBotInstr/CBotExprLitChar.h" #include "CBot/CBotInstr/CBotExprLitNan.h" #include "CBot/CBotInstr/CBotExprLitNull.h" #include "CBot/CBotInstr/CBotExprLitNum.h" @@ -169,6 +170,13 @@ CBotInstr* CBotParExpr::CompileLitExpr(CBotToken* &p, CBotCStack* pStack) return pStack->Return(inst, pStk); } + // is this a character? + if (p->GetType() == TokenTypChar) + { + CBotInstr* inst = CBotExprLitChar::Compile(p, pStk); + return pStack->Return(inst, pStk); + } + // is this a chaine? if (p->GetType() == TokenTypString) { diff --git a/src/CBot/CBotToken.cpp b/src/CBot/CBotToken.cpp index d5afe1fe..9764d237 100644 --- a/src/CBot/CBotToken.cpp +++ b/src/CBot/CBotToken.cpp @@ -304,12 +304,53 @@ CBotToken* CBotToken::NextToken(const char*& program, bool first) stop = true; } + // special case for characters + if (token[0] == '\'') + { + if (c == '\\') // escape sequence + { + token += c; + c = *(program++); + + if (c == 'u' || c == 'U') // unicode escape + { + int maxlen = (c == 'u') ? 4 : 8; + token += c; + c = *(program++); + for (int i = 0; i < maxlen; i++) + { + if (c == 0 || !CharInList(c, hexnum)) break; + token += c; + c = *(program++); + } + } + else if (c != 0 && !CharInList(c, nch)) // other escape char + { + token += c; + c = *(program++); + } + } + else if (c != 0 && c != '\'' && !CharInList(c, nch)) // single character + { + token += c; + c = *(program++); + } + + if (c == '\'') // close quote + { + token += c; + c = *(program++); + } + stop = true; + } + // special case for numbers if ( CharInList(token[0], num )) { bool bdot = false; // found a point? bool bexp = false; // found an exponent? + char bin[] = "01"; char* liste = num; if (token[0] == '0' && c == 'x') // hexadecimal value? { @@ -317,6 +358,12 @@ CBotToken* CBotToken::NextToken(const char*& program, bool first) c = *(program++); // next character liste = hexnum; } + else if (token[0] == '0' && c == 'b') // binary literal + { + liste = bin; + token += c; + c = *(program++); + } cw: while (c != 0 && CharInList(c, liste)) { @@ -399,6 +446,7 @@ bis: if (CharInList(token[0], num )) t->m_type = TokenTypNum; if (token[0] == '\"') t->m_type = TokenTypString; + if (token[0] == '\'') t->m_type = TokenTypChar; if (first) t->m_type = TokenTypNone; t->m_keywordId = GetKeyWord(token); diff --git a/src/CBot/CBotUtils.cpp b/src/CBot/CBotUtils.cpp index 4d663bea..afa05bbc 100644 --- a/src/CBot/CBotUtils.cpp +++ b/src/CBot/CBotUtils.cpp @@ -155,6 +155,18 @@ long GetNumInt(const std::string& str) break; } } + else if (*p == 'b') + { + while (*++p != 0) + { + if (*p == '0' || *p == '1') + { + num = (num << 1) + *p - '0'; + continue; + } + break; + } + } return num; } diff --git a/src/CBot/CMakeLists.txt b/src/CBot/CMakeLists.txt index 4be97841..45af8639 100644 --- a/src/CBot/CMakeLists.txt +++ b/src/CBot/CMakeLists.txt @@ -44,6 +44,8 @@ set(SOURCES CBotInstr/CBotEmpty.h CBotInstr/CBotExprLitBool.cpp CBotInstr/CBotExprLitBool.h + CBotInstr/CBotExprLitChar.cpp + CBotInstr/CBotExprLitChar.h CBotInstr/CBotExprLitNan.cpp CBotInstr/CBotExprLitNan.h CBotInstr/CBotExprLitNull.cpp diff --git a/src/common/restext.cpp b/src/common/restext.cpp index 4aa21564..065f6c92 100644 --- a/src/common/restext.cpp +++ b/src/common/restext.cpp @@ -742,6 +742,7 @@ void InitializeRestext() 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::CBotErrCharEmpty] = TR("Empty character constant"); 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 22ae1024..0b5828aa 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -589,16 +589,17 @@ void CScript::UpdateList(Ui::CList* list) list->SetState(Ui::STATE_ENABLE); } -// Colorize a string literal with escape sequences also colored +// Colorize a string or character literal with escape sequences also colored static 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; + auto it = s.cbegin(); + char endQuote = *(it++); ++start; - while (it != s.cend() && *it != '\"') + while (it != s.cend() && *it != endQuote) { if (*(it++) != '\\') // not escape sequence { @@ -697,7 +698,7 @@ void CScript::ColorizeScript(Ui::CEdit* edit, int rangeStart, int rangeEnd) { color = Gfx::FONT_HIGHLIGHT_STRING; } - else if (type == CBot::TokenTypString) // string literals + else if (type == CBot::TokenTypString || type == CBot::TokenTypChar) // string literals and character literals { HighlightString(edit, token, cursor1); bt = bt->GetNext(); diff --git a/test/unit/CBot/CBot_test.cpp b/test/unit/CBot/CBot_test.cpp index 6f2201b9..4c60b8fa 100644 --- a/test/unit/CBot/CBot_test.cpp +++ b/test/unit/CBot/CBot_test.cpp @@ -619,6 +619,27 @@ TEST_F(CBotUT, IntegerMathNearLimits_Issue993) ); } +TEST_F(CBotUT, BinaryLiterals) +{ + ExecuteTest( + "extern void TestBinaryLiterals() {\n" + " ASSERT( 8 == 0b00001000);\n" + " ASSERT( 12 == 0b00001100);\n" + " ASSERT( 16 == 0b00010000);\n" + " ASSERT( 24 == 0b00011000);\n" + " ASSERT( 32 == 0b00100000);\n" + " ASSERT( 48 == 0b00110000);\n" + " ASSERT( 64 == 0b01000000);\n" + " ASSERT( 96 == 0b01100000);\n" + " ASSERT(128 == 0b10000000);\n" + " ASSERT(192 == 0b11000000);\n" + " ASSERT(256 == 0b100000000);\n" + " ASSERT(384 == 0b110000000);\n" + " ASSERT(512 == 0b1000000000);\n" + "}\n" + ); +} + TEST_F(CBotUT, ToString) { ExecuteTest( @@ -1752,6 +1773,107 @@ TEST_F(CBotUT, StringFunctions) ); } +TEST_F(CBotUT, LiteralCharacters) +{ + ExecuteTest( + "extern void TestCharValue()\n" + "{\n" + " ASSERT('A' == 65);\n" + " ASSERT('B' == 66);\n" + " ASSERT('C' == 67);\n" + " ASSERT('\\a' == 0x07);\n" + " ASSERT('\\b' == 0x08);\n" + " ASSERT('\\t' == 0x09);\n" + " ASSERT('\\n' == 0x0A);\n" + " ASSERT('\\v' == 0x0B);\n" + " ASSERT('\\f' == 0x0C);\n" + " ASSERT('\\r' == 0x0D);\n" + " ASSERT('\\\"' == 0x22);\n" + " ASSERT('\\\'' == 0x27);\n" + " ASSERT('\\\\' == 0x5C);\n" + "}\n" + "extern void TestCharUnicodeEscape()\n" + "{\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" + "extern void AssignCharToString_ToUTF_8()\n" + "{\n" + " string test = '\\u00A9';\n" + " test += '\\u00AE';\n" + " ASSERT(test == \"\\xC2\\xA9\\xC2\\xAE\");\n" + "}\n" + "extern void AddCharToString_ToUTF_8()\n" + "{\n" + " ASSERT(\"\" + 'A' + 'B' + 'C' == \"ABC\");\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" + " ASSERT(\"\" + '\\U0010FFFF' == \"\\xF4\\x8F\\xBF\\xBF\");\n" + "}\n" + ); + + ExecuteTest( + "extern void MissingEndQuote()\n" + "{\n" + " '\n" + "}\n", + CBotErrEndQuote + ); + + ExecuteTest( + "extern void MissingEndQuote()\n" + "{\n" + " 'a\n" + "}\n", + CBotErrEndQuote + ); + + ExecuteTest( + "extern void EmptyQuotes()\n" + "{\n" + " '';\n" + "}\n", + CBotErrCharEmpty + ); + + ExecuteTest( + "extern void UnknownEscapeSequence()\n" + "{\n" + " '\\p';\n" + "}\n", + CBotErrBadEscape + ); + + ExecuteTest( + "extern void MissingHexDigits()\n" + "{\n" + " '\\u';\n" + "}\n", + CBotErrHexDigits + ); + + ExecuteTest( + "extern void BadUnicodeCharacterName()\n" + "{\n" + " '\\U00110000';\n" + "}\n", + CBotErrUnicodeName + ); +} + TEST_F(CBotUT, TestNANParam_Issue642) { ExecuteTest(