/* * This file is part of the Colobot: Gold Edition source code * Copyright (C) 2001-2023, Daniel Roux, EPSITEC SA & TerranovaTeam * http://epsitec.ch; http://colobot.info; http://github.com/colobot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://gnu.org/licenses */ #include "CBot/CBotClass.h" #include "CBot/CBotInstr/CBotInstrUtils.h" #include "CBot/CBotInstr/CBotNew.h" #include "CBot/CBotInstr/CBotLeftExprVar.h" #include "CBot/CBotInstr/CBotExprLitNull.h" #include "CBot/CBotInstr/CBotTwoOpExpr.h" #include "CBot/CBotInstr/CBotFunction.h" #include "CBot/CBotInstr/CBotExpression.h" #include "CBot/CBotInstr/CBotListArray.h" #include "CBot/CBotInstr/CBotEmpty.h" #include "CBot/CBotVar/CBotVar.h" #include "CBot/CBotExternalCall.h" #include "CBot/CBotStack.h" #include "CBot/CBotCStack.h" #include "CBot/CBotDefParam.h" #include "CBot/CBotUtils.h" #include namespace CBot { //////////////////////////////////////////////////////////////////////////////// std::set CBotClass::m_publicClasses{}; //////////////////////////////////////////////////////////////////////////////// CBotClass::CBotClass(const std::string& name, CBotClass* parent, bool bIntrinsic) { m_parent = parent; m_name = name; m_pVar = nullptr; m_externalMethods = new CBotExternalCallList(); m_rUpdate = nullptr; m_IsDef = true; m_bIntrinsic= bIntrinsic; m_nbVar = m_parent == nullptr ? 0 : m_parent->m_nbVar; m_publicClasses.insert(this); } //////////////////////////////////////////////////////////////////////////////// CBotClass::~CBotClass() { m_publicClasses.erase(this); delete m_pVar; delete m_externalMethods; } //////////////////////////////////////////////////////////////////////////////// CBotClass* CBotClass::Create(const std::string& name, CBotClass* parent, bool intrinsic) { return new CBotClass(name, parent, intrinsic); } //////////////////////////////////////////////////////////////////////////////// void CBotClass::ClearPublic() { while ( !m_publicClasses.empty() ) { auto it = m_publicClasses.begin(); delete *it; // calling destructor removes the class from the list } } //////////////////////////////////////////////////////////////////////////////// void CBotClass::Purge() { delete m_pVar; m_pVar = nullptr; m_externalMethods->Clear(); for (CBotFunction* f : m_pMethod) delete f; m_pMethod.clear(); m_IsDef = false; m_nbVar = m_parent == nullptr ? 0 : m_parent->m_nbVar; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::Lock(CBotProgram* prog) { if (m_lockProg.size() == 0) { m_lockCurrentCount = 1; m_lockProg.push_back(prog); return true; } if (prog == m_lockProg[0]) { m_lockCurrentCount++; return true; } if (std::find(m_lockProg.begin(), m_lockProg.end(), prog) != m_lockProg.end()) { return false; // already pending } m_lockProg.push_back(prog); return false; } //////////////////////////////////////////////////////////////////////////////// void CBotClass::Unlock() { if (--m_lockCurrentCount > 0) return; // if called Lock() multiple times, wait for all to unlock m_lockProg.pop_front(); } //////////////////////////////////////////////////////////////////////////////// void CBotClass::FreeLock(CBotProgram* prog) { for (CBotClass* pClass : m_publicClasses) { if (pClass->m_lockProg.size() > 0 && prog == pClass->m_lockProg[0]) { pClass->m_lockCurrentCount = 0; } // Note: erasing an end iterator is undefined behaviour auto it = std::remove(pClass->m_lockProg.begin(), pClass->m_lockProg.end(), prog); if (it != pClass->m_lockProg.end()) { pClass->m_lockProg.erase(it); } } } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::AddItem(std::string name, CBotTypResult type, CBotVar::ProtectionLevel mPrivate) { CBotClass* pClass = type.GetClass(); CBotVar* pVar = CBotVar::Create( name, type ); /// pVar->SetUniqNum(CBotVar::NextUniqNum()); pVar->SetPrivate( mPrivate ); if ( pClass != nullptr ) { // pVar->SetClass(pClass); if ( type.Eq(CBotTypClass) ) { // adds a new statement for the object initialization pVar->m_InitExpr = new CBotNew() ; CBotToken nom( pClass->GetName() ); pVar->m_InitExpr->SetToken(&nom); } } return AddItem( pVar ); } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::AddItem(CBotVar* pVar) { pVar->SetUniqNum(++m_nbVar); if ( m_pVar == nullptr ) m_pVar = pVar; else m_pVar->AddNext(pVar); return true; } //////////////////////////////////////////////////////////////////////////////// const std::string& CBotClass::GetName() { return m_name; } //////////////////////////////////////////////////////////////////////////////// CBotClass* CBotClass::GetParent() { return m_parent; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::IsChildOf(CBotClass* pClass) { CBotClass* p = this; while ( p != nullptr ) { if ( p == pClass ) return true; p = p->m_parent; } return false; } //////////////////////////////////////////////////////////////////////////////// CBotVar* CBotClass::GetVar() { return m_pVar; } //////////////////////////////////////////////////////////////////////////////// CBotVar* CBotClass::GetItem(const std::string& name) { CBotVar* p = m_pVar; while ( p != nullptr ) { if ( p->GetName() == name ) return p; p = p->GetNext(); } if (m_parent != nullptr ) return m_parent->GetItem(name); return nullptr; } //////////////////////////////////////////////////////////////////////////////// CBotVar* CBotClass::GetItemRef(int nIdent) { CBotVar* p = m_pVar; while ( p != nullptr ) { if ( p->GetUniqNum() == nIdent ) return p; p = p->GetNext(); } if (m_parent != nullptr ) return m_parent->GetItemRef(nIdent); return nullptr; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::CheckVar(const std::string &name) { CBotVar* p = m_pVar; while ( p != nullptr ) { if ( p->GetName() == name ) return true; p = p->GetNext(); } return false; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::IsIntrinsic() { return m_bIntrinsic; } //////////////////////////////////////////////////////////////////////////////// CBotClass* CBotClass::Find(CBotToken* &pToken) { return Find(pToken->GetString()); } //////////////////////////////////////////////////////////////////////////////// CBotClass* CBotClass::Find(const std::string& name) { for (CBotClass* p : m_publicClasses) { if ( p->GetName() == name ) return p; } return nullptr; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::AddFunction(const std::string& name, bool rExec(CBotVar* pThis, CBotVar* pVar, CBotVar* pResult, int& Exception, void* user), CBotTypResult rCompile(CBotVar* pThis, CBotVar*& pVar)) { return m_externalMethods->AddFunction(name, std::unique_ptr(new CBotExternalCallClass(rExec, rCompile))); } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::SetUpdateFunc(void rUpdate(CBotVar* thisVar, void* user)) { m_rUpdate = rUpdate; return true; } //////////////////////////////////////////////////////////////////////////////// CBotTypResult CBotClass::CompileMethode(CBotToken* name, CBotVar* pThis, CBotVar** ppParams, CBotCStack* pStack, long &nIdent) { nIdent = 0; // forget the previous one if necessary // find the methods declared by AddFunction CBotTypResult r = m_externalMethods->CompileCall(name, pThis, ppParams, pStack); if ( r.GetType() >= 0) return r; // find the methods declared by user 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; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::ExecuteMethode(long& nIdent, CBotVar* pThis, CBotVar** ppParams, CBotTypResult pResultType, CBotStack*& pStack, CBotToken* pToken) { int ret = m_externalMethods->DoCall(pToken, pThis, ppParams, pStack, pResultType); if (ret >= 0) return ret; ret = CBotFunction::DoCall(nIdent, pToken->GetString(), pThis, ppParams, pStack, pToken, this); if (ret >= 0) return ret; if (m_parent != nullptr) { ret = m_parent->ExecuteMethode(nIdent, pThis, ppParams, pResultType, pStack, pToken); } return ret; } //////////////////////////////////////////////////////////////////////////////// void CBotClass::RestoreMethode(long& nIdent, CBotToken* name, CBotVar* pThis, CBotVar** ppParams, CBotStack*& pStack) { if (m_externalMethods->RestoreCall(name, pThis, ppParams, pStack)) return; CBotClass* pClass = this; while (pClass != nullptr) { bool ok = CBotFunction::RestoreCall(nIdent, name->GetString(), pThis, ppParams, pStack, pClass); if (ok) return; pClass = pClass->m_parent; } assert(false); } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::SaveStaticState(std::ostream &ostr) { if (!WriteLong(ostr, CBOTVERSION*2)) return false; // saves the state of static variables in classes for (CBotClass* p : m_publicClasses) { if (!WriteWord(ostr, 1)) return false; // save the name of the class if (!WriteString(ostr, p->GetName())) return false; CBotVar* pv = p->GetVar(); while( pv != nullptr ) { if ( pv->IsStatic() ) { if (!WriteWord(ostr, 1)) return false; if (!WriteString(ostr, pv->GetName())) return false; if (!pv->Save0State(ostr)) return false; // common header if (!pv->Save1State(ostr)) return false; // saves as the child class if (!WriteWord(ostr, 0)) return false; } pv = pv->GetNext(); } if (!WriteWord(ostr, 0)) return false; } if (!WriteWord(ostr, 0)) return false; return true; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::RestoreStaticState(std::istream &istr) { std::string ClassName, VarName; CBotClass* pClass; unsigned short w; long version; if (!ReadLong(istr, version)) return false; if (version != CBOTVERSION*2) return false; while (true) { if (!ReadWord(istr, w)) return false; if ( w == 0 ) return true; if (!ReadString(istr, ClassName)) return false; pClass = Find(ClassName); while (true) { if (!ReadWord(istr, w)) return false; if ( w == 0 ) break; CBotVar* pVar = nullptr; CBotVar* pv = nullptr; if (!ReadString(istr, VarName)) return false; if ( pClass != nullptr ) pVar = pClass->GetItem(VarName); if (!CBotVar::RestoreState(istr, pv)) return false; // the temp variable if ( pVar != nullptr ) pVar->Copy(pv); delete pv; } } return true; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::CheckCall(CBotProgram* program, CBotDefParam* pParam, CBotToken*& pToken) { std::string name = pToken->GetString(); if ( program->GetExternalCalls()->CheckCall(name) ) return true; for (CBotFunction* pp : m_pMethod) { if ( pToken->GetString() == pp->GetName() ) { // are their parameters exactly the same? if ( pp->CheckParam( pParam ) ) return true; } } return false; } //////////////////////////////////////////////////////////////////////////////// CBotClass* CBotClass::Compile1(CBotToken* &p, CBotCStack* pStack) { if ( !IsOfType(p, ID_PUBLIC) ) { pStack->SetError(CBotErrNoPublic, p); return nullptr; } if ( !IsOfType(p, ID_CLASS) ) return nullptr; std::string name = p->GetString(); // a name of the class is there? if (IsOfType(p, TokenTypVar)) { CBotClass* pOld = CBotClass::Find(name); if ((pOld != nullptr && pOld->m_IsDef) || /* public class exists in different program */ pStack->GetProgram()->ClassExists(name)) /* class exists in this program */ { pStack->SetError(CBotErrRedefClass, p->GetPrev()); return nullptr; } CBotClass* pPapa = nullptr; if ( IsOfType( p, ID_EXTENDS ) ) { std::string name = p->GetString(); pPapa = CBotClass::Find(name); CBotToken* pp = p; if (!IsOfType(p, TokenTypVar) || pPapa == nullptr ) { pStack->SetError(CBotErrNoClassName, pp); return nullptr; } } CBotClass* classe = (pOld == nullptr) ? new CBotClass(name, pPapa) : pOld; classe->Purge(); // empty the old definitions classe->m_IsDef = false; // current definition classe->m_pOpenblk = p; if ( !IsOfType( p, ID_OPBLK) ) { pStack->SetError(CBotErrOpenBlock, p); return nullptr; } int level = 1; while (level > 0 && p != nullptr) { int type = p->GetType(); p = p->GetNext(); if (type == ID_OPBLK) level++; if (type == ID_CLBLK) level--; } if (level > 0) pStack->SetError(CBotErrCloseBlock, classe->m_pOpenblk); if (pStack->IsOk()) return classe; } else pStack->SetError(CBotErrNoClassName, p); pStack->SetError(CBotErrNoTerminator, p); return nullptr; } //////////////////////////////////////////////////////////////////////////////// void CBotClass::DefineClasses(std::list pClassList, CBotCStack* pStack) { for (CBotClass* pClass : pClassList) { CBotClass* pParent = pClass->m_parent; pClass->m_nbVar = (pParent == nullptr) ? 0 : pParent->m_nbVar; CBotToken* p = pClass->m_pOpenblk->GetNext(); while (pStack->IsOk() && !IsOfType(p, ID_CLBLK)) { pClass->CompileDefItem(p, pStack, false); } if (!pStack->IsOk()) return; } } const std::list& CBotClass::GetFunctions() const { return m_pMethod; } //////////////////////////////////////////////////////////////////////////////// bool CBotClass::CompileDefItem(CBotToken* &p, CBotCStack* pStack, bool bSecond) { bool bStatic = false; CBotVar::ProtectionLevel mProtect = CBotVar::ProtectionLevel::Public; bool bSynchro = false; while (IsOfType(p, ID_SEP)) ; CBotTypResult type( -1 ); if ( IsOfType(p, ID_SYNCHO) ) bSynchro = true; CBotToken* pBase = p; if ( IsOfType(p, ID_STATIC) ) bStatic = true; if ( IsOfType(p, ID_PUBLIC) ) mProtect = CBotVar::ProtectionLevel::Public; if ( IsOfType(p, ID_PRIVATE) ) mProtect = CBotVar::ProtectionLevel::Private; if ( IsOfType(p, ID_PROTECTED) ) mProtect = CBotVar::ProtectionLevel::Protected; if ( IsOfType(p, ID_STATIC) ) bStatic = true; // CBotClass* pClass = nullptr; type = TypeParam(p, pStack); // type of the result if ( type.Eq(-1) ) { pStack->SetError(CBotErrNoType, p); return false; } while (pStack->IsOk()) { CBotTypResult type2 = CBotTypResult(type); // reset type after comma CBotToken* varToken = p; std::string pp = p->GetString(); if ( IsOfType(p, ID_NOT) ) { pp = std::string("~") + p->GetString(); } if (IsOfType(p, TokenTypVar)) { if ( p->GetType() == ID_OPENPAR ) { if ( !bSecond ) { p = pBase; CBotFunction* f = CBotFunction::Compile1(p, pStack, this); if ( f == nullptr ) return false; m_pMethod.push_back(f); } else { // return a method precompiled in pass 1 CBotCStack* pStk = pStack->TokenStack(nullptr, true); CBotDefParam* params = CBotDefParam::Compile(p, pStk ); pStack->DeleteNext(); auto pfIter = std::find_if(m_pMethod.begin(), m_pMethod.end(), [&pp, ¶ms](CBotFunction* x) { return x->GetName() == pp && x->CheckParam( params ); }); assert(pfIter != m_pMethod.end()); CBotFunction* pf = *pfIter; delete params; CBotCStack* pile = pStack->TokenStack(nullptr, true); // compiles a method p = pBase; CBotFunction* f = CBotFunction::Compile(p, pile, pf/*, false*/); if ( f != nullptr ) { f->m_pProg = pStack->GetProgram(); f->m_bSynchro = bSynchro; } pStack->DeleteNext(); } return pStack->IsOk(); } // definition of an element if (type.Eq(0)) { pStack->SetError(CBotErrNoTerminator, p); return false; } if (pp[0] == '~' || pp == GetName()) // bad variable name { pStack->SetError(CBotErrNoVar, varToken); return false; } if (!bSecond && CheckVar(pp)) // variable already exists { pStack->SetError(CBotErrRedefVar, varToken); return false; } CBotInstr* limites = nullptr; while ( IsOfType( p, ID_OPBRK ) ) // an array { CBotInstr* i = nullptr; pStack->SetStartError( p->GetStart() ); if ( p->GetType() != ID_CLBRK ) { i = CBotExpression::Compile( p, pStack ); // expression for the value if (i == nullptr || pStack->GetType() != CBotTypInt) // must be a number { pStack->SetError(CBotErrBadIndex, p->GetStart()); } } else i = new CBotEmpty(); // special if not a formula type2 = CBotTypResult(CBotTypArrayPointer, type2); if (limites == nullptr) limites = i; else limites->AddNext3(i); if (pStack->IsOk() && IsOfType(p, ID_CLBRK)) continue; pStack->SetError(CBotErrCloseIndex, p->GetStart()); delete limites; return false; } CBotInstr* i = nullptr; if ( IsOfType(p, ID_ASS ) ) { pStack->SetStartError(p->GetStart()); if ( IsOfType(p, ID_SEP) ) { pStack->SetError(CBotErrNoExpression, p->GetStart()); return false; } if ( type2.Eq(CBotTypArrayPointer) ) { if ( nullptr == (i = CBotListArray::Compile(p, pStack, type2.GetTypElem())) ) { if (pStack->IsOk()) { i = CBotTwoOpExpr::Compile(p, pStack); if (i == nullptr || !pStack->GetTypResult().Compare(type2)) { pStack->SetError(CBotErrBadType1, p->GetStart()); return false; } } } } else { // it has an assignmet to calculate i = CBotTwoOpExpr::Compile(p, pStack); if ( !(type.Eq(CBotTypPointer) && pStack->GetTypResult().Eq(CBotTypNullPointer)) && !TypesCompatibles( type2, pStack->GetTypResult()) ) { pStack->SetError(CBotErrBadType1, p->GetStart()); return false; } } if ( !pStack->IsOk() ) return false; } else if ( type2.Eq(CBotTypArrayPointer) ) i = new CBotExprLitNull(); if ( !bSecond ) { CBotVar* pv = CBotVar::Create(pp, type2); pv -> SetStatic( bStatic ); pv -> SetPrivate( mProtect ); AddItem( pv ); pv->m_InitExpr = i; pv->m_LimExpr = limites; if ( pv->IsStatic() && pv->m_InitExpr != nullptr ) { CBotStack* pile = CBotStack::AllocateStack(); // independent stack if ( type2.Eq(CBotTypArrayPointer) ) { while(pile->IsOk() && !pv->m_InitExpr->Execute(pile, pv)); } else { while(pile->IsOk() && !pv->m_InitExpr->Execute(pile)); // evaluates the expression without timer pv->SetVal( pile->GetVar() ) ; } pile->Delete(); } } else { delete i; delete limites; } if ( IsOfType(p, ID_COMMA) ) continue; if ( IsOfType(p, ID_SEP) ) break; } pStack->SetError(CBotErrNoTerminator, p); } return pStack->IsOk(); } //////////////////////////////////////////////////////////////////////////////// CBotClass* CBotClass::Compile(CBotToken* &p, CBotCStack* pStack) { if ( !IsOfType(p, ID_PUBLIC) ) return nullptr; if ( !IsOfType(p, ID_CLASS) ) return nullptr; std::string name = p->GetString(); // a name for the class is there? if (IsOfType(p, TokenTypVar)) { // the class was created by Compile1 CBotClass* pOld = CBotClass::Find(name); if ( IsOfType( p, ID_EXTENDS ) ) { // TODO: Not sure how correct is that - I have no idea how the precompilation (Compile1 method) works ~krzys_h std::string name = p->GetString(); CBotClass* pPapa = CBotClass::Find(name); CBotToken* pp = p; if (!IsOfType(p, TokenTypVar) || pPapa == nullptr) { pStack->SetError(CBotErrNoClassName, pp); return nullptr; } pOld->m_parent = pPapa; } else { if (pOld != nullptr) { pOld->m_parent = nullptr; } } IsOfType( p, ID_OPBLK); // necessarily while ( pStack->IsOk() && !IsOfType( p, ID_CLBLK ) ) { pOld->CompileDefItem(p, pStack, true); } pOld->m_IsDef = true; // complete definition if (pStack->IsOk()) return pOld; } pStack->SetError(CBotErrNoTerminator, p); return nullptr; } void CBotClass::Update(CBotVar* var, void* user) { m_rUpdate(var, user); } } // namespace CBot