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