Add syntax for parameters with default values

Also fixes #642
dev-buzzingcars
melex750 2017-01-24 15:19:03 -05:00
parent baba6081b3
commit 92a8c48953
14 changed files with 218 additions and 16 deletions

View File

@ -1745,6 +1745,9 @@ msgstr ""
msgid "Non-void function needs \"return;\"" msgid "Non-void function needs \"return;\""
msgstr "" msgstr ""
msgid "This parameter needs a default value"
msgstr ""
msgid "Dividing by zero" msgid "Dividing by zero"
msgstr "" msgstr ""

View File

@ -1522,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

@ -1494,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

@ -1496,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

@ -1510,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

@ -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,6 +56,7 @@ 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;
if (!IsOfType(p, ID_CLOSEPAR)) while (true) if (!IsOfType(p, ID_CLOSEPAR)) while (true)
{ {
@ -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);
@ -109,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:
@ -152,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

@ -240,6 +240,7 @@ enum CBotError : int
CBotErrFuncNotVoid = 5045, //!< function needs return type "void" CBotErrFuncNotVoid = 5045, //!< function needs return type "void"
CBotErrNoClassName = 5046, //!< class name expected CBotErrNoClassName = 5046, //!< class name expected
CBotErrNoReturn = 5047, //!< non-void function needs "return;" 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

@ -503,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);
@ -561,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);
@ -690,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();
@ -812,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();
} }

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

@ -722,6 +722,7 @@ void InitializeRestext()
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::CBotErrNoClassName] = TR("Class name expected");
stringsCbot[CBot::CBotErrNoReturn] = TR("Non-void function needs \"return;\""); 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

@ -1594,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 ??
@ -2308,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
);
}