diff --git a/src/CBot/CBotInstr/CBotDefClass.cpp b/src/CBot/CBotInstr/CBotDefClass.cpp index bfd0dedf..cd73d9cc 100644 --- a/src/CBot/CBotInstr/CBotDefClass.cpp +++ b/src/CBot/CBotInstr/CBotDefClass.cpp @@ -19,6 +19,7 @@ #include "CBot/CBotInstr/CBotDefClass.h" +#include "CBot/CBotInstr/CBotExprRetVar.h" #include "CBot/CBotInstr/CBotInstrUtils.h" #include "CBot/CBotInstr/CBotLeftExprVar.h" @@ -44,6 +45,7 @@ CBotDefClass::CBotDefClass() m_expr = nullptr; m_hasParams = false; m_nMethodeIdent = 0; + m_exprRetVar = nullptr; } //////////////////////////////////////////////////////////////////////////////// @@ -150,6 +152,16 @@ CBotInstr* CBotDefClass::Compile(CBotToken* &p, CBotCStack* pStack, CBotClass* p goto error; } + pStk->SetCopyVar(var); + // chained method ? + if (nullptr != (inst->m_exprRetVar = CBotExprRetVar::Compile(p, pStk, true))) + { + inst->m_exprRetVar->SetToken(vartoken); + delete pStk->TokenStack(); + } + pStk->SetVar(nullptr); + + if ( !pStk->IsOk() ) goto error; } if (IsOfType(p, ID_ASS)) // with a assignment? @@ -232,6 +244,19 @@ bool CBotDefClass::Execute(CBotStack* &pj) CBotStack* pile = pj->AddStack(this);//essential for SetState() // if ( pile == EOX ) return true; + if (m_exprRetVar != nullptr) // Class c().method(); + { + if (pile->IfStep()) return false; + if (pile->GetState() == 4) + { + CBotStack* pile3 = pile->AddStack(); + if (!m_exprRetVar->Execute(pile3)) return false; + pile3->SetVar(nullptr); + pile->Return(pile3); // release pile3 stack + pile->SetState(5); + } + } + CBotToken* pt = &m_token; CBotClass* pClass = CBotClass::Find(pt); @@ -363,6 +388,14 @@ bool CBotDefClass::Execute(CBotStack* &pj) pile->SetState(3); // finished this part } + if (m_exprRetVar != nullptr && pile->GetState() == 3) // Class c().method(); + { + CBotStack* pile3 = pile->AddStack(); + pile3->SetCopyVar(pThis); + pile->SetState(4); + return false; // go back to the top ^^^ + } + if ( pile->IfStep() ) return false; if ( m_next2b != nullptr && @@ -387,6 +420,16 @@ void CBotDefClass::RestoreState(CBotStack* &pj, bool bMain) pThis->SetUniqNum((static_cast(m_var))->m_nIdent); // its attribute a unique number } + if (m_exprRetVar != nullptr) // Class c().method(); + { + if (pile->GetState() == 4) + { + CBotStack* pile3 = pile->RestoreStack(); + m_exprRetVar->RestoreState(pile3, bMain); + return; + } + } + CBotToken* pt = &m_token; CBotClass* pClass = CBotClass::Find(pt); bool bIntrincic = pClass->IsIntrinsic(); diff --git a/src/CBot/CBotInstr/CBotDefClass.h b/src/CBot/CBotInstr/CBotDefClass.h index 82c59528..abad7952 100644 --- a/src/CBot/CBotInstr/CBotDefClass.h +++ b/src/CBot/CBotInstr/CBotDefClass.h @@ -85,6 +85,9 @@ private: //! Constructor method unique identifier long m_nMethodeIdent; + //! Instruction to chain method calls after constructor + CBotInstr* m_exprRetVar; + }; } // namespace CBot diff --git a/src/CBot/CBotInstr/CBotExprRetVar.cpp b/src/CBot/CBotInstr/CBotExprRetVar.cpp index 9a1eeb72..96ff03c0 100644 --- a/src/CBot/CBotInstr/CBotExprRetVar.cpp +++ b/src/CBot/CBotInstr/CBotExprRetVar.cpp @@ -41,17 +41,13 @@ CBotExprRetVar::~CBotExprRetVar() } //////////////////////////////////////////////////////////////////////////////// -CBotInstr* CBotExprRetVar::Compile(CBotToken*& p, CBotCStack* pStack) +CBotInstr* CBotExprRetVar::Compile(CBotToken*& p, CBotCStack* pStack, bool bMethodsOnly) { if (p->GetType() == ID_DOT) { CBotVar* var = pStack->GetVar(); - if (var == nullptr) - { - pStack->SetError(CBotErrNoTerminator, p->GetStart()); - return nullptr; - } + if (var == nullptr) return nullptr; CBotCStack* pStk = pStack->TokenStack(); CBotInstr* inst = new CBotExprRetVar(); @@ -61,6 +57,8 @@ CBotInstr* CBotExprRetVar::Compile(CBotToken*& p, CBotCStack* pStack) pStk->SetStartError(p->GetStart()); if (var->GetType() == CBotTypArrayPointer) { + if (bMethodsOnly) goto err; + if (IsOfType( p, ID_OPBRK )) { CBotIndexExpr* i = new CBotIndexExpr(); @@ -92,11 +90,16 @@ CBotInstr* CBotExprRetVar::Compile(CBotToken*& p, CBotCStack* pStack) { if (p->GetNext()->GetType() == ID_OPENPAR) { - CBotInstr* i = CBotInstrMethode::Compile(p, pStk, var); + CBotInstr* i = CBotInstrMethode::Compile(p, pStk, var, bMethodsOnly); if (!pStk->IsOk()) goto err; inst->AddNext3(i); return pStack->Return(inst, pStk); } + else if (bMethodsOnly) + { + p = p->GetPrev(); + goto err; + } else { CBotFieldExpr* i = new CBotFieldExpr(); diff --git a/src/CBot/CBotInstr/CBotExprRetVar.h b/src/CBot/CBotInstr/CBotExprRetVar.h index a1c37f55..2c8321e7 100644 --- a/src/CBot/CBotInstr/CBotExprRetVar.h +++ b/src/CBot/CBotInstr/CBotExprRetVar.h @@ -36,7 +36,7 @@ public: CBotExprRetVar(); ~CBotExprRetVar(); - static CBotInstr* Compile(CBotToken*& p, CBotCStack* pStack); + static CBotInstr* Compile(CBotToken*& p, CBotCStack* pStack, bool bMethodsOnly = false); /*! * \brief Execute diff --git a/src/CBot/CBotInstr/CBotInstrMethode.cpp b/src/CBot/CBotInstr/CBotInstrMethode.cpp index cbc3a608..bf4ec41e 100644 --- a/src/CBot/CBotInstr/CBotInstrMethode.cpp +++ b/src/CBot/CBotInstr/CBotInstrMethode.cpp @@ -46,7 +46,7 @@ CBotInstrMethode::~CBotInstrMethode() } //////////////////////////////////////////////////////////////////////////////// -CBotInstr* CBotInstrMethode::Compile(CBotToken* &p, CBotCStack* pStack, CBotVar* var) +CBotInstr* CBotInstrMethode::Compile(CBotToken* &p, CBotCStack* pStack, CBotVar* var, bool bMethodChain) { CBotInstrMethode* inst = new CBotInstrMethode(); inst->SetToken(p); // corresponding token @@ -90,9 +90,10 @@ CBotInstr* CBotInstrMethode::Compile(CBotToken* &p, CBotCStack* pStack, CBotVar* } else pStack->SetVar(nullptr); - if (nullptr != (inst->m_exprRetVar = CBotExprRetVar::Compile(p, pStack))) + pp = p; + if (nullptr != (inst->m_exprRetVar = CBotExprRetVar::Compile(p, pStack, bMethodChain))) { - inst->m_exprRetVar->SetToken(&inst->m_token); + inst->m_exprRetVar->SetToken(pp); delete pStack->TokenStack(); } diff --git a/src/CBot/CBotInstr/CBotInstrMethode.h b/src/CBot/CBotInstr/CBotInstrMethode.h index a74a49a4..502dcf4e 100644 --- a/src/CBot/CBotInstr/CBotInstrMethode.h +++ b/src/CBot/CBotInstr/CBotInstrMethode.h @@ -38,9 +38,10 @@ public: * \param p * \param pStack * \param pVar + * \param bMethodChain If true, allows chaining methods only * \return */ - static CBotInstr* Compile(CBotToken* &p, CBotCStack* pStack, CBotVar* pVar); + static CBotInstr* Compile(CBotToken* &p, CBotCStack* pStack, CBotVar* pVar, bool bMethodChain = false); /*! * \brief Execute diff --git a/src/CBot/CBotInstr/CBotNew.cpp b/src/CBot/CBotInstr/CBotNew.cpp index a302b394..e7d973c9 100644 --- a/src/CBot/CBotInstr/CBotNew.cpp +++ b/src/CBot/CBotInstr/CBotNew.cpp @@ -24,6 +24,7 @@ #include "CBot/CBotCStack.h" #include "CBot/CBotClass.h" +#include "CBot/CBotInstr/CBotExprRetVar.h" #include "CBot/CBotInstr/CBotInstrUtils.h" #include "CBot/CBotVar/CBotVar.h" @@ -105,7 +106,17 @@ CBotInstr* CBotNew::Compile(CBotToken* &p, CBotCStack* pStack) // makes pointer to the object on the stack pStk->SetVar(pVar); - return pStack->Return(inst, pStk); + + pp = p; + // chained method ? + if (nullptr != (inst->m_exprRetVar = CBotExprRetVar::Compile(p, pStk, true))) + { + inst->m_exprRetVar->SetToken(pp); + delete pStk->TokenStack(); + } + + if (pStack->IsOk()) + return pStack->Return(inst, pStk); } error: delete inst; @@ -117,6 +128,16 @@ bool CBotNew::Execute(CBotStack* &pj) { CBotStack* pile = pj->AddStack(this); //main stack + if (m_exprRetVar != nullptr) // new Class().method() + { + if (pile->GetState() == 2) + { + CBotStack* pile3 = pile->AddStack(); + if (!m_exprRetVar->Execute(pile3)) return false; + return pj->Return(pile3); + } + } + if (pile->IfStep()) return false; CBotStack* pile1 = pj->AddStack2(); //secondary stack @@ -186,6 +207,16 @@ bool CBotNew::Execute(CBotStack* &pj) pThis->ConstructorSet(); // indicates that the constructor has been called } + if (m_exprRetVar != nullptr) // new Class().method() + { + pile->AddStack()->Delete(); // release pile2 stack + CBotStack* pile3 = pile->AddStack(); // add new stack + pile3->SetCopyVar(pThis); // copy the pointer (from pile1) + pile1->Delete(); // release secondary stack(pile1) + pile->SetState(2); + return false; // go back to the top ^^^ + } + return pj->Return(pile1); // passes below } @@ -197,6 +228,16 @@ void CBotNew::RestoreState(CBotStack* &pj, bool bMain) CBotStack* pile = pj->RestoreStack(this); //primary stack if (pile == nullptr) return; + if (m_exprRetVar != nullptr) // new Class().method() + { + if (pile->GetState() == 2) + { + CBotStack* pile3 = pile->RestoreStack(); + m_exprRetVar->RestoreState(pile3, bMain); + return; + } + } + CBotStack* pile1 = pj->AddStack2(); //secondary stack CBotToken* pt = &m_vartoken; diff --git a/src/CBot/CBotInstr/CBotNew.h b/src/CBot/CBotInstr/CBotNew.h index 76cf3e92..c69da3aa 100644 --- a/src/CBot/CBotInstr/CBotNew.h +++ b/src/CBot/CBotInstr/CBotNew.h @@ -66,6 +66,9 @@ private: long m_nMethodeIdent; CBotToken m_vartoken; + //! Instruction to chain method calls after constructor + CBotInstr* m_exprRetVar; + }; } // namespace CBot diff --git a/test/unit/CBot/CBot_test.cpp b/test/unit/CBot/CBot_test.cpp index da9fd95b..071abb94 100644 --- a/test/unit/CBot/CBot_test.cpp +++ b/test/unit/CBot/CBot_test.cpp @@ -1751,3 +1751,38 @@ TEST_F(CBotUT, InstrCallAccessMemberNewObjectDestructor) "}\n" ); } + +TEST_F(CBotUT, ClassConstructorMethodChain) +{ + ExecuteTest( + "public class TestClass {\n" + " int a = 123;\n" + " int b = 246;\n" + " TestClass testSetA(int x) { a = x; return this; }\n" + " TestClass testSetB(int y) { b = y; return this; }\n" + "}\n" + "extern void ConstructorMethodChain() {\n" + " TestClass tc().testSetA(111).testSetB(222);\n" + " ASSERT(tc.a == 111);\n" + " ASSERT(tc.b == 222);\n" + "}\n" + ); +} + +TEST_F(CBotUT, ClassNewConstructorMethodChain) +{ + ExecuteTest( + "public class TestClass {\n" + " int a = 123;\n" + " int b = 246;\n" + " TestClass testSetA(int x) { a = x; return this; }\n" + " TestClass testSetB(int y) { b = y; return this; }\n" + "}\n" + "extern void NewConstructorMethodChain() {\n" + " TestClass tc;\n" + " tc = new TestClass().testSetA(111).testSetB(222);\n" + " ASSERT(tc.a == 111);\n" + " ASSERT(tc.b == 222);\n" + "}\n" + ); +}