1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 // links aul scripts; i.e. resolves includes & appends, etc
17
18 #include "C4Include.h"
19 #include "script/C4Aul.h"
20
21 #include "game/C4Game.h"
22 #include "landscape/C4Material.h"
23 #include "object/C4Def.h"
24 #include "object/C4DefList.h"
25 #include "object/C4GameObjects.h"
26 #include "script/C4Effect.h"
27
DoAppend(C4Def * def)28 void C4ScriptHost::DoAppend(C4Def *def)
29 {
30 if (std::find(def->Script.SourceScripts.begin(), def->Script.SourceScripts.end(), this) == def->Script.SourceScripts.end())
31 {
32 def->Script.SourceScripts.push_back(this);
33 if (!Includes.empty())
34 {
35 Warn("#appendto contains #include");
36 // Try to fullfil #include, but it won't work properly: #appendtos
37 // are always appended, but #includes are prepended to the script.
38 def->Script.Includes.insert(def->Script.Includes.end(), Includes.begin(), Includes.end());
39 }
40
41 }
42 }
43
44 // ResolveAppends and ResolveIncludes must be called both
45 // for each script. ResolveAppends has to be called first!
ResolveAppends(C4DefList * rDefs)46 bool C4ScriptHost::ResolveAppends(C4DefList *rDefs)
47 {
48 // resolve local appends
49 if (State != ASS_PREPARSED) return false;
50 for (auto & Append : Appends)
51 {
52 if (Append != "*" || !rDefs)
53 {
54 C4Def *Def = rDefs ? rDefs->GetByName(Append) : nullptr;
55 if (Def)
56 {
57 DoAppend(Def);
58 }
59 else
60 {
61 // save id in buffer because AulWarn will use the buffer of C4IdText
62 // to get the id of the object in which the error occurs...
63 // (stupid static buffers...)
64 Warn("#appendto %s not found", Append.getData());
65 }
66 }
67 else
68 {
69 // append to all defs
70 for (int i = 0; i < rDefs->GetDefCount(); i++)
71 {
72 C4Def *pDef = rDefs->GetDef(i);
73 if (!pDef) break;
74 if (pDef == GetPropList()) continue;
75 // append
76 DoAppend(pDef);
77 }
78 }
79 }
80 return true;
81 }
82
ResolveIncludes(C4DefList * rDefs)83 bool C4ScriptHost::ResolveIncludes(C4DefList *rDefs)
84 {
85 // Had been preparsed?
86 if (State != ASS_PREPARSED) return false;
87 // has already been resolved?
88 if (IncludesResolved) return true;
89 // catch circular includes
90 if (Resolving)
91 {
92 Engine->GetErrorHandler()->OnError(C4AulParseError(this, "Circular include chain detected - ignoring all includes!").what());
93 IncludesResolved = true;
94 State = ASS_LINKED;
95 return false;
96 }
97 Resolving=true;
98 // append all includes to local script
99 for (std::list<StdCopyStrBuf>::reverse_iterator i = Includes.rbegin(); i != Includes.rend(); ++i)
100 {
101 C4Def *Def = rDefs ? rDefs->GetByName(*i) : nullptr;
102 if (Def)
103 {
104 // resolve #includes in included script first (#include-chains :( )
105 if (!Def->Script.IncludesResolved)
106 if (!Def->Script.ResolveIncludes(rDefs))
107 continue; // skip this #include
108
109 for (std::list<C4ScriptHost *>::reverse_iterator s = Def->Script.SourceScripts.rbegin(); s != Def->Script.SourceScripts.rend(); ++s)
110 {
111 if (std::find(SourceScripts.begin(), SourceScripts.end(), *s) == SourceScripts.end())
112 SourceScripts.push_front(*s);
113 }
114 }
115 else
116 {
117 // save id in buffer because AulWarn will use the buffer of C4IdText
118 // to get the id of the object in which the error occurs...
119 // (stupid static buffers...)
120 Warn("#include %s not found", i->getData());
121 }
122 }
123 IncludesResolved = true;
124 // includes/appends are resolved now (for this script)
125 Resolving=false;
126 State = ASS_LINKED;
127 return true;
128 }
129
UnLink()130 void C4ScriptHost::UnLink()
131 {
132 C4PropList * p = GetPropList();
133 if (p)
134 {
135 p->C4PropList::Clear();
136 p->SetProperty(P_Prototype, C4VPropList(Engine->GetPropList()));
137 }
138
139 // Delete cyclic references of owned proplists
140 DeleteOwnedPropLists();
141
142 // includes will have to be re-resolved now
143 IncludesResolved = false;
144
145 if (State > ASS_PREPARSED) State = ASS_PREPARSED;
146 }
147
UnLink()148 void C4AulScriptEngine::UnLink()
149 {
150 warnCnt = errCnt = lineCnt = 0;
151
152 // Make everything writeable
153 GetPropList()->ThawRecursively();
154 for (C4ScriptHost *s = Child0; s; s = s->Next)
155 s->GetPropList()->ThawRecursively();
156
157 // unlink scripts
158 for (C4ScriptHost *s = Child0; s; s = s->Next)
159 s->UnLink();
160 // Do not clear global variables and constants, because they are registered by the
161 // preparser or other parts. Note that keeping those fields means that you cannot delete a global
162 // variable or constant at runtime by removing it from the script.
163 }
164
Link(C4DefList * rDefs)165 void C4AulScriptEngine::Link(C4DefList *rDefs)
166 {
167 try
168 {
169 // resolve appends
170 for (C4ScriptHost *s = Child0; s; s = s->Next)
171 s->ResolveAppends(rDefs);
172
173 // resolve includes
174 for (C4ScriptHost *s = Child0; s; s = s->Next)
175 s->ResolveIncludes(rDefs);
176
177 // parse the scripts to byte code
178 for (C4ScriptHost *s = Child0; s; s = s->Next)
179 s->Parse();
180
181 if (rDefs)
182 {
183 rDefs->SortByPriority();
184 rDefs->CallEveryDefinition();
185 }
186
187 // Done modifying the proplists now
188 for (C4ScriptHost *s = Child0; s; s = s->Next)
189 s->GetPropList()->FreezeAndMakeStaticRecursively(&s->ownedPropLists);
190
191 GetPropList()->FreezeAndMakeStaticRecursively(&OwnedPropLists);
192 }
193 catch (C4AulError &err)
194 {
195 // error??! show it!
196 ErrorHandler->OnError(err.what());
197 }
198
199 // Set name list for globals (FIXME: is this necessary?)
200 ScriptEngine.GlobalNamed.SetNameList(&ScriptEngine.GlobalNamedNames);
201 }
202
203
ReLink(C4DefList * rDefs)204 void C4AulScriptEngine::ReLink(C4DefList *rDefs)
205 {
206 // unlink scripts
207 UnLink();
208
209 // unlink defs
210 if (rDefs) rDefs->ResetIncludeDependencies();
211
212 // re-link
213 Link(rDefs);
214
215 // display state
216 LogF("C4AulScriptEngine linked - %d line%s, %d warning%s, %d error%s",
217 lineCnt, (lineCnt != 1 ? "s" : ""), warnCnt, (warnCnt != 1 ? "s" : ""), errCnt, (errCnt != 1 ? "s" : ""));
218
219 // adjust global effects
220 if (pGlobalEffects) pGlobalEffects->ReAssignAllCallbackFunctions();
221 if (GameScript.pScenarioEffects) GameScript.pScenarioEffects->ReAssignAllCallbackFunctions();
222 }
223
ReloadScript(const char * szScript,const char * szLanguage)224 bool C4AulScriptEngine::ReloadScript(const char *szScript, const char *szLanguage)
225 {
226 C4ScriptHost * s;
227 for (s = Child0; s; s = s->Next)
228 if (s->ReloadScript(szScript, szLanguage))
229 break;
230 return !!s;
231 }
232
233