Merge pull request #1439 from melex750/dev-cbot-repeat

Restore repeat(n) instruction in CBOT
fix-squashed-planets
Emxx52 2021-08-14 20:15:00 +02:00 committed by GitHub
commit 3ab153225a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 303 additions and 1 deletions

View File

@ -100,6 +100,7 @@ enum TokenId
ID_STATIC, ID_STATIC,
ID_PROTECTED, ID_PROTECTED,
ID_PRIVATE, ID_PRIVATE,
ID_REPEAT,
ID_INT, ID_INT,
ID_FLOAT, ID_FLOAT,
ID_BOOLEAN, ID_BOOLEAN,

View File

@ -30,6 +30,7 @@
#include "CBot/CBotInstr/CBotExpression.h" #include "CBot/CBotInstr/CBotExpression.h"
#include "CBot/CBotInstr/CBotFor.h" #include "CBot/CBotInstr/CBotFor.h"
#include "CBot/CBotInstr/CBotIf.h" #include "CBot/CBotInstr/CBotIf.h"
#include "CBot/CBotInstr/CBotRepeat.h"
#include "CBot/CBotInstr/CBotReturn.h" #include "CBot/CBotInstr/CBotReturn.h"
#include "CBot/CBotInstr/CBotSwitch.h" #include "CBot/CBotInstr/CBotSwitch.h"
#include "CBot/CBotInstr/CBotThrow.h" #include "CBot/CBotInstr/CBotThrow.h"
@ -176,7 +177,7 @@ CBotInstr* CBotInstr::Compile(CBotToken* &p, CBotCStack* pStack)
{ {
type = pp->GetType(); type = pp->GetType();
// Allow only instructions that accept a label // Allow only instructions that accept a label
if (!IsOfTypeList(pp, ID_WHILE, ID_FOR, ID_DO, 0)) if (!IsOfTypeList(pp, ID_WHILE, ID_FOR, ID_DO, ID_REPEAT, 0))
{ {
pStack->SetError(CBotErrLabel, pp->GetStart()); pStack->SetError(CBotErrLabel, pp->GetStart());
return nullptr; return nullptr;
@ -195,6 +196,9 @@ CBotInstr* CBotInstr::Compile(CBotToken* &p, CBotCStack* pStack)
case ID_DO: case ID_DO:
return CBotDo::Compile(p, pStack); return CBotDo::Compile(p, pStack);
case ID_REPEAT:
return CBotRepeat::Compile(p, pStack);
case ID_BREAK: case ID_BREAK:
case ID_CONTINUE: case ID_CONTINUE:
return CBotBreak::Compile(p, pStack); return CBotBreak::Compile(p, pStack);

View File

@ -0,0 +1,165 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2020, 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/CBotRepeat.h"
#include "CBot/CBotInstr/CBotBlock.h"
#include "CBot/CBotInstr/CBotExpression.h"
#include "CBot/CBotCStack.h"
#include "CBot/CBotStack.h"
namespace CBot
{
CBotRepeat::CBotRepeat()
{
m_expr = nullptr;
m_block = nullptr;
}
CBotRepeat::~CBotRepeat()
{
delete m_expr;
delete m_block;
}
CBotInstr* CBotRepeat::Compile(CBotToken* &p, CBotCStack* pStack)
{
CBotRepeat* inst = new CBotRepeat(); // creates the object
CBotToken* pp = p; // preserves at the ^ token (starting position)
if ( IsOfType( p, TokenTypVar ) && IsOfType( p, ID_DOTS ) )
inst->m_label = pp->GetString(); // register the name of label
inst->SetToken(p);
if (!IsOfType(p, ID_REPEAT)) return nullptr; // should never happen
CBotCStack* pStk = pStack->TokenStack(pp);
if ( IsOfType(p, ID_OPENPAR ) )
{
CBotToken* ppp = p; // preserves the ^ token (starting position)
if ( nullptr != (inst->m_expr = CBotExpression::Compile( p, pStk )) )
{
if ( pStk->GetType() < CBotTypLong )
{
if ( IsOfType(p, ID_CLOSEPAR ) )
{
IncLvl(inst->m_label);
inst->m_block = CBotBlock::CompileBlkOrInst( p, pStk, true );
DecLvl();
if ( pStk->IsOk() ) // the statement block is ok (it may be empty!)
return pStack->Return(inst, pStk);
}
pStack->SetError(CBotErrClosePar, p->GetStart());
}
pStk->SetStartError(ppp->GetStart());
pStk->SetError(CBotErrBadType1, p->GetStart());
}
pStack->SetError(CBotErrBadNum, p);
}
pStack->SetError(CBotErrOpenPar, p->GetStart());
delete inst;
return pStack->Return(nullptr, pStk);
}
// execution of intruction "repeat"
bool CBotRepeat::Execute(CBotStack* &pj)
{
CBotStack* pile = pj->AddStack(this); // adds an item to the stack
// or find in case of recovery
if ( pile->IfStep() ) return false;
while( true ) switch( pile->GetState() ) // executes the loop
{ // there are two possible states (depending on recovery)
case 0:
// evaluates the number of iterations
if ( !m_expr->Execute(pile) ) return false; // interrupted here ?
// the result of the condition is on the stack
// terminates if an error or if the condition is false
int n;
if ( !pile->IsOk() || ( n = pile->GetVal() ) < 1 )
return pj->Return(pile); // releases the stack
// puts the number of iterations +1 to the "state"
if (!pile->SetState(n+1)) return false; // ready for further
continue; // continue as a result
case 1:
// normal end of the loop
return pj->Return(pile); // releases the stack
default:
// evaluates the associated statement block
if ( m_block != nullptr && !m_block->Execute(pile) )
{
if (pile->IfContinue(pile->GetState()-1, m_label)) continue; // if continued, will return to test
return pj->BreakReturn(pile, m_label); // releases the stack
}
// terminates if there is an error
if (!pile->IsOk()) return pj->Return(pile); // releases the stack
// returns to the test again
if (!pile->SetState(pile->GetState()-1, 0)) return false;
continue;
}
}
void CBotRepeat::RestoreState(CBotStack* &pj, bool bMain)
{
if ( !bMain ) return;
CBotStack* pile = pj->RestoreStack(this); // adds an item to the stack
if ( pile == nullptr ) return;
switch( pile->GetState() )
{ // there are two possible states (depending on recovery)
case 0:
// evaluates the condition
m_expr->RestoreState(pile, bMain);
return;
case 1:
// evaluates the associated statement block
if ( m_block != nullptr ) m_block->RestoreState(pile, bMain);
return;
}
}
std::string CBotRepeat::GetDebugData()
{
return !m_label.empty() ? "m_label = " + m_label : "";
}
std::map<std::string, CBotInstr*> CBotRepeat::GetDebugLinks()
{
auto links = CBotInstr::GetDebugLinks();
links["m_expr"] = m_expr;
links["m_block"] = m_block;
return links;
}
} // namespace CBot

View File

@ -0,0 +1,62 @@
/*
* This file is part of the Colobot: Gold Edition source code
* Copyright (C) 2001-2020, 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 The "repeat" loop - repeat (times) { ... }
*/
class CBotRepeat : public CBotInstr
{
public:
CBotRepeat();
~CBotRepeat();
/// Static method used for compilation
static CBotInstr* Compile(CBotToken* &p, CBotCStack* pStack);
/// Execute
bool Execute(CBotStack* &pj) override;
/// Restore state
void RestoreState(CBotStack* &pj, bool bMain) override;
protected:
virtual const std::string GetDebugName() override { return "CBotRepeat"; }
virtual std::string GetDebugData() override;
virtual std::map<std::string, CBotInstr*> GetDebugLinks() override;
private:
/// Number of iterations
CBotInstr* m_expr;
/// Instructions
CBotInstr* m_block;
/// Label
std::string m_label; // a label if there is
};
} // namespace CBot

View File

@ -65,6 +65,7 @@ static const boost::bimap<TokenId, std::string> KEYWORDS = makeBimap<TokenId, st
{ID_STATIC, "static"}, {ID_STATIC, "static"},
{ID_PROTECTED, "protected"}, {ID_PROTECTED, "protected"},
{ID_PRIVATE, "private"}, {ID_PRIVATE, "private"},
{ID_REPEAT, "repeat"},
{ID_INT, "int"}, {ID_INT, "int"},
{ID_FLOAT, "float"}, {ID_FLOAT, "float"},
{ID_BOOLEAN, "boolean"}, {ID_BOOLEAN, "boolean"},

View File

@ -100,6 +100,8 @@ set(SOURCES
CBotInstr/CBotPostIncExpr.h CBotInstr/CBotPostIncExpr.h
CBotInstr/CBotPreIncExpr.cpp CBotInstr/CBotPreIncExpr.cpp
CBotInstr/CBotPreIncExpr.h CBotInstr/CBotPreIncExpr.h
CBotInstr/CBotRepeat.cpp
CBotInstr/CBotRepeat.h
CBotInstr/CBotReturn.cpp CBotInstr/CBotReturn.cpp
CBotInstr/CBotReturn.h CBotInstr/CBotReturn.h
CBotInstr/CBotSwitch.cpp CBotInstr/CBotSwitch.cpp

View File

@ -261,6 +261,7 @@ std::string GetHelpFilename(const char *token)
if ( strcmp(token, "if" ) == 0 ) helpfile = "cbot/if"; if ( strcmp(token, "if" ) == 0 ) helpfile = "cbot/if";
if ( strcmp(token, "else" ) == 0 ) helpfile = "cbot/if"; if ( strcmp(token, "else" ) == 0 ) helpfile = "cbot/if";
if ( strcmp(token, "repeat" ) == 0 ) helpfile = "cbot/repeat";
if ( strcmp(token, "for" ) == 0 ) helpfile = "cbot/for"; if ( strcmp(token, "for" ) == 0 ) helpfile = "cbot/for";
if ( strcmp(token, "while" ) == 0 ) helpfile = "cbot/while"; if ( strcmp(token, "while" ) == 0 ) helpfile = "cbot/while";
if ( strcmp(token, "do" ) == 0 ) helpfile = "cbot/do"; if ( strcmp(token, "do" ) == 0 ) helpfile = "cbot/do";
@ -543,6 +544,7 @@ const char* GetHelpText(const char *token)
{ {
if ( strcmp(token, "if" ) == 0 ) return "if ( condition ) { code }"; if ( strcmp(token, "if" ) == 0 ) return "if ( condition ) { code }";
if ( strcmp(token, "else" ) == 0 ) return "else { code }"; if ( strcmp(token, "else" ) == 0 ) return "else { code }";
if ( strcmp(token, "repeat" ) == 0 ) return "repeat ( number )";
if ( strcmp(token, "for" ) == 0 ) return "for ( before ; condition ; end )"; if ( strcmp(token, "for" ) == 0 ) return "for ( before ; condition ; end )";
if ( strcmp(token, "while" ) == 0 ) return "while ( condition ) { code }"; if ( strcmp(token, "while" ) == 0 ) return "while ( condition ) { code }";
if ( strcmp(token, "do" ) == 0 ) return "do { code } while ( condition );"; if ( strcmp(token, "do" ) == 0 ) return "do { code } while ( condition );";

View File

@ -726,6 +726,71 @@ TEST_F(CBotUT, TestSwitchCase)
); );
} }
TEST_F(CBotUT, TestRepeatInstruction)
{
ExecuteTest(
"extern void TestRepeat() {\n"
" int c = 0;\n"
" for (int i = 1; i < 11; ++i)\n"
" {\n"
" repeat (i) ++c;\n"
" }\n"
" ASSERT(c == 55);\n"
"}\n"
"extern void TestRepeatBreakAndContinue() {\n"
" int c = 0;\n"
" repeat (10)\n"
" {\n"
" if (++c == 5) break;\n"
" continue;\n"
" FAIL();\n"
" }\n"
" ASSERT(c == 5);\n"
" label:repeat (10)\n"
" {\n"
" if (++c == 10) break label;\n"
" continue label;\n"
" FAIL();\n"
" }\n"
" ASSERT(c == 10);\n"
"}\n"
"extern void NoRepeatNumberLessThanOne() {\n"
" repeat (0) FAIL();\n"
" repeat (-1) FAIL();\n"
" repeat (-2) FAIL();\n"
"}\n"
"extern void EvaluateExpressionOnlyOnce() {\n"
" int c = 0;\n"
" repeat (c + 5) ASSERT(++c < 6);\n"
" ASSERT(c == 5);\n"
"}\n"
);
ExecuteTest(
"extern void MissingOpenParen() {\n"
" repeat ;\n"
"}\n",
CBotErrOpenPar
);
ExecuteTest(
"extern void MissingNumber() {\n"
" repeat (;\n"
"}\n",
CBotErrBadNum
);
ExecuteTest(
"extern void WrongType() {\n"
" repeat (\"not number\");\n"
"}\n",
CBotErrBadType1
);
ExecuteTest(
"extern void MissingCloseParen() {\n"
" repeat (2;\n"
"}\n",
CBotErrClosePar
);
}
TEST_F(CBotUT, ToString) TEST_F(CBotUT, ToString)
{ {
ExecuteTest( ExecuteTest(