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