1 /*
2  * This file is part of the Colobot: Gold Edition source code
3  * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam
4  * http://epsitec.ch; http://colobot.info; http://github.com/colobot
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://gnu.org/licenses
18  */
19 
20 #include "CBot/CBotVar/CBotVar.h"
21 
22 #include "CBot/CBotExternalCall.h"
23 #include "CBot/CBotStack.h"
24 #include "CBot/CBotCStack.h"
25 #include "CBot/CBotClass.h"
26 #include "CBot/CBotUtils.h"
27 
28 #include "CBot/CBotInstr/CBotFunction.h"
29 
30 #include "CBot/stdlib/stdlib.h"
31 
32 #include <algorithm>
33 
34 namespace CBot
35 {
36 
37 std::unique_ptr<CBotExternalCallList> CBotProgram::m_externalCalls;
38 
CBotProgram()39 CBotProgram::CBotProgram()
40 {
41 }
42 
CBotProgram(CBotVar * thisVar)43 CBotProgram::CBotProgram(CBotVar* thisVar)
44 : m_thisVar(thisVar)
45 {
46 }
47 
~CBotProgram()48 CBotProgram::~CBotProgram()
49 {
50 //  delete  m_classes;
51     for (CBotClass* c : m_classes)
52         c->Purge();
53     m_classes.clear();
54 
55     CBotClass::FreeLock(this);
56 
57     for (CBotFunction* f : m_functions) delete f;
58     m_functions.clear();
59 }
60 
Compile(const std::string & program,std::vector<std::string> & externFunctions,void * pUser)61 bool CBotProgram::Compile(const std::string& program, std::vector<std::string>& externFunctions, void* pUser)
62 {
63     // Cleanup the previously compiled program
64     Stop();
65 
66     for (CBotClass* c : m_classes)
67         c->Purge();      // purge the old definitions of classes
68                          // but without destroying the object
69 
70     m_classes.clear();
71     for (CBotFunction* f : m_functions) delete f;
72     m_functions.clear();
73 
74     externFunctions.clear();
75     m_error = CBotNoErr;
76 
77     // Step 1. Process the code into tokens
78     auto tokens = CBotToken::CompileTokens(program);
79     if (tokens == nullptr) return false;
80 
81     auto pStack = std::unique_ptr<CBotCStack>(new CBotCStack(nullptr));
82     CBotToken* p = tokens.get()->GetNext();                 // skips the first token (separator)
83 
84     pStack->SetProgram(this);                               // defined used routines
85     m_externalCalls->SetUserPtr(pUser);
86 
87     // Step 2. Find all function and class definitions
88     while ( pStack->IsOk() && p != nullptr && p->GetType() != 0)
89     {
90         if ( IsOfType(p, ID_SEP) ) continue;                // semicolons lurking
91 
92         if ( p->GetType() == ID_CLASS ||
93             ( p->GetType() == ID_PUBLIC && p->GetNext()->GetType() == ID_CLASS ))
94         {
95             CBotClass* newclass = CBotClass::Compile1(p, pStack.get());
96             if (newclass != nullptr)
97                 m_classes.push_back(newclass);
98         }
99         else
100         {
101             CBotFunction* newfunc  = CBotFunction::Compile1(p, pStack.get(), nullptr);
102             if (newfunc != nullptr)
103                 m_functions.push_back(newfunc);
104         }
105     }
106 
107     // Define fields and pre-compile methods for each class in this program
108     if (pStack->IsOk()) CBotClass::DefineClasses(m_classes, pStack.get());
109 
110     if ( !pStack->IsOk() )
111     {
112         m_error = pStack->GetError(m_errorStart, m_errorEnd);
113         for (CBotFunction* f : m_functions) delete f;
114         m_functions.clear();
115         return false;
116     }
117 
118     // Step 3. Real compilation
119     std::list<CBotFunction*>::iterator next = m_functions.begin();
120     p  = tokens.get()->GetNext();                             // returns to the beginning
121     while ( pStack->IsOk() && p != nullptr && p->GetType() != 0 )
122     {
123         if ( IsOfType(p, ID_SEP) ) continue;                // semicolons lurking
124 
125         if ( p->GetType() == ID_CLASS ||
126             ( p->GetType() == ID_PUBLIC && p->GetNext()->GetType() == ID_CLASS ))
127         {
128             CBotClass::Compile(p, pStack.get());                  // completes the definition of the class
129         }
130         else
131         {
132             CBotFunction::Compile(p, pStack.get(), *next);
133             if ((*next)->IsExtern()) externFunctions.push_back((*next)->GetName()/* + next->GetParams()*/);
134             if ((*next)->IsPublic()) CBotFunction::AddPublic(*next);
135             (*next)->m_pProg = this;                           // keeps pointers to the module
136             ++next;
137         }
138     }
139 
140     if ( !pStack->IsOk() )
141     {
142         m_error = pStack->GetError(m_errorStart, m_errorEnd);
143         for (CBotFunction* f : m_functions) delete f;
144         m_functions.clear();
145     }
146 
147     return !m_functions.empty();
148 }
149 
Start(const std::string & name)150 bool CBotProgram::Start(const std::string& name)
151 {
152     Stop();
153 
154     auto it = std::find_if(m_functions.begin(), m_functions.end(), [&name](CBotFunction* x) { return x->GetName() == name; });
155     if (it == m_functions.end())
156     {
157         m_error = CBotErrNoRun;
158         return false;
159     }
160     m_entryPoint = *it;
161 
162     m_stack = CBotStack::AllocateStack();
163     m_stack->SetProgram(this);
164 
165     return true; // we are ready for Run()
166 }
167 
GetPosition(const std::string & name,int & start,int & stop,CBotGet modestart,CBotGet modestop)168 bool CBotProgram::GetPosition(const std::string& name, int& start, int& stop, CBotGet modestart, CBotGet modestop)
169 {
170     auto it = std::find_if(m_functions.begin(), m_functions.end(), [&name](CBotFunction* x) { return x->GetName() == name; });
171     if (it == m_functions.end()) return false;
172 
173     (*it)->GetPosition(start, stop, modestart, modestop);
174     return true;
175 }
176 
Run(void * pUser,int timer)177 bool CBotProgram::Run(void* pUser, int timer)
178 {
179     if (m_stack == nullptr || m_entryPoint == nullptr)
180     {
181         m_error = CBotErrNoRun;
182         return true;
183     }
184 
185     m_error = CBotNoErr;
186 
187     m_stack->SetUserPtr(pUser);
188     if ( timer >= 0 ) m_stack->SetTimer(timer); // TODO: Check if changing order here fixed ipf()
189     m_stack->Reset();                         // reset the possible previous error, and resets the timer
190 
191     m_stack->SetProgram(this);                     // bases for routines
192 
193     // resumes execution on the top of the stack
194     bool ok = m_stack->Execute();
195     if (ok)
196     {
197         // returns to normal execution
198         ok = m_entryPoint->Execute(nullptr, m_stack, m_thisVar);
199     }
200 
201     // completed on a mistake?
202     if (ok || !m_stack->IsOk())
203     {
204         m_error = m_stack->GetError(m_errorStart, m_errorEnd);
205         m_stack->Delete();
206         m_stack = nullptr;
207         CBotClass::FreeLock(this);
208         m_entryPoint = nullptr;
209         return true;                                // execution is finished!
210     }
211 
212     return ok;
213 }
214 
Stop()215 void CBotProgram::Stop()
216 {
217     if (m_stack != nullptr)
218     {
219         m_stack->Delete();
220         m_stack = nullptr;
221     }
222     m_entryPoint = nullptr;
223     CBotClass::FreeLock(this);
224 }
225 
226 ////////////////////////////////////////////////////////////////////////////////
GetRunPos(std::string & functionName,int & start,int & end)227 bool CBotProgram::GetRunPos(std::string& functionName, int& start, int& end)
228 {
229     functionName = "";
230     start = end = 0;
231     if (m_stack == nullptr) return false;
232 
233     m_stack->GetRunPos(functionName, start, end);
234     return true;
235 }
236 
237 
GetStackVars(std::string & functionName,int level)238 CBotVar* CBotProgram::GetStackVars(std::string& functionName, int level)
239 {
240     functionName.clear();
241     if (m_stack == nullptr) return nullptr;
242 
243     return m_stack->GetStackVars(functionName, level);
244 }
245 
GetError()246 CBotError CBotProgram::GetError()
247 {
248     return m_error;
249 }
250 
251 
GetError(CBotError & code,int & start,int & end)252 bool CBotProgram::GetError(CBotError& code, int& start, int& end)
253 {
254     code  = m_error;
255     start = m_errorStart;
256     end   = m_errorEnd;
257     return code > 0;
258 }
259 
260 
GetError(CBotError & code,int & start,int & end,CBotProgram * & pProg)261 bool CBotProgram::GetError(CBotError& code, int& start, int& end, CBotProgram*& pProg)
262 {
263     code    = m_error;
264     start   = m_errorStart;
265     end     = m_errorEnd;
266     pProg   = this;
267     return code > 0;
268 }
269 
270 ////////////////////////////////////////////////////////////////////////////////
GetFunctions()271 const std::list<CBotFunction*>& CBotProgram::GetFunctions()
272 {
273     return m_functions;
274 }
275 
ClassExists(std::string name)276 bool CBotProgram::ClassExists(std::string name)
277 {
278     for (CBotClass* p : m_classes)
279     {
280         if ( p->GetName() == name ) return true;
281     }
282 
283     return false;
284 }
285 
286 ////////////////////////////////////////////////////////////////////////////////
cSizeOf(CBotVar * & pVar,void * pUser)287 static CBotTypResult cSizeOf( CBotVar* &pVar, void* pUser )
288 {
289     if ( pVar == nullptr ) return CBotTypResult( CBotErrLowParam );
290     if ( pVar->GetType() != CBotTypArrayPointer )
291                         return CBotTypResult( CBotErrBadParam );
292     return CBotTypResult( CBotTypInt );
293 }
294 
rSizeOf(CBotVar * pVar,CBotVar * pResult,int & ex,void * pUser)295 static bool rSizeOf( CBotVar* pVar, CBotVar* pResult, int& ex, void* pUser )
296 {
297     if ( pVar == nullptr ) { ex = CBotErrLowParam; return true; }
298 
299     int i = 0;
300     pVar = pVar->GetItemList();
301 
302     while ( pVar != nullptr )
303     {
304         i++;
305         pVar = pVar->GetNext();
306     }
307 
308     pResult->SetValInt(i);
309     return true;
310 }
311 
312 ////////////////////////////////////////////////////////////////////////////////
AddFunction(const std::string & name,bool rExec (CBotVar * pVar,CBotVar * pResult,int & Exception,void * pUser),CBotTypResult rCompile (CBotVar * & pVar,void * pUser))313 bool CBotProgram::AddFunction(const std::string& name,
314                               bool rExec(CBotVar* pVar, CBotVar* pResult, int& Exception, void* pUser),
315                               CBotTypResult rCompile(CBotVar*& pVar, void* pUser))
316 {
317     return m_externalCalls->AddFunction(name, std::unique_ptr<CBotExternalCall>(new CBotExternalCallDefault(rExec, rCompile)));
318 }
319 
DefineNum(const std::string & name,long val)320 bool CBotProgram::DefineNum(const std::string& name, long val)
321 {
322     CBotToken::DefineNum(name, val);
323     return true;
324 }
325 
326 ////////////////////////////////////////////////////////////////////////////////
SaveState(std::ostream & ostr)327 bool CBotProgram::SaveState(std::ostream &ostr)
328 {
329     if (!WriteLong(ostr, CBOTVERSION)) return false;
330 
331 
332     if (m_stack != nullptr )
333     {
334         if (!WriteWord(ostr, 1)) return false;
335         if (!WriteString(ostr, m_entryPoint->GetName())) return false;
336         if (!m_stack->SaveState(ostr)) return false;
337     }
338     else
339     {
340         if (!WriteWord(ostr, 0)) return false;
341     }
342     return true;
343 }
344 
RestoreState(std::istream & istr)345 bool CBotProgram::RestoreState(std::istream &istr)
346 {
347     unsigned short  w;
348     std::string      s;
349 
350     Stop();
351 
352     long version;
353     if (!ReadLong(istr, version)) return false;
354     if ( version != CBOTVERSION ) return false;
355 
356     if (!ReadWord(istr, w)) return false;
357     if ( w == 0 ) return true;
358 
359     // don't restore if compile error exists
360     if (m_error != CBotNoErr) return false;
361 
362     if (!ReadString(istr, s)) return false;
363     if (!Start(s)) return false; // point de reprise
364     // Start() already created the new stack
365     // and called m_stack->SetProgram(this);
366 
367     // retrieves the stack from the memory
368     if (!m_stack->RestoreState(istr, m_stack))
369     {
370         m_stack->Delete();
371         m_stack = nullptr;
372         m_stack = CBotStack::AllocateStack(); // start from the top
373         m_stack->SetProgram(this);
374         return false; // signal error
375     }
376 
377     // restored some states in the stack according to the structure
378     m_entryPoint->RestoreState(nullptr, m_stack, m_thisVar);
379     return true;
380 }
381 
382 ////////////////////////////////////////////////////////////////////////////////
383 
GetVersion()384 int CBotProgram::GetVersion()
385 {
386     return  CBOTVERSION;
387 }
388 
Init()389 void CBotProgram::Init()
390 {
391     m_externalCalls.reset(new CBotExternalCallList);
392 
393     CBotProgram::DefineNum("CBotErrZeroDiv",    CBotErrZeroDiv);     // division by zero
394     CBotProgram::DefineNum("CBotErrNotInit",    CBotErrNotInit);     // uninitialized variable
395     CBotProgram::DefineNum("CBotErrBadThrow",   CBotErrBadThrow);    // throw a negative value
396     CBotProgram::DefineNum("CBotErrNoRetVal",   CBotErrNoRetVal);    // function did not return results
397     CBotProgram::DefineNum("CBotErrNoRun",      CBotErrNoRun);       // active Run () without a function // TODO: Is this actually a runtime error?
398     CBotProgram::DefineNum("CBotErrUndefFunc",  CBotErrUndefFunc);   // Calling a function that no longer exists
399     CBotProgram::DefineNum("CBotErrNotClass",   CBotErrNotClass);    // Class no longer exists
400     CBotProgram::DefineNum("CBotErrNull",       CBotErrNull);        // Attempted to use a null pointer
401     CBotProgram::DefineNum("CBotErrNan",        CBotErrNan);         // Can't do operations on nan
402     CBotProgram::DefineNum("CBotErrOutArray",   CBotErrOutArray);    // Attempted access out of bounds of an array
403     CBotProgram::DefineNum("CBotErrStackOver",  CBotErrStackOver);   // Stack overflow
404     CBotProgram::DefineNum("CBotErrDeletedPtr", CBotErrDeletedPtr);  // Attempted to use deleted object
405 
406     CBotProgram::AddFunction("sizeof", rSizeOf, cSizeOf);
407 
408     InitStringFunctions();
409     InitMathFunctions();
410     InitFileFunctions();
411 }
412 
Free()413 void CBotProgram::Free()
414 {
415     CBotToken::ClearDefineNum();
416     m_externalCalls->Clear();
417     CBotClass::ClearPublic();
418     m_externalCalls.reset();
419 }
420 
GetExternalCalls()421 const std::unique_ptr<CBotExternalCallList>& CBotProgram::GetExternalCalls()
422 {
423     return m_externalCalls;
424 }
425 
426 } // namespace CBot
427