1 // Emacs style mode select -*- C++ -*-
2 //----------------------------------------------------------------------------
3 //
4 // Copyright(C) 2000 Simon Howard
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 //
20 //--------------------------------------------------------------------------
21 //
22 // Preprocessor.
23 //
24 // The preprocessor must be called when the script is first loaded.
25 // It performs 2 functions:
26 //      1: blank out comments (which could be misinterpreted)
27 //      2: makes a list of all the sections held within {} braces
28 //      3: 'dry' runs the script: goes thru each statement and
29 //         sets the types of all the DFsSection's in the script
30 //      4: Saves locations of all goto() labels
31 //
32 // the system of DFsSection's is pretty horrible really, but it works
33 // and its probably the only way i can think of of saving scripts
34 // half-way thru running
35 //
36 // By Simon Howard
37 //
38 //---------------------------------------------------------------------------
39 //
40 // FraggleScript is from SMMU which is under the GPL. Technically,
41 // therefore, combining the FraggleScript code with the non-free
42 // ZDoom code is a violation of the GPL.
43 //
44 // As this may be a problem for you, I hereby grant an exception to my
45 // copyright on the SMMU source (including FraggleScript). You may use
46 // any code from SMMU in (G)ZDoom, provided that:
47 //
48 //    * For any binary release of the port, the source code is also made
49 //      available.
50 //    * The copyright notice is kept on any file containing my code.
51 //
52 //
53 
54 /* includes ************************/
55 
56 #include "t_script.h"
57 #include "i_system.h"
58 #include "w_wad.h"
59 #include "farchive.h"
60 
61 
62 //==========================================================================
63 //
64 // {} sections
65 //
66 // during preprocessing all of the {} sections
67 // are found. these are stored in a hash table
68 // according to their offset in the script.
69 // functions here deal with creating new sections
70 // and finding them from a given offset.
71 //
72 //==========================================================================
73 
74 IMPLEMENT_POINTY_CLASS(DFsSection)
DECLARE_POINTER(next)75  DECLARE_POINTER(next)
76 END_POINTERS
77 
78 //==========================================================================
79 //
80 //
81 //
82 //==========================================================================
83 
84 void DFsSection::Serialize(FArchive &ar)
85 {
86 	Super::Serialize(ar);
87 	ar << type << start_index << end_index << loop_index << next;
88 }
89 
90 //==========================================================================
91 //
92 //
93 //
94 //==========================================================================
95 
SectionStart(const DFsSection * sec)96 char *DFsScript::SectionStart(const DFsSection *sec)
97 {
98 	return data + sec->start_index;
99 }
100 
101 //==========================================================================
102 //
103 //
104 //
105 //==========================================================================
106 
SectionEnd(const DFsSection * sec)107 char *DFsScript::SectionEnd(const DFsSection *sec)
108 {
109 	return data + sec->end_index;
110 }
111 
112 //==========================================================================
113 //
114 //
115 //
116 //==========================================================================
117 
SectionLoop(const DFsSection * sec)118 char *DFsScript::SectionLoop(const DFsSection *sec)
119 {
120 	return data + sec->loop_index;
121 }
122 
123 //==========================================================================
124 //
125 //
126 //
127 //==========================================================================
128 
ClearSections()129 void DFsScript::ClearSections()
130 {
131 	for(int i=0;i<SECTIONSLOTS;i++)
132 	{
133 		DFsSection * var = sections[i];
134 		while(var)
135 		{
136 			DFsSection *next = var->next;
137 			var->Destroy();
138 			var = next;
139 		}
140 		sections[i] = NULL;
141 	}
142 }
143 
144 //==========================================================================
145 //
146 // create section
147 //
148 //==========================================================================
149 
NewSection(const char * brace)150 DFsSection *DFsScript::NewSection(const char *brace)
151 {
152 	int n = section_hash(brace);
153 	DFsSection *newsec = new DFsSection;
154 
155 	newsec->start_index = MakeIndex(brace);
156 	newsec->next = sections[n];
157 	sections[n] = newsec;
158 	GC::WriteBarrier(this, newsec);
159 	return newsec;
160 }
161 
162 //==========================================================================
163 //
164 // find a Section from the location of the starting { brace
165 //
166 //==========================================================================
167 
FindSectionStart(const char * brace)168 DFsSection *DFsScript::FindSectionStart(const char *brace)
169 {
170 	int n = section_hash(brace);
171 	DFsSection *current = sections[n];
172 
173 	// use the hash table: check the appropriate hash chain
174 
175 	while(current)
176     {
177 		if(SectionStart(current) == brace) return current;
178 		current = current->next;
179     }
180 
181 	return NULL;    // not found
182 }
183 
184 
185 //==========================================================================
186 //
187 // find a Section from the location of the closing } brace
188 //
189 //==========================================================================
190 
FindSectionEnd(const char * brace)191 DFsSection *DFsScript::FindSectionEnd(const char *brace)
192 {
193 	int n;
194 
195 	// hash table is no use, they are hashed according to
196 	// the offset of the starting brace
197 
198 	// we have to go through every entry to find from the
199 	// ending brace
200 
201 	for(n=0; n<SECTIONSLOTS; n++)      // check all sections in all chains
202 	{
203 		DFsSection *current = sections[n];
204 
205 		while(current)
206 		{
207 			if(SectionEnd(current) == brace) return current;        // found it
208 			current = current->next;
209 		}
210 	}
211 	return NULL;    // not found
212 }
213 
214 //==========================================================================
215 //
216 // preproocessor main loop
217 //
218 // This works by recursion. when a { opening
219 // brace is found, another instance of the
220 // function is called for the data inside
221 // the {} section.
222 // At the same time, the sections are noted
223 // down and hashed. Goto() labels are noted
224 // down, and comments are blanked out
225 //
226 //==========================================================================
227 
ProcessFindChar(char * datap,char find)228 char *DFsScript::ProcessFindChar(char *datap, char find)
229 {
230 	while(*datap)
231     {
232 		if(*datap==find) return datap;
233 		if(*datap=='\"')       // found a quote: ignore stuff in it
234 		{
235 			datap++;
236 			while(*datap && *datap != '\"')
237 			{
238 				// escape sequence ?
239 				if(*datap=='\\') datap++;
240 				datap++;
241 			}
242 			// error: end of script in a constant
243 			if(!*datap) return NULL;
244 		}
245 
246 		// comments: blank out
247 
248 		if(*datap=='/' && *(datap+1)=='*')        // /* -- */ comment
249 		{
250 			while(*datap && (*datap != '*' || *(datap+1) != '/') )
251 			{
252 				*datap=' '; datap++;
253 			}
254 			if(*datap)
255 				*datap = *(datap+1) = ' ';   // blank the last bit
256 			else
257 			{
258 				// script terminated in comment
259 				script_error("script terminated inside comment\n");
260 			}
261 		}
262 		if(*datap=='/' && *(datap+1)=='/')        // // -- comment
263 		{
264 			while(*datap != '\n')
265 			{
266 				*datap=' '; datap++;       // blank out
267 			}
268 		}
269 
270 		/********** labels ****************/
271 
272 		// labels are also found during the
273 		// preprocessing. these are of the form
274 		//
275 		//      label_name:
276 		//
277 		// and are used for the goto function.
278 		// goto labels are stored as variables.
279 
280 		if(*datap==':' && scriptnum != -1) // not in global scripts
281 		{
282 			char *labelptr = datap-1;
283 
284 			while(!isop(*labelptr)) labelptr--;
285 
286 			FString labelname(labelptr+1, strcspn(labelptr+1, ":"));
287 
288 			if (labelname.Len() == 0)
289 			{
290 				Printf(PRINT_BOLD,"Script %d: ':' encountrered in incorrect position!\n",scriptnum);
291 			}
292 
293 			DFsVariable *newlabel = NewVariable(labelname, svt_label);
294 			newlabel->value.i = MakeIndex(labelptr);
295 		}
296 
297 		if(*datap=='{')  // { -- } sections: add 'em
298 		{
299 			DFsSection *newsec = NewSection(datap);
300 
301 			newsec->type = st_empty;
302 			// find the ending } and save
303 			char * theend = ProcessFindChar(datap+1, '}');
304 			if(!theend)
305 			{                // brace not found
306 				// This is fatal because it will cause a crash later
307 				// if the game isn't terminated.
308 				I_Error("Script %d: section error: no ending brace\n", scriptnum);
309 			}
310 
311 			newsec->end_index = MakeIndex(theend);
312 			// continue from the end of the section
313 			datap = theend;
314 		}
315 		datap++;
316     }
317 	return NULL;
318 }
319 
320 
321 //==========================================================================
322 //
323 // second stage parsing
324 //
325 // second stage preprocessing considers the script
326 // in terms of tokens rather than as plain data.
327 //
328 // we 'dry' run the script: go thru each statement and
329 // collect types for Sections
330 //
331 // this is an important thing to do, it cannot be done
332 // at runtime for 2 reasons:
333 //      1. gotos() jumping inside loops will pass thru
334 //         the end of the loop
335 //      2. savegames. loading a script saved inside a
336 //         loop will let it pass thru the loop
337 //
338 // this is basically a cut-down version of the normal
339 // parsing loop.
340 //
341 //==========================================================================
342 
DryRunScript()343 void DFsScript::DryRunScript()
344 {
345 	char *end = data + len;
346 	char *rover = data;
347 
348 	// allocate space for the tokens
349 	FParser parse(this);
350 	try
351 	{
352 		while(rover < end && *rover)
353 		{
354 			rover = parse.GetTokens(rover);
355 
356 			if(!parse.NumTokens) continue;
357 
358 			if(parse.Section && parse.TokenType[0] == function)
359 			{
360 				if(!strcmp(parse.Tokens[0], "if"))
361 				{
362 					parse.Section->type = st_if;
363 					continue;
364 				}
365 				else if(!strcmp(parse.Tokens[0], "elseif")) // haleyjd: SoM's else code
366 				{
367 					parse.Section->type = st_elseif;
368 					continue;
369 				}
370 				else if(!strcmp(parse.Tokens[0], "else"))
371 				{
372 					parse.Section->type = st_else;
373 					continue;
374 				}
375 				else if(!strcmp(parse.Tokens[0], "while") ||
376 					!strcmp(parse.Tokens[0], "for"))
377 				{
378 					parse.Section->type = st_loop;
379 					parse.Section->loop_index = MakeIndex(parse.LineStart);
380 					continue;
381 				}
382 			}
383 		}
384 	}
385 	catch (CFsError err)
386 	{
387 		parse.ErrorMessage(err.msg);
388 	}
389 }
390 
391 //==========================================================================
392 //
393 // main preprocess function
394 //
395 //==========================================================================
396 
Preprocess()397 void DFsScript::Preprocess()
398 {
399 	len = (int)strlen(data);
400 	ProcessFindChar(data, 0);  // fill in everything
401 	DryRunScript();
402 }
403 
404 //==========================================================================
405 //
406 // FraggleScript allows 'including' of other lumps.
407 // we divert input from the current script (normally
408 // levelscript) to a seperate lump. This of course
409 // first needs to be preprocessed to remove comments
410 // etc.
411 //
412 // parse an 'include' lump
413 //
414 //==========================================================================
415 
ParseInclude(char * lumpname)416 void DFsScript::ParseInclude(char *lumpname)
417 {
418 	int lumpnum;
419 	char *lump;
420 
421 	if((lumpnum = Wads.CheckNumForName(lumpname)) == -1)
422     {
423 		I_Error("include lump '%s' not found!\n", lumpname);
424 		return;
425     }
426 
427 	int lumplen=Wads.LumpLength(lumpnum);
428 	lump=new char[lumplen+10];
429 	Wads.ReadLump(lumpnum,lump);
430 
431 	lump[lumplen]=0;
432 
433 	// preprocess the include
434 	// we assume that it does not include sections or labels or
435 	// other nasty things
436 	ProcessFindChar(lump, 0);
437 
438 	// now parse the lump
439 	FParser parse(this);
440 	parse.Run(lump, lump, lump+lumplen);
441 
442 	// free the lump
443 	delete[] lump;
444 }
445 
446