diff --git a/po/colobot.pot b/po/colobot.pot index 72b447ac..30a4c1c8 100644 --- a/po/colobot.pot +++ b/po/colobot.pot @@ -1745,6 +1745,9 @@ msgstr "" msgid "Non-void function needs \"return;\"" msgstr "" +msgid "This parameter needs a default value" +msgstr "" + msgid "Dividing by zero" msgstr "" diff --git a/po/de.po b/po/de.po index cdcc3f39..255608e3 100644 --- a/po/de.po +++ b/po/de.po @@ -1522,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 "" diff --git a/po/fr.po b/po/fr.po index 71b45fb2..818948a0 100644 --- a/po/fr.po +++ b/po/fr.po @@ -1494,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" diff --git a/po/pl.po b/po/pl.po index 19b0f2b7..aaa50b8a 100644 --- a/po/pl.po +++ b/po/pl.po @@ -1496,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ć" diff --git a/po/ru.po b/po/ru.po index c8ed6048..f479b47e 100644 --- a/po/ru.po +++ b/po/ru.po @@ -1510,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 "Эта программа только для чтения, для редактирования клонируйте её" diff --git a/src/CBot/CBotDefParam.cpp b/src/CBot/CBotDefParam.cpp index e66c8622..8b54f5a1 100644 --- a/src/CBot/CBotDefParam.cpp +++ b/src/CBot/CBotDefParam.cpp @@ -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,6 +56,7 @@ CBotDefParam* CBotDefParam::Compile(CBotToken* &p, CBotCStack* pStack) if (IsOfType(p, ID_OPENPAR)) { CBotDefParam* list = nullptr; + bool prevHasDefault = false; if (!IsOfType(p, ID_CLOSEPAR)) while (true) { @@ -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); @@ -109,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(newvar))->Copy(ppVars[i], false); + (static_cast(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: @@ -152,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) { diff --git a/src/CBot/CBotDefParam.h b/src/CBot/CBotDefParam.h index c23855db..bc9d9d0a 100644 --- a/src/CBot/CBotDefParam.h +++ b/src/CBot/CBotDefParam.h @@ -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 diff --git a/src/CBot/CBotEnums.h b/src/CBot/CBotEnums.h index 22022c77..f98d4e96 100644 --- a/src/CBot/CBotEnums.h +++ b/src/CBot/CBotEnums.h @@ -240,6 +240,7 @@ enum CBotError : int 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 diff --git a/src/CBot/CBotInstr/CBotFunction.cpp b/src/CBot/CBotInstr/CBotFunction.cpp index 78d55fb5..0f6a9800 100644 --- a/src/CBot/CBotInstr/CBotFunction.cpp +++ b/src/CBot/CBotInstr/CBotFunction.cpp @@ -503,8 +503,13 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list& 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); @@ -561,8 +566,13 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list& 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); @@ -690,7 +700,14 @@ int CBotFunction::DoCall(CBotProgram* program, const std::list& 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(); @@ -812,7 +829,14 @@ int CBotFunction::DoCall(const std::list& 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(); } diff --git a/src/CBot/CBotInstr/CBotNew.cpp b/src/CBot/CBotInstr/CBotNew.cpp index 9fab3824..1c84dc53 100644 --- a/src/CBot/CBotInstr/CBotNew.cpp +++ b/src/CBot/CBotInstr/CBotNew.cpp @@ -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 } diff --git a/src/CBot/CBotInstr/CBotParExpr.cpp b/src/CBot/CBotInstr/CBotParExpr.cpp index a32363fb..b0c2f1c1 100644 --- a/src/CBot/CBotInstr/CBotParExpr.cpp +++ b/src/CBot/CBotInstr/CBotParExpr.cpp @@ -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 ) diff --git a/src/CBot/CBotInstr/CBotParExpr.h b/src/CBot/CBotInstr/CBotParExpr.h index cd92467b..0bcdd0e1 100644 --- a/src/CBot/CBotInstr/CBotParExpr.h +++ b/src/CBot/CBotInstr/CBotParExpr.h @@ -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; diff --git a/src/common/restext.cpp b/src/common/restext.cpp index 5d1f012f..035389b4 100644 --- a/src/common/restext.cpp +++ b/src/common/restext.cpp @@ -722,6 +722,7 @@ void InitializeRestext() 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"); diff --git a/test/unit/CBot/CBot_test.cpp b/test/unit/CBot/CBot_test.cpp index 1d02b8a0..2f8bb503 100644 --- a/test/unit/CBot/CBot_test.cpp +++ b/test/unit/CBot/CBot_test.cpp @@ -1594,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 ?? @@ -2308,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 + ); +}