1 #include "main.h" |
|
2 #include "scriptreader.h" |
|
3 |
|
4 #define STORE_POSITION \ |
|
5 bool stored_atnewline = atnewline; \ |
|
6 unsigned int stored_curline = curline[fc]; \ |
|
7 unsigned int stored_curchar = curchar[fc]; |
|
8 |
|
9 #define RESTORE_POSITION \ |
|
10 atnewline = stored_atnewline; \ |
|
11 curline[fc] = stored_curline; \ |
|
12 curchar[fc] = stored_curchar; |
|
13 |
|
14 // ============================================================================ |
|
15 ScriptReader::ScriptReader (string path) { |
|
16 token = ""; |
|
17 prevtoken = ""; |
|
18 prevpos = 0; |
|
19 fc = -1; |
|
20 |
|
21 for (unsigned int u = 0; u < MAX_FILESTACK; u++) |
|
22 fp[u] = null; |
|
23 |
|
24 OpenFile (path); |
|
25 commentmode = 0; |
|
26 } |
|
27 |
|
28 // ============================================================================ |
|
29 ScriptReader::~ScriptReader () { |
|
30 // If comment mode is 2 by the time the file ended, the |
|
31 // comment was left unterminated. 1 is no problem, since |
|
32 // it's terminated by newlines anyway. |
|
33 if (commentmode == 2) |
|
34 ParserError ("unterminated `/*`-style comment"); |
|
35 |
|
36 for (unsigned int u = 0; u < MAX_FILESTACK; u++) { |
|
37 if (fp[u]) { |
|
38 ParserWarning ("file idx %u remained open after parsing", u); |
|
39 CloseFile (u); |
|
40 } |
|
41 } |
|
42 } |
|
43 |
|
44 // ============================================================================ |
|
45 // Opens a file and pushes its pointer to stack |
|
46 void ScriptReader::OpenFile (string path) { |
|
47 if (fc+1 >= MAX_FILESTACK) |
|
48 ParserError ("supposed to open file `%s` but file stack is full! do you have recursive `#include` directives?", |
|
49 path.chars()); |
|
50 |
|
51 // Save the position first. |
|
52 if (fc != -1) { |
|
53 savedpos[fc] = ftell (fp[fc]); |
|
54 } |
|
55 |
|
56 fc++; |
|
57 |
|
58 fp[fc] = fopen (path, "r"); |
|
59 if (!fp[fc]) { |
|
60 ParserError ("couldn't open %s for reading!\n", path.chars ()); |
|
61 exit (1); |
|
62 } |
|
63 |
|
64 fseek (fp[fc], 0, SEEK_SET); |
|
65 filepath[fc] = path.chars(); |
|
66 curline[fc] = 1; |
|
67 curchar[fc] = 1; |
|
68 pos[fc] = 0; |
|
69 atnewline = 0; |
|
70 } |
|
71 |
|
72 // ============================================================================ |
|
73 // Closes the current file |
|
74 void ScriptReader::CloseFile (unsigned int u) { |
|
75 if (u >= MAX_FILESTACK) |
|
76 u = fc; |
|
77 |
|
78 if (!fp[u]) |
|
79 return; |
|
80 |
|
81 fclose (fp[u]); |
|
82 fp[u] = null; |
|
83 fc--; |
|
84 |
|
85 if (fc != -1) |
|
86 fseek (fp[fc], savedpos[fc], SEEK_SET); |
|
87 } |
|
88 |
|
89 // ============================================================================ |
|
90 char ScriptReader::ReadChar () { |
|
91 if (feof (fp[fc])) |
|
92 return 0; |
|
93 |
|
94 char c; |
|
95 if (!fread (&c, 1, 1, fp[fc])) |
|
96 return 0; |
|
97 |
|
98 // We're at a newline, thus next char read will begin the next line |
|
99 if (atnewline) { |
|
100 atnewline = false; |
|
101 curline[fc]++; |
|
102 curchar[fc] = 0; // gets incremented to 1 |
|
103 } |
|
104 |
|
105 if (c == '\n') { |
|
106 atnewline = true; |
|
107 |
|
108 // Check for pre-processor directives |
|
109 PreprocessDirectives (); |
|
110 } |
|
111 |
|
112 curchar[fc]++; |
|
113 return c; |
|
114 } |
|
115 |
|
116 // ============================================================================ |
|
117 // Peeks the next character |
|
118 char ScriptReader::PeekChar (int offset) { |
|
119 // Store current position |
|
120 long curpos = ftell (fp[fc]); |
|
121 STORE_POSITION |
|
122 |
|
123 // Forward by offset |
|
124 fseek (fp[fc], offset, SEEK_CUR); |
|
125 |
|
126 // Read the character |
|
127 char* c = (char*)malloc (sizeof (char)); |
|
128 |
|
129 if (!fread (c, sizeof (char), 1, fp[fc])) { |
|
130 fseek (fp[fc], curpos, SEEK_SET); |
|
131 return 0; |
|
132 } |
|
133 |
|
134 // Rewind back |
|
135 fseek (fp[fc], curpos, SEEK_SET); |
|
136 RESTORE_POSITION |
|
137 |
|
138 return c[0]; |
|
139 } |
|
140 |
|
141 // ============================================================================ |
|
142 // Read a token from the file buffer. Returns true if token was found, false if not. |
|
143 bool ScriptReader::Next (bool peek) { |
|
144 prevpos = ftell (fp[fc]); |
|
145 string tmp = ""; |
|
146 |
|
147 while (1) { |
|
148 // Check end-of-file |
|
149 if (feof (fp[fc])) { |
|
150 // If we're just peeking, we shouldn't |
|
151 // actually close anything.. |
|
152 if (peek) |
|
153 break; |
|
154 |
|
155 CloseFile (); |
|
156 if (fc == -1) |
|
157 break; |
|
158 } |
|
159 |
|
160 // Check if the next token possibly starts a comment. |
|
161 if (PeekChar () == '/' && !tmp.len()) { |
|
162 char c2 = PeekChar (1); |
|
163 // C++-style comment |
|
164 if (c2 == '/') |
|
165 commentmode = 1; |
|
166 else if (c2 == '*') |
|
167 commentmode = 2; |
|
168 |
|
169 // We don't need to actually read in the |
|
170 // comment characters, since they will get |
|
171 // ignored due to comment mode anyway. |
|
172 } |
|
173 |
|
174 c = ReadChar (); |
|
175 |
|
176 // If this is a comment we're reading, check if this character |
|
177 // gets the comment terminated, otherwise ignore it. |
|
178 if (commentmode > 0) { |
|
179 if (commentmode == 1 && c == '\n') { |
|
180 // C++-style comments are terminated by a newline |
|
181 commentmode = 0; |
|
182 continue; |
|
183 } else if (commentmode == 2 && c == '*') { |
|
184 // C-style comments are terminated by a `*/` |
|
185 if (PeekChar() == '/') { |
|
186 commentmode = 0; |
|
187 ReadChar (); |
|
188 } |
|
189 } |
|
190 |
|
191 // Otherwise, ignore it. |
|
192 continue; |
|
193 } |
|
194 |
|
195 // Non-alphanumber characters (sans underscore) break the word too. |
|
196 // If there was prior data, the delimeter pushes the cursor back so |
|
197 // that the next character will be the same delimeter. If there isn't, |
|
198 // the delimeter itself is included (and thus becomes a token itself.) |
|
199 if ((c >= 33 && c <= 47) || |
|
200 (c >= 58 && c <= 64) || |
|
201 (c >= 91 && c <= 96 && c != '_') || |
|
202 (c >= 123 && c <= 126)) { |
|
203 if (tmp.len()) |
|
204 fseek (fp[fc], ftell (fp[fc]) - 1, SEEK_SET); |
|
205 else |
|
206 tmp += c; |
|
207 break; |
|
208 } |
|
209 |
|
210 if (isspace (c)) { |
|
211 // Don't break if we haven't gathered anything yet. |
|
212 if (tmp.len()) |
|
213 break; |
|
214 } else { |
|
215 tmp += c; |
|
216 } |
|
217 } |
|
218 |
|
219 // If we got nothing here, read failed. This should |
|
220 // only happen in the case of EOF. |
|
221 if (!tmp.len()) { |
|
222 token = ""; |
|
223 return false; |
|
224 } |
|
225 |
|
226 pos[fc]++; |
|
227 prevtoken = token; |
|
228 token = tmp; |
|
229 return true; |
|
230 } |
|
231 |
|
232 // ============================================================================ |
|
233 // Returns the next token without advancing the cursor. |
|
234 string ScriptReader::PeekNext (int offset) { |
|
235 // Store current information |
|
236 string storedtoken = token; |
|
237 int cpos = ftell (fp[fc]); |
|
238 STORE_POSITION |
|
239 |
|
240 // Advance on the token. |
|
241 while (offset >= 0) { |
|
242 if (!Next (true)) |
|
243 return ""; |
|
244 offset--; |
|
245 } |
|
246 |
|
247 string tmp = token; |
|
248 |
|
249 // Restore position |
|
250 fseek (fp[fc], cpos, SEEK_SET); |
|
251 pos[fc]--; |
|
252 token = storedtoken; |
|
253 RESTORE_POSITION |
|
254 return tmp; |
|
255 } |
|
256 |
|
257 // ============================================================================ |
|
258 void ScriptReader::Seek (unsigned int n, int origin) { |
|
259 switch (origin) { |
|
260 case SEEK_SET: |
|
261 fseek (fp[fc], 0, SEEK_SET); |
|
262 pos[fc] = 0; |
|
263 break; |
|
264 case SEEK_CUR: |
|
265 break; |
|
266 case SEEK_END: |
|
267 printf ("ScriptReader::Seek: SEEK_END not yet supported.\n"); |
|
268 break; |
|
269 } |
|
270 |
|
271 for (unsigned int i = 0; i < n+1; i++) |
|
272 Next(); |
|
273 } |
|
274 |
|
275 // ============================================================================ |
|
276 void ScriptReader::MustNext (const char* c) { |
|
277 if (!Next()) { |
|
278 if (strlen (c)) |
|
279 ParserError ("expected `%s`, reached end of file instead\n", c); |
|
280 else |
|
281 ParserError ("expected a token, reached end of file instead\n"); |
|
282 } |
|
283 |
|
284 if (strlen (c)) |
|
285 MustThis (c); |
|
286 } |
|
287 |
|
288 // ============================================================================ |
|
289 void ScriptReader::MustThis (const char* c) { |
|
290 if (token != c) |
|
291 ParserError ("expected `%s`, got `%s` instead", c, token.chars()); |
|
292 } |
|
293 |
|
294 // ============================================================================ |
|
295 void ScriptReader::ParserError (const char* message, ...) { |
|
296 char buf[512]; |
|
297 va_list va; |
|
298 va_start (va, message); |
|
299 sprintf (buf, message, va); |
|
300 va_end (va); |
|
301 ParserMessage ("\nError: ", buf); |
|
302 exit (1); |
|
303 } |
|
304 |
|
305 // ============================================================================ |
|
306 void ScriptReader::ParserWarning (const char* message, ...) { |
|
307 char buf[512]; |
|
308 va_list va; |
|
309 va_start (va, message); |
|
310 sprintf (buf, message, va); |
|
311 va_end (va); |
|
312 ParserMessage ("Warning: ", buf); |
|
313 } |
|
314 |
|
315 // ============================================================================ |
|
316 void ScriptReader::ParserMessage (const char* header, string message) { |
|
317 if (fc >= 0 && fc < MAX_FILESTACK) |
|
318 fprintf (stderr, "%s%s:%u:%u: %s\n", |
|
319 header, filepath[fc].c_str(), curline[fc], curchar[fc], message.c_str()); |
|
320 else |
|
321 fprintf (stderr, "%s%s\n", header, message.c_str()); |
|
322 } |
|
323 |
|
324 // ============================================================================ |
|
325 // if gotquote == 1, the current token already holds the quotation mark. |
|
326 void ScriptReader::MustString (bool gotquote) { |
|
327 if (gotquote) |
|
328 MustThis ("\""); |
|
329 else |
|
330 MustNext ("\""); |
|
331 |
|
332 string string; |
|
333 // Keep reading characters until we find a terminating quote. |
|
334 while (1) { |
|
335 // can't end here! |
|
336 if (feof (fp[fc])) |
|
337 ParserError ("unterminated string"); |
|
338 |
|
339 char c = ReadChar (); |
|
340 if (c == '"') |
|
341 break; |
|
342 |
|
343 string += c; |
|
344 } |
|
345 |
|
346 token = string; |
|
347 } |
|
348 |
|
349 // ============================================================================ |
|
350 void ScriptReader::MustNumber (bool fromthis) { |
|
351 if (!fromthis) |
|
352 MustNext (); |
|
353 |
|
354 string num = token; |
|
355 if (num == "-") { |
|
356 MustNext (); |
|
357 num += token; |
|
358 } |
|
359 |
|
360 // "true" and "false" are valid numbers |
|
361 if (-token == "true") |
|
362 token = "1"; |
|
363 else if (-token == "false") |
|
364 token = "0"; |
|
365 else { |
|
366 bool ok; |
|
367 int num = token.to_long (&ok); |
|
368 |
|
369 if (!ok) |
|
370 ParserError ("expected a number, got `%s`", token.chars()); |
|
371 |
|
372 token = num; |
|
373 } |
|
374 } |
|