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\"" msgid "Function needs return type \"void\""
msgstr "" 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" msgid "Dividing by zero"
msgstr "" msgstr ""

View File

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

View File

@ -346,6 +346,9 @@ msgstr "Console de triche\\Montre la console de triche"
msgid "Checkpoint" msgid "Checkpoint"
msgstr "Indicateur" msgstr "Indicateur"
msgid "Class name expected"
msgstr ""
msgid "Climb\\Increases the power of the jet" msgid "Climb\\Increases the power of the jet"
msgstr "Monter\\Augmenter la puissance du réacteur" msgstr "Monter\\Augmenter la puissance du réacteur"
@ -921,6 +924,9 @@ msgstr "Pas d'uranium à transformer"
msgid "No userlevels installed!" msgid "No userlevels installed!"
msgstr "Pas de niveaux spéciaux installés !" msgstr "Pas de niveaux spéciaux installés !"
msgid "Non-void function needs \"return;\""
msgstr ""
msgid "Normal size" msgid "Normal size"
msgstr "Taille normale" msgstr "Taille normale"
@ -1488,6 +1494,9 @@ msgstr ""
msgid "This object is not a member of a class" msgid "This object is not a member of a class"
msgstr "L'objet n'est pas une instance d'une classe" 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" msgid "This program is read-only, clone it to edit"
msgstr "Ce programme est en lecture-seule, le dupliquer pour pouvoir l'éditer" 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" msgid "Checkpoint"
msgstr "Punkt kontrolny" msgstr "Punkt kontrolny"
msgid "Class name expected"
msgstr ""
msgid "Climb\\Increases the power of the jet" msgid "Climb\\Increases the power of the jet"
msgstr "W górę\\Zwiększa moc silnika" msgstr "W górę\\Zwiększa moc silnika"
@ -923,6 +926,9 @@ msgstr "Brak uranu do przetworzenia"
msgid "No userlevels installed!" msgid "No userlevels installed!"
msgstr "Brak zainstalowanych poziomów użytkownika!" msgstr "Brak zainstalowanych poziomów użytkownika!"
msgid "Non-void function needs \"return;\""
msgstr ""
msgid "Normal size" msgid "Normal size"
msgstr "Normalna wielkość" msgstr "Normalna wielkość"
@ -1490,6 +1496,9 @@ msgstr "Ten objekt jest obecnie zajęty"
msgid "This object is not a member of a class" msgid "This object is not a member of a class"
msgstr "Ten obiekt nie jest członkiem klasy" 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" msgid "This program is read-only, clone it to edit"
msgstr "Ten program jest tylko do odczytu, skopiuj go, aby edytować" msgstr "Ten program jest tylko do odczytu, skopiuj go, aby edytować"

View File

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

View File

@ -471,26 +471,27 @@ CBotClass* CBotClass::Compile1(CBotToken* &p, CBotCStack* pStack)
std::string name = p->GetString(); 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? // a name of the class is there?
if (IsOfType(p, TokenTypVar)) 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; CBotClass* pPapa = nullptr;
if ( IsOfType( p, ID_EXTENDS ) ) if ( IsOfType( p, ID_EXTENDS ) )
{ {
std::string name = p->GetString(); std::string name = p->GetString();
pPapa = CBotClass::Find(name); pPapa = CBotClass::Find(name);
CBotToken* pp = p;
if (!IsOfType(p, TokenTypVar) || pPapa == nullptr ) if (!IsOfType(p, TokenTypVar) || pPapa == nullptr )
{ {
pStack->SetError( CBotErrNotClass, p ); pStack->SetError(CBotErrNoClassName, pp);
return nullptr; return nullptr;
} }
} }
@ -519,6 +520,9 @@ CBotClass* CBotClass::Compile1(CBotToken* &p, CBotCStack* pStack)
if (pStack->IsOk()) return classe; if (pStack->IsOk()) return classe;
} }
else
pStack->SetError(CBotErrNoClassName, p);
pStack->SetError(CBotErrNoTerminator, p); pStack->SetError(CBotErrNoTerminator, p);
return nullptr; 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 // TODO: Not sure how correct is that - I have no idea how the precompilation (Compile1 method) works ~krzys_h
std::string name = p->GetString(); std::string name = p->GetString();
CBotClass* pPapa = CBotClass::Find(name); CBotClass* pPapa = CBotClass::Find(name);
CBotToken* pp = p;
if (!IsOfType(p, TokenTypVar) || pPapa == nullptr) if (!IsOfType(p, TokenTypVar) || pPapa == nullptr)
{ {
pStack->SetError( CBotErrNotClass, p ); pStack->SetError(CBotErrNoClassName, pp);
return nullptr; return nullptr;
} }
pOld->m_parent = pPapa; pOld->m_parent = pPapa;

View File

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

View File

@ -63,6 +63,12 @@ public:
*/ */
bool Execute(CBotVar** ppVars, CBotStack* &pj); 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 * \brief RestoreState
* \param pj * \param pj
@ -96,6 +102,9 @@ private:
//! Type of paramteter. //! Type of paramteter.
CBotTypResult m_type; CBotTypResult m_type;
long m_nIdent; long m_nIdent;
//! Default value expression for the parameter.
CBotInstr* m_expr;
}; };
} // namespace CBot } // namespace CBot

View File

@ -238,6 +238,9 @@ enum CBotError : int
CBotErrNoExpression = 5043, //!< expression expected after = CBotErrNoExpression = 5043, //!< expression expected after =
CBotErrAmbiguousCall = 5044, //!< ambiguous call to overloaded function CBotErrAmbiguousCall = 5044, //!< ambiguous call to overloaded function
CBotErrFuncNotVoid = 5045, //!< function needs return type "void" 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 // Runtime errors
CBotErrZeroDiv = 6000, //!< division by zero 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_MasterClass = pp->GetString();
func->m_classToken = *pp; func->m_classToken = *pp;
CBotClass* pClass = CBotClass::Find(pp); CBotClass* pClass = CBotClass::Find(pp);
if ( pClass == nullptr ) goto bad; if ( pClass == nullptr )
{
pStk->SetError(CBotErrNoClassName, pp);
goto bad;
}
// pp = p; // pp = p;
func->m_token = *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(); func->m_closeblk = (p != nullptr && p->GetPrev() != nullptr) ? *(p->GetPrev()) : CBotToken();
if ( pStk->IsOk() ) 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); 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 if ( IsOfType( p, ID_DBLDOTS ) ) // method for a class
{ {
func->m_MasterClass = pp->GetString(); func->m_MasterClass = pp->GetString();
CBotClass* pClass = CBotClass::Find(pp); // existence of the class is checked
if ( pClass == nullptr ) // later in CBotFunction::Compile()
{
pStk->SetError(CBotErrNotClass, pp);
goto bad;
}
pp = p; pp = p;
func->m_token = *p; func->m_token = *p;
if (!IsOfType(p, TokenTypVar)) goto bad; if (!IsOfType(p, TokenTypVar)) goto bad;
@ -498,8 +503,13 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list<CBotFunction*>& lo
// parameters are compatible? // parameters are compatible?
CBotDefParam* pv = pt->m_param; // expected list of parameters CBotDefParam* pv = pt->m_param; // expected list of parameters
CBotVar* pw = ppVars[i++]; // provided list parameter 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 paramType = pv->GetTypResult();
CBotTypResult argType = pw->GetTypResult(CBotVar::GetTypeMode::CLASS_AS_INTRINSIC); 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 ? // parameters sont-ils compatibles ?
CBotDefParam* pv = pt->m_param; // list of expected parameters CBotDefParam* pv = pt->m_param; // list of expected parameters
CBotVar* pw = ppVars[i++]; // list of provided 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 paramType = pv->GetTypResult();
CBotTypResult argType = pw->GetTypResult(CBotVar::GetTypeMode::CLASS_AS_INTRINSIC); 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 // initializes the variables as parameters
if (pt->m_param != nullptr) 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(); pStk1->IncState();
@ -807,7 +829,14 @@ int CBotFunction::DoCall(const std::list<CBotFunction*>& localFunctionList, long
// initializes the variables as parameters // initializes the variables as parameters
if (pt->m_param != nullptr) 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(); pStk->IncState();
} }
@ -939,6 +968,12 @@ void CBotFunction::AddPublic(CBotFunction* func)
m_publicFunctions.insert(func); m_publicFunctions.insert(func);
} }
bool CBotFunction::HasReturn()
{
if (m_block != nullptr) return m_block->HasReturn();
return false;
}
std::string CBotFunction::GetDebugData() std::string CBotFunction::GetDebugData()
{ {
std::stringstream ss; std::stringstream ss;

View File

@ -235,6 +235,12 @@ public:
CBotGet modestart, CBotGet modestart,
CBotGet modestop); 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: protected:
virtual const std::string GetDebugName() override { return "CBotFunction"; } virtual const std::string GetDebugName() override { return "CBotFunction"; }
virtual std::string GetDebugData() override; 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() std::map<std::string, CBotInstr*> CBotIf::GetDebugLinks()
{ {
auto links = CBotInstr::GetDebugLinks(); auto links = CBotInstr::GetDebugLinks();

View File

@ -56,6 +56,14 @@ public:
*/ */
void RestoreState(CBotStack* &pj, bool bMain) override; 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: protected:
virtual const std::string GetDebugName() override { return "CBotIf"; } virtual const std::string GetDebugName() override { return "CBotIf"; }
virtual std::map<std::string, CBotInstr*> GetDebugLinks() override; virtual std::map<std::string, CBotInstr*> GetDebugLinks() override;

View File

@ -359,6 +359,13 @@ CBotInstr* CBotInstr::CompileArray(CBotToken* &p, CBotCStack* pStack, CBotTypRes
return nullptr; 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() std::map<std::string, CBotInstr*> CBotInstr::GetDebugLinks()
{ {
return { return {

View File

@ -281,6 +281,12 @@ public:
*/ */
static bool ChkLvl(const std::string& label, int type); 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: protected:
friend class CBotDebug; 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 (IsOfType(p, ID_SEP)) continue; // empty statement ignored
if (p->GetType() == ID_CLBLK) break; if (p->GetType() == ID_CLBLK) break;
if (IsOfType(p, 0)) if (p->GetType() == TokenTypNone)
{ {
pStack->SetError(CBotErrCloseBlock, p->GetStart()); pStack->SetError(CBotErrCloseBlock, p->GetStart());
delete inst; delete inst;
@ -117,6 +117,12 @@ void CBotListInstr::RestoreState(CBotStack* &pj, bool bMain)
if (p != nullptr) p->RestoreState(pile, true); 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() std::map<std::string, CBotInstr*> CBotListInstr::GetDebugLinks()
{ {
auto links = CBotInstr::GetDebugLinks(); auto links = CBotInstr::GetDebugLinks();

View File

@ -56,6 +56,13 @@ public:
*/ */
void RestoreState(CBotStack* &pj, bool bMain) override; 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: protected:
virtual const std::string GetDebugName() override { return "CBotListInstr"; } virtual const std::string GetDebugName() override { return "CBotListInstr"; }
virtual std::map<std::string, CBotInstr*> GetDebugLinks() override; virtual std::map<std::string, CBotInstr*> GetDebugLinks() override;

View File

@ -200,7 +200,7 @@ bool CBotNew::Execute(CBotStack* &pj)
} }
ppVars[i] = nullptr; 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 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 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? // is it a number or DefineNum?
if (p->GetType() == TokenTypNum || if (p->GetType() == TokenTypNum ||
p->GetType() == TokenTypDef ) p->GetType() == TokenTypDef )

View File

@ -54,6 +54,14 @@ public:
*/ */
static CBotInstr* Compile(CBotToken* &p, CBotCStack* pStack); 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: private:
CBotParExpr() = delete; CBotParExpr() = delete;
CBotParExpr(const 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() std::map<std::string, CBotInstr*> CBotReturn::GetDebugLinks()
{ {
auto links = CBotInstr::GetDebugLinks(); auto links = CBotInstr::GetDebugLinks();

View File

@ -55,6 +55,12 @@ public:
*/ */
void RestoreState(CBotStack* &pj, bool bMain) override; 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: protected:
virtual const std::string GetDebugName() override { return "CBotReturn"; } virtual const std::string GetDebugName() override { return "CBotReturn"; }
virtual std::map<std::string, CBotInstr*> GetDebugLinks() override; 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; 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); return std::unique_ptr<CBotToken>(tokenbase);
} }

View File

@ -720,6 +720,9 @@ void InitializeRestext()
stringsCbot[CBot::CBotErrNoExpression] = TR("Expression expected after ="); stringsCbot[CBot::CBotErrNoExpression] = TR("Expression expected after =");
stringsCbot[CBot::CBotErrAmbiguousCall] = TR("Ambiguous call to overloaded function"); stringsCbot[CBot::CBotErrAmbiguousCall] = TR("Ambiguous call to overloaded function");
stringsCbot[CBot::CBotErrFuncNotVoid] = TR("Function needs return type \"void\""); 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::CBotErrZeroDiv] = TR("Dividing by zero");
stringsCbot[CBot::CBotErrNotInit] = TR("Variable not initialized"); 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); ASSERT_EQ(token->GetType(), correct.type) << "type mismatch at token #" << (i+1);
i++; i++;
} }
while((token = token->GetNext()) != nullptr); while((token = token->GetNext()) != nullptr && !IsOfType(token, TokenTypNone));
ASSERT_EQ(i, data.size()) << "not enough tokens processed"; 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) TEST_F(CBotUT, DivideByZero)
{ {
ExecuteTest( ExecuteTest(
@ -711,8 +801,7 @@ TEST_F(CBotUT, FunctionBadReturn)
); );
} }
// TODO: Doesn't work TEST_F(CBotUT, FunctionNoReturn)
TEST_F(CBotUT, DISABLED_FunctionNoReturn)
{ {
ExecuteTest( ExecuteTest(
"int func()\n" "int func()\n"
@ -722,7 +811,49 @@ TEST_F(CBotUT, DISABLED_FunctionNoReturn)
"{\n" "{\n"
" func();\n" " func();\n"
"}\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( ExecuteTest(
"float test(float x) {\n" "float test(float x) {\n"
" return x;\n" " ASSERT(x == nan);\n"
" return x;\n"
"}\n" "}\n"
"extern void TestNANParam() {\n" "extern void TestNANParam() {\n"
" ASSERT(nan == nan);\n" // TODO: Shouldn't it be nan != nan ?? " ASSERT(nan == nan);\n" // TODO: Shouldn't it be nan != nan ??
@ -2177,3 +2309,80 @@ TEST_F(CBotUT, IncrementDecrementSyntax)
CBotErrBadType1 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
);
}