Merge pull request #897 from melex750/dev

* Fix game crashing related to syntax errors
* Fix problems with not returning correct value from function
* Add default values for parameters
* Fix custom functions not accepting nan
* Fix 'point' constructor not executing when called with new
dev-buzzingcars
krzys_h 2017-01-26 18:46:42 +01:00 committed by GitHub
commit 967fb9e30f
26 changed files with 495 additions and 43 deletions

View File

@ -1739,6 +1739,15 @@ msgstr ""
msgid "Function needs return type \"void\""
msgstr ""
msgid "Class name expected"
msgstr ""
msgid "Non-void function needs \"return;\""
msgstr ""
msgid "This parameter needs a default value"
msgstr ""
msgid "Dividing by zero"
msgstr ""

View File

@ -356,6 +356,9 @@ msgstr ""
msgid "Checkpoint"
msgstr "Checkpoint"
msgid "Class name expected"
msgstr ""
msgid "Climb\\Increases the power of the jet"
msgstr "Steigen\\Leistung des Triebwerks steigern"
@ -939,6 +942,9 @@ msgstr "Kein konvertierbares Platin"
msgid "No userlevels installed!"
msgstr ""
msgid "Non-void function needs \"return;\""
msgstr ""
msgid "Normal size"
msgstr "Normale Größe"
@ -1516,6 +1522,9 @@ msgstr ""
msgid "This object is not a member of a class"
msgstr "Das Objekt ist nicht eine Instanz einer Klasse"
msgid "This parameter needs a default value"
msgstr ""
msgid "This program is read-only, clone it to edit"
msgstr ""

View File

@ -346,6 +346,9 @@ msgstr "Console de triche\\Montre la console de triche"
msgid "Checkpoint"
msgstr "Indicateur"
msgid "Class name expected"
msgstr ""
msgid "Climb\\Increases the power of the jet"
msgstr "Monter\\Augmenter la puissance du réacteur"
@ -921,6 +924,9 @@ msgstr "Pas d'uranium à transformer"
msgid "No userlevels installed!"
msgstr "Pas de niveaux spéciaux installés !"
msgid "Non-void function needs \"return;\""
msgstr ""
msgid "Normal size"
msgstr "Taille normale"
@ -1488,6 +1494,9 @@ msgstr ""
msgid "This object is not a member of a class"
msgstr "L'objet n'est pas une instance d'une classe"
msgid "This parameter needs a default value"
msgstr ""
msgid "This program is read-only, clone it to edit"
msgstr "Ce programme est en lecture-seule, le dupliquer pour pouvoir l'éditer"

View File

@ -348,6 +348,9 @@ msgstr "Konsola komend\\Pokaż konsolę komend"
msgid "Checkpoint"
msgstr "Punkt kontrolny"
msgid "Class name expected"
msgstr ""
msgid "Climb\\Increases the power of the jet"
msgstr "W górę\\Zwiększa moc silnika"
@ -923,6 +926,9 @@ msgstr "Brak uranu do przetworzenia"
msgid "No userlevels installed!"
msgstr "Brak zainstalowanych poziomów użytkownika!"
msgid "Non-void function needs \"return;\""
msgstr ""
msgid "Normal size"
msgstr "Normalna wielkość"
@ -1490,6 +1496,9 @@ msgstr "Ten objekt jest obecnie zajęty"
msgid "This object is not a member of a class"
msgstr "Ten obiekt nie jest członkiem klasy"
msgid "This parameter needs a default value"
msgstr ""
msgid "This program is read-only, clone it to edit"
msgstr "Ten program jest tylko do odczytu, skopiuj go, aby edytować"

View File

@ -353,6 +353,9 @@ msgstr "Консоль чит-кодов\\Показать консоль для
msgid "Checkpoint"
msgstr "Контрольная точка"
msgid "Class name expected"
msgstr ""
msgid "Climb\\Increases the power of the jet"
msgstr "Взлет и подъем\\Увеличивает мощность реактивного двигателя"
@ -932,6 +935,9 @@ msgstr "Нет урана для преобразования"
msgid "No userlevels installed!"
msgstr "Не установленны пользовательские уровни!"
msgid "Non-void function needs \"return;\""
msgstr ""
msgid "Normal size"
msgstr "Нормальный размер"
@ -1504,6 +1510,9 @@ msgstr ""
msgid "This object is not a member of a class"
msgstr "Этот объект не член класса"
msgid "This parameter needs a default value"
msgstr ""
msgid "This program is read-only, clone it to edit"
msgstr "Эта программа только для чтения, для редактирования клонируйте её"

View File

@ -471,26 +471,27 @@ CBotClass* CBotClass::Compile1(CBotToken* &p, CBotCStack* pStack)
std::string name = p->GetString();
CBotClass* pOld = CBotClass::Find(name);
if ( (pOld != nullptr && pOld->m_IsDef) || /* public class exists in different program */
pStack->GetProgram()->ClassExists(name)) /* class exists in this program */
{
pStack->SetError( CBotErrRedefClass, p );
return nullptr;
}
// a name of the class is there?
if (IsOfType(p, TokenTypVar))
{
CBotClass* pOld = CBotClass::Find(name);
if ((pOld != nullptr && pOld->m_IsDef) || /* public class exists in different program */
pStack->GetProgram()->ClassExists(name)) /* class exists in this program */
{
pStack->SetError(CBotErrRedefClass, p->GetPrev());
return nullptr;
}
CBotClass* pPapa = nullptr;
if ( IsOfType( p, ID_EXTENDS ) )
{
std::string name = p->GetString();
pPapa = CBotClass::Find(name);
CBotToken* pp = p;
if (!IsOfType(p, TokenTypVar) || pPapa == nullptr )
{
pStack->SetError( CBotErrNotClass, p );
pStack->SetError(CBotErrNoClassName, pp);
return nullptr;
}
}
@ -519,6 +520,9 @@ CBotClass* CBotClass::Compile1(CBotToken* &p, CBotCStack* pStack)
if (pStack->IsOk()) return classe;
}
else
pStack->SetError(CBotErrNoClassName, p);
pStack->SetError(CBotErrNoTerminator, p);
return nullptr;
}
@ -810,10 +814,11 @@ CBotClass* CBotClass::Compile(CBotToken* &p, CBotCStack* pStack)
// TODO: Not sure how correct is that - I have no idea how the precompilation (Compile1 method) works ~krzys_h
std::string name = p->GetString();
CBotClass* pPapa = CBotClass::Find(name);
CBotToken* pp = p;
if (!IsOfType(p, TokenTypVar) || pPapa == nullptr)
{
pStack->SetError( CBotErrNotClass, p );
pStack->SetError(CBotErrNoClassName, pp);
return nullptr;
}
pOld->m_parent = pPapa;

View File

@ -19,6 +19,9 @@
#include "CBot/CBotDefParam.h"
#include "CBot/CBotInstr/CBotInstrUtils.h"
#include "CBot/CBotInstr/CBotParExpr.h"
#include "CBot/CBotUtils.h"
#include "CBot/CBotCStack.h"
@ -33,11 +36,13 @@ namespace CBot
CBotDefParam::CBotDefParam()
{
m_nIdent = 0;
m_expr = nullptr;
}
////////////////////////////////////////////////////////////////////////////////
CBotDefParam::~CBotDefParam()
{
delete m_expr;
}
////////////////////////////////////////////////////////////////////////////////
@ -51,8 +56,9 @@ CBotDefParam* CBotDefParam::Compile(CBotToken* &p, CBotCStack* pStack)
if (IsOfType(p, ID_OPENPAR))
{
CBotDefParam* list = nullptr;
bool prevHasDefault = false;
while (!IsOfType(p, ID_CLOSEPAR))
if (!IsOfType(p, ID_CLOSEPAR)) while (true)
{
CBotDefParam* param = new CBotDefParam();
if (list == nullptr) list = param;
@ -77,6 +83,26 @@ CBotDefParam* CBotDefParam::Compile(CBotToken* &p, CBotCStack* pStack)
break;
}
if (IsOfType(p, ID_ASS)) // default value assignment
{
CBotCStack* pStk = pStack->TokenStack(nullptr, true);
if (nullptr != (param->m_expr = CBotParExpr::CompileLitExpr(p, pStk)))
{
CBotTypResult valueType = pStk->GetTypResult(CBotVar::GetTypeMode::CLASS_AS_INTRINSIC);
if (!TypesCompatibles(type, valueType))
pStack->SetError(CBotErrBadType1, p->GetPrev());
prevHasDefault = true;
}
else pStack->SetError(CBotErrNoExpression, p);
delete pStk;
}
else
if (prevHasDefault) pStack->SetError(CBotErrDefaultValue, p->GetPrev());
if (!pStack->IsOk()) break;
if ( type.Eq(CBotTypArrayPointer) ) type.SetType(CBotTypArrayBody);
CBotVar* var = CBotVar::Create(pp->GetString(), type); // creates the variable
// if ( pClass ) var->SetClass(pClass);
@ -85,10 +111,12 @@ CBotDefParam* CBotDefParam::Compile(CBotToken* &p, CBotCStack* pStack)
var->SetUniqNum(param->m_nIdent);
pStack->AddVar(var); // place on the stack
if (IsOfType(p, ID_COMMA) || p->GetType() == ID_CLOSEPAR)
continue;
if (IsOfType(p, ID_COMMA)) continue;
if (IsOfType(p, ID_CLOSEPAR)) break;
pStack->SetError(CBotErrClosePar, p->GetStart());
}
pStack->SetError(CBotErrClosePar, p->GetStart());
pStack->SetError(CBotErrNoVar, p->GetStart());
}
pStack->SetError(CBotErrNoType, p);
delete list;
@ -107,40 +135,63 @@ bool CBotDefParam::Execute(CBotVar** ppVars, CBotStack* &pj)
assert(this != nullptr);
CBotDefParam* p = this;
bool useDefault = false;
while ( p != nullptr )
{
// creates a local variable on the stack
CBotVar* newvar = CBotVar::Create(p->m_token.GetString(), p->m_type);
CBotVar* pVar = nullptr;
CBotStack* pile = nullptr; // stack for default expression
if (useDefault || (ppVars == nullptr || ppVars[i] == nullptr))
{
assert(p->m_expr != nullptr);
pile = pj->AddStack();
useDefault = true;
while (pile->IsOk() && !p->m_expr->Execute(pile));
if (!pile->IsOk()) return pj->Return(pile); // return the error
pVar = pile->GetVar();
}
else
pVar = ppVars[i];
// serves to make the transformation of types:
if ( ppVars != nullptr && ppVars[i] != nullptr )
if ((useDefault && pVar != nullptr) ||
(ppVars != nullptr && pVar != nullptr))
{
switch (p->m_type.GetType())
{
case CBotTypInt:
newvar->SetValInt(ppVars[i]->GetValInt());
newvar->SetValInt(pVar->GetValInt());
newvar->SetInit(pVar->GetInit()); // copy nan
break;
case CBotTypFloat:
newvar->SetValFloat(ppVars[i]->GetValFloat());
newvar->SetValFloat(pVar->GetValFloat());
newvar->SetInit(pVar->GetInit()); // copy nan
break;
case CBotTypString:
newvar->SetValString(ppVars[i]->GetValString());
newvar->SetValString(pVar->GetValString());
break;
case CBotTypBoolean:
newvar->SetValInt(ppVars[i]->GetValInt());
newvar->SetValInt(pVar->GetValInt());
break;
case CBotTypIntrinsic:
(static_cast<CBotVarClass*>(newvar))->Copy(ppVars[i], false);
(static_cast<CBotVarClass*>(newvar))->Copy(pVar, false);
break;
case CBotTypPointer:
{
newvar->SetPointer(ppVars[i]->GetPointer());
newvar->SetPointer(pVar->GetPointer());
newvar->SetType(p->m_type); // keep pointer type
}
break;
case CBotTypArrayPointer:
{
newvar->SetPointer(ppVars[i]->GetPointer());
newvar->SetPointer(pVar->GetPointer());
}
break;
default:
@ -150,12 +201,19 @@ bool CBotDefParam::Execute(CBotVar** ppVars, CBotStack* &pj)
newvar->SetUniqNum(p->m_nIdent);
pj->AddVar(newvar); // add a variable
p = p->m_next;
i++;
if (!useDefault) i++;
if (pile != nullptr) pile->Delete();
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CBotDefParam::HasDefault()
{
return (m_expr != nullptr);
}
////////////////////////////////////////////////////////////////////////////////
void CBotDefParam::RestoreState(CBotStack* &pj, bool bMain)
{

View File

@ -63,6 +63,12 @@ public:
*/
bool Execute(CBotVar** ppVars, CBotStack* &pj);
/*!
* \brief Check if this parameter has a default value expression.
* \return true if the parameter was compiled with a default value.
*/
bool HasDefault();
/*!
* \brief RestoreState
* \param pj
@ -96,6 +102,9 @@ private:
//! Type of paramteter.
CBotTypResult m_type;
long m_nIdent;
//! Default value expression for the parameter.
CBotInstr* m_expr;
};
} // namespace CBot

View File

@ -238,6 +238,9 @@ enum CBotError : int
CBotErrNoExpression = 5043, //!< expression expected after =
CBotErrAmbiguousCall = 5044, //!< ambiguous call to overloaded function
CBotErrFuncNotVoid = 5045, //!< function needs return type "void"
CBotErrNoClassName = 5046, //!< class name expected
CBotErrNoReturn = 5047, //!< non-void function needs "return;"
CBotErrDefaultValue = 5048, //!< this parameter needs a default value
// Runtime errors
CBotErrZeroDiv = 6000, //!< division by zero

View File

@ -177,7 +177,11 @@ CBotFunction* CBotFunction::Compile(CBotToken* &p, CBotCStack* pStack, CBotFunct
func->m_MasterClass = pp->GetString();
func->m_classToken = *pp;
CBotClass* pClass = CBotClass::Find(pp);
if ( pClass == nullptr ) goto bad;
if ( pClass == nullptr )
{
pStk->SetError(CBotErrNoClassName, pp);
goto bad;
}
// pp = p;
func->m_token = *p;
@ -224,6 +228,12 @@ CBotFunction* CBotFunction::Compile(CBotToken* &p, CBotCStack* pStack, CBotFunct
func->m_closeblk = (p != nullptr && p->GetPrev() != nullptr) ? *(p->GetPrev()) : CBotToken();
if ( pStk->IsOk() )
{
if (!func->m_retTyp.Eq(CBotTypVoid) && !func->HasReturn())
{
int errPos = func->m_closeblk.GetStart();
pStk->ResetError(CBotErrNoReturn, errPos, errPos);
goto bad;
}
return pStack->ReturnFunc(func, pStk);
}
}
@ -280,13 +290,8 @@ CBotFunction* CBotFunction::Compile1(CBotToken* &p, CBotCStack* pStack, CBotClas
if ( IsOfType( p, ID_DBLDOTS ) ) // method for a class
{
func->m_MasterClass = pp->GetString();
CBotClass* pClass = CBotClass::Find(pp);
if ( pClass == nullptr )
{
pStk->SetError(CBotErrNotClass, pp);
goto bad;
}
// existence of the class is checked
// later in CBotFunction::Compile()
pp = p;
func->m_token = *p;
if (!IsOfType(p, TokenTypVar)) goto bad;
@ -498,8 +503,13 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list<CBotFunction*>& lo
// parameters are compatible?
CBotDefParam* pv = pt->m_param; // expected list of parameters
CBotVar* pw = ppVars[i++]; // provided list parameter
while ( pv != nullptr && pw != nullptr)
while ( pv != nullptr && (pw != nullptr || pv->HasDefault()) )
{
if (pw == nullptr) // end of arguments
{
pv = pv->GetNext();
continue; // skip params with default values
}
CBotTypResult paramType = pv->GetTypResult();
CBotTypResult argType = pw->GetTypResult(CBotVar::GetTypeMode::CLASS_AS_INTRINSIC);
@ -556,8 +566,13 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list<CBotFunction*>& lo
// parameters sont-ils compatibles ?
CBotDefParam* pv = pt->m_param; // list of expected parameters
CBotVar* pw = ppVars[i++]; // list of provided parameters
while ( pv != nullptr && pw != nullptr)
while ( pv != nullptr && (pw != nullptr || pv->HasDefault()) )
{
if (pw == nullptr) // end of arguments
{
pv = pv->GetNext();
continue; // skip params with default values
}
CBotTypResult paramType = pv->GetTypResult();
CBotTypResult argType = pw->GetTypResult(CBotVar::GetTypeMode::CLASS_AS_INTRINSIC);
@ -685,7 +700,14 @@ int CBotFunction::DoCall(CBotProgram* program, const std::list<CBotFunction*>& l
// initializes the variables as parameters
if (pt->m_param != nullptr)
{
pt->m_param->Execute(ppVars, pStk3); // cannot be interrupted
if (!pt->m_param->Execute(ppVars, pStk3)) // interupts only if error on a default value
{
if ( pt->m_pProg != program )
{
pStk3->SetPosError(pToken); // indicates the error on the procedure call
}
return pStack->Return(pStk3);
}
}
pStk1->IncState();
@ -807,7 +829,14 @@ int CBotFunction::DoCall(const std::list<CBotFunction*>& localFunctionList, long
// initializes the variables as parameters
if (pt->m_param != nullptr)
{
pt->m_param->Execute(ppVars, pStk3); // cannot be interrupted
if (!pt->m_param->Execute(ppVars, pStk3)) // interupts only if error on a default value
{
if ( pt->m_pProg != pProgCurrent )
{
pStk3->SetPosError(pToken); // indicates the error on the procedure call
}
return pStack->Return(pStk3);
}
}
pStk->IncState();
}
@ -939,6 +968,12 @@ void CBotFunction::AddPublic(CBotFunction* func)
m_publicFunctions.insert(func);
}
bool CBotFunction::HasReturn()
{
if (m_block != nullptr) return m_block->HasReturn();
return false;
}
std::string CBotFunction::GetDebugData()
{
std::stringstream ss;

View File

@ -235,6 +235,12 @@ public:
CBotGet modestart,
CBotGet modestop);
/*!
* \brief Check if the function has a return statment that will execute.
* \return true if a return statment was found.
*/
bool HasReturn() override;
protected:
virtual const std::string GetDebugName() override { return "CBotFunction"; }
virtual std::string GetDebugData() override;

View File

@ -163,6 +163,15 @@ void CBotIf :: RestoreState(CBotStack* &pj, bool bMain)
}
}
bool CBotIf::HasReturn()
{
if (m_block != nullptr && m_blockElse != nullptr)
{
if (m_block->HasReturn() && m_blockElse->HasReturn()) return true;
}
return CBotInstr::HasReturn(); // check next block or instruction
}
std::map<std::string, CBotInstr*> CBotIf::GetDebugLinks()
{
auto links = CBotInstr::GetDebugLinks();

View File

@ -56,6 +56,14 @@ public:
*/
void RestoreState(CBotStack* &pj, bool bMain) override;
/**
* \brief Check 'if' and 'else' for return statements.
* Returns true when 'if' and 'else' have return statements,
* if not, the next block or instruction is checked.
* \return true if a return statement is found.
*/
bool HasReturn() override;
protected:
virtual const std::string GetDebugName() override { return "CBotIf"; }
virtual std::map<std::string, CBotInstr*> GetDebugLinks() override;

View File

@ -359,6 +359,13 @@ CBotInstr* CBotInstr::CompileArray(CBotToken* &p, CBotCStack* pStack, CBotTypRes
return nullptr;
}
bool CBotInstr::HasReturn()
{
assert(this != nullptr);
if (m_next != nullptr) return m_next->HasReturn();
return false; // end of the list
}
std::map<std::string, CBotInstr*> CBotInstr::GetDebugLinks()
{
return {

View File

@ -281,6 +281,12 @@ public:
*/
static bool ChkLvl(const std::string& label, int type);
/**
* \brief Check a list of instructions for a return statement.
* \return true if a return statement was found.
*/
virtual bool HasReturn();
protected:
friend class CBotDebug;
/**

View File

@ -52,7 +52,7 @@ CBotInstr* CBotListInstr::Compile(CBotToken* &p, CBotCStack* pStack, bool bLocal
if (IsOfType(p, ID_SEP)) continue; // empty statement ignored
if (p->GetType() == ID_CLBLK) break;
if (IsOfType(p, 0))
if (p->GetType() == TokenTypNone)
{
pStack->SetError(CBotErrCloseBlock, p->GetStart());
delete inst;
@ -117,6 +117,12 @@ void CBotListInstr::RestoreState(CBotStack* &pj, bool bMain)
if (p != nullptr) p->RestoreState(pile, true);
}
bool CBotListInstr::HasReturn()
{
if (m_instr != nullptr && m_instr->HasReturn()) return true;
return CBotInstr::HasReturn(); // check next block or instruction
}
std::map<std::string, CBotInstr*> CBotListInstr::GetDebugLinks()
{
auto links = CBotInstr::GetDebugLinks();

View File

@ -56,6 +56,13 @@ public:
*/
void RestoreState(CBotStack* &pj, bool bMain) override;
/**
* \brief Check this block of instructions for a return statement.
* If not found, the next block or instruction is checked.
* \return true if a return statement was found.
*/
bool HasReturn() override;
protected:
virtual const std::string GetDebugName() override { return "CBotListInstr"; }
virtual std::map<std::string, CBotInstr*> GetDebugLinks() override;

View File

@ -200,7 +200,7 @@ bool CBotNew::Execute(CBotStack* &pj)
}
ppVars[i] = nullptr;
if ( !pClass->ExecuteMethode(m_nMethodeIdent, pThis, ppVars, CBotTypResult(CBotTypVoid), pile2, GetToken())) return false; // interrupt
if ( !pClass->ExecuteMethode(m_nMethodeIdent, pThis, ppVars, CBotTypResult(CBotTypVoid), pile2, &m_vartoken)) return false; // interrupt
pThis->ConstructorSet(); // indicates that the constructor has been called
}

View File

@ -132,6 +132,16 @@ CBotInstr* CBotParExpr::Compile(CBotToken* &p, CBotCStack* pStack)
return pStack->Return(nullptr, pStk);
}
return CompileLitExpr(p, pStack);
}
////////////////////////////////////////////////////////////////////////////////
CBotInstr* CBotParExpr::CompileLitExpr(CBotToken* &p, CBotCStack* pStack)
{
CBotCStack* pStk = pStack->TokenStack();
CBotToken* pp = p;
// is it a number or DefineNum?
if (p->GetType() == TokenTypNum ||
p->GetType() == TokenTypDef )

View File

@ -54,6 +54,14 @@ public:
*/
static CBotInstr* Compile(CBotToken* &p, CBotCStack* pStack);
/*!
* \brief Compile a literal expression ("string", number, true, false, null, nan, new)
* \param p[in, out] Pointer to first token of the expression, will be updated to point to first token after the expression
* \param pStack Current compilation stack frame
* \return The compiled instruction or nullptr on error
*/
static CBotInstr* CompileLitExpr(CBotToken* &p, CBotCStack* pStack);
private:
CBotParExpr() = delete;
CBotParExpr(const CBotParExpr&) = delete;

View File

@ -111,6 +111,11 @@ void CBotReturn::RestoreState(CBotStack* &pj, bool bMain)
}
}
bool CBotReturn::HasReturn()
{
return true;
}
std::map<std::string, CBotInstr*> CBotReturn::GetDebugLinks()
{
auto links = CBotInstr::GetDebugLinks();

View File

@ -55,6 +55,12 @@ public:
*/
void RestoreState(CBotStack* &pj, bool bMain) override;
/*!
* \brief Always returns true.
* \return true to signal a return statment has been found.
*/
bool HasReturn() override;
protected:
virtual const std::string GetDebugName() override { return "CBotReturn"; }
virtual std::map<std::string, CBotInstr*> GetDebugLinks() override;

View File

@ -439,6 +439,13 @@ std::unique_ptr<CBotToken> CBotToken::CompileTokens(const std::string& program)
pp = p;
}
// terminator token
nxt = new CBotToken();
nxt->m_type = TokenTypNone;
nxt->m_end = nxt->m_start = pos;
prv->m_next = nxt;
nxt->m_prev = prv;
return std::unique_ptr<CBotToken>(tokenbase);
}

View File

@ -720,6 +720,9 @@ void InitializeRestext()
stringsCbot[CBot::CBotErrNoExpression] = TR("Expression expected after =");
stringsCbot[CBot::CBotErrAmbiguousCall] = TR("Ambiguous call to overloaded function");
stringsCbot[CBot::CBotErrFuncNotVoid] = TR("Function needs return type \"void\"");
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::CBotErrZeroDiv] = TR("Dividing by zero");
stringsCbot[CBot::CBotErrNotInit] = TR("Variable not initialized");

View File

@ -60,7 +60,7 @@ protected:
ASSERT_EQ(token->GetType(), correct.type) << "type mismatch at token #" << (i+1);
i++;
}
while((token = token->GetNext()) != nullptr);
while((token = token->GetNext()) != nullptr && !IsOfType(token, TokenTypNone));
ASSERT_EQ(i, data.size()) << "not enough tokens processed";
}
};

View File

@ -298,6 +298,96 @@ TEST_F(CBotUT, EmptyTest)
);
}
TEST_F(CBotUT, FunctionCompileErrors)
{
ExecuteTest(
"public",
CBotErrNoType
);
ExecuteTest(
"extern",
CBotErrNoType
);
ExecuteTest(
"public void",
CBotErrNoFunc
);
ExecuteTest(
"extern void",
CBotErrNoFunc
);
ExecuteTest(
"extern void MissingParameterType(",
CBotErrNoType
);
ExecuteTest(
"extern void MissingParamName(int",
CBotErrNoVar
);
ExecuteTest(
"extern void MissingCloseParen(int i",
CBotErrClosePar
);
ExecuteTest(
"extern void ParamTrailingComma(int i, ) {\n"
"}\n",
CBotErrNoType
);
ExecuteTest(
"extern void MissingOpenBlock(int i)",
CBotErrOpenBlock
);
ExecuteTest(
"extern void MissingCloseBlock()\n"
"{\n",
CBotErrCloseBlock
);
}
TEST_F(CBotUT, ClassCompileErrors)
{
ExecuteTest(
"public class",
CBotErrNoClassName
);
ExecuteTest(
"public class 1234",
CBotErrNoClassName
);
ExecuteTest(
"public class TestClass",
CBotErrOpenBlock
);
ExecuteTest(
"public class TestClass\n"
"{\n",
CBotErrCloseBlock
);
ExecuteTest(
"public class TestClass extends",
CBotErrNoClassName
);
ExecuteTest(
"public class TestClass extends 1234",
CBotErrNoClassName
);
}
TEST_F(CBotUT, DivideByZero)
{
ExecuteTest(
@ -711,8 +801,7 @@ TEST_F(CBotUT, FunctionBadReturn)
);
}
// TODO: Doesn't work
TEST_F(CBotUT, DISABLED_FunctionNoReturn)
TEST_F(CBotUT, FunctionNoReturn)
{
ExecuteTest(
"int func()\n"
@ -722,7 +811,49 @@ TEST_F(CBotUT, DISABLED_FunctionNoReturn)
"{\n"
" func();\n"
"}\n",
static_cast<CBotError>(-1) // TODO: no error for that
CBotErrNoReturn
);
ExecuteTest(
"int FuncDoesNotReturnAValue()\n"
"{\n"
" if (false) return 1;\n"
" while (false) return 1;\n"
" if (true) ; else return 1;\n"
" do { break; return 1; } while (false);\n"
" do { continue; return 1; } while (false);\n"
"}\n",
CBotErrNoReturn
);
ExecuteTest(
"int FuncHasReturn()\n"
"{\n"
" return 1;\n"
"}\n"
"int BlockHasReturn()\n"
"{\n"
" {\n"
" {\n"
" }\n"
" return 2;\n"
" }\n"
"}\n"
"int IfElseHasReturn()\n"
"{\n"
" if (false) {\n"
" return 3;\n"
" } else {\n"
" if (false) return 3;\n"
" else return 3;\n"
" }\n"
"}\n"
"extern void Test()\n"
"{\n"
" ASSERT(1 == FuncHasReturn());\n"
" ASSERT(2 == BlockHasReturn());\n"
" ASSERT(3 == IfElseHasReturn());\n"
"}\n"
);
}
@ -1463,11 +1594,12 @@ TEST_F(CBotUT, StringFunctions)
);
}
TEST_F(CBotUT, DISABLED_TestNANParam_Issue642)
TEST_F(CBotUT, TestNANParam_Issue642)
{
ExecuteTest(
"float test(float x) {\n"
" return x;\n"
" ASSERT(x == nan);\n"
" return x;\n"
"}\n"
"extern void TestNANParam() {\n"
" ASSERT(nan == nan);\n" // TODO: Shouldn't it be nan != nan ??
@ -2177,3 +2309,80 @@ TEST_F(CBotUT, IncrementDecrementSyntax)
CBotErrBadType1
);
}
TEST_F(CBotUT, ParametersWithDefaultValues)
{
ExecuteTest(
"extern void ParametersWithDefaultValues() {\n"
" ASSERT(true == Test());\n"
" ASSERT(true == Test(1));\n"
" ASSERT(true == Test(1, 2));\n"
"}\n"
"bool Test(int i = 1, float f = 2.0) {\n"
" return (i == 1) && (f == 2.0);\n"
"}\n"
);
ExecuteTest(
"extern void NotUsingDefaultValues() {\n"
" ASSERT(true == Test(2, 4.0));\n"
"}\n"
"bool Test(int i = 1, float f = 2.0) {\n"
" return (i == 2) && (f == 4.0);\n"
"}\n"
);
ExecuteTest(
"extern void NextParamNeedsDefaultValue() {\n"
"}\n"
"void Test(int i = 1, float f) {}\n"
"\n",
CBotErrDefaultValue
);
ExecuteTest(
"extern void ParamMissingExpression() {\n"
"}\n"
"void Test(int i = 1, float f = ) {}\n"
"\n",
CBotErrNoExpression
);
ExecuteTest(
"extern void ParamDefaultBadType() {\n"
"}\n"
"void Test(int i = 1, float f = null) {}\n"
"\n",
CBotErrBadType1
);
ExecuteTest(
"extern void DefaultValuesAmbiguousCall() {\n"
" Test();\n"
"}\n"
"void Test(int i = 1) {}\n"
"void Test(float f = 2.0) {}\n"
"\n",
CBotErrAmbiguousCall
);
ExecuteTest(
"extern void AmbiguousCallOneDefault() {\n"
" Test(1);\n"
"}\n"
"void Test(int i, float f = 2) {}\n"
"void Test(int i) {}\n"
"\n",
CBotErrAmbiguousCall
);
ExecuteTest(
"extern void DifferentNumberOfDefaultValues() {\n"
" Test(1, 2.0);\n"
"}\n"
"void Test(int i, float f = 2.0) {}\n"
"void Test(int i, float f = 2.0, int ii = 1) {}\n"
"\n",
CBotErrAmbiguousCall
);
}