1 /* |
|
2 * botc source code |
|
3 * Copyright (C) 2012 Santeri `Dusk` Piippo |
|
4 * All rights reserved. |
|
5 * |
|
6 * Redistribution and use in source and binary forms, with or without |
|
7 * modification, are permitted provided that the following conditions are met: |
|
8 * |
|
9 * 1. Redistributions of source code must retain the above copyright notice, |
|
10 * this list of conditions and the following disclaimer. |
|
11 * 2. Redistributions in binary form must reproduce the above copyright notice, |
|
12 * this list of conditions and the following disclaimer in the documentation |
|
13 * and/or other materials provided with the distribution. |
|
14 * 3. Neither the name of the developer nor the names of its contributors may |
|
15 * be used to endorse or promote products derived from this software without |
|
16 * specific prior written permission. |
|
17 * 4. Redistributions in any form must be accompanied by information on how to |
|
18 * obtain complete source code for the software and any accompanying |
|
19 * software that uses the software. The source code must either be included |
|
20 * in the distribution or be available for no more than the cost of |
|
21 * distribution plus a nominal fee, and must be freely redistributable |
|
22 * under reasonable conditions. For an executable file, complete source |
|
23 * code means the source code for all modules it contains. It does not |
|
24 * include source code for modules or files that typically accompany the |
|
25 * major components of the operating system on which the executable file |
|
26 * runs. |
|
27 * |
|
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
29 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|
32 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
34 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
35 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
38 * POSSIBILITY OF SUCH DAMAGE. |
|
39 */ |
|
40 |
|
41 #include <stdio.h> |
|
42 #include <stdlib.h> |
|
43 #include "string.h" |
|
44 #include "str.h" |
|
45 #include "common.h" |
|
46 #include "scriptreader.h" |
|
47 |
|
48 #define STORE_POSITION \ |
|
49 bool _atnewline = atnewline; \ |
|
50 unsigned int _curline = curline[fc]; \ |
|
51 unsigned int _curchar = curchar[fc]; |
|
52 |
|
53 #define RESTORE_POSITION \ |
|
54 atnewline = _atnewline; \ |
|
55 curline[fc] = _curline; \ |
|
56 curchar[fc] = _curchar; |
|
57 |
|
58 // ============================================================================ |
|
59 ScriptReader::ScriptReader (str path) { |
|
60 token = ""; |
|
61 prevtoken = ""; |
|
62 prevpos = 0; |
|
63 fc = -1; |
|
64 |
|
65 for (unsigned int u = 0; u < MAX_FILESTACK; u++) |
|
66 fp[u] = NULL; |
|
67 |
|
68 OpenFile (path); |
|
69 commentmode = 0; |
|
70 } |
|
71 |
|
72 // ============================================================================ |
|
73 ScriptReader::~ScriptReader () { |
|
74 // If comment mode is 2 by the time the file ended, the |
|
75 // comment was left unterminated. 1 is no problem, since |
|
76 // it's terminated by newlines anyway. |
|
77 if (commentmode == 2) |
|
78 ParserError ("unterminated `/*`-style comment"); |
|
79 |
|
80 for (unsigned int u = 0; u < MAX_FILESTACK; u++) { |
|
81 if (fp[u]) { |
|
82 ParserWarning ("file idx %u remained open after parsing", u); |
|
83 CloseFile (u); |
|
84 } |
|
85 } |
|
86 } |
|
87 |
|
88 // ============================================================================ |
|
89 // Opens a file and pushes its pointer to stack |
|
90 void ScriptReader::OpenFile (str path) { |
|
91 if (fc+1 >= MAX_FILESTACK) |
|
92 ParserError ("supposed to open file `%s` but file stack is full! do you have recursive `#include` directives?", |
|
93 path.chars()); |
|
94 |
|
95 // Save the position first. |
|
96 if (fc != -1) { |
|
97 savedpos[fc] = ftell (fp[fc]); |
|
98 } |
|
99 |
|
100 fc++; |
|
101 |
|
102 fp[fc] = fopen (path, "r"); |
|
103 if (!fp[fc]) { |
|
104 ParserError ("couldn't open %s for reading!\n", path.chars ()); |
|
105 exit (1); |
|
106 } |
|
107 |
|
108 fseek (fp[fc], 0, SEEK_SET); |
|
109 filepath[fc] = path.chars(); |
|
110 curline[fc] = 1; |
|
111 curchar[fc] = 1; |
|
112 pos[fc] = 0; |
|
113 atnewline = 0; |
|
114 } |
|
115 |
|
116 // ============================================================================ |
|
117 // Closes the current file |
|
118 void ScriptReader::CloseFile (unsigned int u) { |
|
119 if (u >= MAX_FILESTACK) |
|
120 u = fc; |
|
121 |
|
122 if (!fp[u]) |
|
123 return; |
|
124 |
|
125 fclose (fp[u]); |
|
126 fp[u] = NULL; |
|
127 fc--; |
|
128 |
|
129 if (fc != -1) |
|
130 fseek (fp[fc], savedpos[fc], SEEK_SET); |
|
131 } |
|
132 |
|
133 // ============================================================================ |
|
134 char ScriptReader::ReadChar () { |
|
135 if (feof (fp[fc])) |
|
136 return 0; |
|
137 |
|
138 char c; |
|
139 if (!fread (&c, 1, 1, fp[fc])) |
|
140 return 0; |
|
141 |
|
142 // We're at a newline, thus next char read will begin the next line |
|
143 if (atnewline) { |
|
144 atnewline = false; |
|
145 curline[fc]++; |
|
146 curchar[fc] = 0; // gets incremented to 1 |
|
147 } |
|
148 |
|
149 if (c == '\n') { |
|
150 atnewline = true; |
|
151 |
|
152 // Check for pre-processor directives |
|
153 PreprocessDirectives (); |
|
154 } |
|
155 |
|
156 curchar[fc]++; |
|
157 return c; |
|
158 } |
|
159 |
|
160 // ============================================================================ |
|
161 // Peeks the next character |
|
162 char ScriptReader::PeekChar (int offset) { |
|
163 // Store current position |
|
164 long curpos = ftell (fp[fc]); |
|
165 STORE_POSITION |
|
166 |
|
167 // Forward by offset |
|
168 fseek (fp[fc], offset, SEEK_CUR); |
|
169 |
|
170 // Read the character |
|
171 char* c = (char*)malloc (sizeof (char)); |
|
172 |
|
173 if (!fread (c, sizeof (char), 1, fp[fc])) { |
|
174 fseek (fp[fc], curpos, SEEK_SET); |
|
175 return 0; |
|
176 } |
|
177 |
|
178 // Rewind back |
|
179 fseek (fp[fc], curpos, SEEK_SET); |
|
180 RESTORE_POSITION |
|
181 |
|
182 return c[0]; |
|
183 } |
|
184 |
|
185 // ============================================================================ |
|
186 // Read a token from the file buffer. Returns true if token was found, false if not. |
|
187 bool ScriptReader::Next (bool peek) { |
|
188 prevpos = ftell (fp[fc]); |
|
189 str tmp = ""; |
|
190 |
|
191 while (1) { |
|
192 // Check end-of-file |
|
193 if (feof (fp[fc])) { |
|
194 // If we're just peeking, we shouldn't |
|
195 // actually close anything.. |
|
196 if (peek) |
|
197 break; |
|
198 |
|
199 CloseFile (); |
|
200 if (fc == -1) |
|
201 break; |
|
202 } |
|
203 |
|
204 // Check if the next token possibly starts a comment. |
|
205 if (PeekChar () == '/' && !tmp.len()) { |
|
206 char c2 = PeekChar (1); |
|
207 // C++-style comment |
|
208 if (c2 == '/') |
|
209 commentmode = 1; |
|
210 else if (c2 == '*') |
|
211 commentmode = 2; |
|
212 |
|
213 // We don't need to actually read in the |
|
214 // comment characters, since they will get |
|
215 // ignored due to comment mode anyway. |
|
216 } |
|
217 |
|
218 c = ReadChar (); |
|
219 |
|
220 // If this is a comment we're reading, check if this character |
|
221 // gets the comment terminated, otherwise ignore it. |
|
222 if (commentmode > 0) { |
|
223 if (commentmode == 1 && c == '\n') { |
|
224 // C++-style comments are terminated by a newline |
|
225 commentmode = 0; |
|
226 continue; |
|
227 } else if (commentmode == 2 && c == '*') { |
|
228 // C-style comments are terminated by a `*/` |
|
229 if (PeekChar() == '/') { |
|
230 commentmode = 0; |
|
231 ReadChar (); |
|
232 } |
|
233 } |
|
234 |
|
235 // Otherwise, ignore it. |
|
236 continue; |
|
237 } |
|
238 |
|
239 // Non-alphanumber characters (sans underscore) break the word too. |
|
240 // If there was prior data, the delimeter pushes the cursor back so |
|
241 // that the next character will be the same delimeter. If there isn't, |
|
242 // the delimeter itself is included (and thus becomes a token itself.) |
|
243 if ((c >= 33 && c <= 47) || |
|
244 (c >= 58 && c <= 64) || |
|
245 (c >= 91 && c <= 96 && c != '_') || |
|
246 (c >= 123 && c <= 126)) { |
|
247 if (tmp.len()) |
|
248 fseek (fp[fc], ftell (fp[fc]) - 1, SEEK_SET); |
|
249 else |
|
250 tmp += c; |
|
251 break; |
|
252 } |
|
253 |
|
254 if (IsCharWhitespace (c)) { |
|
255 // Don't break if we haven't gathered anything yet. |
|
256 if (tmp.len()) |
|
257 break; |
|
258 } else { |
|
259 tmp += c; |
|
260 } |
|
261 } |
|
262 |
|
263 // If we got nothing here, read failed. This should |
|
264 // only happen in the case of EOF. |
|
265 if (!tmp.len()) { |
|
266 token = ""; |
|
267 return false; |
|
268 } |
|
269 |
|
270 pos[fc]++; |
|
271 prevtoken = token; |
|
272 token = tmp; |
|
273 return true; |
|
274 } |
|
275 |
|
276 // ============================================================================ |
|
277 // Returns the next token without advancing the cursor. |
|
278 str ScriptReader::PeekNext (int offset) { |
|
279 // Store current information |
|
280 str storedtoken = token; |
|
281 int cpos = ftell (fp[fc]); |
|
282 STORE_POSITION |
|
283 |
|
284 // Advance on the token. |
|
285 while (offset >= 0) { |
|
286 if (!Next (true)) |
|
287 return ""; |
|
288 offset--; |
|
289 } |
|
290 |
|
291 str tmp = token; |
|
292 |
|
293 // Restore position |
|
294 fseek (fp[fc], cpos, SEEK_SET); |
|
295 pos[fc]--; |
|
296 token = storedtoken; |
|
297 RESTORE_POSITION |
|
298 return tmp; |
|
299 } |
|
300 |
|
301 // ============================================================================ |
|
302 void ScriptReader::Seek (unsigned int n, int origin) { |
|
303 switch (origin) { |
|
304 case SEEK_SET: |
|
305 fseek (fp[fc], 0, SEEK_SET); |
|
306 pos[fc] = 0; |
|
307 break; |
|
308 case SEEK_CUR: |
|
309 break; |
|
310 case SEEK_END: |
|
311 printf ("ScriptReader::Seek: SEEK_END not yet supported.\n"); |
|
312 break; |
|
313 } |
|
314 |
|
315 for (unsigned int i = 0; i < n+1; i++) |
|
316 Next(); |
|
317 } |
|
318 |
|
319 // ============================================================================ |
|
320 void ScriptReader::MustNext (const char* c) { |
|
321 if (!Next()) { |
|
322 if (strlen (c)) |
|
323 ParserError ("expected `%s`, reached end of file instead\n", c); |
|
324 else |
|
325 ParserError ("expected a token, reached end of file instead\n"); |
|
326 } |
|
327 |
|
328 if (strlen (c)) |
|
329 MustThis (c); |
|
330 } |
|
331 |
|
332 // ============================================================================ |
|
333 void ScriptReader::MustThis (const char* c) { |
|
334 if (token != c) |
|
335 ParserError ("expected `%s`, got `%s` instead", c, token.chars()); |
|
336 } |
|
337 |
|
338 // ============================================================================ |
|
339 void ScriptReader::ParserError (const char* message, ...) { |
|
340 PERFORM_FORMAT (message, outmessage); |
|
341 ParserMessage ("\nError: ", outmessage); |
|
342 exit (1); |
|
343 } |
|
344 |
|
345 // ============================================================================ |
|
346 void ScriptReader::ParserWarning (const char* message, ...) { |
|
347 PERFORM_FORMAT (message, outmessage); |
|
348 ParserMessage ("Warning: ", outmessage); |
|
349 } |
|
350 |
|
351 // ============================================================================ |
|
352 void ScriptReader::ParserMessage (const char* header, char* message) { |
|
353 if (fc >= 0 && fc < MAX_FILESTACK) |
|
354 fprintf (stderr, "%s%s:%u:%u: %s\n", |
|
355 header, filepath[fc], curline[fc], curchar[fc], message); |
|
356 else |
|
357 fprintf (stderr, "%s%s\n", header, message); |
|
358 } |
|
359 |
|
360 // ============================================================================ |
|
361 // if gotquote == 1, the current token already holds the quotation mark. |
|
362 void ScriptReader::MustString (bool gotquote) { |
|
363 if (gotquote) |
|
364 MustThis ("\""); |
|
365 else |
|
366 MustNext ("\""); |
|
367 |
|
368 str string; |
|
369 // Keep reading characters until we find a terminating quote. |
|
370 while (1) { |
|
371 // can't end here! |
|
372 if (feof (fp[fc])) |
|
373 ParserError ("unterminated string"); |
|
374 |
|
375 char c = ReadChar (); |
|
376 if (c == '"') |
|
377 break; |
|
378 |
|
379 string += c; |
|
380 } |
|
381 |
|
382 token = string; |
|
383 } |
|
384 |
|
385 // ============================================================================ |
|
386 void ScriptReader::MustNumber (bool fromthis) { |
|
387 if (!fromthis) |
|
388 MustNext (); |
|
389 |
|
390 str num = token; |
|
391 if (num == "-") { |
|
392 MustNext (); |
|
393 num += token; |
|
394 } |
|
395 |
|
396 // "true" and "false" are valid numbers |
|
397 if (!token.icompare ("true")) |
|
398 token = "1"; |
|
399 else if (!token.icompare ("false")) |
|
400 token = "0"; |
|
401 else { |
|
402 if (!token.isnumber()) |
|
403 ParserError ("expected a number, got `%s`", num.chars()); |
|
404 |
|
405 str check; |
|
406 check.appendformat ("%d", atoi (num)); |
|
407 if (token != check) |
|
408 ParserWarning ("integer too large: %s -> %s", num.chars(), check.chars()); |
|
409 |
|
410 token = num; |
|
411 } |
|
412 } |
|