1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2001, Sven2
6 * Copyright (c) 2017-2021, 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// C4Aul script engine CP conversion
19
20#include <C4Include.h>
21#include <C4Aul.h>
22
23#include <C4Config.h>
24#include <C4Def.h>
25#include <C4Log.h>
26#include <C4Components.h>
27
28#include <format>
29
30C4AulError::C4AulError() {}
31
32void C4AulError::show() const
33{
34 // simply log error message
35 if (!message.empty())
36 DebugLog(level: isWarning ? spdlog::level::warn : spdlog::level::err, message);
37}
38
39C4AulFunc::C4AulFunc(C4AulScript *pOwner, const char *pName, bool bAtEnd) :
40 MapNext(nullptr),
41 LinkedTo(nullptr),
42 OverloadedBy(nullptr),
43 NextSNFunc(nullptr)
44{
45 // reg2list (at end or at the beginning)
46 Owner = pOwner;
47 if (bAtEnd)
48 {
49 if (Prev = Owner->FuncL)
50 {
51 Prev->Next = this;
52 Owner->FuncL = this;
53 }
54 else
55 {
56 Owner->Func0 = this;
57 Owner->FuncL = this;
58 }
59 Next = nullptr;
60 }
61 else
62 {
63 if (Next = Owner->Func0)
64 {
65 Next->Prev = this;
66 Owner->Func0 = this;
67 }
68 else
69 {
70 Owner->Func0 = this;
71 Owner->FuncL = this;
72 }
73 Prev = nullptr;
74 }
75
76 // store name
77 SCopy(szSource: pName, sTarget: Name, C4AUL_MAX_Identifier);
78 // add to global lookuptable with this name
79 Owner->Engine->FuncLookUp.Add(func: this, bAtEnd);
80}
81
82C4AulFunc::~C4AulFunc()
83{
84 // if it's a global: remove the global link!
85 if (LinkedTo && Owner)
86 if (LinkedTo->Owner == Owner->Engine)
87 delete LinkedTo;
88 // unlink func
89 if (LinkedTo)
90 {
91 // find prev
92 C4AulFunc *pAkt = this;
93 while (pAkt->LinkedTo != this) pAkt = pAkt->LinkedTo;
94 if (pAkt == LinkedTo)
95 pAkt->LinkedTo = nullptr;
96 else
97 pAkt->LinkedTo = LinkedTo;
98 LinkedTo = nullptr;
99 }
100 // remove from list
101 if (Prev) Prev->Next = Next;
102 if (Next) Next->Prev = Prev;
103 if (Owner)
104 {
105 if (Owner->Func0 == this) Owner->Func0 = Next;
106 if (Owner->FuncL == this) Owner->FuncL = Prev;
107 Owner->Engine->FuncLookUp.Remove(func: this);
108 }
109}
110
111void C4AulFunc::DestroyLinked()
112{
113 // delete all functions linked to this one.
114 while (LinkedTo)
115 delete LinkedTo;
116}
117
118C4AulFunc *C4AulFunc::GetLocalSFunc(const char *szIdtf)
119{
120 // owner is engine, i.e. this is a global func?
121 if (Owner == Owner->Engine && LinkedTo)
122 {
123 // then search linked scope first
124 if (C4AulFunc *pFn = LinkedTo->Owner->GetSFunc(pIdtf: szIdtf)) return pFn;
125 }
126 // search local owner list
127 return Owner->GetSFunc(pIdtf: szIdtf);
128}
129
130C4AulFunc *C4AulFunc::FindSameNameFunc(C4Def *pScope)
131{
132 // Note: NextSNFunc forms a ring, not a list
133 // find function
134 C4AulFunc *pFunc = this, *pResult = nullptr;
135 do
136 {
137 // definition matches? This is the one
138 if (pFunc->Owner->Def == pScope)
139 {
140 pResult = pFunc; break;
141 }
142 // global function? Set func, but continue searching
143 // (may be overloaded by a local fn)
144 if (pFunc->Owner == Owner->Engine)
145 pResult = pFunc;
146 } while ((pFunc = pFunc->NextSNFunc) && pFunc != this);
147 return pResult;
148}
149
150std::string C4AulScriptFunc::GetFullName()
151{
152 // "lost" function?
153 std::string owner;
154 if (!Owner)
155 {
156 owner = "(unknown) ";
157 }
158 else if (Owner->Def)
159 {
160 owner = std::format(fmt: "{}::", args: C4IdText(id: Owner->Def->id));
161 }
162 else if (Owner->Engine == Owner)
163 {
164 owner = "global ";
165 }
166 else
167 {
168 owner = "game ";
169 }
170
171 owner += Name;
172 return owner;
173}
174
175C4AulScript::C4AulScript()
176{
177 // init defaults
178 Default();
179}
180
181void C4AulScript::Default()
182{
183 // not compiled
184 State = ASS_NONE;
185 Script.Clear();
186 Code = CPos = nullptr;
187 CodeSize = CodeBufSize = 0;
188 IncludesResolved = false;
189
190 // defaults
191 idDef = C4ID_None;
192 Strict = C4AulScriptStrict::NONSTRICT;
193 Preparsing = Resolving = false;
194 Temporary = false;
195 LocalNamed.Reset();
196
197 // prepare lists
198 Child0 = ChildL = Prev = Next = nullptr;
199 Owner = Engine = nullptr;
200 Func0 = FuncL = nullptr;
201 // prepare include list
202 Includes.clear();
203 Appends.clear();
204}
205
206C4AulScript::~C4AulScript()
207{
208 // clear
209 Clear();
210 // unreg
211 Unreg();
212}
213
214void C4AulScript::Unreg()
215{
216 // remove from list
217 if (Prev) Prev->Next = Next; else if (Owner) Owner->Child0 = Next;
218 if (Next) Next->Prev = Prev; else if (Owner) Owner->ChildL = Prev;
219 Prev = Next = Owner = nullptr;
220}
221
222void C4AulScript::Clear()
223{
224 // remove includes
225 Includes.clear();
226 Appends.clear();
227 // delete child scripts + funcs
228 while (Child0)
229 if (Child0->Delete()) delete Child0; else Child0->Unreg();
230 while (Func0) delete Func0;
231 // delete script+code
232 Script.Clear();
233 delete[] Code; Code = nullptr;
234 CodeSize = CodeBufSize = 0;
235 // reset flags
236 State = ASS_NONE;
237}
238
239void C4AulScript::Reg2List(C4AulScriptEngine *pEngine, C4AulScript *pOwner)
240{
241 // already regged? (def reloaded)
242 if (Owner) return;
243 // reg to list
244 Engine = pEngine;
245 if (Owner = pOwner)
246 {
247 if (Prev = Owner->ChildL)
248 Prev->Next = this;
249 else
250 Owner->Child0 = this;
251 Owner->ChildL = this;
252 }
253 else
254 Prev = nullptr;
255 Next = nullptr;
256}
257
258C4AulFunc *C4AulScript::GetOverloadedFunc(C4AulFunc *ByFunc)
259{
260 assert(ByFunc);
261 // search local list
262 C4AulFunc *f = ByFunc;
263 if (f) f = f->Prev; else f = FuncL;
264 while (f)
265 {
266 if (SEqual(szStr1: ByFunc->Name, szStr2: f->Name)) break;
267 f = f->Prev;
268 }
269#ifndef NDEBUG
270 C4AulFunc *f2 = Engine ? Engine->GetFunc(ByFunc->Name, this, ByFunc) : nullptr;
271 assert(f == f2);
272#endif
273 // nothing found? then search owner, if existent
274 if (!f && Owner)
275 {
276 if (f = Owner->GetFuncRecursive(pIdtf: ByFunc->Name))
277 // just found the global link?
278 if (ByFunc && f->LinkedTo == ByFunc)
279 f = Owner->GetOverloadedFunc(ByFunc: f);
280 }
281 // return found fn
282 return f;
283}
284
285C4AulFunc *C4AulScript::GetFuncRecursive(const char *pIdtf)
286{
287 // search local list
288 C4AulFunc *f = GetFunc(pIdtf);
289 if (f) return f;
290 // nothing found? then search owner, if existent
291 else if (Owner) return Owner->GetFuncRecursive(pIdtf);
292 return nullptr;
293}
294
295C4AulFunc *C4AulScript::GetFunc(const char *pIdtf)
296{
297 return Engine ? Engine->GetFunc(Name: pIdtf, Owner: this, After: nullptr) : nullptr;
298}
299
300C4AulScriptFunc *C4AulScript::GetSFuncWarn(const char *pIdtf, C4AulAccess AccNeeded, const char *WarnStr)
301{
302 // no identifier
303 if (!pIdtf || !pIdtf[0]) return nullptr;
304 // get func?
305 C4AulScriptFunc *pFn = GetSFunc(pIdtf, AccNeeded, fFailSafe: true);
306 if (!pFn)
307 Warn(msg: std::format(fmt: "Error getting {} function '{}'", args&: WarnStr, args&: pIdtf), pIdtf: nullptr);
308 return pFn;
309}
310
311C4AulScriptFunc *C4AulScript::GetSFunc(const char *pIdtf, C4AulAccess AccNeeded, bool fFailsafe)
312{
313 // failsafe call
314 if (*pIdtf == '~') { fFailsafe = true; pIdtf++; }
315
316 // get function reference from table
317 C4AulScriptFunc *pFn = GetSFunc(pIdtf);
318
319 // undefined function
320 if (!pFn)
321 {
322 // not failsafe?
323 if (!fFailsafe)
324 {
325 // show error
326 C4AulParseError err(this, "Undefined function: ", pIdtf);
327 err.show();
328 }
329 return nullptr;
330 }
331
332 // check access
333 if (pFn->Access < AccNeeded)
334 {
335 // no access? show error
336 C4AulParseError err(this, "insufficient access level");
337 err.show();
338 // don't even break in strict execution, because the caller might be non-strict
339 }
340
341 // return found function
342 return pFn;
343}
344
345C4AulScriptFunc *C4AulScript::GetSFunc(const char *pIdtf)
346{
347 // get func by name; return script func
348 if (!pIdtf) return nullptr;
349 if (!pIdtf[0]) return nullptr;
350 if (pIdtf[0] == '~') pIdtf++;
351 C4AulFunc *f = GetFunc(pIdtf);
352 if (!f) return nullptr;
353 return f->SFunc();
354
355}
356
357C4AulScriptFunc *C4AulScript::GetSFunc(int iIndex, const char *szPattern, C4AulAccess AccNeeded)
358{
359 C4AulFunc *f;
360 C4AulScriptFunc *sf;
361 // loop through script funcs
362 f = FuncL;
363 while (f)
364 {
365 if (sf = f->SFunc(); sf)
366 if (!szPattern || SEqual2(szStr1: sf->Name, szStr2: szPattern))
367 {
368 if (!iIndex)
369 {
370 if (sf->Access < AccNeeded)
371 {
372 C4AulParseError err(this, "insufficient access level");
373 err.show();
374 }
375 return sf;
376 }
377 --iIndex;
378 }
379 f = f->Prev;
380 }
381
382 // indexed script func not found
383 return nullptr;
384}
385
386C4AulAccess C4AulScript::GetAllowedAccess(C4AulFunc *func, C4AulScript *caller)
387{
388 C4AulScriptFunc *sfunc = func->SFunc();
389
390 if (
391 !sfunc ||
392 sfunc->pOrgScript == caller ||
393 std::find(first: sfunc->pOrgScript->Includes.begin(), last: sfunc->pOrgScript->Includes.end(), val: caller->idDef) != sfunc->pOrgScript->Includes.end() ||
394 std::find(first: caller->Includes.begin(), last: caller->Includes.end(), val: sfunc->pOrgScript->idDef) != caller->Includes.end() ||
395 std::find(first: sfunc->pOrgScript->Appends.begin(), last: sfunc->pOrgScript->Appends.end(), val: caller->idDef) != sfunc->pOrgScript->Appends.end() ||
396 std::find(first: caller->Appends.begin(), last: caller->Appends.end(), val: sfunc->pOrgScript->idDef) != caller->Appends.end()
397 )
398 {
399 return AA_PRIVATE;
400 }
401 else if (sfunc->pOrgScript->Strict >= C4AulScriptStrict::STRICT3 &&
402 caller->Strict >= C4AulScriptStrict::STRICT3)
403 {
404 return sfunc->Access;
405 }
406
407 return AA_PRIVATE;
408}
409
410void C4AulScriptFunc::CopyBody(C4AulScriptFunc &FromFunc)
411{
412 // copy some members, that are set before linking
413 Access = FromFunc.Access;
414 Desc.Copy(Buf2: FromFunc.Desc);
415 DescText.Copy(Buf2: FromFunc.DescText);
416 DescLong.Copy(Buf2: FromFunc.DescLong);
417 Condition = FromFunc.Condition;
418 idImage = FromFunc.idImage;
419 iImagePhase = FromFunc.iImagePhase;
420 ControlMethod = FromFunc.ControlMethod;
421 Script = FromFunc.Script;
422 VarNamed = FromFunc.VarNamed;
423 ParNamed = FromFunc.ParNamed;
424 bNewFormat = FromFunc.bNewFormat;
425 bReturnRef = FromFunc.bReturnRef;
426 pOrgScript = FromFunc.pOrgScript;
427 for (int i = 0; i < C4AUL_MAX_Par; i++)
428 ParType[i] = FromFunc.ParType[i];
429 // must reset field here
430 NextSNFunc = nullptr;
431}
432
433// C4AulScriptEngine
434
435C4AulScriptEngine::C4AulScriptEngine() :
436 warnCnt(0), errCnt(0), nonStrictCnt(0), lineCnt(0)
437{
438 // /me r b engine
439 Engine = this;
440 ScriptName = C4CFN_System;
441 Strict = C4AulScriptStrict::MAXSTRICT;
442
443 Global.Reset();
444
445 GlobalNamedNames.Reset();
446 GlobalNamed.Reset();
447 GlobalNamed.SetNameList(&GlobalNamedNames);
448 GlobalConstNames.Reset();
449 GlobalConsts.Reset();
450 GlobalConsts.SetNameList(&GlobalConstNames);
451}
452
453C4AulScriptEngine::~C4AulScriptEngine() { Clear(); }
454
455void C4AulScriptEngine::Clear()
456{
457 // clear inherited
458 C4AulScript::Clear();
459 // clear own stuff
460 // reset values
461 warnCnt = errCnt = nonStrictCnt = lineCnt = 0;
462 // resetting name lists will reset all data lists, too
463 // except not...
464 Global.Reset();
465 GlobalNamedNames.Reset();
466 GlobalConstNames.Reset();
467 GlobalConsts.Reset();
468 GlobalConsts.SetNameList(&GlobalConstNames);
469 GlobalNamed.Reset();
470 GlobalNamed.SetNameList(&GlobalNamedNames);
471}
472
473void C4AulScriptEngine::UnLink()
474{
475 // unlink scripts
476 C4AulScript::UnLink();
477 // clear string table ("hold" strings only)
478 Strings.Clear();
479 // Do not clear global variables and constants, because they are registered by the
480 // preparser. Note that keeping those fields means that you cannot delete a global
481 // variable or constant at runtime by removing it from the script.
482}
483
484void C4AulScriptEngine::RegisterGlobalConstant(const char *szName, const C4Value &rValue)
485{
486 // Register name and set value.
487 // AddName returns the index of existing element if the name is assigned already.
488 // That is OK, since it will only change the value of the global ("overload" it).
489 // A warning would be nice here. However, this warning would show up whenever a script
490 // containing globals constants is recompiled.
491 GlobalConsts[GlobalConstNames.AddName(pnName: szName)] = rValue;
492}
493
494bool C4AulScriptEngine::GetGlobalConstant(const char *szName, C4Value *pTargetValue)
495{
496 // get index of global by name
497 int32_t iConstIndex = GlobalConstNames.GetItemNr(strName: szName);
498 // not found?
499 if (iConstIndex < 0) return false;
500 // if it's found, assign the value if desired
501 if (pTargetValue) *pTargetValue = GlobalConsts[iConstIndex];
502 // constant exists
503 return true;
504}
505
506bool C4AulScriptEngine::DenumerateVariablePointers()
507{
508 Global.DenumeratePointers();
509 GlobalNamed.DenumeratePointers();
510 // runtime data only: don't denumerate consts
511 return true;
512}
513
514void C4AulScriptEngine::CompileFunc(StdCompiler *pComp)
515{
516 pComp->Value(rStruct: mkNamingAdapt(rValue&: Global, szName: "Globals", rDefault: C4ValueList()));
517 C4ValueMapData GlobalNamedDefault;
518 GlobalNamedDefault.SetNameList(&GlobalNamedNames);
519 pComp->Value(rStruct: mkNamingAdapt(rValue&: GlobalNamed, szName: "GlobalNamed", rDefault: GlobalNamedDefault));
520}
521
522// C4AulFuncMap
523
524static const size_t CapacityInc = 1024;
525
526C4AulFuncMap::C4AulFuncMap() : Capacity(CapacityInc), FuncCnt(0), Funcs(new C4AulFunc *[CapacityInc])
527{
528 memset(s: Funcs, c: 0, n: sizeof(C4AulFunc *) * Capacity);
529}
530
531C4AulFuncMap::~C4AulFuncMap()
532{
533 delete[] Funcs;
534}
535
536unsigned int C4AulFuncMap::Hash(const char *name)
537{
538 // Fowler/Noll/Vo hash
539 unsigned int h = 2166136261u;
540 while (*name)
541 h = (h ^ * (name++)) * 16777619;
542 return h;
543}
544
545C4AulFunc *C4AulFuncMap::GetFirstFunc(const char *Name)
546{
547 if (!Name) return nullptr;
548 C4AulFunc *Func = Funcs[Hash(name: Name) % Capacity];
549 while (Func && !SEqual(szStr1: Name, szStr2: Func->Name))
550 Func = Func->MapNext;
551 return Func;
552}
553
554C4AulFunc *C4AulFuncMap::GetNextSNFunc(const C4AulFunc *After)
555{
556 C4AulFunc *Func = After->MapNext;
557 while (Func && !SEqual(szStr1: After->Name, szStr2: Func->Name))
558 Func = Func->MapNext;
559 return Func;
560}
561
562C4AulFunc *C4AulFuncMap::GetFunc(const char *Name, const C4AulScript *Owner, const C4AulFunc *After)
563{
564 if (!Name) return nullptr;
565 C4AulFunc *Func = Funcs[Hash(name: Name) % Capacity];
566 if (After)
567 {
568 while (Func && Func != After)
569 Func = Func->MapNext;
570 if (Func)
571 Func = Func->MapNext;
572 }
573 while (Func && (Func->Owner != Owner || !SEqual(szStr1: Name, szStr2: Func->Name)))
574 Func = Func->MapNext;
575 return Func;
576}
577
578void C4AulFuncMap::Add(C4AulFunc *func, bool bAtStart)
579{
580 if (++FuncCnt > Capacity)
581 {
582 int NCapacity = Capacity + CapacityInc;
583 C4AulFunc **NFuncs = new C4AulFunc *[NCapacity];
584 memset(s: NFuncs, c: 0, n: sizeof(C4AulFunc *) * NCapacity);
585 for (int i = 0; i < Capacity; ++i)
586 {
587 while (Funcs[i])
588 {
589 // Get a pointer to the bucket
590 C4AulFunc **pNFunc = &(NFuncs[Hash(name: Funcs[i]->Name) % NCapacity]);
591 // get a pointer to the end of the linked list
592 while (*pNFunc) pNFunc = &((*pNFunc)->MapNext);
593 // Move the func over
594 *pNFunc = Funcs[i];
595 // proceed with the next list member
596 Funcs[i] = Funcs[i]->MapNext;
597 // Terminate the linked list
598 (*pNFunc)->MapNext = nullptr;
599 }
600 }
601 Capacity = NCapacity;
602 delete[] Funcs;
603 Funcs = NFuncs;
604 }
605 // Get a pointer to the bucket
606 C4AulFunc **pFunc = &(Funcs[Hash(name: func->Name) % Capacity]);
607 if (bAtStart)
608 {
609 // move the current first to the second position
610 func->MapNext = *pFunc;
611 }
612 else
613 {
614 // get a pointer to the end of the linked list
615 while (*pFunc)
616 {
617 pFunc = &((*pFunc)->MapNext);
618 }
619 }
620 // Add the func
621 *pFunc = func;
622}
623
624void C4AulFuncMap::Remove(C4AulFunc *func)
625{
626 C4AulFunc **pFunc = &Funcs[Hash(name: func->Name) % Capacity];
627 while (*pFunc != func)
628 {
629 pFunc = &((*pFunc)->MapNext);
630 assert(*pFunc); // crash on remove of a not contained func
631 }
632 *pFunc = (*pFunc)->MapNext;
633 --FuncCnt;
634}
635