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