From 4a62e9ed76ca962a9d2ffbdba1ebe28ff5b990d5 Mon Sep 17 00:00:00 2001 From: melex750 Date: Wed, 15 Nov 2017 16:21:01 -0500 Subject: [PATCH] Separate searching for functions and methods fixes #207 fixes #1016 --- src/CBot/CBotCStack.cpp | 55 ++++- src/CBot/CBotCStack.h | 26 ++- src/CBot/CBotClass.cpp | 48 +---- src/CBot/CBotClass.h | 6 + src/CBot/CBotInstr/CBotFunction.cpp | 216 ++++++++++++++++---- src/CBot/CBotInstr/CBotFunction.h | 86 ++++++-- test/unit/CBot/CBot_test.cpp | 300 ++++++++++++++++++++++++++++ 7 files changed, 641 insertions(+), 96 deletions(-) diff --git a/src/CBot/CBotCStack.cpp b/src/CBot/CBotCStack.cpp index 0abcb3ae..a22c03bb 100644 --- a/src/CBot/CBotCStack.cpp +++ b/src/CBot/CBotCStack.cpp @@ -20,6 +20,7 @@ #include "CBot/CBotCStack.h" +#include "CBot/CBotClass.h" #include "CBot/CBotToken.h" #include "CBot/CBotExternalCall.h" @@ -320,6 +321,50 @@ void CBotCStack::AddVar(CBotVar* pVar) *pp = pVar; // added after } +//////////////////////////////////////////////////////////////////////////////// +void CBotCStack::CreateVarThis(CBotClass* pClass) +{ + if ( pClass == nullptr ) return; + + CBotVar* pThis = CBotVar::Create("this", CBotTypResult(CBotTypClass, pClass)); + + pThis->SetUniqNum(-2); // special ID for "this" + AddVar(pThis); +} + +//////////////////////////////////////////////////////////////////////////////// +void CBotCStack::CreateVarSuper(CBotClass* pClass) +{ + if ( pClass == nullptr ) return; + + CBotVar* pSuper = CBotVar::Create("super", CBotTypResult(CBotTypClass, pClass)); + + pSuper->SetUniqNum(-3); // special ID for "super" + AddVar(pSuper); +} + +//////////////////////////////////////////////////////////////////////////////// +void CBotCStack::CreateMemberVars(CBotClass* pClass, bool setDefined) +{ + while (pClass != nullptr) + { + CBotVar* pv = pClass->GetVar(); + while (pv != nullptr) + { + CBotVar* pcopy = CBotVar::Create(pv); + CBotVar::InitType initType = CBotVar::InitType::UNDEF; + if (setDefined || pv->IsStatic()) + initType = CBotVar::InitType::DEF; + pcopy->SetInit(initType); + pcopy->SetUniqNum(pv->GetUniqNum()); + pcopy->SetPrivate(pv->GetPrivate()); + AddVar(pcopy); + pv = pv->GetNext(); + } + pClass = pClass->GetParent(); + } +} + //////////////////////////////////////////////////////////////////////////////// bool CBotCStack::CheckVarLocal(CBotToken* &pToken) { @@ -350,7 +395,7 @@ CBotTypResult CBotCStack::CompileCall(CBotToken* &p, CBotVar** ppVars, long& nId val = m_prog->GetExternalCalls()->CompileCall(p, nullptr, ppVars, this); if (val.GetType() < 0) { - val = CBotFunction::CompileCall(m_prog->GetFunctions(), p->GetString(), ppVars, nIdent); + val = CBotFunction::CompileCall(p->GetString(), ppVars, nIdent, m_prog); if ( val.GetType() < 0 ) { // pVar = nullptr; // the error is not on a particular parameter @@ -363,7 +408,7 @@ CBotTypResult CBotCStack::CompileCall(CBotToken* &p, CBotVar** ppVars, long& nId } //////////////////////////////////////////////////////////////////////////////// -bool CBotCStack::CheckCall(CBotToken* &pToken, CBotDefParam* pParam) +bool CBotCStack::CheckCall(CBotToken* &pToken, CBotDefParam* pParam, const std::string& className) { std::string name = pToken->GetString(); @@ -373,6 +418,9 @@ bool CBotCStack::CheckCall(CBotToken* &pToken, CBotDefParam* pParam) { if ( pToken->GetString() == pp->GetName() ) { + // ignore methods for a different class + if ( className != pp->GetClassName() ) + continue; // are parameters exactly the same? if ( pp->CheckParam( pParam ) ) return true; @@ -383,6 +431,9 @@ bool CBotCStack::CheckCall(CBotToken* &pToken, CBotDefParam* pParam) { if ( pToken->GetString() == pp->GetName() ) { + // ignore methods for a different class + if ( className != pp->GetClassName() ) + continue; // are parameters exactly the same? if ( pp->CheckParam( pParam ) ) return true; diff --git a/src/CBot/CBotCStack.h b/src/CBot/CBotCStack.h index 40ad9424..93620013 100644 --- a/src/CBot/CBotCStack.h +++ b/src/CBot/CBotCStack.h @@ -100,6 +100,25 @@ public: */ void AddVar(CBotVar* p); + /*! + * \brief Create 'this' as a local variable. + * \param pClass The current class referred to by 'this' + */ + void CreateVarThis(CBotClass* pClass); + + /*! + * \brief Create 'super' as a local variable. + * \param pClass The parent class referred to by 'super' + */ + void CreateVarSuper(CBotClass* pClass); + + /*! + * \brief Create member variables of the current class as local variables. + * \param pClass The current class. + * \param setDefined Whether to mark the variables as initialized. + */ + void CreateMemberVars(CBotClass* pClass, bool setDefined); + /*! * \brief FindVar Finds a variable. Seeks a variable on the stack the token * may be a result of TokenTypVar (object of a class) or a pointer in the @@ -235,11 +254,12 @@ public: /*! * \brief CheckCall Test if a procedure name is already defined somewhere. - * \param pToken - * \param pParam + * \param pToken Token representing the name of a function. + * \param pParam List of parameters. + * \param className Name of a class when checking for methods. * \return */ - bool CheckCall(CBotToken* &pToken, CBotDefParam* pParam); + bool CheckCall(CBotToken* &pToken, CBotDefParam* pParam, const std::string& className); /*! * \brief NextToken diff --git a/src/CBot/CBotClass.cpp b/src/CBot/CBotClass.cpp index 628bb907..a200d4a5 100644 --- a/src/CBot/CBotClass.cpp +++ b/src/CBot/CBotClass.cpp @@ -315,7 +315,7 @@ CBotTypResult CBotClass::CompileMethode(CBotToken* name, // find the methods declared by user - r = CBotFunction::CompileCall(m_pMethod, name->GetString(), ppParams, nIdent); + r = CBotFunction::CompileMethodCall(name->GetString(), ppParams, nIdent, pStack, this); if ( r.Eq(CBotErrUndefCall) && m_parent != nullptr ) return m_parent->CompileMethode(name, pThis, ppParams, pStack, nIdent); return r; @@ -332,7 +332,7 @@ bool CBotClass::ExecuteMethode(long& nIdent, int ret = m_externalMethods->DoCall(pToken, pThis, ppParams, pStack, pResultType); if (ret >= 0) return ret; - ret = CBotFunction::DoCall(m_pMethod, nIdent, pToken->GetString(), pThis, ppParams, pStack, pToken, this); + ret = CBotFunction::DoCall(nIdent, pToken->GetString(), pThis, ppParams, pStack, pToken, this); if (ret >= 0) return ret; if (m_parent != nullptr) @@ -355,7 +355,7 @@ void CBotClass::RestoreMethode(long& nIdent, CBotClass* pClass = this; while (pClass != nullptr) { - bool ok = CBotFunction::RestoreCall(pClass->m_pMethod, nIdent, name->GetString(), pThis, ppParams, pStack, pClass); + bool ok = CBotFunction::RestoreCall(nIdent, name->GetString(), pThis, ppParams, pStack, pClass); if (ok) return; pClass = pClass->m_parent; } @@ -542,6 +542,11 @@ void CBotClass::DefineClasses(std::list pClassList, CBotCStack* pSta } } +const std::list& CBotClass::GetFunctions() +{ + return m_pMethod; +} + //////////////////////////////////////////////////////////////////////////////// bool CBotClass::CompileDefItem(CBotToken* &p, CBotCStack* pStack, bool bSecond) { @@ -609,45 +614,8 @@ bool CBotClass::CompileDefItem(CBotToken* &p, CBotCStack* pStack, bool bSecond) CBotFunction* pf = *pfIter; delete params; - bool bConstructor = (pp == GetName()); CBotCStack* pile = pStack->TokenStack(nullptr, true); - // make "this" known - CBotToken TokenThis(std::string("this"), std::string()); - CBotVar* pThis = CBotVar::Create(TokenThis, CBotTypResult( CBotTypClass, this ) ); - pThis->SetUniqNum(-2); - pile->AddVar(pThis); - - if (m_parent) - { - // makes "super" known - CBotToken TokenSuper(std::string("super"), std::string()); - CBotVar* pThis = CBotVar::Create(TokenSuper, CBotTypResult(CBotTypClass, m_parent) ); - pThis->SetUniqNum(-3); - pile->AddVar(pThis); - } - -// int num = 1; - CBotClass* my = this; - while (my != nullptr) - { - // places a copy of variables of a class (this) on a stack - CBotVar* pv = my->m_pVar; - while (pv != nullptr) - { - CBotVar* pcopy = CBotVar::Create(pv); - CBotVar::InitType initType = CBotVar::InitType::UNDEF; - if (!bConstructor || pv->IsStatic()) - initType = CBotVar::InitType::DEF; - pcopy->SetInit(initType); - pcopy->SetUniqNum(pv->GetUniqNum()); - pcopy->SetPrivate(pv->GetPrivate()); - pile->AddVar(pcopy); - pv = pv->GetNext(); - } - my = my->m_parent; - } - // compiles a method p = pBase; CBotFunction* f = diff --git a/src/CBot/CBotClass.h b/src/CBot/CBotClass.h index eeafe2d1..fbf03f1f 100644 --- a/src/CBot/CBotClass.h +++ b/src/CBot/CBotClass.h @@ -303,6 +303,12 @@ public: */ static void DefineClasses(std::list pClassList, CBotCStack* pStack); + /*! + * \brief Get the list of user-defined methods in this class. + * \return List of methods, can be empty. + */ + const std::list& GetFunctions(); + /*! * \brief CompileDefItem * \param p diff --git a/src/CBot/CBotInstr/CBotFunction.cpp b/src/CBot/CBotInstr/CBotFunction.cpp index 4916d870..5004cd66 100644 --- a/src/CBot/CBotInstr/CBotFunction.cpp +++ b/src/CBot/CBotInstr/CBotFunction.cpp @@ -129,12 +129,10 @@ CBotFunction* CBotFunction::Compile(CBotToken* &p, CBotCStack* pStack, CBotFunct { CBotToken* pp; CBotFunction* func = finput; - if ( func == nullptr ) func = new CBotFunction(); + assert(func != nullptr); // a pre-compiled function is required CBotCStack* pStk = pStack->TokenStack(p, bLocal); -// func->m_nFuncIdent = CBotVar::NextUniqNum(); - while (true) { if ( IsOfType(p, ID_PUBLIC) ) @@ -183,10 +181,23 @@ CBotFunction* CBotFunction::Compile(CBotToken* &p, CBotCStack* pStack, CBotFunct goto bad; } -// pp = p; + pp = p; func->m_token = *p; if (!IsOfType(p, TokenTypVar)) goto bad; - + // check if the class has a method like this + if (pClass->CheckCall(pStack->GetProgram(), func->m_param, pp)) + { + pStk->SetStartError(func->m_classToken.GetStart()); + pStk->SetError(CBotErrRedefFunc, pp->GetEnd()); + goto bad; + } + // check if a constructor has return type void + if (func->GetName() == pClass->GetName() && !func->m_retTyp.Eq(CBotTypVoid)) + { + pp = &(func->m_retToken); + pStk->SetError(CBotErrFuncNotVoid, pp); + goto bad; + } } func->m_openpar = *p; delete func->m_param; @@ -198,28 +209,13 @@ CBotFunction* CBotFunction::Compile(CBotToken* &p, CBotCStack* pStack, CBotFunct if (!func->m_MasterClass.empty()) { - // return "this" known - CBotVar* pThis = CBotVar::Create("this", CBotTypResult( CBotTypClass, func->m_MasterClass )); - pThis->SetInit(CBotVar::InitType::IS_POINTER); -// pThis->SetUniqNum(func->m_nThisIdent = -2); //CBotVar::NextUniqNum() will not - pThis->SetUniqNum(-2); - pStk->AddVar(pThis); + CBotClass* pClass = CBotClass::Find(func->m_MasterClass); - // initialize variables acording to This - // only saves the pointer to the first, - // the rest is chained - CBotVar* pv = pThis->GetItemList(); -// int num = 1; - while (pv != nullptr) - { - CBotVar* pcopy = CBotVar::Create(pv); -// pcopy->SetInit(2); - pcopy->Copy(pv); - pcopy->SetPrivate(pv->GetPrivate()); -// pcopy->SetUniqNum(pv->GetUniqNum()); //num++); - pStk->AddVar(pcopy); - pv = pv->GetNext(); - } + pStk->CreateVarThis(pClass); + pStk->CreateVarSuper(pClass->GetParent()); + + bool bConstructor = (func->GetName() == func->m_MasterClass); + pStk->CreateMemberVars(pClass, !bConstructor); } // and compiles the following instruction block @@ -242,7 +238,6 @@ bad: pStk->SetError(CBotErrNoFunc, p); } pStk->SetError(CBotErrNoType, p); - if ( finput == nullptr ) delete func; return pStack->ReturnFunc(nullptr, pStk); } @@ -303,6 +298,7 @@ CBotFunction* CBotFunction::Compile1(CBotToken* &p, CBotCStack* pStack, CBotClas if (pStk->IsOk() && pClass != nullptr) // method in a class { + func->m_MasterClass = pClass->GetName(); // check if a constructor has return type void if (func->GetName() == pClass->GetName() && !func->m_retTyp.Eq(CBotTypVoid)) { @@ -331,7 +327,7 @@ CBotFunction* CBotFunction::Compile1(CBotToken* &p, CBotCStack* pStack, CBotClas { // looks if the function exists elsewhere pp = &(func->m_token); - if (( pClass != nullptr || !pStack->CheckCall(pp, func->m_param)) && + if (( pClass != nullptr || !pStack->CheckCall(pp, func->m_param, func->m_MasterClass)) && ( pClass == nullptr || !pClass->CheckCall(pStack->GetProgram(), func->m_param, pp)) ) { if (IsOfType(p, ID_OPBLK)) @@ -472,10 +468,10 @@ void CBotFunction::RestoreState(CBotVar** ppVars, CBotStack* &pj, CBotVar* pInst } //////////////////////////////////////////////////////////////////////////////// -CBotTypResult CBotFunction::CompileCall(const std::list& localFunctionList, const std::string &name, CBotVar** ppVars, long &nIdent) +CBotTypResult CBotFunction::CompileCall(const std::string &name, CBotVar** ppVars, long &nIdent, CBotProgram* program) { CBotTypResult type; - if (!FindLocalOrPublic(localFunctionList, nIdent, name, ppVars, type)) + if (!FindLocalOrPublic(program->GetFunctions(), nIdent, name, ppVars, type, program)) { // Reset the identifier to "not found" value nIdent = 0; @@ -485,7 +481,7 @@ CBotTypResult CBotFunction::CompileCall(const std::list& localFun //////////////////////////////////////////////////////////////////////////////// CBotFunction* CBotFunction::FindLocalOrPublic(const std::list& localFunctionList, long &nIdent, const std::string &name, - CBotVar** ppVars, CBotTypResult &TypeOrError, bool bPublic) + CBotVar** ppVars, CBotTypResult &TypeOrError, CBotProgram* baseProg) { TypeOrError.SetType(CBotErrUndefCall); // no routine of the name @@ -515,10 +511,39 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list& lo std::map funcMap; - for (CBotFunction* pt : localFunctionList) + CBotFunction::SearchList(localFunctionList, name, ppVars, TypeOrError, funcMap); + + CBotFunction::SearchPublic(name, ppVars, TypeOrError, funcMap); + + if (baseProg != nullptr && baseProg->m_thisVar != nullptr) + { + // find object:: functions + CBotClass* pClass = baseProg->m_thisVar->GetClass(); + CBotFunction::SearchList(localFunctionList, name, ppVars, TypeOrError, funcMap, pClass); + CBotFunction::SearchPublic(name, ppVars, TypeOrError, funcMap, pClass); + } + + return CBotFunction::BestFunction(funcMap, nIdent, TypeOrError); +} + +//////////////////////////////////////////////////////////////////////////////// +void CBotFunction::SearchList(const std::list& functionList, + const std::string& name, CBotVar** ppVars, CBotTypResult& TypeOrError, + std::map& funcMap, CBotClass* pClass) +{ + for (CBotFunction* pt : functionList) { if ( pt->m_token.GetString() == name ) { + if (pClass != nullptr) // looking for a method ? + { + if (pt->m_MasterClass != pClass->GetName()) continue; + } + else // looking for a function + { + if (!pt->m_MasterClass.empty()) continue; + } + int i = 0; int alpha = 0; // signature of parameters // parameters are compatible? @@ -575,13 +600,26 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list& lo funcMap.insert( std::pair(pt, alpha) ); } } +} - if ( bPublic ) +//////////////////////////////////////////////////////////////////////////////// +void CBotFunction::SearchPublic(const std::string& name, CBotVar** ppVars, CBotTypResult& TypeOrError, + std::map& funcMap, CBotClass* pClass) +{ { for (CBotFunction* pt : m_publicFunctions) { if ( pt->m_token.GetString() == name ) { + if (pClass != nullptr) // looking for a method ? + { + if (pt->m_MasterClass != pClass->GetName()) continue; + } + else // looking for a function + { + if (!pt->m_MasterClass.empty()) continue; + } + int i = 0; int alpha = 0; // signature of parameters // are parameters compatible ? @@ -639,7 +677,12 @@ CBotFunction* CBotFunction::FindLocalOrPublic(const std::list& lo } } } +} +//////////////////////////////////////////////////////////////////////////////// +CBotFunction* CBotFunction::BestFunction(std::map& funcMap, + long& nIdent, CBotTypResult& TypeOrError) +{ if ( !funcMap.empty() ) { auto it = funcMap.begin(); @@ -675,7 +718,7 @@ int CBotFunction::DoCall(CBotProgram* program, const std::list& l CBotFunction* pt = nullptr; CBotProgram* baseProg = pStack->GetProgram(true); - pt = FindLocalOrPublic(localFunctionList, nIdent, name, ppVars, type); + pt = FindLocalOrPublic(localFunctionList, nIdent, name, ppVars, type, baseProg); if ( pt != nullptr ) { @@ -766,7 +809,7 @@ void CBotFunction::RestoreCall(const std::list& localFunctionList CBotStack* pStk3; CBotProgram* baseProg = pStack->GetProgram(true); - pt = FindLocalOrPublic(localFunctionList, nIdent, name, ppVars, type); + pt = FindLocalOrPublic(localFunctionList, nIdent, name, ppVars, type, baseProg); if ( pt != nullptr ) { @@ -824,13 +867,102 @@ void CBotFunction::RestoreCall(const std::list& localFunctionList } //////////////////////////////////////////////////////////////////////////////// -int CBotFunction::DoCall(const std::list& localFunctionList, long &nIdent, const std::string &name, CBotVar* pThis, +CBotTypResult CBotFunction::CompileMethodCall(const std::string& name, CBotVar** ppVars, + long& nIdent, CBotCStack* pStack, CBotClass* pClass) +{ + nIdent = 0; + CBotTypResult type; + + CBotFunction* pt = FindMethod(nIdent, name, ppVars, type, pClass, pStack->GetProgram()); + + if (pt != nullptr) + { + } + + return type; +} + +//////////////////////////////////////////////////////////////////////////////// +CBotFunction* CBotFunction::FindMethod(long& nIdent, const std::string& name, + CBotVar** ppVars, CBotTypResult& TypeOrError, + CBotClass* pClass, CBotProgram* program) +{ + TypeOrError.SetType(CBotErrUndefCall); // no routine of the name + + auto methods = pClass->GetFunctions(); + + if ( nIdent ) + { + // search methods in the class + for (CBotFunction* pt : methods) + { + if ( pt->m_nFuncIdent == nIdent ) + { + TypeOrError = pt->m_retTyp; + return pt; + } + } + + bool skipPublic = false; + if (program != nullptr) + { + // search the current program + for (CBotFunction* pt : program->GetFunctions()) + { + if ( pt->m_nFuncIdent == nIdent ) + { + // check if the method is inherited + if ( pt->GetClassName() != pClass->GetName() ) + { + skipPublic = true; + break; // break in case there is an override + } + TypeOrError = pt->m_retTyp; + return pt; + } + } + } + + // search the list of public functions + if (!skipPublic) + { + for (CBotFunction* pt : m_publicFunctions) + { + if (pt->m_nFuncIdent == nIdent) + { + // check if the method is inherited, break in case there is an override + if ( pt->GetClassName() != pClass->GetName() ) break; + TypeOrError = pt->m_retTyp; + return pt; + } + } + } + } + + if ( name.empty() ) return nullptr; + + std::map funcMap; + + // search methods in the class + CBotFunction::SearchList(methods, name, ppVars, TypeOrError, funcMap, pClass); + + // search the current program for methods + if (program != nullptr) + CBotFunction::SearchList(program->GetFunctions(), name, ppVars, TypeOrError, funcMap, pClass); + + CBotFunction::SearchPublic(name, ppVars, TypeOrError, funcMap, pClass); + + return CBotFunction::BestFunction(funcMap, nIdent, TypeOrError); +} + +//////////////////////////////////////////////////////////////////////////////// +int CBotFunction::DoCall(long &nIdent, const std::string &name, CBotVar* pThis, CBotVar** ppVars, CBotStack* pStack, CBotToken* pToken, CBotClass* pClass) { CBotTypResult type; CBotProgram* pProgCurrent = pStack->GetProgram(); - CBotFunction* pt = FindLocalOrPublic(localFunctionList, nIdent, name, ppVars, type, false); + CBotFunction* pt = FindMethod(nIdent, name, ppVars, type, pClass, pProgCurrent); if ( pt != nullptr ) { @@ -925,11 +1057,11 @@ int CBotFunction::DoCall(const std::list& localFunctionList, long } //////////////////////////////////////////////////////////////////////////////// -bool CBotFunction::RestoreCall(const std::list& localFunctionList, long &nIdent, const std::string &name, CBotVar* pThis, +bool CBotFunction::RestoreCall(long &nIdent, const std::string &name, CBotVar* pThis, CBotVar** ppVars, CBotStack* pStack, CBotClass* pClass) { CBotTypResult type; - CBotFunction* pt = FindLocalOrPublic(localFunctionList, nIdent, name, ppVars, type); + CBotFunction* pt = FindMethod(nIdent, name, ppVars, type, pClass, pStack->GetProgram()); if ( pt != nullptr ) { @@ -1020,6 +1152,12 @@ std::string CBotFunction::GetParams() return params; } +//////////////////////////////////////////////////////////////////////////////// +const std::string& CBotFunction::GetClassName() +{ + return m_MasterClass; +} + //////////////////////////////////////////////////////////////////////////////// void CBotFunction::AddPublic(CBotFunction* func) { diff --git a/src/CBot/CBotInstr/CBotFunction.h b/src/CBot/CBotInstr/CBotFunction.h index b16780cd..da8b0b2d 100644 --- a/src/CBot/CBotInstr/CBotFunction.h +++ b/src/CBot/CBotInstr/CBotFunction.h @@ -105,21 +105,19 @@ public: * * See FindLocalOrPublic for more detailed explanation * - * \param localFunctionList Linked list of local functions to search in, can be null * \param name Name of the function * \param ppVars List of function arguments * \param nIdent[in, out] Unique identifier of the function + * \param program The current program, to search for functions. * \return Type returned by the function or error code * \see FindLocalOrPublic */ - static CBotTypResult CompileCall(const std::list& localFunctionList, - const std::string &name, CBotVar** ppVars, long &nIdent); + static CBotTypResult CompileCall(const std::string &name, CBotVar** ppVars, + long &nIdent, CBotProgram* program); /*! * \brief Finds a local or public function * - *

Finds a local or (if bPublic is true) public function to call - * *

First, it looks for a function according to its unique identifier.
* If the identifier is not found, looks by name and parameters. * @@ -128,11 +126,46 @@ public: * \param name Name of the function * \param ppVars List of function arguments * \param TypeOrError Type returned by the function or error code - * \param bPublic Whether to look in public functions or not + * \param baseProg Initial program, for context of the object/bot * \return Pointer to found CBotFunction instance, or nullptr in case of no match or ambiguity (see TypeOrError for error code) */ static CBotFunction* FindLocalOrPublic(const std::list& localFunctionList, long &nIdent, const std::string &name, - CBotVar** ppVars, CBotTypResult &TypeOrError, bool bPublic = true); + CBotVar** ppVars, CBotTypResult &TypeOrError, CBotProgram* baseProg); + + /*! + * \brief Find all functions that match the name and arguments. + * \param functionList List of functions to search, can be empty. + * \param name Name of the function to find. + * \param ppVars Arguments to compare with parameters. + * \param TypeOrError Contains a CBotError when no useable function has been found. + * \param funcMap Container for suitable functions and their signature values. + * \param pClass Pointer to class when searching for methods. + */ + static void SearchList(const std::list& functionList, + const std::string& name, CBotVar** ppVars, CBotTypResult& TypeOrError, + std::map& funcMap, CBotClass* pClass = nullptr); + + /*! + * \brief Find all public functions that match the name and arguments. + * \param name Name of the function to find. + * \param ppVars Arguments to compare with parameters. + * \param TypeOrError Contains a CBotError when no useable function has been found. + * \param funcMap Container for suitable functions and their signature values. + * \param pClass Pointer to class when searching for methods. + */ + static void SearchPublic(const std::string& name, CBotVar** ppVars, CBotTypResult& TypeOrError, + std::map& funcMap, CBotClass* pClass = nullptr); + + /*! + * \brief Find the function with the lowest signature value. If there is more + * than one of the same signature value, TypeOrError is set to CBotErrAmbiguousCall. + * \param funcMap List of functions and their signature values, can be empty. + * \param[out] nIdent Unique identifier of the function. + * \param TypeOrError Type returned by the function or error code. + * \return Pointer to the function with the lowest signature or nullptr. + */ + static CBotFunction* BestFunction(std::map& funcMap, + long& nIdent, CBotTypResult& TypeOrError); /*! * \brief DoCall Fait un appel à une fonction. @@ -160,10 +193,34 @@ public: static void RestoreCall(const std::list& localFunctionList, long &nIdent, const std::string &name, CBotVar** ppVars, CBotStack* pStack); + /*! + * \brief Find a method matching the name and arguments. + * \param name Name of the method to find. + * \param ppVars Arguments to compare with parameters. + * \param[out] nIdent Unique identifier of the method. + * \param pStack Current compilation stack frame. + * \param pClass Pointer to the class. + * \return The return type for the method or a CBotError. + */ + static CBotTypResult CompileMethodCall(const std::string& name, CBotVar** ppVars, + long& nIdent, CBotCStack* pStack, CBotClass* pClass); + + /*! + * \brief Find a method by its unique identifier or by name and parameters. + * \param[in,out] nIdent Unique identifier of the method. + * \param name Name of the method to find. + * \param ppVars Arguments to compare with parameters. + * \param TypeOrError The return type for the method or a CBotError. + * \param pClass Pointer to the class. + * \param program The current program, to search for out-of-class methods. + * \return Pointer to the method that best matches the given arguments or nullptr. + */ + static CBotFunction* FindMethod(long& nIdent, const std::string& name, + CBotVar** ppVars, CBotTypResult& TypeOrError, + CBotClass* pClass, CBotProgram* program); + /*! * \brief DoCall Makes call of a method - * note: this is already on the stack, the pointer pThis is just to simplify. - * \param localFunctionList * \param nIdent * \param name * \param pThis @@ -173,12 +230,11 @@ public: * \param pClass * \return */ - static int DoCall(const std::list& localFunctionList, long &nIdent, const std::string &name, CBotVar* pThis, + static int DoCall(long &nIdent, const std::string &name, CBotVar* pThis, CBotVar** ppVars, CBotStack* pStack, CBotToken* pToken, CBotClass* pClass); /*! * \brief RestoreCall - * \param localFunctionList * \param nIdent * \param name * \param pThis @@ -187,7 +243,7 @@ public: * \param pClass * \return Returns true if the method call was restored. */ - static bool RestoreCall(const std::list& localFunctionList, long &nIdent, const std::string &name, CBotVar* pThis, + static bool RestoreCall(long &nIdent, const std::string &name, CBotVar* pThis, CBotVar** ppVars, CBotStack* pStack, CBotClass* pClass); /*! @@ -215,6 +271,12 @@ public: */ std::string GetParams(); + /*! + * \brief Get the name of the class for a method. + * \return The name of a class or empty string if it's not a method. + */ + const std::string& GetClassName(); + /*! * \brief IsPublic * \return diff --git a/test/unit/CBot/CBot_test.cpp b/test/unit/CBot/CBot_test.cpp index 78009ffc..701dbf2f 100644 --- a/test/unit/CBot/CBot_test.cpp +++ b/test/unit/CBot/CBot_test.cpp @@ -2768,3 +2768,303 @@ TEST_F(CBotUT, ParametersWithDefaultValues) "}\n" ); } + +TEST_F(CBotUT, ClassMethodsOutOfClass_Issue207) +{ + auto publicProgram = ExecuteTest( + "public class OtherClass {}\n" + "public class TestClass {}\n" + ); + + ExecuteTest( + "extern void TestCallWithoutObject()\n" + "{\n" + " TestMethod();\n" + "}\n" + "public void TestClass::TestMethod() {}\n", + CBotErrUndefCall + ); + + ExecuteTest( + "extern void TestCallWithWrongObject()\n" + "{\n" + " OtherClass oc();\n" + " oc.TestMethod();\n" + "}\n" + "public void TestClass::TestMethod() {}\n", + CBotErrUndefCall + ); + + ExecuteTest( + "extern void OtherClass::TestCallWithWrongThis()\n" + "{\n" + " this.TestMethod();\n" + "}\n" + "public void TestClass::TestMethod() {}\n", + CBotErrUndefCall + ); +} + +TEST_F(CBotUT, ClassMethodsOutOfClass) +{ + auto publicProgram = ExecuteTest( + "public class TestClass\n" + "{\n" + " int i = 123, j = 456, k = 789;\n" + " int InsideClass()\n" + " {\n" + " ASSERT(456 == PublicMethod());\n" + " ASSERT(789 == NotPublicMethod());\n" + " return this.i;\n" + " }\n" + "}\n" + "extern void TestMethodsOutOfClass()\n" + "{\n" + " TestClass tc();\n" + " ASSERT(123 == tc.InsideClass());\n" + " ASSERT(456 == tc.PublicMethod());\n" + " ASSERT(789 == tc.NotPublicMethod());\n" + "}\n" + "public int TestClass::PublicMethod()\n" + "{\n" + " return this.j;\n" + "}\n" + "int TestClass::NotPublicMethod()\n" + "{\n" + " return k;\n" + "}\n" + ); + + ExecuteTest( + "extern void TestFromOtherProgram()\n" + "{\n" + " TestClass tc();\n" + " ASSERT(123 == tc.InsideClass());\n" + " ASSERT(456 == tc.PublicMethod());\n" + " ASSERT(789 == tc.MethodInThisProgram());\n" + "}\n" + "int TestClass::MethodInThisProgram()\n" + "{\n" + " ASSERT(123 == InsideClass());\n" + " ASSERT(456 == PublicMethod());\n" + " ASSERT(123 == this.InsideClass());\n" + " ASSERT(456 == this.PublicMethod());\n" + " ASSERT(i == 123 && this.j == 456);\n" + " return this.k;\n" + "}\n" + ); +} + +TEST_F(CBotUT, ClassInheritanceMethodsOutOfClass) +{ + ExecuteTest( + "public class BaseClass {\n" + " int a = 123;\n" + " int b = 456;\n" + " int c = 789;\n" + "}\n" + "int BaseClass::testOverride() { return 123; }\n" + "int BaseClass::testNoOverride() { return 456; }\n" + "int BaseClass::testInsideBaseClass() {\n" + " ASSERT(a == 123);\n" + " ASSERT(b == 456);\n" + " ASSERT(c == 789);\n" + " ASSERT(456 == testNoOverride());\n" + " return c;\n" + "}\n" + "int BaseClass::testInsideBaseOverride() { return testOverride(); }\n" + "\n" + "public class MidClass extends BaseClass {\n" + " int b = 1011;\n" + " int c = 1213;\n" + " int d = 1415;\n" + "}\n" + "int MidClass::testOverride() { return 1011; }\n" + "int MidClass::testInsideMidClass() {\n" + " ASSERT(a == 123);\n" + " ASSERT(b == 1011);\n" + " ASSERT(c == 1213);\n" + " ASSERT(d == 1415);\n" + " ASSERT(456 == testNoOverride());\n" + " ASSERT(789 == testInsideBaseClass());\n" + " return c;\n" + "}\n" + "int MidClass::testSuper() {\n" + " ASSERT(super.a == 123);\n" + " ASSERT(super.b == 456);\n" + " ASSERT(super.c == 789);\n" + " ASSERT(123 == super.testOverride());\n" + " ASSERT(789 == super.testInsideBaseClass());\n" + " return super.testInsideBaseOverride();\n" + "}\n" + "int MidClass::testInsideMidOverride() { return testOverride(); }\n" + "\n" + "public class SubClass extends MidClass {\n" + " int c = 1617;\n" + " int d = 1819;\n" + " int e = 2021;\n" + "}\n" + "int SubClass::testOverride() { return 1617; }\n" + "int SubClass::testInsideSubClass() {\n" + " ASSERT(a == 123);\n" + " ASSERT(b == 1011);\n" + " ASSERT(c == 1617);\n" + " ASSERT(d == 1819);\n" + " ASSERT(e == 2021);\n" + " ASSERT(456 == testNoOverride());\n" + " ASSERT(789 == testInsideBaseClass());\n" + " ASSERT(1213 == testInsideMidClass());\n" + " return c;\n" + "}\n" + "int SubClass::testSuper() {\n" + " ASSERT(super.a == 123);\n" + " ASSERT(super.b == 1011);\n" + " ASSERT(super.c == 1213);\n" + " ASSERT(super.d == 1415);\n" + " ASSERT(1011 == super.testOverride());\n" + " ASSERT(789 == super.testInsideBaseClass());\n" + " ASSERT(1213 == super.testInsideMidClass());\n" + " return super.testSuper();\n" + "}\n" + "int SubClass::testInsideSubOverride() { return testOverride(); }\n" + "\n" + "extern void InheritanceMethodsOutOfClass()\n" + "{\n" + " BaseClass bc();\n" + " ASSERT(123 == bc.testOverride());\n" + " ASSERT(456 == bc.testNoOverride());\n" + " ASSERT(789 == bc.testInsideBaseClass());\n" + " ASSERT(123 == bc.testInsideBaseOverride());\n" + " MidClass mc();\n" + " ASSERT(1011 == mc.testSuper());\n" + " ASSERT(1011 == mc.testOverride());\n" + " ASSERT(456 == mc.testNoOverride());\n" + " ASSERT(789 == mc.testInsideBaseClass());\n" + " ASSERT(1213 == mc.testInsideMidClass());\n" + " ASSERT(1011 == mc.testInsideBaseOverride());\n" + " ASSERT(1011 == mc.testInsideMidOverride());\n" + " SubClass sc();\n" + " ASSERT(1617 == sc.testSuper());\n" + " ASSERT(1617 == sc.testOverride());\n" + " ASSERT(456 == sc.testNoOverride());\n" + " ASSERT(789 == sc.testInsideBaseClass());\n" + " ASSERT(1213 == sc.testInsideMidClass());\n" + " ASSERT(1617 == sc.testInsideSubClass());\n" + " ASSERT(1617 == sc.testInsideBaseOverride());\n" + " ASSERT(1617 == sc.testInsideMidOverride());\n" + " ASSERT(1617 == sc.testInsideSubOverride());\n" + // Test polymorphism + " bc = mc;\n" + " ASSERT(1011 == bc.testOverride());\n" + " ASSERT(789 == bc.testInsideBaseClass());\n" + " ASSERT(1011 == bc.testInsideBaseOverride());\n" + " bc = sc;\n" + " ASSERT(1617 == bc.testOverride());\n" + " ASSERT(789 == bc.testInsideBaseClass());\n" + " ASSERT(1617 == bc.testInsideBaseOverride());\n" + " mc = sc;\n" + " ASSERT(1617 == mc.testSuper());\n" + " ASSERT(1617 == mc.testOverride());\n" + " ASSERT(789 == mc.testInsideBaseClass());\n" + " ASSERT(1213 == mc.testInsideMidClass());\n" + " ASSERT(1617 == mc.testInsideBaseOverride());\n" + " ASSERT(1617 == mc.testInsideMidOverride());\n" + "}\n" + ); +} + +TEST_F(CBotUT, ClassInheritanceTestThisOutOfClass) +{ + ExecuteTest( + "public class BaseClass {\n" + " int a = 123;\n" + " int b = 456;\n" + " int c = 789;\n" + "}\n" + "void BaseClass::testBaseMembersAndParams(int a, int b, int c) {\n" + " ASSERT(a != 123);\n" + " ASSERT(b != 456);\n" + " ASSERT(c != 789);\n" + " ASSERT(this.a == 123);\n" + " ASSERT(this.b == 456);\n" + " ASSERT(this.c == 789);\n" + "}\n" + "BaseClass BaseClass::testSuperReturnThis(){ return this; }\n" + "BaseClass BaseClass::testReturnThisFromBaseClass() { return this; }\n" + "\n" + "public class MidClass extends BaseClass {\n" + " int b = 1011;\n" + " int c = 1213;\n" + " int d = 1415;\n" + "}\n" + "void MidClass::testMidMembersAndParams(int a, int b, int c, int d) {\n" + " ASSERT(a != 123);\n" + " ASSERT(b != 1011);\n" + " ASSERT(c != 1213);\n" + " ASSERT(d != 1415);\n" + " ASSERT(this.a == 123);\n" + " ASSERT(this.b == 1011);\n" + " ASSERT(this.c == 1213);\n" + " ASSERT(this.d == 1415);\n" + "}\n" + "MidClass MidClass::testSuperReturnThis(){ return super.testSuperReturnThis(); }\n" + "MidClass MidClass::testReturnThisFromMidClass() { return this; }\n" + "\n" + "public class SubClass extends MidClass {\n" + " int c = 1617;\n" + " int d = 1819;\n" + " int e = 2021;\n" + "}\n" + "void SubClass::testSubMembersAndParams(int a, int b, int c, int d, int e) {\n" + " ASSERT(a != 123);\n" + " ASSERT(b != 1011);\n" + " ASSERT(c != 1617);\n" + " ASSERT(d != 1819);\n" + " ASSERT(e != 2021);\n" + " ASSERT(this.a == 123);\n" + " ASSERT(this.b == 1011);\n" + " ASSERT(this.c == 1617);\n" + " ASSERT(this.d == 1819);\n" + " ASSERT(this.e == 2021);\n" + "}\n" + "SubClass SubClass::testSuperReturnThis(){ return super.testSuperReturnThis(); }\n" + "SubClass SubClass::testReturnThisFromSubClass() { return this; }\n" + "\n" + "extern void ClassInheritanceTestThisOutOfClass()\n" + "{\n" + " BaseClass bc();\n" + " MidClass mc();\n" + " SubClass sc();\n" + " ASSERT(bc == bc.testSuperReturnThis());\n" + " ASSERT(bc == bc.testReturnThisFromBaseClass());\n" + " bc.testBaseMembersAndParams(-1, -2, -3);\n" + " ASSERT(mc == mc.testSuperReturnThis());\n" + " ASSERT(mc == mc.testReturnThisFromBaseClass());\n" + " ASSERT(mc == mc.testReturnThisFromMidClass());\n" + " mc.testBaseMembersAndParams(-1, -2, -3);\n" + " mc.testMidMembersAndParams(-1, -2, -3, -4);\n" + " ASSERT(sc == sc.testSuperReturnThis());\n" + " ASSERT(sc == sc.testReturnThisFromBaseClass());\n" + " ASSERT(sc == sc.testReturnThisFromMidClass());\n" + " ASSERT(sc == sc.testReturnThisFromSubClass());\n" + " sc.testBaseMembersAndParams(-1, -2, -3);\n" + " sc.testMidMembersAndParams(-1, -2, -3, -4);\n" + " sc.testSubMembersAndParams(-1, -2, -3, -4, -5);\n" + // Test polymorphism + " bc = mc;\n" + " ASSERT(mc == bc.testSuperReturnThis());\n" + " ASSERT(mc == bc.testReturnThisFromBaseClass());\n" + " bc.testBaseMembersAndParams(-1, -2, -3);\n" + " bc = sc;\n" + " ASSERT(sc == bc.testSuperReturnThis());\n" + " ASSERT(sc == bc.testReturnThisFromBaseClass());\n" + " bc.testBaseMembersAndParams(-1, -2, -3);\n" + " mc = sc;\n" + " ASSERT(sc == mc.testSuperReturnThis());\n" + " ASSERT(sc == mc.testReturnThisFromBaseClass());\n" + " ASSERT(sc == mc.testReturnThisFromMidClass());\n" + " mc.testBaseMembersAndParams(-1, -2, -3);\n" + " mc.testMidMembersAndParams(-1, -2, -3, -4);\n" + "}\n" + ); +}