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// links aul scripts; i.e. resolves includes & appends, etc
19
20#include <C4Include.h>
21#include <C4Aul.h>
22
23#include <C4Def.h>
24#include <C4Game.h>
25#include <C4Log.h>
26
27// ResolveAppends and ResolveIncludes must be called both
28// for each script. ResolveAppends has to be called first!
29bool C4AulScript::ResolveAppends(C4DefList *rDefs)
30{
31 // resolve children appends
32 for (C4AulScript *s = Child0; s; s = s->Next) s->ResolveAppends(rDefs);
33 // resolve local appends
34 if (State != ASS_PREPARSED) return false;
35 for (const auto [a, nowarn] : Appends)
36 {
37 if (a != static_cast<C4ID>(-1))
38 {
39 C4Def *Def = rDefs->ID2Def(id: a);
40 if (Def)
41 AppendTo(Scr&: Def->Script, bHighPrio: true);
42 else if (!nowarn)
43 {
44 // save id in buffer because AulWarn will use the buffer of C4IdText
45 // to get the id of the object in which the error occurs...
46 // (stupid static buffers...)
47 char strID[5]; *strID = 0;
48 strcpy(dest: strID, src: C4IdText(id: a));
49 Warn(msg: "script to #appendto not found: ", pIdtf: strID);
50 }
51 }
52 else
53 {
54 // append to all defs
55 for (std::size_t i{0}; const auto pDef = rDefs->GetDef(index: i); ++i)
56 {
57 if (pDef == Def) continue;
58 // append
59 AppendTo(Scr&: pDef->Script, bHighPrio: true);
60 }
61 }
62 }
63 return true;
64}
65
66bool C4AulScript::ResolveIncludes(C4DefList *rDefs)
67{
68 // resolve children includes
69 for (C4AulScript *s = Child0; s; s = s->Next) s->ResolveIncludes(rDefs);
70 // Had been preparsed?
71 if (State != ASS_PREPARSED) return false;
72 // has already been resolved?
73 if (IncludesResolved) return true;
74 // catch circular includes
75 if (Resolving)
76 {
77 C4AulParseError(this, "Circular include chain detected - ignoring all includes!").show();
78 IncludesResolved = true;
79 State = ASS_LINKED;
80 return false;
81 }
82 Resolving = true;
83 // append all includes to local script
84 for (const auto i : Includes)
85 {
86 C4Def *Def = rDefs->ID2Def(id: i);
87 if (Def)
88 {
89 // resolve #includes in included script first (#include-chains :( )
90 if (!(static_cast<C4AulScript &>(Def->Script)).IncludesResolved)
91 if (!Def->Script.ResolveIncludes(rDefs))
92 continue; // skip this #include
93
94 Def->Script.AppendTo(Scr&: *this, bHighPrio: false);
95 }
96 else
97 {
98 // save id in buffer because AulWarn will use the buffer of C4IdText
99 // to get the id of the object in which the error occurs...
100 // (stupid static buffers...)
101 char strID[5]; *strID = 0;
102 strcpy(dest: strID, src: C4IdText(id: i));
103 Warn(msg: "script to #include not found: ", pIdtf: strID);
104 }
105 }
106 IncludesResolved = true;
107 // includes/appends are resolved now (for this script)
108 Resolving = false;
109 State = ASS_LINKED;
110 return true;
111}
112
113void C4AulScript::AppendTo(C4AulScript &Scr, bool bHighPrio)
114{
115 // definition appends
116 if (Def && Scr.Def) Scr.Def->IncludeDefinition(pIncludeDef: Def);
117 // append all funcs
118 // (in reverse order if inserted at begin of list)
119 C4AulScriptFunc *sf;
120 for (C4AulFunc *f = bHighPrio ? Func0 : FuncL; f; f = bHighPrio ? f->Next : f->Prev)
121 // script funcs only
122 if (sf = f->SFunc())
123 // no need to append global funcs
124 if (sf->Access != AA_GLOBAL)
125 {
126 // append: create copy
127 // (if high priority, insert at end, otherwise at the beginning)
128 C4AulScriptFunc *sfc = new C4AulScriptFunc(&Scr, sf->Name, bHighPrio);
129 sfc->CopyBody(FromFunc&: *sf);
130 // link the copy to a local function
131 if (sf->LinkedTo)
132 {
133 sfc->LinkedTo = sf->LinkedTo;
134 sf->LinkedTo = sfc;
135 }
136 else
137 {
138 sfc->LinkedTo = sf;
139 sf->LinkedTo = sfc;
140 }
141 }
142 // mark as linked
143 // increase code size needed
144 Scr.CodeSize += CodeSize + 1;
145 // append all local vars (if any existing)
146 assert(!Def || this == &Def->Script);
147 assert(!Scr.Def || &Scr.Def->Script == &Scr);
148 if (LocalNamed.iSize == 0)
149 return;
150 if (!Scr.Def)
151 {
152 Warn(msg: "could not append local variables to global script!", pIdtf: "");
153 return;
154 }
155 // copy local var definitions
156 for (int ivar = 0; ivar < LocalNamed.iSize; ivar++)
157 Scr.LocalNamed.AddName(pnName: LocalNamed.pNames[ivar]);
158}
159
160void C4AulScript::UnLink()
161{
162 // unlink children
163 for (C4AulScript *s = Child0; s; s = s->Next) s->UnLink();
164
165 // do not unlink temporary (e.g., DirectExec-script in ReloadDef)
166 if (Temporary) return;
167
168 // check if byte code needs to be freed
169 delete[] Code; Code = nullptr;
170
171 // delete included/appended functions
172 C4AulFunc *pFunc = Func0;
173 while (pFunc)
174 {
175 C4AulFunc *pNextFunc = pFunc->Next;
176
177 // clear stuff that's set in AfterLink
178 pFunc->UnLink();
179
180 if (pFunc->SFunc()) if (pFunc->Owner != pFunc->SFunc()->pOrgScript)
181 if (!pFunc->LinkedTo || pFunc->LinkedTo->SFunc()) // do not kill global links; those will be deleted if corresponding sfunc in script is deleted
182 delete pFunc;
183
184 pFunc = pNextFunc;
185 }
186 // includes will have to be re-resolved now
187 IncludesResolved = false;
188
189 if (State > ASS_PREPARSED) State = ASS_PREPARSED;
190}
191
192void C4AulScriptFunc::UnLink()
193{
194 OwnerOverloaded = nullptr;
195
196 // clear desc information, ParseDesc will set these later on
197 idImage = C4ID_None;
198 iImagePhase = 0;
199 Condition = nullptr;
200 ControlMethod = C4AUL_ControlMethod_All;
201
202 C4AulFunc::UnLink();
203}
204
205void C4AulScript::AfterLink()
206{
207 // for all funcs: search functions that have the same name in
208 // the whole script tree (for great fast direct object call)
209 for (C4AulFunc *Func = Func0; Func; Func = Func->Next)
210 // same-name ring not yet build for this function name?
211 if (!Func->NextSNFunc && !Func->OverloadedBy)
212 {
213 // init
214 Func->NextSNFunc = Func;
215 // search complete tree for functions with same name
216 // (expect all scripts "behind" this point to be already checked
217 // - so after-link calls for childs must be done after this).
218 C4AulScript *pPos = this;
219 while (pPos)
220 {
221 // has children? go down in hierarchy
222 if (pPos->Child0)
223 pPos = pPos->Child0;
224 else
225 {
226 // last child? go up in hierarchy
227 while (!pPos->Next && pPos->Owner)
228 pPos = pPos->Owner;
229 // next node
230 pPos = pPos->Next;
231 }
232 if (!pPos) break;
233 // has function with same name?
234 C4AulFunc *pFn = pPos->GetFunc(pIdtf: Func->Name);
235 if (pFn)
236 {
237 // resolve overloads
238 while (pFn->OverloadedBy) pFn = pFn->OverloadedBy;
239 // link
240 pFn->NextSNFunc = Func->NextSNFunc;
241 Func->NextSNFunc = pFn;
242 }
243 }
244 }
245 // call for childs
246 for (C4AulScript *s = Child0; s; s = s->Next) s->AfterLink();
247}
248
249bool C4AulScript::ReloadScript(const char *szPath)
250{
251 // call for childs
252 for (C4AulScript *s = Child0; s; s = s->Next)
253 if (s->ReloadScript(szPath))
254 return true;
255 return false;
256}
257
258void C4AulScriptEngine::Link(C4DefList *rDefs)
259{
260 try
261 {
262 // resolve appends
263 ResolveAppends(rDefs);
264
265 // resolve includes
266 ResolveIncludes(rDefs);
267
268 // parse script funcs descs
269 ParseDescs();
270
271 // parse the scripts to byte code
272 Parse();
273
274 // engine is always parsed (for global funcs)
275 State = ASS_PARSED;
276
277 // get common funcs
278 AfterLink();
279
280 // non-strict scripts?
281 if (nonStrictCnt)
282 {
283 // warn!
284 // find first non-#strict script
285 C4AulScript *pNonStrictScr = FindFirstNonStrictScript();
286 if (pNonStrictScr)
287 pNonStrictScr->Warn(msg: "using non-#strict syntax!", pIdtf: nullptr);
288 else
289 {
290 Warn(msg: "non-#strict script detected, but def is lost", pIdtf: nullptr);
291 Warn(msg: "please contact piracy@treffpunktclonk.net for further instructions", pIdtf: nullptr);
292 }
293 Warn(msg: std::format(fmt: "{} script{} use non-#strict syntax!", args&: nonStrictCnt, args: (nonStrictCnt != 1 ? "s" : "")), pIdtf: nullptr);
294 }
295
296 // update material pointers
297 Game.Material.UpdateScriptPointers();
298
299 // display state
300 LogNTr(level: spdlog::level::info, fmt: "C4AulScriptEngine linked - {} line{}, {} warning{}, {} error{}",
301 args&: lineCnt, args: (lineCnt != 1 ? "s" : ""), args&: warnCnt, args: (warnCnt != 1 ? "s" : ""), args&: errCnt, args: (errCnt != 1 ? "s" : ""));
302
303 // reset counters
304 warnCnt = errCnt = nonStrictCnt = lineCnt = 0;
305 }
306 catch (const C4AulError &err)
307 {
308 // error??! show it!
309 err.show();
310 }
311}
312
313void C4AulScriptEngine::ReLink(C4DefList *rDefs)
314{
315 // unlink scripts
316 UnLink();
317
318 // unlink defs
319 if (rDefs) rDefs->ResetIncludeDependencies();
320
321 // clear string table
322 Game.Script.UnLink();
323
324 // re-link
325 Link(rDefs);
326
327 // update effect pointers
328 Game.Objects.UpdateScriptPointers();
329
330 // update material pointers
331 Game.Material.UpdateScriptPointers();
332}
333
334bool C4AulScriptEngine::ReloadScript(const char *szScript, C4DefList *pDefs)
335{
336 // reload
337 if (!C4AulScript::ReloadScript(szPath: szScript))
338 return false;
339 // relink
340 ReLink(rDefs: pDefs);
341 // ok
342 return true;
343}
344