Merge pull request #1439 from melex750/dev-cbot-repeat
Restore repeat(n) instruction in CBOTfix-squashed-planets
commit
3ab153225a
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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"},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 );";
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in New Issue