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// C4Aul script engine CP conversion
19// (cut C4Aul of classes/structs and put everything in namespace C4Aul instead?)
20// drop uncompiled scripts when not in developer mode
21// -> build string table
22// -> clear the string table in UnLink? ReLink won't happen to be called in player mode anyway
23
24#pragma once
25
26#include <C4AulScriptStrict.h>
27#include <C4ValueList.h>
28#include <C4ValueMap.h>
29#include <C4Id.h>
30#include "C4Log.h"
31#include <C4Script.h>
32#include <C4StringTable.h>
33
34#include <cstdint>
35#include <list>
36#include <vector>
37
38// class predefs
39class C4AulError;
40class C4AulFunc;
41class C4AulScriptFunc;
42class C4AulScript;
43class C4AulScriptEngine;
44
45struct C4AulContext;
46struct C4AulBCC;
47
48class C4Def;
49class C4DefList;
50
51// consts
52#define C4AUL_MAX_String 1024 // max string length
53#define C4AUL_MAX_Identifier 100 // max length of function identifiers
54#define C4AUL_MAX_Par 10 // max number of parameters
55
56#define C4AUL_ControlMethod_None 0
57#define C4AUL_ControlMethod_Classic 1
58#define C4AUL_ControlMethod_JumpAndRun 2
59#define C4AUL_ControlMethod_All 3
60
61// generic C4Aul error class
62class C4AulError
63{
64protected:
65 std::string message;
66 bool isWarning{false};
67
68public:
69 C4AulError();
70 C4AulError(const C4AulError &Error) : message{Error.message}, isWarning{Error.isWarning} {}
71 virtual ~C4AulError() {}
72 virtual void show() const; // present error message
73};
74
75// parse error
76class C4AulParseError : public C4AulError
77{
78 C4AulParseError(std::string_view message, const char *identifier, bool warn);
79
80public:
81 C4AulParseError(C4AulScript *pScript, std::string_view msg, const char *pIdtf = nullptr, bool Warn = false);
82 C4AulParseError(class C4AulParseState *state, std::string_view msg, const char *pIdtf = nullptr, bool Warn = false);
83};
84
85// execution error
86class C4AulExecError : public C4AulError
87{
88 C4Object *cObj;
89
90public:
91 C4AulExecError(C4Object *pObj, std::string_view error);
92 virtual void show() const override; // present error message
93};
94
95// function access
96enum C4AulAccess
97{
98 AA_PRIVATE,
99 AA_PROTECTED,
100 AA_PUBLIC,
101 AA_GLOBAL
102};
103
104struct C4AulParSet
105{
106 C4Value Par[C4AUL_MAX_Par];
107
108 C4AulParSet() {}
109 C4AulParSet(const C4Value &par0, const C4Value &par1 = C4Value(), const C4Value &par2 = C4Value(), const C4Value &par3 = C4Value(), const C4Value &par4 = C4Value(),
110 const C4Value &par5 = C4Value(), const C4Value &par6 = C4Value(), const C4Value &par7 = C4Value(), const C4Value &par8 = C4Value(), const C4Value &par9 = C4Value())
111 {
112 Par[0].Set(par0); Par[1].Set(par1); Par[2].Set(par2); Par[3].Set(par3); Par[4].Set(par4);
113 Par[5].Set(par5); Par[6].Set(par6); Par[7].Set(par7); Par[8].Set(par8); Par[9].Set(par9);
114 }
115
116 C4Value &operator[](int iIdx) { return Par[iIdx]; }
117 const C4Value &operator[](int iIdx) const { return Par[iIdx]; }
118};
119
120#define Copy2ParSet8(Pars, Vars) Pars[0].Set(Vars##0); Pars[1].Set(Vars##1); Pars[2].Set(Vars##2); Pars[3].Set(Vars##3); Pars[4].Set(Vars##4); Pars[5].Set(Vars##5); Pars[6].Set(Vars##6); Pars[7].Set(Vars##7);
121#define Copy2ParSet9(Pars, Vars) Pars[0].Set(Vars##0); Pars[1].Set(Vars##1); Pars[2].Set(Vars##2); Pars[3].Set(Vars##3); Pars[4].Set(Vars##4); Pars[5].Set(Vars##5); Pars[6].Set(Vars##6); Pars[7].Set(Vars##7); Pars[8].Set(Vars##8);
122
123// byte code chunk type
124// some special script functions defined hard-coded to reduce the exec context
125enum C4AulBCCType
126{
127 AB_DEREF, // deref the current value
128 AB_MAPA_R, // map access via .
129 AB_MAPA_V, // not creating a reference
130 AB_ARRAYA_R, // array access
131 AB_ARRAYA_V, // not creating a reference
132 AB_ARRAY_APPEND, // always as a reference
133 AB_VARN_R, // a named var
134 AB_VARN_V,
135 AB_PARN_R, // a named parameter
136 AB_PARN_V,
137 AB_LOCALN_R, // a named local
138 AB_LOCALN_V,
139 AB_GLOBALN_R, // a named global
140 AB_GLOBALN_V,
141 AB_VAR_R, // Var statement
142 AB_VAR_V,
143 AB_PAR_R, // Par statement
144 AB_PAR_V,
145 AB_FUNC, // function
146
147 // prefix
148 AB_Inc1, // ++
149 AB_Dec1, // --
150 AB_BitNot, // ~
151 AB_Not, // !
152 AB_Neg, // -
153
154 // postfix (whithout second statement)
155 AB_Inc1_Postfix, // ++
156 AB_Dec1_Postfix, // --
157
158 // postfix
159 AB_Pow, // **
160 AB_Div, // /
161 AB_Mul, // *
162 AB_Mod, // %
163 AB_Sub, // -
164 AB_Sum, // +
165 AB_LeftShift, // <<
166 AB_RightShift, // >>
167 AB_LessThan, // <
168 AB_LessThanEqual, // <=
169 AB_GreaterThan, // >
170 AB_GreaterThanEqual, // >=
171 AB_Concat, // ..
172 AB_EqualIdent, // old ==
173 AB_Equal, // new ==
174 AB_NotEqualIdent, // old !=
175 AB_NotEqual, // new !=
176 AB_SEqual, // S=, eq
177 AB_SNEqual, // ne
178 AB_BitAnd, // &
179 AB_BitXOr, // ^
180 AB_BitOr, // |
181 AB_And, // &&
182 AB_Or, // ||
183 AB_NilCoalescing, // ??
184 AB_PowIt, // **=
185 AB_MulIt, // *=
186 AB_DivIt, // /=
187 AB_ModIt, // %=
188 AB_Inc, // +=
189 AB_Dec, // -=
190 AB_LeftShiftIt, // <<=
191 AB_RightShiftIt, // >>=
192 AB_ConcatIt, // ..=
193 AB_AndIt, // &=
194 AB_OrIt, // |=
195 AB_XOrIt, // ^=
196 AB_NilCoalescingIt, // ??= only the jumping part
197 AB_Set, // =
198
199 AB_CALLGLOBAL, // global context call
200 AB_CALL, // direct object call
201 AB_CALLFS, // failsafe direct call
202 AB_CALLNS, // direct object call: namespace operator
203 AB_STACK, // push nulls / pop
204 AB_NIL, // constant: nil
205 AB_INT, // constant: int
206 AB_BOOL, // constant: bool
207 AB_STRING, // constant: string
208 AB_C4ID, // constant: C4ID
209 AB_ARRAY, // semi-constant: array
210 AB_MAP, // semi-constant: map
211 AB_IVARN, // initialization of named var
212 AB_JUMP, // jump
213 AB_JUMPAND, // jump if zero, else pop the stack
214 AB_JUMPOR, // jump if not zero, else pop the stack
215 AB_JUMPNIL, // jump if nil, otherwise just continue
216 AB_JUMPNOTNIL, // jump if not nil, otherwise just continue
217 AB_CONDN, // conditional jump (negated, pops stack)
218 AB_FOREACH_NEXT, // foreach: next element in array
219 AB_FOREACH_MAP_NEXT, // foreach: next key-value pair in map
220 AB_RETURN, // return statement
221 AB_ERR, // parse error at this position
222 AB_EOFN, // end of function
223 AB_EOF, // end of file
224};
225
226// ** a definition of an operator
227// there are two classes of operators, the postfix-operators (+,-,&,...) and the
228// prefix-operators (mainly !,~,...).
229struct C4ScriptOpDef
230{
231 unsigned short Priority;
232 const char *Identifier;
233 C4AulBCCType Code;
234 bool Postfix;
235 bool RightAssociative; // right or left-associative?
236 bool NoSecondStatement; // no second statement expected (++/-- postfix)
237 C4V_Type RetType; // type returned. ignored by C4V
238 C4V_Type Type1;
239 C4V_Type Type2;
240};
241extern C4ScriptOpDef C4ScriptOpMap[];
242
243// byte code chunk
244struct C4AulBCC
245{
246 C4AulBCCType bccType; // chunk type
247 std::intptr_t bccX;
248 const char *SPos;
249};
250
251// call context
252struct C4AulContext
253{
254 C4Object *Obj;
255 C4Def *Def;
256 struct C4AulScriptContext *Caller;
257
258 bool CalledWithStrictNil() const noexcept;
259};
260
261// execution context
262struct C4AulScriptContext : public C4AulContext
263{
264 C4Value *Return;
265 C4Value *Pars;
266 C4Value *Vars;
267 C4AulScriptFunc *Func;
268 bool TemporaryScript;
269 C4ValueList NumVars;
270 C4AulBCC *CPos;
271 time_t tTime; // initialized only by profiler if active
272
273 size_t ParCnt() const { return Vars - Pars; }
274 void dump(std::string Dump = "");
275};
276
277// base function class
278class C4AulFunc
279{
280 friend class C4AulScript;
281 friend class C4AulScriptEngine;
282 friend class C4AulFuncMap;
283 friend class C4AulParseState;
284
285public:
286 C4AulFunc(C4AulScript *pOwner, const char *pName, bool bAtEnd = true);
287 virtual ~C4AulFunc();
288
289 C4AulScript *Owner; // owner
290 char Name[C4AUL_MAX_Identifier]; // function name
291
292protected:
293 C4AulFunc *Prev, *Next; // linked list members
294 C4AulFunc *MapNext; // map member
295 C4AulFunc *LinkedTo; // points to next linked function; destructor will destroy linked func, too
296
297public:
298 C4AulFunc *OverloadedBy; // function by which this one is overloaded
299 C4AulFunc *NextSNFunc; // next script func using the same name (list build in AfterLink)
300
301 virtual C4AulScriptFunc *SFunc() { return nullptr; } // type check func...
302
303 // Wether this function should be visible to players
304 virtual bool GetPublic() { return false; }
305 virtual int GetParCount() { return C4AUL_MAX_Par; }
306 virtual const C4V_Type *GetParType() { return nullptr; }
307 virtual C4V_Type GetRetType() { return C4V_Any; }
308 virtual C4Value Exec(C4AulContext *pCallerCtx, const C4Value pPars[], bool fPassErrors = false) { return C4Value(); } // execute func (script call)
309 virtual C4Value Exec(C4Object *pObj = nullptr, const C4AulParSet &pPars = C4AulParSet{}, bool fPassErrors = false, bool nonStrict3WarnConversionOnly = false, bool convertNilToIntBool = true); // execute func (engine call)
310 virtual void UnLink() { OverloadedBy = NextSNFunc = nullptr; }
311
312 C4AulFunc *GetLocalSFunc(const char *szIdtf); // find script function in own scope
313
314 C4AulFunc *FindSameNameFunc(C4Def *pScope); // Find a function of the same name for given scope
315
316protected:
317 void DestroyLinked(); // destroys linked functions
318};
319
320// script function class
321class C4AulScriptFunc : public C4AulFunc
322{
323public:
324 C4AulFunc *OwnerOverloaded; // overloaded owner function; if present
325 C4AulScriptFunc *SFunc() override { return this; } // type check func...
326
327protected:
328 void ParseDesc(); // evaluate desc (i.e. get idImage and Condition
329
330public:
331 C4AulAccess Access;
332 StdStrBuf Desc; // full function description block, including image and condition
333 StdStrBuf DescText; // short function description text (name of menu entry)
334 StdStrBuf DescLong; // secondary function description
335 C4ID idImage; // associated image
336 int32_t iImagePhase; // Image phase
337 C4AulFunc *Condition; // func condition
338 int32_t ControlMethod; // 0 = all, 1 = Classic, 2 = Jump+Run
339 const char *Script; // script pos
340 C4AulBCC *Code; // code pos
341 C4ValueMapNames VarNamed; // list of named vars in this function
342 C4ValueMapNames ParNamed; // list of named pars in this function
343 C4V_Type ParType[C4AUL_MAX_Par]; // parameter types
344 bool bNewFormat; // new func format? [ func xyz(par abc) { ... } ]
345 bool bReturnRef; // return reference
346 C4AulScript *pOrgScript; // the original script (!= Owner if included or appended)
347
348 C4AulScriptFunc(C4AulScript *pOwner, const char *pName, bool bAtEnd = true) : C4AulFunc(pOwner, pName, bAtEnd),
349 idImage(C4ID_None), iImagePhase(0), Condition(nullptr), ControlMethod(C4AUL_ControlMethod_All), OwnerOverloaded(nullptr),
350 bReturnRef(false), tProfileTime(0)
351 {
352 for (int i = 0; i < C4AUL_MAX_Par; i++) ParType[i] = C4V_Any;
353 }
354
355 virtual void UnLink() override;
356
357 virtual bool GetPublic() override { return true; }
358 virtual const C4V_Type *GetParType() override { return ParType; }
359 virtual C4V_Type GetRetType() override { return bReturnRef ? C4V_pC4Value : C4V_Any; }
360 virtual C4Value Exec(C4AulContext *pCallerCtx, const C4Value pPars[], bool fPassErrors = false) override; // execute func (script call, should not happen)
361 virtual C4Value Exec(C4Object *pObj = nullptr, const C4AulParSet &pPars = C4AulParSet{}, bool fPassErrors = false, bool nonStrict3WarnConversionOnly = false, bool convertNilToIntBool = true) override; // execute func (engine call)
362
363 void CopyBody(C4AulScriptFunc &FromFunc); // copy script/code, etc from given func
364
365 std::string GetFullName(); // get a fully classified name (C4ID::Name) for debug output
366
367 time_t tProfileTime; // internally set by profiler
368
369 bool HasStrictNil() const noexcept;
370
371 friend class C4AulScript;
372};
373
374class C4AulFuncMap
375{
376public:
377 C4AulFuncMap();
378 ~C4AulFuncMap();
379 C4AulFunc *GetFunc(const char *Name, const C4AulScript *Owner, const C4AulFunc *After);
380 C4AulFunc *GetFirstFunc(const char *Name);
381 C4AulFunc *GetNextSNFunc(const C4AulFunc *After);
382
383private:
384 C4AulFunc **Funcs;
385 int FuncCnt;
386 int Capacity;
387 static unsigned int Hash(const char *Name);
388
389protected:
390 void Add(C4AulFunc *func, bool bAtEnd = true);
391 void Remove(C4AulFunc *func);
392
393 friend class C4AulFunc;
394};
395
396// aul script state
397enum C4AulScriptState
398{
399 ASS_ERROR, // erroneous script
400 ASS_NONE, // nothing
401 ASS_PREPARSED, // function list built; CodeSize set
402 ASS_LINKED, // includes and appends resolved
403 ASS_PARSED // byte code generated
404};
405
406// script profiler entry
407class C4AulProfiler
408{
409private:
410 // map entry
411 struct Entry
412 {
413 C4AulScriptFunc *pFunc;
414 time_t tProfileTime;
415
416 bool operator<(const Entry &e2) const { return tProfileTime < e2.tProfileTime; }
417 };
418
419 // items
420 std::vector<Entry> Times;
421 std::shared_ptr<spdlog::logger> logger;
422
423public:
424 C4AulProfiler(std::shared_ptr<spdlog::logger> logger) : logger{std::move(logger)} {}
425
426 void CollectEntry(C4AulScriptFunc *pFunc, time_t tProfileTime);
427 void Show();
428
429 static void Abort();
430 static void StartProfiling(C4AulScript *pScript);
431 static void StopProfiling();
432};
433
434C4LOGGERCONFIG_NAME_TYPE(C4AulProfiler);
435
436template<>
437struct C4LoggerConfig::Defaults<C4AulProfiler>
438{
439 static constexpr spdlog::level::level_enum GuiLogLevel{spdlog::level::info};
440 static constexpr bool ShowLoggerNameInGui{false};
441};
442
443// script class
444class C4AulScript
445{
446public:
447 C4AulScript();
448 virtual ~C4AulScript();
449 void Default(); // init
450 void Clear(); // remove script, byte code and children
451 void Reg2List(C4AulScriptEngine *pEngine, C4AulScript *pOwner); // reg to linked list
452 void Unreg(); // remove from list
453 virtual bool Delete() { return true; } // allow deletion on pure class
454
455protected:
456 struct Append
457 {
458 const C4ID id;
459 const bool nowarn;
460
461 bool operator==(C4ID other) const { return id == other; }
462 };
463
464 C4AulFunc *Func0, *FuncL; // owned functions
465 C4AulScriptEngine *Engine; // owning engine
466 C4AulScript *Owner, *Prev, *Next, *Child0, *ChildL; // tree structure
467
468 StdStrBuf Script; // script
469 C4AulBCC *Code, *CPos; // compiled script (/pos)
470 C4AulScriptState State; // script state
471 int CodeSize; // current number of byte code chunks in Code
472 int CodeBufSize; // size of Code buffer
473 bool Preparsing; // set while preparse
474 bool Resolving; // set while include-resolving, to catch circular includes
475
476 std::list<C4ID> Includes; // include list
477 std::list<Append> Appends; // append list
478
479 // internal function used to find overloaded functions
480 C4AulFunc *GetOverloadedFunc(C4AulFunc *ByFunc);
481 C4AulFunc *GetFunc(const char *pIdtf); // get local function by name
482
483 void AddBCC(C4AulBCCType eType, std::intptr_t = 0, const char *SPos = nullptr); // add byte code chunk and advance
484 bool Preparse(); // preparse script; return if successful
485 void ParseFn(C4AulScriptFunc *Fn, bool fExprOnly = false); // parse single script function
486
487 bool Parse(); // parse preparsed script; return if successful
488 void ParseDescs(); // parse function descs
489
490 bool ResolveIncludes(C4DefList *rDefs); // resolve includes
491 bool ResolveAppends(C4DefList *rDefs); // resolve appends
492 bool IncludesResolved;
493 void AppendTo(C4AulScript &Scr, bool bHighPrio); // append to given script
494 void UnLink(); // reset to unlinked state
495 virtual void AfterLink(); // called after linking is completed; presearch common funcs here & search same-named funcs
496 virtual bool ReloadScript(const char *szPath); // reload given script
497
498 C4AulScript *FindFirstNonStrictScript(); // find first script that is not #strict
499
500 size_t GetCodePos() const { return CPos - Code; }
501 C4AulBCC *GetCodeByPos(size_t iPos) { return Code + iPos; }
502
503public:
504 std::string ScriptName; // script name
505 C4Def *Def; // owning def file
506 C4ValueMapNames LocalNamed;
507 C4ID idDef; // script id (to resolve includes)
508 C4AulScriptStrict Strict; // new or even newer syntax?
509 bool Temporary; // set for DirectExec-scripts; do not parse those
510
511 C4AulScriptEngine *GetEngine() { return Engine; }
512 const char *GetScript() const { return Script.getData(); }
513
514 C4AulFunc *GetFuncRecursive(const char *pIdtf); // search function by identifier, including global funcs
515 C4AulScriptFunc *GetSFunc(const char *pIdtf, C4AulAccess AccNeeded, bool fFailSafe = false); // get local sfunc, check access, check '~'-safety
516 C4AulScriptFunc *GetSFunc(const char *pIdtf); // get local script function by name
517 C4AulScriptFunc *GetSFunc(int iIndex, const char *szPattern = nullptr, C4AulAccess AccNeeded = AA_PRIVATE); // get local script function by index
518 C4AulScriptFunc *GetSFuncWarn(const char *pIdtf, C4AulAccess AccNeeded, const char *WarnStr); // get function; return nullptr and warn if not existent
519 C4AulAccess GetAllowedAccess(C4AulFunc *func, C4AulScript *caller);
520
521public:
522 C4Value DirectExec(C4Object *pObj, const char *szScript, const char *szContext, bool fPassErrors = false, C4AulScriptStrict Strict = C4AulScriptStrict::MAXSTRICT); // directly parse uncompiled script (WARG! CYCLES!)
523 void ResetProfilerTimes(); // zero all profiler times of owned functions
524 void CollectProfilerTimes(class C4AulProfiler &rProfiler);
525
526 bool IsReady() { return State == ASS_PARSED; } // whether script calls may be done
527
528 // helper functions
529 void Warn(std::string_view msg, const char *pIdtf);
530
531 friend class C4AulParseError;
532 friend class C4AulFunc;
533 friend class C4AulScriptFunc;
534 friend class C4AulScriptEngine;
535 friend class C4AulParseState;
536};
537
538// holds all C4AulScripts
539class C4AulScriptEngine : public C4AulScript
540{
541protected:
542 C4AulFuncMap FuncLookUp;
543
544public:
545 int warnCnt, errCnt; // number of warnings/errors
546 int nonStrictCnt; // number of non-strict scripts
547 int lineCnt; // line count parsed
548
549 C4ValueList Global;
550 C4ValueMapNames GlobalNamedNames;
551 C4ValueMapData GlobalNamed;
552
553 C4StringTable Strings;
554
555 // global constants (such as "static const C4D_Structure = 2;")
556 // cannot share var lists, because it's so closely tied to the data lists
557 // constants are used by the Parser only, anyway, so it's not
558 // necessary to pollute the global var list here
559 C4ValueMapNames GlobalConstNames;
560 C4ValueMapData GlobalConsts;
561
562 C4AulScriptEngine();
563 ~C4AulScriptEngine();
564 void Clear(); // clear data
565 void Link(C4DefList *rDefs); // link and parse all scripts
566 void ReLink(C4DefList *rDefs); // unlink + relink and parse all scripts
567 bool ReloadScript(const char *szScript, C4DefList *pDefs); // search script and reload + relink, if found
568
569 C4AulFunc *GetFirstFunc(const char *Name)
570 {
571 return FuncLookUp.GetFirstFunc(Name);
572 }
573
574 C4AulFunc *GetFunc(const char *Name, const C4AulScript *Owner, const C4AulFunc *After)
575 {
576 return FuncLookUp.GetFunc(Name, Owner, After);
577 }
578
579 C4AulFunc *GetNextSNFunc(const C4AulFunc *After)
580 {
581 return FuncLookUp.GetNextSNFunc(After);
582 }
583
584 // For the list of functions in the PropertyDlg
585 C4AulFunc *GetFirstFunc() { return Func0; }
586 C4AulFunc *GetNextFunc(C4AulFunc *pFunc) { return pFunc->Next; }
587
588 void RegisterGlobalConstant(const char *szName, const C4Value &rValue); // creates a new constants or overwrites an old one
589 bool GetGlobalConstant(const char *szName, C4Value *pTargetValue); // check if a constant exists; assign value to pTargetValue if not nullptr
590
591 bool DenumerateVariablePointers();
592 void UnLink(); // called when a script is being reloaded (clears string table)
593 // Compile scenario script data (without strings and constants)
594 void CompileFunc(StdCompiler *pComp);
595
596 friend class C4AulFunc;
597 friend class C4AulParseState;
598};
599
600class C4AulExec;
601
602C4LOGGERCONFIG_NAME_TYPE(C4AulExec);
603
604template<>
605struct C4LoggerConfig::Defaults<C4AulExec>
606{
607 static constexpr auto GuiLogLevel = spdlog::level::info;
608 static constexpr bool ShowLoggerNameInGui{false};
609};
610