1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 // executes script functions
17
18 #include "C4Include.h"
19 #include "script/C4AulExec.h"
20
21 #include "control/C4Record.h"
22 #include "object/C4Def.h"
23 #include "object/C4Object.h"
24 #include "script/C4Aul.h"
25 #include "script/C4AulScriptFunc.h"
26 #include "script/C4AulDebug.h"
27 #include "script/C4ScriptHost.h"
28
29 C4AulExec AulExec;
30
C4AulExecError(const char * szError)31 C4AulExecError::C4AulExecError(const char *szError)
32 {
33 assert(szError);
34 // direct error message string
35 sMessage.Copy(szError ? szError : "(no error message)");
36 }
37
ReturnDump(StdStrBuf Dump)38 StdStrBuf C4AulScriptContext::ReturnDump(StdStrBuf Dump)
39 {
40 if (!Func)
41 return StdStrBuf("");
42 // Context
43 if (Obj && Obj->Status)
44 {
45 C4Value ObjVal(Obj);
46 Dump.Append(ObjVal.GetDataString(0));
47 Dump.Append("->");
48 }
49 bool fDirectExec = !Func->GetName();
50 if (!fDirectExec)
51 {
52 // Function name
53 Dump.Append(Func->GetName());
54 // Parameters
55 Dump.AppendChar('(');
56 int iNullPars = 0;
57 for (int i = 0; i < Func->GetParCount(); i++)
58 {
59 if (!Pars[i])
60 iNullPars++;
61 else
62 {
63 if (i > iNullPars)
64 Dump.AppendChar(',');
65 // Insert missing null parameters
66 while (iNullPars > 0)
67 {
68 Dump.Append("0,");
69 iNullPars--;
70 }
71 // Insert parameter
72 Dump.Append(Pars[i].GetDataString());
73 }
74 }
75 Dump.AppendChar(')');
76 }
77 else
78 Dump.Append(Func->Parent->GetDataString());
79 // Script
80 if (!fDirectExec && Func->pOrgScript)
81 Dump.AppendFormat(" (%s:%d)",
82 Func->pOrgScript->ScriptName.getData(),
83 CPos ? Func->GetLineOfCode(CPos) : SGetLine(Func->pOrgScript->GetScript(), Func->Script));
84 // Return it
85 return Dump;
86 }
87
dump(StdStrBuf Dump)88 void C4AulScriptContext::dump(StdStrBuf Dump)
89 {
90 // Log it
91 DebugLog(ReturnDump(Dump).getData());
92 }
93
LogCallStack()94 void C4AulExec::LogCallStack()
95 {
96 for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
97 pCtx->dump(StdStrBuf(" by: "));
98 }
99
FnTranslate(C4PropList * _this,C4String * text)100 C4String *C4AulExec::FnTranslate(C4PropList * _this, C4String *text)
101 {
102 #define ReturnIfTranslationAvailable(script, key) do \
103 { \
104 const auto &s = script; \
105 const auto &k = key; \
106 if (s) \
107 { \
108 try \
109 { \
110 return ::Strings.RegString(s->Translate(k).c_str()); \
111 } \
112 catch (C4LangStringTable::NoSuchTranslation &) {} \
113 } \
114 } while(0)
115
116 if (!text || text->GetData().isNull()) return nullptr;
117 // Find correct script: translations of the context if possible, containing script as fallback
118 if (_this && _this->GetDef())
119 ReturnIfTranslationAvailable(&(_this->GetDef()->Script), text->GetCStr());
120 ReturnIfTranslationAvailable(AulExec.pCurCtx[0].Func->pOrgScript, text->GetCStr());
121
122 // No translation available, log
123 DebugLogF(R"(WARNING: Translate: no translation for string "%s")", text->GetCStr());
124 // Trace
125 AulExec.LogCallStack();
126 return text;
127 #undef ReturnIfTranslationAvailable
128 }
129
FnLogCallStack(C4PropList * _this)130 bool C4AulExec::FnLogCallStack(C4PropList * _this)
131 {
132 AulExec.LogCallStack();
133 return true;
134 }
135
ClearPointers(C4Object * obj)136 void C4AulExec::ClearPointers(C4Object * obj)
137 {
138 for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
139 {
140 if (pCtx->Obj == obj)
141 pCtx->Obj = nullptr;
142 }
143 }
144
Exec(C4AulScriptFunc * pSFunc,C4PropList * p,C4Value * pnPars,bool fPassErrors)145 C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4PropList * p, C4Value *pnPars, bool fPassErrors)
146 {
147 // Save start context
148 C4AulScriptContext *pOldCtx = pCurCtx;
149 C4Value *pPars = pCurVal + 1;
150 try
151 {
152 // Push parameters
153 assert(pnPars);
154 for (int i = 0; i < pSFunc->GetParCount(); i++)
155 PushValue(pnPars[i]);
156
157 // Push a new context
158 C4AulScriptContext ctx;
159 ctx.tTime = 0;
160 ctx.Obj = p;
161 ctx.Return = nullptr;
162 ctx.Pars = pPars;
163 ctx.Func = pSFunc;
164 ctx.CPos = nullptr;
165 PushContext(ctx);
166
167 // Execute
168 return Exec(pSFunc->GetCode());
169 }
170 catch (C4AulError &e)
171 {
172 if (!fPassErrors)
173 ::ScriptEngine.GetErrorHandler()->OnError(e.what());
174 // Unwind stack
175 // TODO: The stack dump should be passed to the error handler somehow
176 while (pCurCtx > pOldCtx)
177 {
178 pCurCtx->dump(StdStrBuf(" by: "));
179 PopContext();
180 }
181 PopValuesUntil(pPars - 1);
182 // Pass?
183 if (fPassErrors)
184 throw;
185 // Trace
186 LogCallStack();
187 }
188
189 // Return nothing
190 return C4VNull;
191 }
192
Exec(C4AulBCC * pCPos)193 C4Value C4AulExec::Exec(C4AulBCC *pCPos)
194 {
195 try
196 {
197
198 for (;;)
199 {
200
201 bool fJump = false;
202 switch (pCPos->bccType)
203 {
204 case AB_INT:
205 PushInt(pCPos->Par.i);
206 break;
207
208 case AB_BOOL:
209 PushBool(!!pCPos->Par.i);
210 break;
211
212 case AB_STRING:
213 PushString(pCPos->Par.s);
214 break;
215
216 case AB_CPROPLIST:
217 PushPropList(pCPos->Par.p);
218 break;
219
220 case AB_CARRAY:
221 PushArray(pCPos->Par.a);
222 break;
223
224 case AB_CFUNCTION:
225 PushFunction(pCPos->Par.f);
226 break;
227
228 case AB_NIL:
229 PushValue(C4VNull);
230 break;
231
232 case AB_DUP:
233 PushValue(pCurVal[pCPos->Par.i]);
234 break;
235 case AB_STACK_SET:
236 pCurVal[pCPos->Par.i] = pCurVal[0];
237 break;
238 case AB_POP_TO:
239 pCurVal[pCPos->Par.i] = pCurVal[0];
240 PopValue();
241 break;
242
243 case AB_EOFN:
244 throw C4AulExecError("internal error: function didn't return");
245
246 case AB_ERR:
247 if (pCPos->Par.s)
248 throw C4AulExecError((std::string("syntax error: ") + pCPos->Par.s->GetCStr()).c_str());
249 else
250 throw C4AulExecError("syntax error: see above for details");
251
252 case AB_DUP_CONTEXT:
253 PushValue(AulExec.GetContext(AulExec.GetContextDepth()-2)->Pars[pCPos->Par.i]);
254 break;
255
256 case AB_LOCALN:
257 if (!pCurCtx->Obj)
258 throw C4AulExecError("can't access local variables without this");
259 PushNullVals(1);
260 pCurCtx->Obj->GetPropertyByS(pCPos->Par.s, pCurVal);
261 break;
262 case AB_LOCALN_SET:
263 if (!pCurCtx->Obj)
264 throw C4AulExecError("can't access local variables without this");
265 if (pCurCtx->Obj->IsFrozen())
266 throw C4AulExecError("local variable: this is readonly");
267 pCurCtx->Obj->SetPropertyByS(pCPos->Par.s, pCurVal[0]);
268 break;
269
270 case AB_PROP:
271 if (!pCurVal->CheckConversion(C4V_PropList))
272 throw C4AulExecError(FormatString("proplist access: proplist expected, got %s", pCurVal->GetTypeName()).getData());
273 if (!pCurVal->_getPropList()->GetPropertyByS(pCPos->Par.s, pCurVal))
274 pCurVal->Set0();
275 break;
276 case AB_PROP_SET:
277 {
278 C4Value *pPropList = pCurVal - 1;
279 if (!pPropList->CheckConversion(C4V_PropList))
280 throw C4AulExecError(FormatString("proplist write: proplist expected, got %s", pPropList->GetTypeName()).getData());
281 if (pPropList->_getPropList()->IsFrozen())
282 throw C4AulExecError("proplist write: proplist is readonly");
283 pPropList->_getPropList()->SetPropertyByS(pCPos->Par.s, pCurVal[0]);
284 pPropList->Set(pCurVal[0]);
285 PopValue();
286 break;
287 }
288
289 case AB_GLOBALN:
290 PushValue(*::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i));
291 break;
292 case AB_GLOBALN_SET:
293 ::ScriptEngine.GlobalNamed.GetItem(pCPos->Par.i)->Set(pCurVal[0]);
294 break;
295
296 // prefix
297 case AB_BitNot: // ~
298 CheckOpPar(C4V_Int, "~");
299 pCurVal->SetInt(~pCurVal->_getInt());
300 break;
301 case AB_Not: // !
302 pCurVal->SetBool(!pCurVal->getBool());
303 break;
304 case AB_Neg: // -
305 CheckOpPar(C4V_Int, "-");
306 pCurVal->SetInt(-pCurVal->_getInt());
307 break;
308 case AB_Inc: // ++
309 CheckOpPar(C4V_Int, "++");
310 pCurVal->SetInt(pCurVal->_getInt() + 1);
311 break;
312 case AB_Dec: // --
313 CheckOpPar(C4V_Int, "--");
314 pCurVal->SetInt(pCurVal->_getInt() - 1);
315 break;
316 // postfix
317 case AB_Pow: // **
318 {
319 CheckOpPars(C4V_Int, C4V_Int, "**");
320 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
321 pPar1->SetInt(Pow(pPar1->_getInt(), pPar2->_getInt()));
322 PopValue();
323 break;
324 }
325 case AB_Div: // /
326 {
327 CheckOpPars(C4V_Int, C4V_Int, "/");
328 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
329 if (!pPar2->_getInt())
330 throw C4AulExecError("division by zero");
331 // INT_MIN/-1 cannot be represented in an int and would cause an uncaught exception
332 if (pPar1->_getInt()==INT32_MIN && pPar2->_getInt()==-1)
333 throw C4AulExecError("division overflow");
334 pPar1->SetInt(pPar1->_getInt() / pPar2->_getInt());
335 PopValue();
336 break;
337 }
338 case AB_Mul: // *
339 {
340 CheckOpPars(C4V_Int, C4V_Int, "*");
341 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
342 pPar1->SetInt(pPar1->_getInt() * pPar2->_getInt());
343 PopValue();
344 break;
345 }
346 case AB_Mod: // %
347 {
348 CheckOpPars(C4V_Int, C4V_Int, "%");
349 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
350 // INT_MIN%-1 cannot be represented in an int and would cause an uncaught exception
351 if (pPar1->_getInt()==INT32_MIN && pPar2->_getInt()==-1)
352 throw C4AulExecError("modulo division overflow");
353 if (pPar2->_getInt())
354 pPar1->SetInt(pPar1->_getInt() % pPar2->_getInt());
355 else
356 pPar1->Set0();
357 PopValue();
358 break;
359 }
360 case AB_Sub: // -
361 {
362 CheckOpPars(C4V_Int, C4V_Int, "-");
363 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
364 pPar1->SetInt(pPar1->_getInt() - pPar2->_getInt());
365 PopValue();
366 break;
367 }
368 case AB_Sum: // +
369 {
370 CheckOpPars(C4V_Int, C4V_Int, "+");
371 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
372 pPar1->SetInt(pPar1->_getInt() + pPar2->_getInt());
373 PopValue();
374 break;
375 }
376 case AB_LeftShift: // <<
377 {
378 CheckOpPars(C4V_Int, C4V_Int, "<<");
379 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
380 pPar1->SetInt(pPar1->_getInt() << pPar2->_getInt());
381 PopValue();
382 break;
383 }
384 case AB_RightShift: // >>
385 {
386 CheckOpPars(C4V_Int, C4V_Int, ">>");
387 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
388 pPar1->SetInt(pPar1->_getInt() >> pPar2->_getInt());
389 PopValue();
390 break;
391 }
392 case AB_LessThan: // <
393 {
394 CheckOpPars(C4V_Int, C4V_Int, "<");
395 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
396 pPar1->SetBool(pPar1->_getInt() < pPar2->_getInt());
397 PopValue();
398 break;
399 }
400 case AB_LessThanEqual: // <=
401 {
402 CheckOpPars(C4V_Int, C4V_Int, "<=");
403 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
404 pPar1->SetBool(pPar1->_getInt() <= pPar2->_getInt());
405 PopValue();
406 break;
407 }
408 case AB_GreaterThan: // >
409 {
410 CheckOpPars(C4V_Int, C4V_Int, ">");
411 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
412 pPar1->SetBool(pPar1->_getInt() > pPar2->_getInt());
413 PopValue();
414 break;
415 }
416 case AB_GreaterThanEqual: // >=
417 {
418 CheckOpPars(C4V_Int, C4V_Int, ">=");
419 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
420 pPar1->SetBool(pPar1->_getInt() >= pPar2->_getInt());
421 PopValue();
422 break;
423 }
424 case AB_Equal: // ==
425 {
426 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
427 pPar1->SetBool(pPar1->IsIdenticalTo(*pPar2));
428 PopValue();
429 break;
430 }
431 case AB_NotEqual: // !=
432 {
433 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
434 pPar1->SetBool(!pPar1->IsIdenticalTo(*pPar2));
435 PopValue();
436 break;
437 }
438 case AB_BitAnd: // &
439 {
440 CheckOpPars(C4V_Int, C4V_Int, "&");
441 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
442 pPar1->SetInt(pPar1->_getInt() & pPar2->_getInt());
443 PopValue();
444 break;
445 }
446 case AB_BitXOr: // ^
447 {
448 CheckOpPars(C4V_Int, C4V_Int, "^");
449 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
450 pPar1->SetInt(pPar1->_getInt() ^ pPar2->_getInt());
451 PopValue();
452 break;
453 }
454 case AB_BitOr: // |
455 {
456 CheckOpPars(C4V_Int, C4V_Int, "|");
457 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
458 pPar1->SetInt(pPar1->_getInt() | pPar2->_getInt());
459 PopValue();
460 break;
461 }
462
463 case AB_NEW_ARRAY:
464 {
465 // Create array
466 C4ValueArray *pArray = new C4ValueArray(pCPos->Par.i);
467
468 // Pop values from stack
469 for (int i = 0; i < pCPos->Par.i; i++)
470 (*pArray)[i] = pCurVal[i - pCPos->Par.i + 1];
471
472 // Push array
473 PopValues(pCPos->Par.i);
474 PushArray(pArray);
475
476 break;
477 }
478
479 case AB_NEW_PROPLIST:
480 {
481 C4PropList * pPropList = C4PropList::New();
482
483 for (int i = 0; i < pCPos->Par.i; i++)
484 pPropList->SetPropertyByS(pCurVal[-2 * i - 1]._getStr(), pCurVal[-2 * i]);
485
486 PopValues(pCPos->Par.i * 2);
487 PushPropList(pPropList);
488 break;
489 }
490
491 case AB_ARRAYA:
492 {
493 C4Value *pIndex = pCurVal, *pStruct = pCurVal - 1, *pResult = pCurVal - 1;
494 // Typcheck to determine whether it's an array or a proplist
495 if(CheckArrayAccess(pStruct, pIndex) == C4V_Array)
496 {
497 *pResult = pStruct->_getArray()->GetItem(pIndex->_getInt());
498 }
499 else
500 {
501 assert(pStruct->GetType() == C4V_PropList);
502 C4PropList *pPropList = pStruct->_getPropList();
503 if (!pPropList->GetPropertyByS(pIndex->_getStr(), pResult))
504 pResult->Set0();
505 }
506 // Remove index
507 PopValue();
508 break;
509 }
510 case AB_ARRAYA_SET:
511 {
512 C4Value *pValue = pCurVal, *pIndex = pCurVal - 1, *pStruct = pCurVal - 2, *pResult = pCurVal - 2;
513 // Typcheck to determine whether it's an array or a proplist
514 if(CheckArrayAccess(pStruct, pIndex) == C4V_Array)
515 {
516 if (pStruct->_getArray()->IsFrozen())
517 throw C4AulExecError("array write: array is readonly");
518 pStruct->_getArray()->SetItem(pIndex->_getInt(), *pValue);
519 }
520 else
521 {
522 assert(pStruct->GetType() == C4V_PropList);
523 C4PropList *pPropList = pStruct->_getPropList();
524 if (pPropList->IsFrozen())
525 throw C4AulExecError("proplist write: proplist is readonly");
526 pPropList->SetPropertyByS(pIndex->_getStr(), *pValue);
527 }
528 // Set result, remove array and index from stack
529 *pResult = *pValue;
530 PopValues(2);
531 break;
532 }
533 case AB_ARRAY_SLICE:
534 {
535 C4Value &Array = pCurVal[-2];
536 C4Value &StartIndex = pCurVal[-1];
537 C4Value &EndIndex = pCurVal[0];
538
539 // Typcheck
540 if (!Array.CheckConversion(C4V_Array))
541 throw C4AulExecError(FormatString("array slice: can't access %s as an array", Array.GetTypeName()).getData());
542 if (!StartIndex.CheckConversion(C4V_Int))
543 throw C4AulExecError(FormatString("array slice: start index of type %s, int expected", StartIndex.GetTypeName()).getData());
544 if (!EndIndex.CheckConversion(C4V_Int))
545 throw C4AulExecError(FormatString("array slice: end index of type %s, int expected", EndIndex.GetTypeName()).getData());
546
547 Array.SetArray(Array.GetData().Array->GetSlice(StartIndex._getInt(), EndIndex._getInt()));
548
549 // Remove both indices
550 PopValues(2);
551 break;
552 }
553
554 case AB_ARRAY_SLICE_SET:
555 {
556 C4Value &Array = pCurVal[-3];
557 C4Value &StartIndex = pCurVal[-2];
558 C4Value &EndIndex = pCurVal[-1];
559 C4Value &Value = pCurVal[0];
560
561 // Typcheck
562 if (!Array.CheckConversion(C4V_Array))
563 throw C4AulExecError(FormatString("array slice: can't access %s as an array", Array.GetTypeName()).getData());
564 if (!StartIndex.CheckConversion(C4V_Int))
565 throw C4AulExecError(FormatString("array slice: start index of type %s, int expected", StartIndex.GetTypeName()).getData());
566 if (!EndIndex.CheckConversion(C4V_Int))
567 throw C4AulExecError(FormatString("array slice: end index of type %s, int expected", EndIndex.GetTypeName()).getData());
568
569 C4ValueArray *pArray = Array._getArray();
570 if (pArray->IsFrozen()) throw C4AulExecError("array write: array is readonly");
571 pArray->SetSlice(StartIndex._getInt(), EndIndex._getInt(), Value);
572
573 // Set value as result, remove both indices and first copy of value
574 Array = Value;
575 PopValues(3);
576 break;
577 }
578
579 case AB_STACK:
580 if (pCPos->Par.i < 0)
581 PopValues(-pCPos->Par.i);
582 else
583 PushNullVals(pCPos->Par.i);
584 break;
585
586 case AB_JUMP:
587 fJump = true;
588 pCPos += pCPos->Par.i;
589 break;
590
591 case AB_JUMPAND:
592 if (!pCurVal[0])
593 {
594 fJump = true;
595 pCPos += pCPos->Par.i;
596 }
597 else
598 {
599 PopValue();
600 }
601 break;
602
603 case AB_JUMPOR:
604 if (!!pCurVal[0])
605 {
606 fJump = true;
607 pCPos += pCPos->Par.i;
608 }
609 else
610 {
611 PopValue();
612 }
613 break;
614
615 case AB_JUMPNNIL: // ??
616 {
617 if (pCurVal[0].GetType() != C4V_Nil)
618 {
619 fJump = true;
620 pCPos += pCPos->Par.i;
621 }
622 else
623 {
624 PopValue();
625 }
626 break;
627 }
628
629 case AB_CONDN:
630 if (!pCurVal[0])
631 {
632 fJump = true;
633 pCPos += pCPos->Par.i;
634 }
635 PopValue();
636 break;
637
638 case AB_COND:
639 if (pCurVal[0])
640 {
641 fJump = true;
642 pCPos += pCPos->Par.i;
643 }
644 PopValue();
645 break;
646
647 case AB_RETURN:
648 {
649 // Trace
650 if (iTraceStart >= 0)
651 {
652 StdStrBuf Buf("T");
653 Buf.AppendChars('>', ContextStackSize() - iTraceStart);
654 LogF("%s%s returned %s", Buf.getData(), pCurCtx->Func->GetName(), pCurVal->GetDataString().getData());
655 }
656
657 C4Value *pReturn = pCurCtx->Return;
658
659 // External call?
660 if (!pReturn)
661 {
662 // Get return value and stop executing.
663 C4Value rVal = *pCurVal;
664 PopValuesUntil(pCurCtx->Pars - 1);
665 PopContext();
666 return rVal;
667 }
668
669 // Save return value
670 if (pCurVal != pReturn)
671 pReturn->Set(*pCurVal);
672
673 // Pop context
674 PopContext();
675
676 // Clear value stack, except return value
677 PopValuesUntil(pReturn);
678
679 // Jump back, continue.
680 pCPos = pCurCtx->CPos + 1;
681 fJump = true;
682
683 break;
684 }
685
686 case AB_FUNC:
687 {
688 // Get function call data
689 C4AulFunc *pFunc = pCPos->Par.f;
690 C4Value *pPars = pCurVal - pFunc->GetParCount() + 1;
691 // Save current position
692 pCurCtx->CPos = pCPos;
693 assert(pCurCtx->Func->GetCode() <= pCPos);
694 // Do the call
695 C4AulBCC *pJump = Call(pFunc, pPars, pPars, nullptr);
696 if (pJump)
697 {
698 pCPos = pJump;
699 fJump = true;
700 }
701 break;
702 }
703
704 case AB_PAR:
705 if (!pCurVal->CheckConversion(C4V_Int))
706 throw C4AulExecError(FormatString("Par: index of type %s, int expected", pCurVal->GetTypeName()).getData());
707 // Push reference to parameter on the stack
708 if (pCurVal->_getInt() >= 0 && pCurVal->_getInt() < pCurCtx->Func->GetParCount())
709 pCurVal->Set(pCurCtx->Pars[pCurVal->_getInt()]);
710 else
711 pCurVal->Set0();
712 break;
713
714 case AB_THIS:
715 if (!pCurCtx->Obj || !pCurCtx->Obj->Status)
716 PushNullVals(1);
717 else
718 PushPropList(pCurCtx->Obj);
719 break;
720
721 case AB_FOREACH_NEXT:
722 {
723 // This should always hold
724 assert(pCurVal->CheckConversion(C4V_Int));
725 int iItem = pCurVal->_getInt();
726 // Check array the first time only
727 if (!iItem)
728 {
729 if (!pCurVal[-1].CheckConversion(C4V_Array))
730 throw C4AulExecError(FormatString("for: array expected, but got %s", pCurVal[-1].GetTypeName()).getData());
731 }
732 C4ValueArray *pArray = pCurVal[-1]._getArray();
733 // No more entries?
734 if (pCurVal->_getInt() >= pArray->GetSize())
735 break;
736 // Get next
737 pCurVal[pCPos->Par.i] = pArray->GetItem(iItem);
738 // Save position
739 pCurVal->SetInt(iItem + 1);
740 // Jump over next instruction
741 pCPos += 2;
742 fJump = true;
743 break;
744 }
745
746 case AB_CALL:
747 case AB_CALLFS:
748 {
749
750 C4Value *pPars = pCurVal - C4AUL_MAX_Par + 1;
751 C4Value *pTargetVal = pCurVal - C4AUL_MAX_Par;
752
753 C4PropList *pDest;
754 if (pTargetVal->CheckConversion(C4V_PropList))
755 {
756 pDest = pTargetVal->_getPropList();
757 }
758 else
759 throw C4AulExecError(FormatString("'->': invalid target type %s, expected proplist", pTargetVal->GetTypeName()).getData());
760
761 // Search function for given context
762 C4AulFunc * pFunc = pDest->GetFunc(pCPos->Par.s);
763 if (!pFunc && pCPos->bccType == AB_CALLFS)
764 {
765 PopValuesUntil(pTargetVal);
766 pTargetVal->Set0();
767 break;
768 }
769
770 // Function not found?
771 if (!pFunc)
772 throw C4AulExecError(FormatString(R"('->': no function "%s" in object "%s")", pCPos->Par.s->GetCStr(), pTargetVal->GetDataString().getData()).getData());
773
774 // Save current position
775 pCurCtx->CPos = pCPos;
776 assert(pCurCtx->Func->GetCode() <= pCPos);
777
778 // adjust parameter count
779 if (pCurVal + 1 - pPars > pFunc->GetParCount())
780 PopValues(pCurVal + 1 - pPars - pFunc->GetParCount());
781 else
782 PushNullVals(pFunc->GetParCount() - (pCurVal + 1 - pPars));
783
784 // Call function
785 C4AulBCC *pNewCPos = Call(pFunc, pTargetVal, pPars, pDest);
786 if (pNewCPos)
787 {
788 // Jump
789 pCPos = pNewCPos;
790 fJump = true;
791 }
792
793 break;
794 }
795
796 case AB_DEBUG:
797 #ifndef NOAULDEBUG
798 if (C4AulDebug *pDebug = C4AulDebug::GetDebugger())
799 pDebug->DebugStep(pCPos, pCurVal);
800 #endif
801 break;
802 }
803
804 // Continue
805 if (!fJump)
806 pCPos++;
807 }
808
809 }
810 catch (C4AulError &)
811 {
812 // Save current position
813 assert(pCurCtx->Func->GetCode() <= pCPos);
814 pCurCtx->CPos = pCPos;
815 throw;
816 }
817 }
818
Call(C4AulFunc * pFunc,C4Value * pReturn,C4Value * pPars,C4PropList * pContext)819 C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4PropList *pContext)
820 {
821 // No object given? Use current context
822 if (!pContext)
823 {
824 assert(pCurCtx >= Contexts);
825 pContext = pCurCtx->Obj;
826 }
827
828 pFunc->CheckParTypes(pPars, true);
829
830 // Script function?
831 C4AulScriptFunc *pSFunc = pFunc->SFunc();
832 if (pSFunc)
833 {
834 // Push a new context
835 C4AulScriptContext ctx;
836 ctx.Obj = pContext;
837 if (ctx.Obj && !ctx.Obj->Status)
838 throw C4AulExecError("using removed object");
839 ctx.Return = pReturn;
840 ctx.Pars = pPars;
841 ctx.Func = pSFunc;
842 ctx.CPos = nullptr;
843 PushContext(ctx);
844
845 // Jump to code
846 return pSFunc->GetCode();
847 }
848 else
849 {
850 if (pContext && !pContext->Status)
851 throw C4AulExecError("using removed object");
852
853 if (DEBUGREC_SCRIPT && Config.General.DebugRec)
854 {
855 StdStrBuf sCallText;
856 if (pContext && pContext->GetObject())
857 sCallText.AppendFormat("Object(%d): ", pContext->GetObject()->Number);
858 sCallText.Append(pFunc->GetName());
859 sCallText.AppendChar('(');
860 for (int i=0; i<C4AUL_MAX_Par; ++i)
861 {
862 if (i) sCallText.AppendChar(',');
863 C4Value &rV = pPars[i];
864 if (rV.GetType() == C4V_String)
865 {
866 C4String *s = rV.getStr();
867 if (!s)
868 sCallText.Append("(Snull)");
869 else
870 {
871 sCallText.Append(R"(")");
872 sCallText.Append(s->GetData());
873 sCallText.Append(R"(")");
874 }
875 }
876 else
877 sCallText.Append(rV.GetDataString());
878 }
879 sCallText.AppendChar(')');
880 sCallText.AppendChar(';');
881 AddDbgRec(RCT_AulFunc, sCallText.getData(), sCallText.getLength()+1);
882 }
883
884 // Execute
885 #ifdef _DEBUG
886 C4AulScriptContext *pCtx = pCurCtx;
887 #endif
888 if (pReturn > pCurVal)
889 PushValue(pFunc->Exec(pContext, pPars, true));
890 else
891 pReturn->Set(pFunc->Exec(pContext, pPars, true));
892 #ifdef _DEBUG
893 assert(pCtx == pCurCtx);
894 #endif
895
896 // Remove parameters from stack
897 PopValuesUntil(pReturn);
898
899 // Continue
900 return nullptr;
901 }
902
903 }
904
StartTrace()905 void C4AulExec::StartTrace()
906 {
907 if (iTraceStart < 0)
908 iTraceStart = ContextStackSize();
909 }
910
StartProfiling(C4ScriptHost * pProfiledScript)911 void C4AulExec::StartProfiling(C4ScriptHost *pProfiledScript)
912 {
913 // stop previous profiler run
914 if (fProfiling) StopProfiling();
915 fProfiling = true;
916 // resets profling times and starts recording the times
917 this->pProfiledScript = pProfiledScript;
918 C4TimeMilliseconds tNow = C4TimeMilliseconds::Now();
919 tDirectExecStart = tNow; // in case profiling is started from DirectExec
920 tDirectExecTotal = 0;
921 for (C4AulScriptContext *pCtx = Contexts; pCtx <= pCurCtx; ++pCtx)
922 pCtx->tTime = tNow;
923 }
924
PushContext(const C4AulScriptContext & rContext)925 void C4AulExec::PushContext(const C4AulScriptContext &rContext)
926 {
927 if (pCurCtx >= Contexts + MAX_CONTEXT_STACK - 1)
928 throw C4AulExecError("context stack overflow");
929 *++pCurCtx = rContext;
930 // Trace?
931 if (iTraceStart >= 0)
932 {
933 StdStrBuf Buf("T");
934 Buf.AppendChars('>', ContextStackSize() - iTraceStart);
935 pCurCtx->dump(Buf);
936 }
937 // Profiler: Safe time to measure difference afterwards
938 if (fProfiling) pCurCtx->tTime = C4TimeMilliseconds::Now();
939 }
940
PopContext()941 void C4AulExec::PopContext()
942 {
943 if (pCurCtx < Contexts)
944 throw C4AulExecError("internal error: context stack underflow");
945 // Profiler adding up times
946 if (fProfiling)
947 {
948 uint32_t dt = C4TimeMilliseconds::Now() - pCurCtx->tTime;
949 if (pCurCtx->Func)
950 pCurCtx->Func->tProfileTime += dt;
951 }
952 // Trace done?
953 if (iTraceStart >= 0)
954 {
955 if (ContextStackSize() <= iTraceStart)
956 {
957 iTraceStart = -1;
958 }
959 }
960 pCurCtx--;
961 }
962
StartProfiling(C4ScriptHost * pScript)963 void C4AulProfiler::StartProfiling(C4ScriptHost *pScript)
964 {
965 AulExec.StartProfiling(pScript);
966 if(pScript)
967 ResetTimes(pScript->GetPropList());
968 else
969 ResetTimes();
970 }
971
StopProfiling()972 void C4AulProfiler::StopProfiling()
973 {
974 if (!AulExec.IsProfiling()) return;
975 AulExec.StopProfiling();
976 // collect profiler times
977 C4AulProfiler Profiler;
978 Profiler.CollectEntry(nullptr, AulExec.tDirectExecTotal);
979 if(AulExec.pProfiledScript)
980 Profiler.CollectTimes(AulExec.pProfiledScript->GetPropList());
981 else
982 Profiler.CollectTimes();
983 Profiler.Show();
984 }
985
CollectEntry(C4AulScriptFunc * pFunc,uint32_t tProfileTime)986 void C4AulProfiler::CollectEntry(C4AulScriptFunc *pFunc, uint32_t tProfileTime)
987 {
988 // zero entries are not collected to have a cleaner list
989 if (!tProfileTime) return;
990 // add entry to list
991 Entry e;
992 e.pFunc = pFunc;
993 e.tProfileTime = tProfileTime;
994 Times.push_back(e);
995 }
996
Show()997 void C4AulProfiler::Show()
998 {
999 // sort by time
1000 std::sort(Times.rbegin(), Times.rend());
1001 // display them
1002 Log("Profiler statistics:");
1003 Log("==============================");
1004 typedef std::vector<Entry> EntryList;
1005 for (auto & e : Times)
1006 {
1007 LogF("%05ums\t%s", e.tProfileTime, e.pFunc ? (e.pFunc->GetFullName().getData()) : "Direct exec");
1008 }
1009 Log("==============================");
1010 // done!
1011 }
1012
DirectExec(C4PropList * p,const char * szScript,const char * szContext,bool fPassErrors,C4AulScriptContext * context,bool parse_function)1013 C4Value C4AulExec::DirectExec(C4PropList *p, const char *szScript, const char *szContext, bool fPassErrors, C4AulScriptContext* context, bool parse_function)
1014 {
1015 if (DEBUGREC_SCRIPT && Config.General.DebugRec)
1016 {
1017 AddDbgRec(RCT_DirectExec, szScript, strlen(szScript)+1);
1018 int32_t iObjNumber = p && p->GetPropListNumbered() ? p->GetPropListNumbered()->Number : -1;
1019 AddDbgRec(RCT_DirectExec, &iObjNumber, sizeof(int32_t));
1020 }
1021 // profiler
1022 StartDirectExec();
1023 C4PropListStatic * script = ::GameScript.GetPropList();
1024 if (p && p->IsStatic())
1025 script = p->IsStatic();
1026 else if (p && p->GetDef())
1027 script = p->GetDef();
1028 // Add a new function
1029 auto pFunc = std::make_unique<C4AulScriptFunc>(script, nullptr, nullptr, szScript);
1030 // Parse function
1031 try
1032 {
1033 if (parse_function)
1034 {
1035 // Expect a full function (e.g. "func foo() { return bar(); }")
1036 pFunc->ParseDirectExecFunc(&::ScriptEngine, context);
1037 }
1038 else
1039 {
1040 // Expect a single statement (e.g. "bar()")
1041 pFunc->ParseDirectExecStatement(&::ScriptEngine, context);
1042 }
1043 C4AulParSet Pars;
1044 C4Value vRetVal(Exec(pFunc.get(), p, Pars.Par, fPassErrors));
1045 // profiler
1046 StopDirectExec();
1047 return vRetVal;
1048 }
1049 catch (C4AulError &ex)
1050 {
1051 if(fPassErrors)
1052 throw;
1053 ::ScriptEngine.GetErrorHandler()->OnError(ex.what());
1054 LogCallStack();
1055 StopDirectExec();
1056 return C4VNull;
1057 }
1058 }
1059
ResetTimes(C4PropListStatic * p)1060 void C4AulProfiler::ResetTimes(C4PropListStatic * p)
1061 {
1062 // zero all profiler times of owned functions
1063 C4AulScriptFunc *pSFunc;
1064 for (C4String *pFn = p->EnumerateOwnFuncs(); pFn; pFn = p->EnumerateOwnFuncs(pFn))
1065 if ((pSFunc = p->GetFunc(pFn)->SFunc()))
1066 pSFunc->tProfileTime = 0;
1067 }
1068
CollectTimes(C4PropListStatic * p)1069 void C4AulProfiler::CollectTimes(C4PropListStatic * p)
1070 {
1071 // collect all profiler times of owned functions
1072 C4AulScriptFunc *pSFunc;
1073 for (C4String *pFn = p->EnumerateOwnFuncs(); pFn; pFn = p->EnumerateOwnFuncs(pFn))
1074 if ((pSFunc = p->GetFunc(pFn)->SFunc()))
1075 CollectEntry(pSFunc, pSFunc->tProfileTime);
1076 }
1077
ResetTimes()1078 void C4AulProfiler::ResetTimes()
1079 {
1080 // zero all profiler times of owned functions
1081 ResetTimes(::ScriptEngine.GetPropList());
1082 // reset sub-scripts
1083 for (C4ScriptHost *pScript = ::ScriptEngine.Child0; pScript; pScript = pScript->Next)
1084 ResetTimes(pScript->GetPropList());
1085 }
1086
CollectTimes()1087 void C4AulProfiler::CollectTimes()
1088 {
1089 // collect all profiler times of owned functions
1090 CollectTimes(::ScriptEngine.GetPropList());
1091 // collect sub-scripts
1092 for (C4ScriptHost *pScript = ::ScriptEngine.Child0; pScript; pScript = pScript->Next)
1093 CollectTimes(pScript->GetPropList());
1094 }
1095