| 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 } |
|