Add binary and character literals in CBOT

blender-script
melex750 2019-04-11 05:14:11 -04:00
parent bc572aa52f
commit 1058a326ad
18 changed files with 436 additions and 7 deletions

View File

@ -1795,6 +1795,9 @@ msgstr ""
msgid "Invalid universal character name" msgid "Invalid universal character name"
msgstr "" msgstr ""
msgid "Empty character constant"
msgstr ""
msgid "Dividing by zero" msgid "Dividing by zero"
msgstr "" msgstr ""

View File

@ -507,6 +507,9 @@ msgstr "Upravit vybraný program"
msgid "Egg" msgid "Egg"
msgstr "Vejce" msgstr "Vejce"
msgid "Empty character constant"
msgstr ""
msgid "End of block missing" msgid "End of block missing"
msgstr "Chybí konec bloku" msgstr "Chybí konec bloku"

View File

@ -508,6 +508,9 @@ msgstr "Gewähltes Programm bearbeiten"
msgid "Egg" msgid "Egg"
msgstr "Ei" msgstr "Ei"
msgid "Empty character constant"
msgstr ""
msgid "End of block missing" msgid "End of block missing"
msgstr "Es fehlt eine geschlossene geschweifte Klammer \"}\" (Ende des Blocks)" msgstr "Es fehlt eine geschlossene geschweifte Klammer \"}\" (Ende des Blocks)"

View File

@ -510,6 +510,9 @@ msgstr "Éditer le programme sélectionné"
msgid "Egg" msgid "Egg"
msgstr "Oeuf" msgstr "Oeuf"
msgid "Empty character constant"
msgstr ""
msgid "End of block missing" msgid "End of block missing"
msgstr "Il manque la fin du bloc" msgstr "Il manque la fin du bloc"

View File

@ -506,6 +506,9 @@ msgstr "Edytuj zaznaczony program"
msgid "Egg" msgid "Egg"
msgstr "Jajo" msgstr "Jajo"
msgid "Empty character constant"
msgstr ""
msgid "End of block missing" msgid "End of block missing"
msgstr "Brak końca bloku" msgstr "Brak końca bloku"

View File

@ -505,6 +505,9 @@ msgstr "Alterar o programa selecionado"
msgid "Egg" msgid "Egg"
msgstr "Ovo" msgstr "Ovo"
msgid "Empty character constant"
msgstr ""
msgid "End of block missing" msgid "End of block missing"
msgstr "Fim do bloco ausente" msgstr "Fim do bloco ausente"

View File

@ -514,6 +514,9 @@ msgstr "Изменить выбранную программу"
msgid "Egg" msgid "Egg"
msgstr "Яйцо" msgstr "Яйцо"
msgid "Empty character constant"
msgstr ""
msgid "End of block missing" msgid "End of block missing"
msgstr "Отсутствует конец блока" msgstr "Отсутствует конец блока"

View File

@ -182,7 +182,8 @@ enum TokenType
TokenTypNum = 2, //!< number TokenTypNum = 2, //!< number
TokenTypString = 3, //!< string TokenTypString = 3, //!< string
TokenTypVar = 4, //!< a variable name 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 CBotErrHexDigits = 5052, //!< missing hex digits after escape sequence
CBotErrHexRange = 5053, //!< hex value out of range CBotErrHexRange = 5053, //!< hex value out of range
CBotErrUnicodeName = 5054, //!< invalid universal character name CBotErrUnicodeName = 5054, //!< invalid universal character name
CBotErrCharEmpty = 5055, //!< empty character constant
// Runtime errors // Runtime errors
CBotErrZeroDiv = 6000, //!< division by zero CBotErrZeroDiv = 6000, //!< division by zero

View File

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

View File

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

View File

@ -42,7 +42,7 @@ CBotInstr* CBotExprLitString::Compile(CBotToken* &p, CBotCStack* pStack)
{ {
CBotCStack* pStk = pStack->TokenStack(); CBotCStack* pStk = pStack->TokenStack();
std::string s = p->GetString(); const auto& s = p->GetString();
auto it = s.cbegin(); auto it = s.cbegin();
if (++it != s.cend()) if (++it != s.cend())
@ -51,7 +51,7 @@ CBotInstr* CBotExprLitString::Compile(CBotToken* &p, CBotCStack* pStack)
std::string valstring = ""; std::string valstring = "";
while (it != s.cend() && *it != '\"') while (it != s.cend() && *it != '\"')
{ {
pStk->SetStartError(++pos); ++pos;
if (*it != '\\') // not escape sequence ? if (*it != '\\') // not escape sequence ?
{ {
valstring += *(it++); valstring += *(it++);
@ -59,6 +59,7 @@ CBotInstr* CBotExprLitString::Compile(CBotToken* &p, CBotCStack* pStack)
} }
if (++it == s.cend()) break; if (++it == s.cend()) break;
pStk->SetStartError(pos);
if (CharInList(*it, "01234567")) // octal if (CharInList(*it, "01234567")) // octal
{ {

View File

@ -21,6 +21,7 @@
#include "CBot/CBotInstr/CBotExpression.h" #include "CBot/CBotInstr/CBotExpression.h"
#include "CBot/CBotInstr/CBotExprLitBool.h" #include "CBot/CBotInstr/CBotExprLitBool.h"
#include "CBot/CBotInstr/CBotExprLitChar.h"
#include "CBot/CBotInstr/CBotExprLitNan.h" #include "CBot/CBotInstr/CBotExprLitNan.h"
#include "CBot/CBotInstr/CBotExprLitNull.h" #include "CBot/CBotInstr/CBotExprLitNull.h"
#include "CBot/CBotInstr/CBotExprLitNum.h" #include "CBot/CBotInstr/CBotExprLitNum.h"
@ -169,6 +170,13 @@ CBotInstr* CBotParExpr::CompileLitExpr(CBotToken* &p, CBotCStack* pStack)
return pStack->Return(inst, pStk); 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? // is this a chaine?
if (p->GetType() == TokenTypString) if (p->GetType() == TokenTypString)
{ {

View File

@ -304,12 +304,53 @@ CBotToken* CBotToken::NextToken(const char*& program, bool first)
stop = true; 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 // special case for numbers
if ( CharInList(token[0], num )) if ( CharInList(token[0], num ))
{ {
bool bdot = false; // found a point? bool bdot = false; // found a point?
bool bexp = false; // found an exponent? bool bexp = false; // found an exponent?
char bin[] = "01";
char* liste = num; char* liste = num;
if (token[0] == '0' && c == 'x') // hexadecimal value? if (token[0] == '0' && c == 'x') // hexadecimal value?
{ {
@ -317,6 +358,12 @@ CBotToken* CBotToken::NextToken(const char*& program, bool first)
c = *(program++); // next character c = *(program++); // next character
liste = hexnum; liste = hexnum;
} }
else if (token[0] == '0' && c == 'b') // binary literal
{
liste = bin;
token += c;
c = *(program++);
}
cw: cw:
while (c != 0 && CharInList(c, liste)) while (c != 0 && CharInList(c, liste))
{ {
@ -399,6 +446,7 @@ bis:
if (CharInList(token[0], num )) t->m_type = TokenTypNum; if (CharInList(token[0], num )) t->m_type = TokenTypNum;
if (token[0] == '\"') t->m_type = TokenTypString; if (token[0] == '\"') t->m_type = TokenTypString;
if (token[0] == '\'') t->m_type = TokenTypChar;
if (first) t->m_type = TokenTypNone; if (first) t->m_type = TokenTypNone;
t->m_keywordId = GetKeyWord(token); t->m_keywordId = GetKeyWord(token);

View File

@ -155,6 +155,18 @@ long GetNumInt(const std::string& str)
break; break;
} }
} }
else if (*p == 'b')
{
while (*++p != 0)
{
if (*p == '0' || *p == '1')
{
num = (num << 1) + *p - '0';
continue;
}
break;
}
}
return num; return num;
} }

View File

@ -44,6 +44,8 @@ set(SOURCES
CBotInstr/CBotEmpty.h CBotInstr/CBotEmpty.h
CBotInstr/CBotExprLitBool.cpp CBotInstr/CBotExprLitBool.cpp
CBotInstr/CBotExprLitBool.h CBotInstr/CBotExprLitBool.h
CBotInstr/CBotExprLitChar.cpp
CBotInstr/CBotExprLitChar.h
CBotInstr/CBotExprLitNan.cpp CBotInstr/CBotExprLitNan.cpp
CBotInstr/CBotExprLitNan.h CBotInstr/CBotExprLitNan.h
CBotInstr/CBotExprLitNull.cpp CBotInstr/CBotExprLitNull.cpp

View File

@ -742,6 +742,7 @@ void InitializeRestext()
stringsCbot[CBot::CBotErrHexDigits] = TR("Missing hex digits after escape sequence"); stringsCbot[CBot::CBotErrHexDigits] = TR("Missing hex digits after escape sequence");
stringsCbot[CBot::CBotErrHexRange] = TR("Hex value out of range"); stringsCbot[CBot::CBotErrHexRange] = TR("Hex value out of range");
stringsCbot[CBot::CBotErrUnicodeName] = TR("Invalid universal character name"); stringsCbot[CBot::CBotErrUnicodeName] = TR("Invalid universal character name");
stringsCbot[CBot::CBotErrCharEmpty] = TR("Empty character constant");
stringsCbot[CBot::CBotErrZeroDiv] = TR("Dividing by zero"); stringsCbot[CBot::CBotErrZeroDiv] = TR("Dividing by zero");
stringsCbot[CBot::CBotErrNotInit] = TR("Variable not initialized"); stringsCbot[CBot::CBotErrNotInit] = TR("Variable not initialized");

View File

@ -589,16 +589,17 @@ void CScript::UpdateList(Ui::CList* list)
list->SetState(Ui::STATE_ENABLE); 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) static void HighlightString(Ui::CEdit* edit, const std::string& s, int start)
{ {
edit->SetFormat(start, start + 1, Gfx::FONT_HIGHLIGHT_STRING); edit->SetFormat(start, start + 1, Gfx::FONT_HIGHLIGHT_STRING);
auto it = s.cbegin() + 1; auto it = s.cbegin();
char endQuote = *(it++);
++start; ++start;
while (it != s.cend() && *it != '\"') while (it != s.cend() && *it != endQuote)
{ {
if (*(it++) != '\\') // not escape sequence if (*(it++) != '\\') // not escape sequence
{ {
@ -697,7 +698,7 @@ void CScript::ColorizeScript(Ui::CEdit* edit, int rangeStart, int rangeEnd)
{ {
color = Gfx::FONT_HIGHLIGHT_STRING; 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); HighlightString(edit, token, cursor1);
bt = bt->GetNext(); bt = bt->GetNext();

View File

@ -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) TEST_F(CBotUT, ToString)
{ {
ExecuteTest( 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) TEST_F(CBotUT, TestNANParam_Issue642)
{ {
ExecuteTest( ExecuteTest(