1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// executes script functions
19
20#include <C4Include.h>
21#include <C4Aul.h>
22
23#include <C4Object.h>
24#include <C4Config.h>
25#include <C4Game.h>
26#include <C4ValueHash.h>
27#include <C4Wrappers.h>
28
29#include <format>
30
31C4AulExecError::C4AulExecError(C4Object *pObj, const std::string_view error)
32 : cObj(pObj)
33{
34 // direct error message string
35 if (!error.empty())
36 {
37 message = error;
38 }
39 else
40 {
41 message = "(no error message)";
42 }
43}
44
45void C4AulExecError::show() const
46{
47 // log
48 C4AulError::show();
49 // debug mode object message
50 if (Game.DebugMode)
51 if (cObj)
52 Game.Messages.New(iType: C4GM_Target, Text: StdStrBuf{message.c_str(), message.size(), false}, pTarget: cObj, iPlayer: NO_OWNER);
53 else
54 Game.Messages.New(iType: C4GM_Global, Text: StdStrBuf{message.c_str(), message.size(), false}, pTarget: nullptr, iPlayer: ANY_OWNER);
55}
56
57bool C4AulContext::CalledWithStrictNil() const noexcept
58{
59 return Caller && Caller->Func->HasStrictNil();
60}
61
62const int MAX_CONTEXT_STACK = 512;
63const int MAX_VALUE_STACK = 1024;
64
65void C4AulScriptContext::dump(std::string Dump)
66{
67 bool fDirectExec = !*Func->Name;
68 if (!fDirectExec)
69 {
70 // Function name
71 Dump += Func->Name;
72 // Parameters
73 Dump += '(';
74 int iNullPars = 0;
75 for (int i = 0; i < C4AUL_MAX_Par; i++)
76 if (Pars + i < Vars)
77 if (!Pars[i].IsRef() && Pars[i].GetType() == C4V_Any)
78 iNullPars++;
79 else
80 {
81 if (i > iNullPars)
82 Dump += ',';
83 // Insert missing null parameters
84 while (iNullPars > 0)
85 {
86 Dump += "nil,";
87 iNullPars--;
88 }
89 // Insert parameter
90 Dump += Pars[i].GetDataString();
91 }
92 Dump += ')';
93 }
94 else
95 Dump += Func->Owner->ScriptName;
96 // Context
97 if (Obj)
98 Dump += std::format(fmt: " (obj {})", args: C4VObj(pObj: Obj).GetDataString());
99 else if (Func->Owner->Def != nullptr)
100 Dump += std::format(fmt: " (def {})", args: Func->Owner->Def->Name.getData());
101 // Script
102 if (!fDirectExec && Func->Owner)
103 Dump += std::format(fmt: " ({}:{})",
104 args&: Func->pOrgScript->ScriptName,
105 args: SGetLine(szText: Func->pOrgScript->GetScript(), cpPosition: CPos ? CPos->SPos : Func->Script));
106 // Log it
107 DebugLog(message: Dump);
108}
109
110class C4AulExec
111{
112public:
113 C4AulExec()
114 : pCurCtx(Contexts - 1), pCurVal(Values - 1), iTraceStart(-1) {}
115
116private:
117 C4AulScriptContext Contexts[MAX_CONTEXT_STACK];
118 C4Value Values[MAX_VALUE_STACK];
119
120 C4AulScriptContext *pCurCtx;
121 C4Value *pCurVal;
122
123 std::shared_ptr<spdlog::logger> traceLogger;
124 int iTraceStart;
125 bool fProfiling;
126 time_t tDirectExecStart, tDirectExecTotal; // profiler time for DirectExec
127 C4AulScript *pProfiledScript;
128
129public:
130 C4Value Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, const C4Value pPars[], bool fPassErrors, bool fTemporaryScript = false);
131 C4Value Exec(C4AulBCC *pCPos, bool fPassErrors);
132
133 void StartTrace();
134 void StartProfiling(C4AulScript *pScript); // resets profling times and starts recording the times
135 void StopProfiling(); // stop the profiler and displays results
136 void AbortProfiling() { fProfiling = false; }
137 inline void StartDirectExec() { if (fProfiling) tDirectExecStart = timeGetTime(); }
138 inline void StopDirectExec() { if (fProfiling) tDirectExecTotal += timeGetTime() - tDirectExecStart; }
139
140private:
141 void PushContext(const C4AulScriptContext &rContext)
142 {
143 if (pCurCtx >= Contexts + MAX_CONTEXT_STACK - 1)
144 throw C4AulExecError(pCurCtx->Obj, "context stack overflow!");
145 *++pCurCtx = rContext;
146 // Trace?
147 if (iTraceStart >= 0)
148 {
149 std::string buf{"T"};
150 buf.append(n: ContextStackSize() - iTraceStart, c: '>');
151 pCurCtx->dump(Dump: std::move(buf));
152 }
153 // Profiler: Safe time to measure difference afterwards
154 if (fProfiling) pCurCtx->tTime = timeGetTime();
155 }
156
157 void PopContext()
158 {
159 if (pCurCtx < Contexts)
160 throw C4AulExecError(pCurCtx->Obj, "context stack underflow!");
161 // Profiler adding up times
162 if (fProfiling)
163 {
164 time_t dt = timeGetTime() - pCurCtx->tTime;
165 if (dt && pCurCtx->Func)
166 pCurCtx->Func->tProfileTime += dt;
167 }
168 // Trace done?
169 if (iTraceStart >= 0)
170 {
171 if (ContextStackSize() <= iTraceStart)
172 {
173 iTraceStart = -1;
174 traceLogger.reset();
175 }
176 }
177 if (pCurCtx->TemporaryScript)
178 delete pCurCtx->Func->Owner;
179 pCurCtx--;
180 }
181
182 void CheckOverflow(intptr_t iCnt)
183 {
184 if (ValueStackSize() + iCnt > MAX_VALUE_STACK)
185 throw C4AulExecError(pCurCtx->Obj, "internal error: value stack overflow!");
186 }
187
188 void PushString(C4String *Str)
189 {
190 CheckOverflow(iCnt: 1);
191 (++pCurVal)->SetString(Str);
192 }
193
194 void PushArray(C4ValueArray *Array)
195 {
196 CheckOverflow(iCnt: 1);
197 (++pCurVal)->SetArray(Array);
198 }
199
200 void PushMap(C4ValueHash *Map)
201 {
202 CheckOverflow(iCnt: 1);
203 (++pCurVal)->SetMap(Map);
204 }
205
206 void PushValue(const C4Value &rVal)
207 {
208 CheckOverflow(iCnt: 1);
209 (++pCurVal)->Set(rVal);
210 }
211
212 void PushValueRef(C4Value &rVal)
213 {
214 CheckOverflow(iCnt: 1);
215 (++pCurVal)->SetRef(&rVal);
216 }
217
218 void PushNullVals(intptr_t iCnt)
219 {
220 CheckOverflow(iCnt);
221 pCurVal += iCnt;
222 }
223
224 bool PopValue()
225 {
226 if (LocalValueStackSize() < 1)
227 throw C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
228 (pCurVal--)->Set0();
229 return true;
230 }
231
232 void PopValues(intptr_t n)
233 {
234 if (LocalValueStackSize() < n)
235 throw C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
236 while (n--)
237 (pCurVal--)->Set0();
238 }
239
240 void PopValuesUntil(C4Value *pUntilVal)
241 {
242 if (pUntilVal < Values - 1)
243 throw C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
244 while (pCurVal > pUntilVal)
245 (pCurVal--)->Set0();
246 }
247
248 int ContextStackSize() const
249 {
250 return pCurCtx - Contexts + 1;
251 }
252
253 int ValueStackSize() const
254 {
255 return pCurVal - Values + 1;
256 }
257
258 int LocalValueStackSize() const
259 {
260 return ContextStackSize()
261 ? pCurVal - pCurCtx->Vars - pCurCtx->Func->VarNamed.iSize + 1
262 : pCurVal - Values + 1;
263 }
264
265 template<bool asReference = false, bool allowAny = true>
266 void CheckOpPar(C4Value *value, C4V_Type expectedType, const char *operatorName, const char *operandPosition = "")
267 {
268 const bool isAtLeastStrict3 = pCurCtx->Func->pOrgScript->Strict >= C4AulScriptStrict::STRICT3;
269 if constexpr (asReference)
270 {
271 if (!value->ConvertTo(vtToType: C4V_pC4Value))
272 {
273 throw C4AulExecError(pCurCtx->Obj,
274 std::format(fmt: "operator \"{}\"{}: got \"{}\", but expected \"{}&\"!",
275 args&: operatorName, args&: operandPosition, args: value->GetTypeInfo(), args: GetC4VName(Type: expectedType)));
276 }
277
278 if (!isAtLeastStrict3 && (expectedType != C4V_pC4Value) && !*value)
279 {
280 value->GetRefVal().Set0();
281 }
282 if (!value->GetRefVal().ConvertTo(vtToType: expectedType))
283 {
284 throw C4AulExecError(pCurCtx->Obj,
285 std::format(fmt: "operator \"{}\"{}: got \"{}&\", but expected \"{}&\"!",
286 args&: operatorName, args&: operandPosition, args: value->GetRefVal().GetTypeInfo(), args: GetC4VName(Type: expectedType)));
287 }
288 }
289 else
290 {
291 if (!isAtLeastStrict3 && (expectedType != C4V_pC4Value) && !*value)
292 {
293 value->Set0();
294 }
295 if (!value->ConvertTo(vtToType: expectedType))
296 throw C4AulExecError(pCurCtx->Obj,
297 std::format(fmt: "operator \"{}\"{}: got \"{}\", but expected \"{}\"!",
298 args&: operatorName, args&: operandPosition, args: value->GetTypeInfo(), args: GetC4VName(Type: expectedType)));
299 }
300 if constexpr (!allowAny)
301 {
302 if (isAtLeastStrict3 && value->GetType() == C4V_Any)
303 throw C4AulExecError(pCurCtx->Obj,
304 std::format(fmt: "operator \"{}\"{}: got nil, but expected \"{}\"!",
305 args&: operatorName, args&: operandPosition, args: GetC4VName(Type: expectedType)));
306 }
307 }
308
309 template<C4V_Type leftAsReference = C4V_Any, C4V_Type rightAsReference = C4V_Any, bool leftAllowAny = true, bool rightAllowAny = true>
310 void CheckOpPars(intptr_t iOpID)
311 {
312 CheckOpPar<leftAsReference != C4V_Any, leftAllowAny>(&pCurVal[-1], leftAsReference != C4V_Any ? leftAsReference : C4ScriptOpMap[iOpID].Type1, C4ScriptOpMap[iOpID].Identifier, " left side");
313 CheckOpPar<rightAsReference != C4V_Any, rightAllowAny>(pCurVal, rightAsReference != C4V_Any ? rightAsReference : C4ScriptOpMap[iOpID].Type2, C4ScriptOpMap[iOpID].Identifier, " right side");
314 }
315
316 template<C4V_Type asReference = C4V_Any, bool allowAny = true>
317 void CheckOpPar(intptr_t iOpID)
318 {
319 if constexpr (asReference != C4V_Any)
320 CheckOpPar<true>(value: pCurVal, expectedType: asReference, operatorName: C4ScriptOpMap[iOpID].Identifier);
321 else
322 CheckOpPar<false>(value: pCurVal, expectedType: C4ScriptOpMap[iOpID].Type1, operatorName: C4ScriptOpMap[iOpID].Identifier);
323 }
324
325 C4AulBCC *Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4Object *pObj = nullptr, C4Def *pDef = nullptr, bool globalContext = false);
326};
327
328C4AulExec AulExec;
329
330C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, const C4Value *pnPars, bool fPassErrors, bool fTemporaryScript)
331{
332 // Push parameters
333 C4Value *pPars = pCurVal + 1;
334 if (pnPars)
335 for (int i = 0; i < C4AUL_MAX_Par; i++)
336 PushValue(rVal: pnPars[i]);
337
338 // Push variables
339 C4Value *pVars = pCurVal + 1;
340 PushNullVals(iCnt: pSFunc->VarNamed.iSize);
341
342 // Derive definition context from function owner (legacy)
343 C4Def *pDef = pObj ? pObj->Def : pSFunc->Owner->Def;
344
345 // Executing function in right context?
346 // This must hold: The scripter might try to access local variables that don't exist!
347 assert(!pSFunc->Owner->Def || pDef == pSFunc->Owner->Def);
348
349 // Push a new context
350 C4AulScriptContext ctx;
351 ctx.Obj = pObj;
352 ctx.Def = pDef;
353 ctx.Return = nullptr;
354 ctx.Pars = pPars;
355 ctx.Vars = pVars;
356 ctx.Func = pSFunc;
357 ctx.TemporaryScript = fTemporaryScript;
358 ctx.CPos = nullptr;
359 ctx.Caller = nullptr;
360 PushContext(rContext: ctx);
361
362 // Execute
363 return Exec(pCPos: pSFunc->Code, fPassErrors);
364}
365
366C4Value C4AulExec::Exec(C4AulBCC *pCPos, bool fPassErrors)
367{
368 // Save start context
369 C4AulScriptContext *pOldCtx = pCurCtx;
370
371 try
372 {
373 for (;;)
374 {
375 bool fJump = false;
376 switch (pCPos->bccType)
377 {
378 case AB_NIL:
379 PushValue(rVal: C4VNull);
380 break;
381
382 case AB_INT:
383 PushValue(rVal: C4VInt(iVal: static_cast<C4ValueInt>(pCPos->bccX)));
384 break;
385
386 case AB_BOOL:
387 PushValue(rVal: C4VBool(fVal: !!pCPos->bccX));
388 break;
389
390 case AB_STRING:
391 PushString(Str: reinterpret_cast<C4String *>(pCPos->bccX));
392 break;
393
394 case AB_C4ID:
395 PushValue(rVal: C4VID(idVal: static_cast<C4ID>(pCPos->bccX)));
396 break;
397
398 case AB_EOFN:
399 throw C4AulExecError(pCurCtx->Obj, "function didn't return");
400
401 case AB_ERR:
402 throw C4AulExecError(pCurCtx->Obj, "syntax error: see previous parser error for details.");
403
404 case AB_PARN_R:
405 PushValueRef(rVal&: pCurCtx->Pars[pCPos->bccX]);
406 break;
407 case AB_PARN_V:
408 PushValue(rVal: pCurCtx->Pars[pCPos->bccX]);
409 break;
410
411 case AB_VARN_R:
412 PushValueRef(rVal&: pCurCtx->Vars[pCPos->bccX]);
413 break;
414 case AB_VARN_V:
415 PushValue(rVal: pCurCtx->Vars[pCPos->bccX]);
416 break;
417
418 case AB_LOCALN_R: case AB_LOCALN_V:
419 if (!pCurCtx->Obj)
420 throw C4AulExecError(pCurCtx->Obj, "can't access local variables in a definition call!");
421 if (pCurCtx->Func->Owner->Def != pCurCtx->Obj->Def)
422 {
423 const auto localName = pCurCtx->Func->Owner->Def->Script.LocalNamed.pNames[pCPos->bccX];
424 if (pCurCtx->Func->pOrgScript->Strict >= C4AulScriptStrict::STRICT3 || pCurCtx->Obj->LocalNamed.pNames->iSize <= pCPos->bccX)
425 {
426 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "can't access local variable \"{}\" after ChangeDef!", args: localName));
427 }
428
429 const auto actualLocalName = pCurCtx->Obj->Def->Script.LocalNamed.pNames[pCPos->bccX];
430
431 if (!SEqual(szStr1: localName, szStr2: actualLocalName))
432 {
433 DebugLog(level: spdlog::level::warn, fmt: "accessing local variable \"{}\" actually accesses \"{}\" because of illegal access after ChangeDef", args: localName, args: actualLocalName);
434 pCurCtx->dump(Dump: " by: ");
435 }
436 }
437 if (pCPos->bccType == AB_LOCALN_R)
438 PushValueRef(rVal&: *pCurCtx->Obj->LocalNamed.GetItem(iNr: pCPos->bccX));
439 else
440 PushValue(rVal: *pCurCtx->Obj->LocalNamed.GetItem(iNr: pCPos->bccX));
441 break;
442
443 case AB_GLOBALN_R:
444 PushValueRef(rVal&: *Game.ScriptEngine.GlobalNamed.GetItem(iNr: pCPos->bccX));
445 break;
446 case AB_GLOBALN_V:
447 PushValue(rVal: *Game.ScriptEngine.GlobalNamed.GetItem(iNr: pCPos->bccX));
448 break;
449 // prefix
450 case AB_Inc1: // ++
451 CheckOpPar<C4V_Int, false>(iOpID: pCPos->bccX);
452 ++pCurVal->GetData().Int;
453 pCurVal->HintType(type: C4V_Int);
454 break;
455 case AB_Dec1: // --
456 CheckOpPar<C4V_Int, false>(iOpID: pCPos->bccX);
457 --pCurVal->GetData().Int;
458 pCurVal->HintType(type: C4V_Int);
459 break;
460 case AB_BitNot: // ~
461 CheckOpPar<C4V_Any, false>(iOpID: pCPos->bccX);
462 pCurVal->SetInt(~pCurVal->_getInt());
463 break;
464 case AB_Not: // !
465 CheckOpPar(iOpID: pCPos->bccX);
466 pCurVal->SetBool(!pCurVal->_getRaw());
467 break;
468 case AB_Neg: // -
469 CheckOpPar<C4V_Any, false>(iOpID: pCPos->bccX);
470 pCurVal->SetInt(-pCurVal->_getInt());
471 break;
472 // postfix (whithout second statement)
473 case AB_Inc1_Postfix: // ++
474 {
475 CheckOpPar<C4V_Int, false>(iOpID: pCPos->bccX);
476 auto &orig = pCurVal->GetRefVal();
477 pCurVal->SetInt(orig._getInt());
478 ++orig.GetData().Int;
479 orig.HintType(type: C4V_Int);
480 break;
481 }
482 case AB_Dec1_Postfix: // --
483 {
484 CheckOpPar<C4V_Int, false>(iOpID: pCPos->bccX);
485 auto &orig = pCurVal->GetRefVal();
486 pCurVal->SetInt(orig._getInt());
487 --orig.GetData().Int;
488 orig.HintType(type: C4V_Int);
489 break;
490 }
491 // postfix
492 case AB_Pow: // **
493 {
494 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
495 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
496 pPar1->SetInt(Pow(base: pPar1->_getInt(), exponent: pPar2->_getInt()));
497 PopValue();
498 break;
499 }
500 case AB_Div: // /
501 {
502 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
503 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
504 if (pPar2->_getInt())
505 pPar1->SetInt(pPar1->_getInt() / pPar2->_getInt());
506 else
507 pPar1->Set0();
508 PopValue();
509 break;
510 }
511 case AB_Mul: // *
512 {
513 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
514 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
515 pPar1->SetInt(pPar1->_getInt() * pPar2->_getInt());
516 PopValue();
517 break;
518 }
519 case AB_Mod: // %
520 {
521 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
522 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
523 if (pPar2->_getInt())
524 pPar1->SetInt(pPar1->_getInt() % pPar2->_getInt());
525 else
526 pPar1->Set0();
527 PopValue();
528 break;
529 }
530 case AB_Sub: // -
531 {
532 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
533 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
534 pPar1->SetInt(pPar1->_getInt() - pPar2->_getInt());
535 PopValue();
536 break;
537 }
538 case AB_Sum: // +
539 {
540 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
541 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
542 pPar1->SetInt(pPar1->_getInt() + pPar2->_getInt());
543 PopValue();
544 break;
545 }
546 case AB_LeftShift: // <<
547 {
548 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
549 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
550 pPar1->SetInt(pPar1->_getInt() << pPar2->_getInt());
551 PopValue();
552 break;
553 }
554 case AB_RightShift: // >>
555 {
556 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
557 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
558 pPar1->SetInt(pPar1->_getInt() >> pPar2->_getInt());
559 PopValue();
560 break;
561 }
562 case AB_LessThan: // <
563 {
564 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
565 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
566 pPar1->SetBool(pPar1->_getInt() < pPar2->_getInt());
567 PopValue();
568 break;
569 }
570 case AB_LessThanEqual: // <=
571 {
572 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
573 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
574 pPar1->SetBool(pPar1->_getInt() <= pPar2->_getInt());
575 PopValue();
576 break;
577 }
578 case AB_GreaterThan: // >
579 {
580 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
581 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
582 pPar1->SetBool(pPar1->_getInt() > pPar2->_getInt());
583 PopValue();
584 break;
585 }
586 case AB_GreaterThanEqual: // >=
587 {
588 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
589 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
590 pPar1->SetBool(pPar1->_getInt() >= pPar2->_getInt());
591 PopValue();
592 break;
593 }
594 case AB_Concat: // ..
595 case AB_ConcatIt: // ..=
596 {
597 const auto operatorName = C4ScriptOpMap[pCPos->bccX].Identifier;
598 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
599 C4Value *pPar1 = pCurVal - 1;
600 C4Value *pPar2 = pCurVal;
601 const auto assignmentOperator = pCPos->bccType == AB_ConcatIt;
602 if (assignmentOperator) pPar1 = &pPar1->GetRefVal();
603 switch (pPar1->GetType())
604 {
605 case C4V_Map:
606 {
607 CheckOpPar(value: pPar2, expectedType: C4V_Map, operatorName, operandPosition: " right side");
608 const auto lhsMap = pPar1->_getMap();
609 // copy if necessary
610 const auto lhs = !assignmentOperator ? static_cast<C4ValueHash *>(lhsMap->IncRef()->IncElementRef()) : lhsMap;
611 for (const auto &[key, value] : *pPar2->_getMap())
612 (*lhs)[key] = value;
613
614 if (!assignmentOperator)
615 {
616 pPar1->SetMap(lhs);
617 lhs->DecElementRef();
618 lhs->DecRef();
619 }
620 PopValue();
621 break;
622 }
623 case C4V_Array:
624 {
625 CheckOpPar(value: pPar2, expectedType: C4V_Array, operatorName, operandPosition: " right side");
626 const auto lhsSize = pPar1->_getArray()->GetSize();
627 const auto rhsSize = pPar2->_getArray()->GetSize();
628 pPar1->SetArrayLength(size: lhsSize + rhsSize, cthr: pCurCtx);
629 auto &lhs = *pPar1->_getArray();
630 auto &rhs = *pPar2->_getArray();
631
632 for (auto i = 0; i < rhsSize; ++i)
633 lhs[lhsSize + i] = rhs[i];
634
635 PopValue();
636 break;
637 }
638 default:
639 {
640 auto par1String = pPar1->toString();
641 if (!par1String)
642 {
643 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "operator \"{}\" left side: can not convert \"{}\" to \"string\", \"array\" or \"map\"!", args: operatorName, args: GetC4VName(Type: pPar1->GetType())));
644 }
645 auto par2String = pPar2->toString();
646 if (!par2String)
647 {
648 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "operator \"{}\" right side: can not convert \"{}\" to \"string\"!", args: operatorName, args: GetC4VName(Type: pPar2->GetType())));
649 }
650
651 par1String->Append(Buf2: *par2String);
652 pPar1->SetString(new C4String(std::move(*par1String), &pCurCtx->Func->Owner->GetEngine()->Strings));
653 PopValue();
654 break;
655 }
656 }
657 break;
658 }
659 case AB_EqualIdent: // old ==
660 {
661 CheckOpPars(iOpID: pCPos->bccX);
662 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
663 pPar1->SetBool(pPar1->Equals(other: *pPar2, strict: C4AulScriptStrict::NONSTRICT));
664 PopValue();
665 break;
666 }
667 case AB_Equal: // new ==
668 {
669 CheckOpPars(iOpID: pCPos->bccX);
670 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
671 pPar1->SetBool(pPar1->Equals(other: *pPar2, strict: pCurCtx->Func->pOrgScript->Strict));
672 PopValue();
673 break;
674 }
675 case AB_NotEqualIdent: // old !=
676 {
677 CheckOpPars(iOpID: pCPos->bccX);
678 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
679 pPar1->SetBool(!pPar1->Equals(other: *pPar2, strict: C4AulScriptStrict::NONSTRICT));
680 PopValue();
681 break;
682 }
683 case AB_NotEqual: // new !=
684 {
685 CheckOpPars(iOpID: pCPos->bccX);
686 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
687 pPar1->SetBool(!pPar1->Equals(other: *pPar2, strict: pCurCtx->Func->pOrgScript->Strict));
688 PopValue();
689 break;
690 }
691 case AB_SEqual: // S=, eq
692 {
693 CheckOpPars(iOpID: pCPos->bccX);
694 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
695 pPar1->SetBool(SEqual(szStr1: pPar1->_getStr() ? pPar1->_getStr()->Data.getData() : "",
696 szStr2: pPar2->_getStr() ? pPar2->_getStr()->Data.getData() : ""));
697 PopValue();
698 break;
699 }
700 case AB_SNEqual: // ne
701 {
702 CheckOpPars(iOpID: pCPos->bccX);
703 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
704 pPar1->SetBool(!SEqual(szStr1: pPar1->_getStr() ? pPar1->_getStr()->Data.getData() : "",
705 szStr2: pPar2->_getStr() ? pPar2->_getStr()->Data.getData() : ""));
706 PopValue();
707 break;
708 }
709 case AB_BitAnd: // &
710 {
711 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
712 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
713 pPar1->SetInt(pPar1->_getInt() & pPar2->_getInt());
714 PopValue();
715 break;
716 }
717 case AB_BitXOr: // ^
718 {
719 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
720 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
721 pPar1->SetInt(pPar1->_getInt() ^ pPar2->_getInt());
722 PopValue();
723 break;
724 }
725 case AB_BitOr: // |
726 {
727 CheckOpPars<C4V_Any, C4V_Any, false, false>(iOpID: pCPos->bccX);
728 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
729 pPar1->SetInt(pPar1->_getInt() | pPar2->_getInt());
730 PopValue();
731 break;
732 }
733 case AB_And: // &&
734 {
735 CheckOpPars(iOpID: pCPos->bccX);
736 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
737 pPar1->SetBool(pPar1->_getRaw() && pPar2->_getRaw());
738 PopValue();
739 break;
740 }
741 case AB_Or: // ||
742 {
743 CheckOpPars(iOpID: pCPos->bccX);
744 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
745 pPar1->SetBool(pPar1->_getRaw() || pPar2->_getRaw());
746 PopValue();
747 break;
748 }
749
750 case AB_PowIt: // **=
751 {
752 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
753 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
754 pPar1->GetData().Int = Pow(base: pPar1->GetData().Int, exponent: pPar2->_getInt());
755 pPar1->HintType(type: C4V_Int);
756 PopValue();
757 break;
758 }
759 case AB_MulIt: // *=
760 {
761 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
762 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
763 pPar1->GetData().Int *= pPar2->_getInt();
764 pCurVal->HintType(type: C4V_Int);
765 PopValue();
766 break;
767 }
768 case AB_DivIt: // /=
769 {
770 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
771 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
772 pPar1->GetData().Int = pPar2->_getInt() ? pPar1->GetData().Int / pPar2->_getInt() : 0;
773 pPar1->HintType(type: C4V_Int);
774 PopValue();
775 break;
776 }
777 case AB_ModIt: // %=
778 {
779 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
780 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
781 pPar1->GetData().Int = pPar2->_getInt() ? pPar1->GetData().Int % pPar2->_getInt() : 0;
782 pPar1->HintType(type: C4V_Int);
783 PopValue();
784 break;
785 }
786 case AB_Inc: // +=
787 {
788 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
789 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
790 pPar1->GetData().Int += pPar2->_getInt();
791 pPar1->HintType(type: C4V_Int);
792 PopValue();
793 break;
794 }
795 case AB_Dec: // -=
796 {
797 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
798 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
799 pPar1->GetData().Int -= pPar2->_getInt();
800 pPar1->HintType(type: C4V_Int);
801 PopValue();
802 break;
803 }
804 case AB_LeftShiftIt: // <<=
805 {
806 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
807 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
808 pPar1->GetData().Int <<= pPar2->_getInt();
809 pPar1->HintType(type: C4V_Int);
810 PopValue();
811 break;
812 }
813 case AB_RightShiftIt: // >>=
814 {
815 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
816 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
817 pPar1->GetData().Int >>= pPar2->_getInt();
818 pPar1->HintType(type: C4V_Int);
819 PopValue();
820 break;
821 }
822 case AB_AndIt: // &=
823 {
824 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
825 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
826 pPar1->GetData().Int &= pPar2->_getInt();
827 pPar1->HintType(type: C4V_Int);
828 PopValue();
829 break;
830 }
831 case AB_OrIt: // |=
832 {
833 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
834 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
835 pPar1->GetData().Int |= pPar2->_getInt();
836 pPar1->HintType(type: C4V_Int);
837 PopValue();
838 break;
839 }
840 case AB_XOrIt: // ^=
841 {
842 CheckOpPars<C4V_Int, C4V_Any, false, false>(iOpID: pCPos->bccX);
843 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
844 pPar1->GetData().Int ^= pPar2->_getInt();
845 pPar1->HintType(type: C4V_Int);
846 PopValue();
847 break;
848 }
849 case AB_NilCoalescingIt:
850 {
851 if (pCurVal[0].GetType() != C4V_Any)
852 {
853 fJump = true;
854 pCPos += pCPos->bccX;
855 }
856 break;
857 }
858 case AB_Set: // =
859 {
860 CheckOpPars(iOpID: pCPos->bccX);
861 C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
862 *pPar1 = *pPar2;
863 PopValue();
864 break;
865 }
866 case AB_ARRAY:
867 {
868 // Create array
869 C4ValueArray *pArray = new C4ValueArray(pCPos->bccX);
870
871 // Pop values from stack
872 for (int i = 0; i < pCPos->bccX; i++)
873 pArray->GetItem(index: i) = pCurVal[i - pCPos->bccX + 1];
874
875 // Push array
876 if (pCPos->bccX > 0)
877 {
878 PopValues(n: pCPos->bccX - 1);
879 pCurVal->SetArray(pArray);
880 }
881 else
882 PushArray(Array: pArray);
883
884 break;
885 }
886
887 case AB_MAP:
888 {
889 C4ValueHash *map = new C4ValueHash;
890 for (int i = 0; i < pCPos->bccX; ++i)
891 {
892 (*map)[pCurVal[2 * (i - pCPos->bccX) + 1].GetRefVal()] = pCurVal[2 * (i - pCPos->bccX) + 2].GetRefVal();
893 }
894
895 if (pCPos->bccX > 0)
896 {
897 PopValues(n: 2 * pCPos->bccX - 1);
898 pCurVal->SetMap(map);
899 }
900 else
901 PushMap(Map: map);
902
903 break;
904 }
905
906 case AB_ARRAYA_R: case AB_ARRAYA_V:
907 {
908 C4Value &Container = pCurVal[-1].GetRefVal();
909 C4Value &Index = pCurVal[0];
910 if (Container.GetType() == C4V_Any)
911 {
912 throw C4AulExecError(pCurCtx->Obj, "indexed access [index]: array, map or string expected, but got nil");
913 }
914
915 if (Container.ConvertTo(vtToType: C4V_Map) || Container.ConvertTo(vtToType: C4V_Array) || Container.ConvertTo(vtToType: C4V_C4Object))
916 {
917 Container.GetContainerElement(index: &Index, to&: pCurVal[-1], pctx: pCurCtx, noref: pCPos->bccType == AB_ARRAYA_V);
918 // Remove index
919 PopValue();
920 break;
921 }
922
923 if (Container.ConvertTo(vtToType: C4V_String))
924 {
925 if (!Index.ConvertTo(vtToType: C4V_Int))
926 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "indexed string access: index of type {}, int expected!", args: Index.GetTypeName()));
927
928 auto index = Index._getInt();
929 StdStrBuf &str = Container._getStr()->Data;
930 if (index < 0)
931 {
932 index += checked_cast<C4ValueInt>(from: str.getLength());
933 }
934
935 if (index < 0 || static_cast<std::size_t>(index) >= str.getLength())
936 {
937 pCurVal[-1].Set0();
938 }
939 else
940 {
941 StdStrBuf result;
942 result.AppendChar(cChar: str.getData()[index]);
943 pCurVal[-1].SetString(new C4String(std::move(result), &pCurCtx->Func->Owner->GetEngine()->Strings));
944 }
945 PopValue();
946 break;
947 }
948 else
949 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "indexed access: can't access {} by index!", args: Container.GetTypeName()));
950 }
951
952 case AB_MAPA_R: case AB_MAPA_V:
953 {
954 C4Value &Map = pCurVal->GetRefVal();
955 if (Map.GetType() == C4V_Any)
956 {
957 throw C4AulExecError(pCurCtx->Obj, "map access with .: map expected, but got nil!");
958 }
959
960 if (!Map.ConvertTo(vtToType: C4V_Map) && !Map.ConvertTo(vtToType: C4V_C4Object))
961 {
962 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "map access with .: map expected, but got \"{}\"!", args: GetC4VName(Type: Map.GetType())));
963 }
964
965 C4Value key(reinterpret_cast<C4String *>(pCPos->bccX));
966 Map.GetContainerElement(index: &key, to&: *pCurVal, pctx: pCurCtx, noref: pCPos->bccType == AB_MAPA_V);
967
968 break;
969 }
970
971 case AB_ARRAY_APPEND:
972 {
973 C4Value &Array = pCurVal[0].GetRefVal();
974 // Typcheck
975 if (!Array.ConvertTo(vtToType: C4V_Array) || Array.GetType() != C4V_Array)
976 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "array append accesss: can't access {} as an array!", args: Array.GetType() == C4V_Any ? "nil" : Array.GetTypeName()));
977
978 C4Value index = C4VInt(iVal: Array._getArray()->GetSize());
979 Array.GetContainerElement(index: &index, to&: pCurVal[0], pctx: pCurCtx);
980
981 break;
982 }
983
984 case AB_DEREF:
985 pCurVal[0].Deref();
986
987 case AB_STACK:
988 if (pCPos->bccX < 0)
989 PopValues(n: -pCPos->bccX);
990 else
991 PushNullVals(iCnt: pCPos->bccX);
992 break;
993
994 case AB_JUMP:
995 fJump = true;
996 pCPos += pCPos->bccX;
997 break;
998
999 case AB_JUMPAND:
1000 if (!pCurVal[0])
1001 {
1002 fJump = true;
1003 pCPos += pCPos->bccX;
1004 }
1005 else
1006 {
1007 PopValue();
1008 }
1009 break;
1010
1011 case AB_JUMPOR:
1012 if (pCurVal[0])
1013 {
1014 fJump = true;
1015 pCPos += pCPos->bccX;
1016 }
1017 else
1018 {
1019 PopValue();
1020 }
1021 break;
1022
1023 case AB_JUMPNIL:
1024 if (pCurVal[0].GetType() == C4V_Any)
1025 {
1026 pCurVal[0].Deref();
1027 fJump = true;
1028 pCPos += pCPos->bccX;
1029 }
1030 break;
1031
1032 case AB_JUMPNOTNIL:
1033 if (pCurVal[0].GetType() != C4V_Any)
1034 {
1035 fJump = true;
1036 pCPos += pCPos->bccX;
1037 }
1038 else
1039 {
1040 PopValue();
1041 }
1042 break;
1043
1044 case AB_CONDN:
1045 if (!pCurVal[0])
1046 {
1047 fJump = true;
1048 pCPos += pCPos->bccX;
1049 }
1050 PopValue();
1051 break;
1052
1053 case AB_RETURN:
1054 {
1055 // Resolve reference
1056 if (!pCurCtx->Func->SFunc()->bReturnRef)
1057 pCurVal->Deref();
1058
1059 // Trace
1060 if (iTraceStart >= 0)
1061 {
1062 std::string buf{"T"};
1063 buf.append(n: ContextStackSize() - iTraceStart, c: '>');
1064 traceLogger->info(fmt: "{}{} returned {}", args&: buf, args&: pCurCtx->Func->Name, args: pCurVal->GetDataString());
1065 }
1066
1067 // External call?
1068 C4Value *pReturn = pCurCtx->Return;
1069 if (!pReturn)
1070 {
1071 // Get return value and stop executing.
1072 C4Value rVal = *pCurVal;
1073 PopValuesUntil(pUntilVal: pCurCtx->Pars - 1);
1074 PopContext();
1075 return rVal;
1076 }
1077
1078 // Save return value
1079 if (pCurVal != pReturn)
1080 pReturn->Set(*pCurVal);
1081
1082 // Pop context
1083 PopContext();
1084
1085 // Clear value stack, except return value
1086 PopValuesUntil(pUntilVal: pReturn);
1087
1088 // Jump back, continue.
1089 pCPos = pCurCtx->CPos + 1;
1090 fJump = true;
1091
1092 break;
1093 }
1094
1095 case AB_FUNC:
1096 {
1097 // Get function call data
1098 C4AulFunc *pFunc = reinterpret_cast<C4AulFunc *>(pCPos->bccX);
1099
1100 if (C4AulScriptFunc *sfunc = pFunc->SFunc(); sfunc && sfunc->Access < sfunc->pOrgScript->GetAllowedAccess(func: pFunc, caller: pCurCtx->Func->pOrgScript))
1101 {
1102 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "Insufficient access level for function \"{}\"!", args: +pFunc->Name));
1103 }
1104 C4Value *pPars = pCurVal - pFunc->GetParCount() + 1;
1105 // Save current position
1106 pCurCtx->CPos = pCPos;
1107 // Do the call
1108 C4AulBCC *pJump = Call(pFunc, pReturn: pPars, pPars, pObj: nullptr);
1109 if (pJump)
1110 {
1111 pCPos = pJump;
1112 fJump = true;
1113 }
1114 break;
1115 }
1116
1117 case AB_VAR_R: case AB_VAR_V:
1118 if (!pCurVal->ConvertTo(vtToType: C4V_Int))
1119 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "Var: index of type {}, int expected!", args: pCurVal->GetTypeName()));
1120 // Push reference to variable on the stack
1121 if (pCPos->bccType == AB_VAR_R)
1122 pCurVal->SetRef(&pCurCtx->NumVars.GetItem(index: pCurVal->_getInt()));
1123 else
1124 pCurVal->Set(pCurCtx->NumVars.GetItem(index: pCurVal->_getInt()));
1125 break;
1126
1127 case AB_PAR_R: case AB_PAR_V:
1128 if (!pCurVal->ConvertTo(vtToType: C4V_Int))
1129 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "Par: index of type {}, int expected!", args: pCurVal->GetTypeName()));
1130 // Push reference to parameter on the stack
1131 if (pCurVal->_getInt() >= 0 && pCurVal->_getInt() < static_cast<int>(pCurCtx->ParCnt()))
1132 {
1133 if (pCPos->bccType == AB_PAR_R)
1134 pCurVal->SetRef(&pCurCtx->Pars[pCurVal->_getInt()]);
1135 else
1136 pCurVal->Set(pCurCtx->Pars[pCurVal->_getInt()]);
1137 }
1138 else
1139 pCurVal->Set0();
1140 break;
1141
1142 case AB_FOREACH_NEXT:
1143 {
1144 // This should always hold
1145 assert(pCurVal->ConvertTo(C4V_Int));
1146 int iItem = pCurVal->_getInt();
1147 // Check array the first time only
1148 if (!iItem)
1149 {
1150 if (!pCurVal[-1].ConvertTo(vtToType: C4V_Array))
1151 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "for: array expected, but got {}!", args: pCurVal[-1].GetTypeName()));
1152 if (!pCurVal[-1]._getArray())
1153 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "for: array expected, but got nil!"));
1154 }
1155 C4ValueArray *pArray = pCurVal[-1]._getArray();
1156 // No more entries?
1157 if (pCurVal->_getInt() >= pArray->GetSize())
1158 break;
1159 // Get next
1160 pCurCtx->Vars[pCPos->bccX] = pArray->GetItem(index: iItem);
1161 // Save position
1162 pCurVal->SetInt(iItem + 1);
1163 // Jump over next instruction
1164 pCPos += 2;
1165 fJump = true;
1166 break;
1167 }
1168
1169 case AB_FOREACH_MAP_NEXT:
1170 {
1171 // This should always hold
1172 assert(pCurVal[-1].ConvertTo(C4V_Int));
1173 using Iterator = C4ValueHash::Iterator;
1174 auto iterator = reinterpret_cast<Iterator *>(pCurVal[0]._getRef());
1175 // Check map the first time only
1176 if (!iterator)
1177 {
1178 if (!pCurVal[-2].ConvertTo(vtToType: C4V_Map))
1179 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "for: map expected, but got {}!", args: pCurVal[-1].GetTypeName()));
1180 if (!pCurVal[-2]._getMap())
1181 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "for: map expected, but got nil!"));
1182 }
1183 C4ValueHash *map = pCurVal[-2]._getMap();
1184 if (!iterator)
1185 {
1186 iterator = new Iterator (map->begin());
1187 pCurVal[0].SetInt(1);
1188 pCurVal[0].GetData().Ref = reinterpret_cast<C4Value *>(iterator);
1189 }
1190 // No more entries?
1191 if (*iterator == map->end())
1192 {
1193 delete iterator;
1194 break;
1195 }
1196 // Get next
1197 pCurCtx->Vars[pCPos->bccX] = (**iterator).first;
1198 pCurCtx->Vars[pCurVal[-1]._getInt()] = (**iterator).second;
1199
1200 ++(*iterator);
1201 // Jump over next instruction
1202 pCPos += 2;
1203 fJump = true;
1204 break;
1205 }
1206
1207 case AB_IVARN:
1208 pCurCtx->Vars[pCPos->bccX] = pCurVal[0];
1209 PopValue();
1210 break;
1211
1212 case AB_CALLNS:
1213 // Ignore. TODO: Fix this.
1214 break;
1215
1216 case AB_CALL:
1217 case AB_CALLFS:
1218 case AB_CALLGLOBAL:
1219 {
1220 const auto isGlobal = pCPos->bccType == AB_CALLGLOBAL;
1221 C4Value *pPars = pCurVal - C4AUL_MAX_Par + 1;
1222 C4Value *pTargetVal = pCurVal - C4AUL_MAX_Par;
1223
1224 // Check for call to null
1225 if (!isGlobal && !*pTargetVal)
1226 throw C4AulExecError(pCurCtx->Obj, "Object call: target is zero!");
1227
1228 // Get call target - "object" or "id" are allowed
1229 C4Object *pDestObj{}; C4Def *pDestDef{};
1230 if (!isGlobal)
1231 {
1232 if (pTargetVal->ConvertTo(vtToType: C4V_C4Object))
1233 {
1234 // object call
1235 pDestObj = pTargetVal->_getObj();
1236 pDestDef = pDestObj->Def;
1237 }
1238 else if (pTargetVal->ConvertTo(vtToType: C4V_C4ID))
1239 {
1240 // definition call
1241 pDestObj = nullptr;
1242 pDestDef = C4Id2Def(id: pTargetVal->_getC4ID());
1243 // definition must be known
1244 if (!pDestDef)
1245 throw C4AulExecError(pCurCtx->Obj,
1246 std::format(fmt: "Definition call: Definition for id {} not found!", args: C4IdText(id: pTargetVal->_getC4ID())));
1247 }
1248 else
1249 throw C4AulExecError(pCurCtx->Obj,
1250 std::format(fmt: "Object call: Invalid target type {}, expected object or id!", args: pTargetVal->GetTypeName()));
1251 }
1252
1253 // Resolve overloads
1254 C4AulFunc *pFunc = reinterpret_cast<C4AulFunc *>(pCPos->bccX);
1255 while (pFunc->OverloadedBy)
1256 pFunc = pFunc->OverloadedBy;
1257
1258 if (!isGlobal)
1259 {
1260 // Search function for given context
1261 pFunc = pFunc->FindSameNameFunc(pScope: pDestDef);
1262 if (!pFunc && pCPos->bccType == AB_CALLFS)
1263 {
1264 PopValuesUntil(pUntilVal: pTargetVal);
1265 pTargetVal->Set0();
1266 break;
1267 }
1268 }
1269
1270 // Function not found?
1271 if (!pFunc)
1272 {
1273 const char *szFuncName = reinterpret_cast<C4AulFunc *>(pCPos->bccX)->Name;
1274 if (pDestObj)
1275 throw C4AulExecError(pCurCtx->Obj,
1276 std::format(fmt: "Object call: No function \"{}\" in object \"{}\"!", args&: szFuncName, args: pTargetVal->GetDataString()));
1277 else
1278 throw C4AulExecError(pCurCtx->Obj,
1279 std::format(fmt: "Definition call: No function \"{}\" in definition \"{}\"!", args&: szFuncName, args: pDestDef->Name.getData()));
1280 }
1281 else if (C4AulScriptFunc *sfunc = pFunc->SFunc(); sfunc)
1282 {
1283 C4AulScript *script = sfunc->pOrgScript;
1284 if (sfunc->Access < script->GetAllowedAccess(func: pFunc, caller: sfunc->pOrgScript))
1285 {
1286 throw C4AulExecError(pCurCtx->Obj, std::format(fmt: "Insufficient access level for function \"{}\"!", args: +pFunc->Name));
1287 }
1288 }
1289
1290 // Save function back (optimization)
1291 pCPos->bccX = reinterpret_cast<std::intptr_t>(pFunc);
1292
1293 // Save current position
1294 pCurCtx->CPos = pCPos;
1295
1296 // Call function
1297 C4AulBCC *pNewCPos = Call(pFunc, pReturn: pTargetVal, pPars, pObj: pDestObj, pDef: pDestDef, globalContext: isGlobal);
1298 if (pNewCPos)
1299 {
1300 // Jump
1301 pCPos = pNewCPos;
1302 fJump = true;
1303 }
1304
1305 break;
1306 }
1307
1308 default:
1309 case AB_NilCoalescing:
1310 assert(false);
1311 }
1312
1313 // Continue
1314 if (!fJump)
1315 pCPos++;
1316 }
1317 }
1318 catch (const C4AulError &e)
1319 {
1320 // Save current position
1321 pOldCtx->CPos = pCPos;
1322 // Pass?
1323 if (fPassErrors)
1324 throw;
1325 // Show
1326 e.show();
1327 // Trace
1328 for (C4AulScriptContext *pCtx = pCurCtx; pCtx >= Contexts; pCtx--)
1329 pCtx->dump(Dump: " by: ");
1330 // Unwind stack
1331 C4Value *pUntil = nullptr;
1332 while (pCurCtx >= pOldCtx)
1333 {
1334 pUntil = pCurCtx->Pars - 1;
1335 PopContext();
1336 }
1337 if (pUntil)
1338 PopValuesUntil(pUntilVal: pUntil);
1339 }
1340
1341 // Return nothing
1342 return C4VNull;
1343}
1344
1345static void ErrorOrWarning(C4Object *context, const std::string_view message, bool warning)
1346{
1347 if (warning)
1348 {
1349 if (context)
1350 {
1351 DebugLog(level: spdlog::level::warn, fmt: "{} (obj {})", args: message, args: C4VObj(pObj: context).GetDataString());
1352 }
1353 else
1354 {
1355 DebugLog(level: spdlog::level::warn, message);
1356 }
1357 }
1358 else
1359 {
1360 throw C4AulExecError{context, message};
1361 }
1362}
1363
1364static bool CheckConvertFunctionParameters(C4Object *const ctxObject, C4AulFunc *const pFunc, C4Value *pPars, const bool convertToAnyEagerly, const bool convertNilToIntBool, bool onlyWarn = false)
1365{
1366 auto ok = true;
1367 // Convert parameters (typecheck)
1368 const auto pTypes = pFunc->GetParType();
1369 for (int i = 0; i < pFunc->GetParCount(); i++)
1370 {
1371 if (convertToAnyEagerly && pTypes[i] != C4V_pC4Value && !pPars[i])
1372 {
1373 pPars[i].Set0();
1374 }
1375 if (!pPars[i].ConvertTo(vtToType: pTypes[i]))
1376 {
1377 ErrorOrWarning(context: ctxObject,
1378 message: std::format(fmt: "call to \"{}\" parameter {}: got \"{}\", but expected \"{}\"!",
1379 args: +pFunc->Name, args: i + 1, args: pPars[i].GetTypeName(), args: GetC4VName(Type: pTypes[i])
1380 ), warning: onlyWarn);
1381 ok = false;
1382 }
1383
1384 if (convertNilToIntBool && pPars[i].GetType() == C4V_Any)
1385 {
1386 if (pTypes[i] == C4V_Int)
1387 {
1388 pPars[i].SetInt(0);
1389 }
1390 else if (pTypes[i] == C4V_Bool)
1391 {
1392 pPars[i].SetBool(false);
1393 }
1394 }
1395 }
1396 return ok || onlyWarn;
1397}
1398
1399static bool TryCheckConvertFunctionParameters(C4Object *const ctxObject, C4AulFunc *const pFunc, C4Value *pPars, const bool convertToAnyEagerly, const bool convertNilToIntBool, bool passErrors, bool onlyWarn)
1400{
1401 try
1402 {
1403 return CheckConvertFunctionParameters(ctxObject, pFunc, pPars, convertToAnyEagerly, convertNilToIntBool, onlyWarn);
1404 }
1405 catch (const C4AulError &e)
1406 {
1407 // Pass?
1408 if (passErrors)
1409 throw;
1410 // Show
1411 e.show();
1412 DebugLog(fmt: " by: internal call to {}", args: +pFunc->Name);
1413 return false;
1414 }
1415}
1416
1417C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4Object *pObj, C4Def *pDef, bool globalContext)
1418{
1419 // No object given? Use current context
1420 if (globalContext)
1421 {
1422 pObj = nullptr;
1423 pDef = nullptr;
1424 }
1425 else if (!pObj && !pDef)
1426 {
1427 assert(pCurCtx >= Contexts);
1428 pObj = pCurCtx->Obj;
1429 pDef = pCurCtx->Def;
1430 }
1431
1432 // Script function?
1433 C4AulScriptFunc *pSFunc = pFunc->SFunc();
1434
1435 const bool convertToAnyEagerly = !pCurCtx->Func->HasStrictNil();
1436
1437 const bool convertNilToIntBool = convertToAnyEagerly && pSFunc && pSFunc->HasStrictNil();
1438
1439 CheckConvertFunctionParameters(ctxObject: pCurCtx->Obj, pFunc, pPars, convertToAnyEagerly, convertNilToIntBool);
1440
1441 if (pSFunc)
1442 {
1443 // Push variables
1444 C4Value *pVars = pCurVal + 1;
1445 PushNullVals(iCnt: pSFunc->VarNamed.iSize);
1446
1447 // Check context
1448 assert(!pSFunc->Owner->Def || pDef == pSFunc->Owner->Def);
1449
1450 // Push a new context
1451 C4AulScriptContext ctx;
1452 ctx.Obj = pObj;
1453 ctx.Def = pDef;
1454 ctx.Caller = pCurCtx;
1455 ctx.Return = pReturn;
1456 ctx.Pars = pPars;
1457 ctx.Vars = pVars;
1458 ctx.Func = pSFunc;
1459 ctx.TemporaryScript = false;
1460 ctx.CPos = nullptr;
1461 PushContext(rContext: ctx);
1462
1463 // Jump to code
1464 return pSFunc->Code;
1465 }
1466 else
1467 {
1468 // Create new context
1469 C4AulContext CallCtx;
1470 CallCtx.Obj = pObj;
1471 CallCtx.Def = pDef;
1472 CallCtx.Caller = pCurCtx;
1473
1474#ifdef DEBUGREC_SCRIPT
1475 if (Game.FrameCounter >= DEBUGREC_START_FRAME)
1476 {
1477 std::string callText;
1478 if (pObj)
1479 callText = std::format("Object({}): ", pObj->Number);
1480 callText += pFunc->Name;
1481 callText += '(';
1482 for (int i = 0; i < C4AUL_MAX_Par; ++i)
1483 {
1484 if (i) callText += ',';
1485 C4Value &rV = pPars[i];
1486 if (rV.GetType() == C4V_String)
1487 {
1488 C4String *s = rV.getStr();
1489 if (!s)
1490 callText += "(Snull)";
1491 else
1492 {
1493 callText += "\"";
1494 callText += s->Data.getData();
1495 callText += "\"";
1496 }
1497 }
1498 else
1499 callText += rV.GetDataString();
1500 }
1501 callText += ')';
1502 callText += ';';
1503 AddDbgRec(RCT_AulFunc, callText.c_str(), callText.size() + 1);
1504 }
1505#endif
1506 // Execute
1507#ifndef NDEBUG
1508 C4AulScriptContext *pCtx = pCurCtx;
1509#endif
1510 if (pReturn > pCurVal)
1511 PushValue(rVal: pFunc->Exec(pCallerCtx: &CallCtx, pPars, fPassErrors: true));
1512 else
1513 pReturn->Set(pFunc->Exec(pCallerCtx: &CallCtx, pPars, fPassErrors: true));
1514#ifndef NDEBUG
1515 assert(pCtx == pCurCtx);
1516#endif
1517
1518 // Remove parameters from stack
1519 PopValuesUntil(pUntilVal: pReturn);
1520
1521 // Continue
1522 return nullptr;
1523 }
1524}
1525
1526void C4AulStartTrace()
1527{
1528 AulExec.StartTrace();
1529}
1530
1531void C4AulExec::StartTrace()
1532{
1533 if (iTraceStart < 0)
1534 {
1535 iTraceStart = ContextStackSize();
1536 traceLogger = Application.LogSystem.CreateLogger(config&: Config.Logging.AulExec);
1537 }
1538}
1539
1540void C4AulExec::StartProfiling(C4AulScript *pProfiledScript)
1541{
1542 // stop previous profiler run
1543 if (fProfiling) AbortProfiling();
1544 fProfiling = true;
1545 // resets profling times and starts recording the times
1546 this->pProfiledScript = pProfiledScript;
1547 time_t tNow = timeGetTime();
1548 tDirectExecStart = tNow; // in case profiling is started from DirectExec
1549 tDirectExecTotal = 0;
1550 pProfiledScript->ResetProfilerTimes();
1551 for (C4AulScriptContext *pCtx = Contexts; pCtx <= pCurCtx; ++pCtx)
1552 pCtx->tTime = tNow;
1553}
1554
1555void C4AulExec::StopProfiling()
1556{
1557 // stop the profiler and displays results
1558 if (!fProfiling) return;
1559 fProfiling = false;
1560 // collect profiler times
1561 C4AulProfiler Profiler{Application.LogSystem.CreateLogger(config&: Config.Logging.AulProfiler)};
1562 Profiler.CollectEntry(pFunc: nullptr, tProfileTime: tDirectExecTotal);
1563 pProfiledScript->CollectProfilerTimes(rProfiler&: Profiler);
1564 Profiler.Show();
1565}
1566
1567void C4AulProfiler::StartProfiling(C4AulScript *pScript)
1568{
1569 AulExec.StartProfiling(pProfiledScript: pScript);
1570}
1571
1572void C4AulProfiler::StopProfiling()
1573{
1574 AulExec.StopProfiling();
1575}
1576
1577void C4AulProfiler::Abort()
1578{
1579 AulExec.AbortProfiling();
1580}
1581
1582void C4AulProfiler::CollectEntry(C4AulScriptFunc *pFunc, time_t tProfileTime)
1583{
1584 // zero entries are not collected to have a cleaner list
1585 if (!tProfileTime) return;
1586 // add entry to list
1587 Entry e;
1588 e.pFunc = pFunc;
1589 e.tProfileTime = tProfileTime;
1590 Times.push_back(x: e);
1591}
1592
1593void C4AulProfiler::Show()
1594{
1595 // sort by time
1596 std::sort(first: Times.rbegin(), last: Times.rend());
1597 // display them
1598 logger->info(msg: "Profiler statistics:");
1599 logger->info(msg: "==============================");
1600 typedef std::vector<Entry> EntryList;
1601 for (EntryList::iterator i = Times.begin(); i != Times.end(); ++i)
1602 {
1603 Entry &e = (*i);
1604 logger->info(fmt: "{:05}ms\t{}", args&: e.tProfileTime, args: e.pFunc ? (e.pFunc->GetFullName().c_str()) : "Direct exec");
1605 }
1606 logger->info(msg: "==============================");
1607 // done!
1608}
1609
1610C4Value C4AulFunc::Exec(C4Object *pObj, const C4AulParSet &pPars, bool fPassErrors, bool nonStrict3WarnConversionOnly, bool convertNilToIntBool)
1611{
1612 // construct a dummy caller context
1613 C4AulContext ctx;
1614 ctx.Obj = pObj;
1615 ctx.Def = pObj ? pObj->Def : nullptr;
1616 ctx.Caller = nullptr;
1617
1618 const auto sFunc = SFunc();
1619 const auto hasStrictNil = sFunc && sFunc->HasStrictNil();
1620 auto pars = pPars;
1621 if (TryCheckConvertFunctionParameters(ctxObject: pObj, pFunc: this, pPars: pars.Par, convertToAnyEagerly: !hasStrictNil, convertNilToIntBool: hasStrictNil && convertNilToIntBool, passErrors: fPassErrors, onlyWarn: nonStrict3WarnConversionOnly && !hasStrictNil))
1622 {
1623 // execute
1624 return Exec(pCallerCtx: &ctx, pPars: pars.Par, fPassErrors);
1625 }
1626 return C4VNull;
1627}
1628
1629C4Value C4AulScriptFunc::Exec(C4AulContext *pCtx, const C4Value pPars[], bool fPassErrors)
1630{
1631 // handle easiest case first
1632 if (Owner->State != ASS_PARSED) return C4VNull;
1633
1634 // execute
1635 return AulExec.Exec(pSFunc: this, pObj: pCtx->Obj, pnPars: pPars, fPassErrors);
1636}
1637
1638C4Value C4AulScriptFunc::Exec(C4Object *pObj, const C4AulParSet &pPars, bool fPassErrors, bool nonStrict3WarnConversionOnly, bool convertNilToIntBool)
1639{
1640 // handle easiest case first
1641 if (Owner->State != ASS_PARSED) return C4VNull;
1642
1643 const auto isAtLeastStrict3 = HasStrictNil();
1644 auto pars = pPars;
1645 if (TryCheckConvertFunctionParameters(ctxObject: pObj, pFunc: this, pPars: pars.Par, convertToAnyEagerly: !isAtLeastStrict3, convertNilToIntBool: isAtLeastStrict3 && convertNilToIntBool, passErrors: fPassErrors, onlyWarn: nonStrict3WarnConversionOnly && !isAtLeastStrict3))
1646 {
1647 // execute
1648 return AulExec.Exec(pSFunc: this, pObj, pnPars: pars.Par, fPassErrors);
1649 }
1650 return C4VNull;
1651}
1652
1653bool C4AulScriptFunc::HasStrictNil() const noexcept
1654{
1655 return pOrgScript->Strict >= C4AulScriptStrict::STRICT3;
1656}
1657
1658C4Value C4AulScript::DirectExec(C4Object *pObj, const char *szScript, const char *szContext, bool fPassErrors, C4AulScriptStrict Strict)
1659{
1660#ifdef DEBUGREC_SCRIPT
1661 AddDbgRec(RCT_DirectExec, szScript, strlen(szScript) + 1);
1662 int32_t iObjNumber = pObj ? pObj->Number : -1;
1663 AddDbgRec(RCT_DirectExec, &iObjNumber, sizeof(int32_t));
1664#endif
1665 // profiler
1666 AulExec.StartDirectExec();
1667 // Create a new temporary script as child of this script
1668 C4AulScript *pScript = new C4AulScript();
1669 pScript->Script.Copy(pnData: szScript);
1670 pScript->ScriptName = std::format(fmt: "{} in {}", args&: szContext, args&: ScriptName);
1671 pScript->Strict = Strict;
1672 pScript->Temporary = true;
1673 pScript->State = ASS_LINKED;
1674 if (pObj)
1675 {
1676 pScript->Def = pObj->Def;
1677 pScript->LocalNamed = pObj->Def->Script.LocalNamed;
1678 }
1679 else
1680 {
1681 pScript->Def = nullptr;
1682 }
1683 pScript->Reg2List(pEngine: Engine, pOwner: this);
1684 // Add a new function
1685 C4AulScriptFunc *pFunc = new C4AulScriptFunc(pScript, "");
1686 pFunc->Script = pScript->Script.getData();
1687 pFunc->pOrgScript = pScript;
1688 // Parse function
1689 try
1690 {
1691 pScript->ParseFn(Fn: pFunc, fExprOnly: true);
1692 }
1693 catch (const C4AulError &ex)
1694 {
1695 ex.show();
1696 delete pFunc;
1697 delete pScript;
1698 return C4VNull;
1699 }
1700 pFunc->Code = pScript->Code;
1701 pScript->State = ASS_PARSED;
1702 // Execute. The TemporaryScript-parameter makes sure the script will be deleted later on.
1703 C4Value vRetVal(AulExec.Exec(pSFunc: pFunc, pObj, pnPars: nullptr, fPassErrors, fTemporaryScript: true));
1704 // profiler
1705 AulExec.StopDirectExec();
1706 return vRetVal;
1707}
1708
1709void C4AulScript::ResetProfilerTimes()
1710{
1711 // zero all profiler times of owned functions
1712 C4AulScriptFunc *pSFunc;
1713 for (C4AulFunc *pFn = Func0; pFn; pFn = pFn->Next)
1714 if (pSFunc = pFn->SFunc())
1715 pSFunc->tProfileTime = 0;
1716 // reset sub-scripts
1717 for (C4AulScript *pScript = Child0; pScript; pScript = pScript->Next)
1718 pScript->ResetProfilerTimes();
1719}
1720
1721void C4AulScript::CollectProfilerTimes(class C4AulProfiler &rProfiler)
1722{
1723 // collect all profiler times of owned functions
1724 C4AulScriptFunc *pSFunc;
1725 for (C4AulFunc *pFn = Func0; pFn; pFn = pFn->Next)
1726 if (pSFunc = pFn->SFunc())
1727 rProfiler.CollectEntry(pFunc: pSFunc, tProfileTime: pSFunc->tProfileTime);
1728 // collect sub-scripts
1729 for (C4AulScript *pScript = Child0; pScript; pScript = pScript->Next)
1730 pScript->CollectProfilerTimes(rProfiler);
1731}
1732